mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	fix: chat history with memory
This commit is contained in:
		@@ -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)} />,
 | 
			
		||||
    ]
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]}>
 | 
			
		||||
              历史消息压缩长度阈值
 | 
			
		||||
 
 | 
			
		||||
@@ -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-sub-title {
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
  .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-actions {
 | 
			
		||||
@@ -28,4 +32,4 @@
 | 
			
		||||
 | 
			
		||||
.window-action-button {
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								app/store.ts
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								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) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user