forked from XiaoMo/ChatGPT-Next-Web
fix: chat history with memory
This commit is contained in:
parent
c133cae04b
commit
4d97c269ff
@ -26,7 +26,7 @@ import CloseIcon from "../icons/close.svg";
|
|||||||
import CopyIcon from "../icons/copy.svg";
|
import CopyIcon from "../icons/copy.svg";
|
||||||
import DownloadIcon from "../icons/download.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 { Settings } from "./settings";
|
||||||
import { showModal } from "./ui-lib";
|
import { showModal } from "./ui-lib";
|
||||||
import { copyToClipboard, downloadAs, isIOS } from "../utils";
|
import { copyToClipboard, downloadAs, isIOS } from "../utils";
|
||||||
@ -189,8 +189,8 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.chat} key={session.id}>
|
<div className={styles.chat} key={session.id}>
|
||||||
<div className={styles["window-header"]}>
|
<div className={styles["window-header"]}>
|
||||||
<div>
|
<div className={styles["window-header-title"]}>
|
||||||
<div className={styles["window-header-title"]}>{session.topic}</div>
|
<div className={styles["window-header-main-title"]}>{session.topic}</div>
|
||||||
<div className={styles["window-header-sub-title"]}>
|
<div className={styles["window-header-sub-title"]}>
|
||||||
与 ChatGPT 的 {session.messages.length} 条对话
|
与 ChatGPT 的 {session.messages.length} 条对话
|
||||||
</div>
|
</div>
|
||||||
@ -210,7 +210,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
|||||||
bordered
|
bordered
|
||||||
title="查看压缩后的历史 Prompt"
|
title="查看压缩后的历史 Prompt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showMemoryPrompt(session.memoryPrompt)
|
showMemoryPrompt(session)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -323,12 +323,12 @@ function exportMessages(messages: Message[], topic: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMemoryPrompt(prompt: string) {
|
function showMemoryPrompt(session: ChatSession) {
|
||||||
showModal({
|
showModal({
|
||||||
title: "上下文记忆 Prompt", children: <div className="markdown-body">
|
title: `上下文记忆 Prompt (${session.lastSummarizeIndex} of ${session.messages.length})`, children: <div className="markdown-body">
|
||||||
<pre className={styles['export-content']}>{prompt}</pre>
|
<pre className={styles['export-content']}>{session.memoryPrompt || '无'}</pre>
|
||||||
</div>, actions: [
|
</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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles["window-header"]}>
|
<div className={styles["window-header"]}>
|
||||||
<div>
|
<div className={styles["window-header-title"]}>
|
||||||
<div className={styles["window-header-title"]}>设置</div>
|
<div className={styles["window-header-main-title"]}>设置</div>
|
||||||
<div className={styles["window-header-sub-title"]}>设置选项</div>
|
<div className={styles["window-header-sub-title"]}>设置选项</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["window-actions"]}>
|
<div className={styles["window-actions"]}>
|
||||||
@ -140,14 +140,14 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
</List>
|
</List>
|
||||||
<List>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<div className={styles["settings-title"]}>最大上下文消息数</div>
|
<div className={styles["settings-title"]}>附带历史消息数</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
title={config.historyMessageCount.toString()}
|
title={config.historyMessageCount.toString()}
|
||||||
value={config.historyMessageCount}
|
value={config.historyMessageCount}
|
||||||
min="5"
|
min="2"
|
||||||
max="20"
|
max="25"
|
||||||
step="5"
|
step="2"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateConfig(
|
updateConfig(
|
||||||
(config) =>
|
(config) =>
|
||||||
@ -157,7 +157,6 @@ export function Settings(props: { closeSettings: () => void }) {
|
|||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<div className={styles["settings-title"]}>
|
<div className={styles["settings-title"]}>
|
||||||
历史消息压缩长度阈值
|
历史消息压缩长度阈值
|
||||||
|
@ -8,18 +8,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.window-header-title {
|
.window-header-title {
|
||||||
font-size: 20px;
|
max-width: calc(100% - 100px);
|
||||||
font-weight: bolder;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: block;
|
|
||||||
max-width: 50vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-header-sub-title {
|
.window-header-main-title {
|
||||||
font-size: 14px;
|
font-size: 20px;
|
||||||
margin-top: 5px;
|
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 {
|
.window-actions {
|
||||||
@ -28,4 +32,4 @@
|
|||||||
|
|
||||||
.window-action-button {
|
.window-action-button {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
52
app/store.ts
52
app/store.ts
@ -23,7 +23,7 @@ export enum Theme {
|
|||||||
Light = "light",
|
Light = "light",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatConfig {
|
export interface ChatConfig {
|
||||||
maxToken?: number;
|
maxToken?: number;
|
||||||
historyMessageCount: number; // -1 means all
|
historyMessageCount: number; // -1 means all
|
||||||
compressMessageLengthThreshold: number;
|
compressMessageLengthThreshold: number;
|
||||||
@ -35,7 +35,7 @@ interface ChatConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CONFIG: ChatConfig = {
|
const DEFAULT_CONFIG: ChatConfig = {
|
||||||
historyMessageCount: 5,
|
historyMessageCount: 4,
|
||||||
compressMessageLengthThreshold: 500,
|
compressMessageLengthThreshold: 500,
|
||||||
sendBotMessages: true as boolean,
|
sendBotMessages: true as boolean,
|
||||||
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
||||||
@ -44,13 +44,13 @@ const DEFAULT_CONFIG: ChatConfig = {
|
|||||||
tightBorder: false,
|
tightBorder: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ChatStat {
|
export interface ChatStat {
|
||||||
tokenCount: number;
|
tokenCount: number;
|
||||||
wordCount: number;
|
wordCount: number;
|
||||||
charCount: number;
|
charCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatSession {
|
export interface ChatSession {
|
||||||
id: number;
|
id: number;
|
||||||
topic: string;
|
topic: string;
|
||||||
memoryPrompt: string;
|
memoryPrompt: string;
|
||||||
@ -190,24 +190,20 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
onNewMessage(message) {
|
onNewMessage(message) {
|
||||||
|
get().updateCurrentSession(session => {
|
||||||
|
session.lastUpdate = new Date().toLocaleString()
|
||||||
|
})
|
||||||
get().updateStat(message);
|
get().updateStat(message);
|
||||||
get().summarizeSession();
|
get().summarizeSession();
|
||||||
},
|
},
|
||||||
|
|
||||||
async onUserInput(content) {
|
async onUserInput(content) {
|
||||||
const message: Message = {
|
const userMessage: Message = {
|
||||||
role: "user",
|
role: "user",
|
||||||
content,
|
content,
|
||||||
date: new Date().toLocaleString(),
|
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 = {
|
const botMessage: Message = {
|
||||||
content: "",
|
content: "",
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
@ -215,13 +211,18 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
streaming: true,
|
streaming: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// get recent messages
|
||||||
|
const recentMessages = get().getMessagesWithMemory()
|
||||||
|
const sendMessages = recentMessages.concat(userMessage)
|
||||||
|
|
||||||
|
// save user's and bot's message
|
||||||
get().updateCurrentSession((session) => {
|
get().updateCurrentSession((session) => {
|
||||||
|
session.messages.push(userMessage);
|
||||||
session.messages.push(botMessage);
|
session.messages.push(botMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
const recentMessages = get().getMessagesWithMemory()
|
console.log('[User Input] ', sendMessages)
|
||||||
|
requestChatStream(sendMessages, {
|
||||||
requestChatStream(recentMessages, {
|
|
||||||
onMessage(content, done) {
|
onMessage(content, done) {
|
||||||
if (done) {
|
if (done) {
|
||||||
botMessage.streaming = false;
|
botMessage.streaming = false;
|
||||||
@ -243,11 +244,12 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
getMessagesWithMemory() {
|
getMessagesWithMemory() {
|
||||||
const session = get().currentSession()
|
const session = get().currentSession()
|
||||||
const config = get().config
|
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 = {
|
const memoryPrompt: Message = {
|
||||||
role: 'system',
|
role: 'system',
|
||||||
content: '这是你和用户的历史聊天总结:' + session.memoryPrompt,
|
content: '这是 ai 和用户的历史聊天总结作为前情提要:' + session.memoryPrompt,
|
||||||
date: ''
|
date: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,22 +287,26 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const config = get().config
|
||||||
const messages = get().getMessagesWithMemory()
|
const messages = get().getMessagesWithMemory()
|
||||||
const toBeSummarizedMsgs = messages.slice(session.lastSummarizeIndex)
|
const toBeSummarizedMsgs = get().getMessagesWithMemory()
|
||||||
const historyMsgLength = session.memoryPrompt.length + toBeSummarizedMsgs.reduce((pre, cur) => pre + cur.content.length, 0)
|
const historyMsgLength = toBeSummarizedMsgs.reduce((pre, cur) => pre + cur.content.length, 0)
|
||||||
const lastSummarizeIndex = messages.length
|
const lastSummarizeIndex = session.messages.length
|
||||||
if (historyMsgLength > 500) {
|
|
||||||
|
console.log('[Chat History] ', messages, historyMsgLength, config.compressMessageLengthThreshold)
|
||||||
|
|
||||||
|
if (historyMsgLength > config.compressMessageLengthThreshold) {
|
||||||
requestChatStream(toBeSummarizedMsgs.concat({
|
requestChatStream(toBeSummarizedMsgs.concat({
|
||||||
role: 'system',
|
role: 'system',
|
||||||
content: '总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 50 字以内',
|
content: '总结一下 ai 和用户的对话,用作后续的上下文提示 prompt,控制在 100 字以内,你在回复时用 ai 自称',
|
||||||
date: ''
|
date: ''
|
||||||
}), {
|
}), {
|
||||||
filterBot: false,
|
filterBot: false,
|
||||||
onMessage(message, done) {
|
onMessage(message, done) {
|
||||||
session.memoryPrompt = message
|
session.memoryPrompt = message
|
||||||
session.lastSummarizeIndex = lastSummarizeIndex
|
|
||||||
if (done) {
|
if (done) {
|
||||||
console.log('[Memory] ', session.memoryPrompt)
|
console.log('[Memory] ', session.memoryPrompt)
|
||||||
|
session.lastSummarizeIndex = lastSummarizeIndex
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user