Browse Source

fix: chat history with memory

Yifei Zhang 2 years ago
parent
commit
4d97c269ff
4 changed files with 59 additions and 50 deletions
  1. 8 8
      app/components/home.tsx
  2. 6 7
      app/components/settings.tsx
  3. 16 12
      app/components/window.scss
  4. 29 23
      app/store.ts

+ 8 - 8
app/components/home.tsx

@@ -26,7 +26,7 @@ import CloseIcon from "../icons/close.svg";
 import CopyIcon from "../icons/copy.svg";
 import DownloadIcon from "../icons/download.svg";
 
-import { Message, SubmitKey, useChatStore, Theme } from "../store";
+import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
 import { Settings } from "./settings";
 import { showModal } from "./ui-lib";
 import { copyToClipboard, downloadAs, isIOS } from "../utils";
@@ -189,8 +189,8 @@ export function Chat(props: { showSideBar?: () => void }) {
   return (
     <div className={styles.chat} key={session.id}>
       <div className={styles["window-header"]}>
-        <div>
-          <div className={styles["window-header-title"]}>{session.topic}</div>
+        <div className={styles["window-header-title"]}>
+          <div className={styles["window-header-main-title"]}>{session.topic}</div>
           <div className={styles["window-header-sub-title"]}>
             与 ChatGPT 的 {session.messages.length} 条对话
           </div>
@@ -210,7 +210,7 @@ export function Chat(props: { showSideBar?: () => void }) {
               bordered
               title="查看压缩后的历史 Prompt"
               onClick={() => {
-                showMemoryPrompt(session.memoryPrompt)
+                showMemoryPrompt(session)
               }}
             />
           </div>
@@ -323,12 +323,12 @@ function exportMessages(messages: Message[], topic: string) {
   })
 }
 
-function showMemoryPrompt(prompt: string) {
+function showMemoryPrompt(session: ChatSession) {
   showModal({
-    title: "上下文记忆 Prompt", children: <div className="markdown-body">
-      <pre className={styles['export-content']}>{prompt}</pre>
+    title: `上下文记忆 Prompt (${session.lastSummarizeIndex} of ${session.messages.length})`, children: <div className="markdown-body">
+      <pre className={styles['export-content']}>{session.memoryPrompt || '无'}</pre>
     </div>, actions: [
-      <IconButton key="copy" icon={<CopyIcon />} bordered text="全部复制" onClick={() => copyToClipboard(prompt)} />,
+      <IconButton key="copy" icon={<CopyIcon />} bordered text="全部复制" onClick={() => copyToClipboard(session.memoryPrompt)} />,
     ]
   })
 }

+ 6 - 7
app/components/settings.tsx

@@ -26,8 +26,8 @@ export function Settings(props: { closeSettings: () => void }) {
   return (
     <>
       <div className={styles["window-header"]}>
-        <div>
-          <div className={styles["window-header-title"]}>设置</div>
+        <div className={styles["window-header-title"]}>
+          <div className={styles["window-header-main-title"]}>设置</div>
           <div className={styles["window-header-sub-title"]}>设置选项</div>
         </div>
         <div className={styles["window-actions"]}>
@@ -140,14 +140,14 @@ export function Settings(props: { closeSettings: () => void }) {
         </List>
         <List>
           <ListItem>
-            <div className={styles["settings-title"]}>最大上下文消息数</div>
+            <div className={styles["settings-title"]}>附带历史消息数</div>
             <input
               type="range"
               title={config.historyMessageCount.toString()}
               value={config.historyMessageCount}
-              min="5"
-              max="20"
-              step="5"
+              min="2"
+              max="25"
+              step="2"
               onChange={(e) =>
                 updateConfig(
                   (config) =>
@@ -157,7 +157,6 @@ export function Settings(props: { closeSettings: () => void }) {
             ></input>
           </ListItem>
 
-
           <ListItem>
             <div className={styles["settings-title"]}>
               历史消息压缩长度阈值

+ 16 - 12
app/components/window.scss

@@ -8,18 +8,22 @@
 }
 
 .window-header-title {
-  font-size: 20px;
-  font-weight: bolder;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  display: block;
-  max-width: 50vw;
-}
+  max-width: calc(100% - 100px);
+
+  .window-header-main-title {
+    font-size: 20px;
+    font-weight: bolder;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: block;
+    max-width: 50vw;
+  }
 
-.window-header-sub-title {
-  font-size: 14px;
-  margin-top: 5px;
+  .window-header-sub-title {
+    font-size: 14px;
+    margin-top: 5px;
+  }
 }
 
 .window-actions {
@@ -28,4 +32,4 @@
 
 .window-action-button {
   margin-left: 10px;
-}
+}

+ 29 - 23
app/store.ts

@@ -23,7 +23,7 @@ export enum Theme {
   Light = "light",
 }
 
-interface ChatConfig {
+export interface ChatConfig {
   maxToken?: number;
   historyMessageCount: number; // -1 means all
   compressMessageLengthThreshold: number;
@@ -35,7 +35,7 @@ interface ChatConfig {
 }
 
 const DEFAULT_CONFIG: ChatConfig = {
-  historyMessageCount: 5,
+  historyMessageCount: 4,
   compressMessageLengthThreshold: 500,
   sendBotMessages: true as boolean,
   submitKey: SubmitKey.CtrlEnter as SubmitKey,
@@ -44,13 +44,13 @@ const DEFAULT_CONFIG: ChatConfig = {
   tightBorder: false,
 };
 
-interface ChatStat {
+export interface ChatStat {
   tokenCount: number;
   wordCount: number;
   charCount: number;
 }
 
-interface ChatSession {
+export interface ChatSession {
   id: number;
   topic: string;
   memoryPrompt: string;
@@ -190,24 +190,20 @@ export const useChatStore = create<ChatStore>()(
       },
 
       onNewMessage(message) {
+        get().updateCurrentSession(session => {
+          session.lastUpdate = new Date().toLocaleString()
+        })
         get().updateStat(message);
         get().summarizeSession();
       },
 
       async onUserInput(content) {
-        const message: Message = {
+        const userMessage: Message = {
           role: "user",
           content,
           date: new Date().toLocaleString(),
         };
 
-        get().updateCurrentSession((session) => {
-          session.messages.push(message);
-        });
-
-        // get last five messges
-        const messages = get().currentSession().messages.concat(message);
-
         const botMessage: Message = {
           content: "",
           role: "assistant",
@@ -215,13 +211,18 @@ export const useChatStore = create<ChatStore>()(
           streaming: true,
         };
 
+        // get recent messages 
+        const recentMessages = get().getMessagesWithMemory()
+        const sendMessages = recentMessages.concat(userMessage)
+
+        // save user's and bot's message
         get().updateCurrentSession((session) => {
+          session.messages.push(userMessage);
           session.messages.push(botMessage);
         });
 
-        const recentMessages = get().getMessagesWithMemory()
-
-        requestChatStream(recentMessages, {
+        console.log('[User Input] ', sendMessages)
+        requestChatStream(sendMessages, {
           onMessage(content, done) {
             if (done) {
               botMessage.streaming = false;
@@ -243,11 +244,12 @@ export const useChatStore = create<ChatStore>()(
       getMessagesWithMemory() {
         const session = get().currentSession()
         const config = get().config
-        const recentMessages = session.messages.slice(-config.historyMessageCount);
+        const n = session.messages.length
+        const recentMessages = session.messages.slice(n - config.historyMessageCount);
 
         const memoryPrompt: Message = {
           role: 'system',
-          content: '这是你和用户的历史聊天总结:' + session.memoryPrompt,
+          content: '这是 ai 和用户的历史聊天总结作为前情提要:' + session.memoryPrompt,
           date: ''
         }
 
@@ -285,22 +287,26 @@ export const useChatStore = create<ChatStore>()(
           });
         }
 
+        const config = get().config
         const messages = get().getMessagesWithMemory()
-        const toBeSummarizedMsgs = messages.slice(session.lastSummarizeIndex)
-        const historyMsgLength = session.memoryPrompt.length + toBeSummarizedMsgs.reduce((pre, cur) => pre + cur.content.length, 0)
-        const lastSummarizeIndex = messages.length
-        if (historyMsgLength > 500) {
+        const toBeSummarizedMsgs = get().getMessagesWithMemory()
+        const historyMsgLength = toBeSummarizedMsgs.reduce((pre, cur) => pre + cur.content.length, 0)
+        const lastSummarizeIndex = session.messages.length
+
+        console.log('[Chat History] ', messages, historyMsgLength, config.compressMessageLengthThreshold)
+
+        if (historyMsgLength > config.compressMessageLengthThreshold) {
           requestChatStream(toBeSummarizedMsgs.concat({
             role: 'system',
-            content: '总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 50 字以内',
+            content: '总结一下 ai 和用户的对话,用作后续的上下文提示 prompt,控制在 100 字以内,你在回复时用 ai 自称',
             date: ''
           }), {
             filterBot: false,
             onMessage(message, done) {
               session.memoryPrompt = message
-              session.lastSummarizeIndex = lastSummarizeIndex
               if (done) {
                 console.log('[Memory] ', session.memoryPrompt)
+                session.lastSummarizeIndex = lastSummarizeIndex
               }
             },
             onError(error) {