mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	feat: support compress chat history
This commit is contained in:
		@@ -208,7 +208,10 @@ export function Chat(props: { showSideBar?: () => void }) {
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<BrainIcon />}
 | 
			
		||||
              bordered
 | 
			
		||||
              title="查看压缩后的历史 Prompt(开发中)"
 | 
			
		||||
              title="查看压缩后的历史 Prompt"
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                showMemoryPrompt(session.memoryPrompt)
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["window-action-button"]}>
 | 
			
		||||
@@ -320,6 +323,16 @@ function exportMessages(messages: Message[], topic: string) {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function showMemoryPrompt(prompt: string) {
 | 
			
		||||
  showModal({
 | 
			
		||||
    title: "上下文记忆 Prompt", children: <div className="markdown-body">
 | 
			
		||||
      <pre className={styles['export-content']}>{prompt}</pre>
 | 
			
		||||
    </div>, actions: [
 | 
			
		||||
      <IconButton key="copy" icon={<CopyIcon />} bordered text="全部复制" onClick={() => copyToClipboard(prompt)} />,
 | 
			
		||||
    ]
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Home() {
 | 
			
		||||
  const [createNewSession] = useChatStore((state) => [state.newSession]);
 | 
			
		||||
  const loading = !useChatStore?.persist?.hasHydrated();
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import styles from "./settings.module.scss";
 | 
			
		||||
 | 
			
		||||
import ResetIcon from "../icons/reload.svg";
 | 
			
		||||
import CloseIcon from "../icons/close.svg";
 | 
			
		||||
import ClearIcon from "../icons/clear.svg";
 | 
			
		||||
 | 
			
		||||
import { List, ListItem, Popover } from "./ui-lib";
 | 
			
		||||
 | 
			
		||||
@@ -15,10 +16,11 @@ import { Avatar } from "./home";
 | 
			
		||||
 | 
			
		||||
export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
 | 
			
		||||
  const [config, updateConfig, resetConfig] = useChatStore((state) => [
 | 
			
		||||
  const [config, updateConfig, resetConfig, clearAllData] = useChatStore((state) => [
 | 
			
		||||
    state.config,
 | 
			
		||||
    state.updateConfig,
 | 
			
		||||
    state.resetConfig,
 | 
			
		||||
    state.clearAllData,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@@ -31,10 +33,10 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
        <div className={styles["window-actions"]}>
 | 
			
		||||
          <div className={styles["window-action-button"]}>
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<CloseIcon />}
 | 
			
		||||
              onClick={props.closeSettings}
 | 
			
		||||
              icon={<ClearIcon />}
 | 
			
		||||
              onClick={clearAllData}
 | 
			
		||||
              bordered
 | 
			
		||||
              title="重置所有选项"
 | 
			
		||||
              title="清除所有数据"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["window-action-button"]}>
 | 
			
		||||
@@ -45,6 +47,14 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
              title="重置所有选项"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["window-action-button"]}>
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<CloseIcon />}
 | 
			
		||||
              onClick={props.closeSettings}
 | 
			
		||||
              bordered
 | 
			
		||||
              title="关闭"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className={styles["settings"]}>
 | 
			
		||||
@@ -147,6 +157,24 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
            ></input>
 | 
			
		||||
          </ListItem>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          <ListItem>
 | 
			
		||||
            <div className={styles["settings-title"]}>
 | 
			
		||||
              历史消息压缩长度阈值
 | 
			
		||||
            </div>
 | 
			
		||||
            <input
 | 
			
		||||
              type="number"
 | 
			
		||||
              min={500}
 | 
			
		||||
              max={4000}
 | 
			
		||||
              value={config.compressMessageLengthThreshold}
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                updateConfig(
 | 
			
		||||
                  (config) => (config.compressMessageLengthThreshold = e.currentTarget.valueAsNumber)
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
            ></input>
 | 
			
		||||
          </ListItem>
 | 
			
		||||
 | 
			
		||||
          <ListItem>
 | 
			
		||||
            <div className={styles["settings-title"]}>
 | 
			
		||||
              上下文中包含机器人消息
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .modal-content {
 | 
			
		||||
    height: 40vh;
 | 
			
		||||
    max-height: 40vh;
 | 
			
		||||
    padding: var(--modal-padding);
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
  }
 | 
			
		||||
@@ -118,7 +118,7 @@
 | 
			
		||||
    width: 90vw;
 | 
			
		||||
 | 
			
		||||
    .modal-content {
 | 
			
		||||
      height: 50vh;
 | 
			
		||||
      max-height: 50vh;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								app/icons/clear.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								app/icons/clear.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs><g opacity="1" transform="translate(0 0)  rotate(0 8 8)"><mask id="bg-mask-0" fill="white"><use xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path  id="路径 1" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(2.6666666666666665 5)  rotate(0 5.333333333333333 4.833333333333333)" d="M1,9.67L9.67,9.67L10.67,0L0,0L1,9.67Z " /><path  id="路径 2" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(6.667333333333333 8.334133333333334)  rotate(0 0 1.6666999999999998)" d="M0,0L0,3.33 " /><path  id="路径 3" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(9.334133333333334 8.333166666666667)  rotate(0 0 1.666283333333333)" d="M0,0L0,3.33 " /><path  id="路径 4" style="stroke:#333333; stroke-width:1.3333333333333333; stroke-opacity:1; stroke-dasharray:0 0" transform="translate(4 1)  rotate(0 4 2)" d="M0,4L5.44,0L8,4 " /></g></g></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										82
									
								
								app/store.ts
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								app/store.ts
									
									
									
									
									
								
							@@ -26,6 +26,7 @@ export enum Theme {
 | 
			
		||||
interface ChatConfig {
 | 
			
		||||
  maxToken?: number;
 | 
			
		||||
  historyMessageCount: number; // -1 means all
 | 
			
		||||
  compressMessageLengthThreshold: number;
 | 
			
		||||
  sendBotMessages: boolean; // send bot's message or not
 | 
			
		||||
  submitKey: SubmitKey;
 | 
			
		||||
  avatar: string;
 | 
			
		||||
@@ -35,9 +36,10 @@ interface ChatConfig {
 | 
			
		||||
 | 
			
		||||
const DEFAULT_CONFIG: ChatConfig = {
 | 
			
		||||
  historyMessageCount: 5,
 | 
			
		||||
  sendBotMessages: false as boolean,
 | 
			
		||||
  compressMessageLengthThreshold: 500,
 | 
			
		||||
  sendBotMessages: true as boolean,
 | 
			
		||||
  submitKey: SubmitKey.CtrlEnter as SubmitKey,
 | 
			
		||||
  avatar: "1fae0",
 | 
			
		||||
  avatar: "1f603",
 | 
			
		||||
  theme: Theme.Auto as Theme,
 | 
			
		||||
  tightBorder: false,
 | 
			
		||||
};
 | 
			
		||||
@@ -55,7 +57,7 @@ interface ChatSession {
 | 
			
		||||
  messages: Message[];
 | 
			
		||||
  stat: ChatStat;
 | 
			
		||||
  lastUpdate: string;
 | 
			
		||||
  deleted?: boolean;
 | 
			
		||||
  lastSummarizeIndex: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DEFAULT_TOPIC = "新的聊天";
 | 
			
		||||
@@ -80,6 +82,7 @@ function createEmptySession(): ChatSession {
 | 
			
		||||
      charCount: 0,
 | 
			
		||||
    },
 | 
			
		||||
    lastUpdate: createDate,
 | 
			
		||||
    lastSummarizeIndex: 0,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +96,6 @@ interface ChatStore {
 | 
			
		||||
  currentSession: () => ChatSession;
 | 
			
		||||
  onNewMessage: (message: Message) => void;
 | 
			
		||||
  onUserInput: (content: string) => Promise<void>;
 | 
			
		||||
  onBotResponse: (message: Message) => void;
 | 
			
		||||
  summarizeSession: () => void;
 | 
			
		||||
  updateStat: (message: Message) => void;
 | 
			
		||||
  updateCurrentSession: (updater: (session: ChatSession) => void) => void;
 | 
			
		||||
@@ -102,10 +104,12 @@ interface ChatStore {
 | 
			
		||||
    messageIndex: number,
 | 
			
		||||
    updater: (message?: Message) => void
 | 
			
		||||
  ) => void;
 | 
			
		||||
  getMessagesWithMemory: () => Message[];
 | 
			
		||||
 | 
			
		||||
  getConfig: () => ChatConfig;
 | 
			
		||||
  resetConfig: () => void;
 | 
			
		||||
  updateConfig: (updater: (config: ChatConfig) => void) => void;
 | 
			
		||||
  clearAllData: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const LOCAL_KEY = "chat-next-web-store";
 | 
			
		||||
@@ -186,9 +190,6 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onNewMessage(message) {
 | 
			
		||||
        get().updateCurrentSession((session) => {
 | 
			
		||||
          session.messages.push(message);
 | 
			
		||||
        });
 | 
			
		||||
        get().updateStat(message);
 | 
			
		||||
        get().summarizeSession();
 | 
			
		||||
      },
 | 
			
		||||
@@ -200,9 +201,12 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
          date: new Date().toLocaleString(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        get().updateCurrentSession((session) => {
 | 
			
		||||
          session.messages.push(message);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // get last five messges
 | 
			
		||||
        const messages = get().currentSession().messages.concat(message);
 | 
			
		||||
        get().onNewMessage(message);
 | 
			
		||||
 | 
			
		||||
        const botMessage: Message = {
 | 
			
		||||
          content: "",
 | 
			
		||||
@@ -215,14 +219,13 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
          session.messages.push(botMessage);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const fiveMessages = messages.slice(-5);
 | 
			
		||||
        const recentMessages = get().getMessagesWithMemory()
 | 
			
		||||
 | 
			
		||||
        requestChatStream(fiveMessages, {
 | 
			
		||||
        requestChatStream(recentMessages, {
 | 
			
		||||
          onMessage(content, done) {
 | 
			
		||||
            if (done) {
 | 
			
		||||
              botMessage.streaming = false;
 | 
			
		||||
              get().updateStat(botMessage);
 | 
			
		||||
              get().summarizeSession();
 | 
			
		||||
              get().onNewMessage(botMessage)
 | 
			
		||||
            } else {
 | 
			
		||||
              botMessage.content = content;
 | 
			
		||||
              set(() => ({}));
 | 
			
		||||
@@ -237,6 +240,24 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      getMessagesWithMemory() {
 | 
			
		||||
        const session = get().currentSession()
 | 
			
		||||
        const config = get().config
 | 
			
		||||
        const recentMessages = session.messages.slice(-config.historyMessageCount);
 | 
			
		||||
 | 
			
		||||
        const memoryPrompt: Message = {
 | 
			
		||||
          role: 'system',
 | 
			
		||||
          content: '这是你和用户的历史聊天总结:' + session.memoryPrompt,
 | 
			
		||||
          date: ''
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (session.memoryPrompt) {
 | 
			
		||||
          recentMessages.unshift(memoryPrompt)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return recentMessages
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      updateMessage(
 | 
			
		||||
        sessionIndex: number,
 | 
			
		||||
        messageIndex: number,
 | 
			
		||||
@@ -249,10 +270,6 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
        set(() => ({ sessions }));
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      onBotResponse(message) {
 | 
			
		||||
        get().onNewMessage(message);
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      summarizeSession() {
 | 
			
		||||
        const session = get().currentSession();
 | 
			
		||||
 | 
			
		||||
@@ -260,13 +277,37 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
          // should summarize topic
 | 
			
		||||
          requestWithPrompt(
 | 
			
		||||
            session.messages,
 | 
			
		||||
            "直接返回这句话的简要主题,不要解释"
 | 
			
		||||
            "直接返回这句话的简要主题,不要解释,如果没有主题,请直接返回“闲聊”"
 | 
			
		||||
          ).then((res) => {
 | 
			
		||||
            get().updateCurrentSession(
 | 
			
		||||
              (session) => (session.topic = trimTopic(res))
 | 
			
		||||
            );
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
          requestChatStream(toBeSummarizedMsgs.concat({
 | 
			
		||||
            role: 'system',
 | 
			
		||||
            content: '总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 50 字以内',
 | 
			
		||||
            date: ''
 | 
			
		||||
          }), {
 | 
			
		||||
            filterBot: false,
 | 
			
		||||
            onMessage(message, done) {
 | 
			
		||||
              session.memoryPrompt = message
 | 
			
		||||
              session.lastSummarizeIndex = lastSummarizeIndex
 | 
			
		||||
              if (done) {
 | 
			
		||||
                console.log('[Memory] ', session.memoryPrompt)
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            onError(error) {
 | 
			
		||||
              console.error('[Summarize] ', error)
 | 
			
		||||
            },
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      updateStat(message) {
 | 
			
		||||
@@ -282,6 +323,13 @@ export const useChatStore = create<ChatStore>()(
 | 
			
		||||
        updater(sessions[index]);
 | 
			
		||||
        set(() => ({ sessions }));
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      clearAllData() {
 | 
			
		||||
        if (confirm('确认清除所有聊天、设置数据?')) {
 | 
			
		||||
          localStorage.clear()
 | 
			
		||||
          location.reload()
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    }),
 | 
			
		||||
    {
 | 
			
		||||
      name: LOCAL_KEY,
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,17 @@ input[type="range"]::-webkit-slider-thumb:hover {
 | 
			
		||||
  width: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="number"] {
 | 
			
		||||
  appearance: none;
 | 
			
		||||
  border-radius: 10px;
 | 
			
		||||
  border: var(--border-in-light);
 | 
			
		||||
  height: 32px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  background: var(--white);
 | 
			
		||||
  color: var(--black);
 | 
			
		||||
  padding: 0 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.math {
 | 
			
		||||
  overflow-x: auto;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user