From a19d23848314e8539b40d9fb26544777d53d17df Mon Sep 17 00:00:00 2001 From: Yidadaa Date: Sun, 21 May 2023 01:28:09 +0800 Subject: [PATCH] feat: close #1415 clear context button --- app/components/chat.module.scss | 65 +++++++++++ app/components/chat.tsx | 190 ++++++++++++++++++++------------ app/icons/break.svg | 1 + app/icons/chat-settings.svg | 1 + app/locales/cn.ts | 7 +- app/locales/en.ts | 5 +- app/store/chat.ts | 26 +++-- 7 files changed, 209 insertions(+), 86 deletions(-) create mode 100644 app/icons/break.svg create mode 100644 app/icons/chat-settings.svg diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 3a1be391..3649dfe0 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -107,3 +107,68 @@ user-select: text; } } + +.clear-context { + margin: 20px 0 0 0; + padding: 4px 0; + + border-top: var(--border-in-light); + border-bottom: var(--border-in-light); + box-shadow: var(--card-shadow) inset; + + display: flex; + justify-content: center; + align-items: center; + + opacity: 0.5; + color: var(--black); + transition: all ease 0.3s; + cursor: pointer; + overflow: hidden; + position: relative; + font-size: 12px; + + $linear: linear-gradient( + to right, + rgba(0, 0, 0, 0), + rgba(0, 0, 0, 1), + rgba(0, 0, 0, 0) + ); + mask-image: $linear; + + @mixin show { + transform: translateY(0); + position: relative; + transition: all ease 0.3s; + opacity: 1; + } + + @mixin hide { + transform: translateY(-50%); + position: absolute; + transition: all ease 0.1s; + opacity: 0; + } + + &-tips { + @include show; + } + + &-revert-btn { + color: var(--primary); + @include hide; + } + + &:hover { + opacity: 1; + border-color: var(--primary); + + .clear-context-tips { + @include hide; + } + + .clear-context-revert-btn { + @include show; + } + } +} diff --git a/app/components/chat.tsx b/app/components/chat.tsx index e1cb78de..f2ee8943 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -14,6 +14,8 @@ import MaskIcon from "../icons/mask.svg"; import MaxIcon from "../icons/max.svg"; import MinIcon from "../icons/min.svg"; import ResetIcon from "../icons/reload.svg"; +import BreakIcon from "../icons/break.svg"; +import SettingsIcon from "../icons/chat-settings.svg"; import LightIcon from "../icons/light.svg"; import DarkIcon from "../icons/dark.svg"; @@ -51,7 +53,7 @@ import { IconButton } from "./button"; import styles from "./home.module.scss"; import chatStyle from "./chat.module.scss"; -import { ListItem, Modal, showModal } from "./ui-lib"; +import { ListItem, Modal, showModal, showToast } from "./ui-lib"; import { useLocation, useNavigate } from "react-router-dom"; import { LAST_INPUT_KEY, Path, REQUEST_TIMEOUT_MS } from "../constant"; import { Avatar } from "./emoji"; @@ -289,6 +291,24 @@ export function PromptHints(props: { ); } +function ClearContextDivider() { + const chatStore = useChatStore(); + + return ( +
+ chatStore.updateCurrentSession( + (session) => (session.clearContextIndex = -1), + ) + } + > +
上下文已清除
+
取消清除
+
+ ); +} + function useScrollToBottom() { // for auto-scroll const scrollRef = useRef(null); @@ -321,6 +341,7 @@ export function ChatActions(props: { }) { const config = useAppConfig(); const navigate = useNavigate(); + const chatStore = useChatStore(); // switch themes const theme = config.theme; @@ -359,7 +380,7 @@ export function ChatActions(props: { className={`${chatStyle["chat-input-action"]} clickable`} onClick={props.showPromptModal} > - + )} @@ -391,6 +412,22 @@ export function ChatActions(props: { > + +
{ + chatStore.updateCurrentSession((session) => { + if ((session.clearContextIndex ?? -1) > 0) { + session.clearContextIndex = -1; + } else { + session.clearContextIndex = session.messages.length; + session.memoryPrompt = ""; // will clear memory + } + }); + }} + > + +
); } @@ -602,6 +639,12 @@ export function Chat() { context.push(copiedHello); } + // clear context index = context length + index in messages + const clearContextIndex = + (session.clearContextIndex ?? -1) >= 0 + ? session.clearContextIndex! + context.length + : -1; + // preview messages const messages = context .concat(session.messages as RenderMessage[]) @@ -736,86 +779,91 @@ export function Chat() { !(message.preview || message.content.length === 0); const showTyping = message.preview || message.streaming; + const shouldShowClearContextDivider = i === clearContextIndex - 1; + return ( -
-
-
- {message.role === "user" ? ( - - ) : ( - - )} -
- {showTyping && ( -
- {Locale.Chat.Typing} + <> +
+
+
+ {message.role === "user" ? ( + + ) : ( + + )}
- )} -
- {showActions && ( -
- {message.streaming ? ( + {showTyping && ( +
+ {Locale.Chat.Typing} +
+ )} +
+ {showActions && ( +
+ {message.streaming ? ( +
onUserStop(message.id ?? i)} + > + {Locale.Chat.Actions.Stop} +
+ ) : ( + <> +
onDelete(message.id ?? i)} + > + {Locale.Chat.Actions.Delete} +
+
onResend(message.id ?? i)} + > + {Locale.Chat.Actions.Retry} +
+ + )} +
onUserStop(message.id ?? i)} + onClick={() => copyToClipboard(message.content)} > - {Locale.Chat.Actions.Stop} + {Locale.Chat.Actions.Copy}
- ) : ( - <> -
onDelete(message.id ?? i)} - > - {Locale.Chat.Actions.Delete} -
-
onResend(message.id ?? i)} - > - {Locale.Chat.Actions.Retry} -
- - )} - -
copyToClipboard(message.content)} - > - {Locale.Chat.Actions.Copy} +
+ )} + onRightClick(e, message)} + onDoubleClickCapture={() => { + if (!isMobileScreen) return; + setUserInput(message.content); + }} + fontSize={fontSize} + parentRef={scrollRef} + defaultShow={i >= messages.length - 10} + /> +
+ {!isUser && !message.preview && ( +
+
+ {message.date.toLocaleString()}
)} - onRightClick(e, message)} - onDoubleClickCapture={() => { - if (!isMobileScreen) return; - setUserInput(message.content); - }} - fontSize={fontSize} - parentRef={scrollRef} - defaultShow={i >= messages.length - 10} - />
- {!isUser && !message.preview && ( -
-
- {message.date.toLocaleString()} -
-
- )}
-
+ {shouldShowClearContextDivider && } + ); })}
diff --git a/app/icons/break.svg b/app/icons/break.svg new file mode 100644 index 00000000..64e61709 --- /dev/null +++ b/app/icons/break.svg @@ -0,0 +1 @@ + diff --git a/app/icons/chat-settings.svg b/app/icons/chat-settings.svg new file mode 100644 index 00000000..0a37b294 --- /dev/null +++ b/app/icons/chat-settings.svg @@ -0,0 +1 @@ + diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 4250178c..9a29ecf8 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -160,12 +160,11 @@ const cn = { BotHello: "有什么可以帮你的吗", Error: "出错了,稍后重试吧", Prompt: { - History: (content: string) => - "这是 ai 和用户的历史聊天总结作为前情提要:" + content, + History: (content: string) => "这是历史聊天总结作为前情提要:" + content, Topic: "使用四到五个字直接返回这句话的简要主题,不要解释、不要标点、不要语气词、不要多余文本,如果没有主题,请直接返回“闲聊”", Summarize: - "简要总结一下你和用户的对话,用作后续的上下文提示 prompt,控制在 200 字以内", + "简要总结一下对话内容,用作后续的上下文提示 prompt,控制在 200 字以内", }, }, Copy: { @@ -173,7 +172,7 @@ const cn = { Failed: "复制失败,请赋予剪切板权限", }, Context: { - Toast: (x: any) => `已设置 ${x} 条前置上下文`, + Toast: (x: any) => `包含 ${x} 条预设提示词`, Edit: "当前对话设置", Add: "新增预设对话", }, diff --git a/app/locales/en.ts b/app/locales/en.ts index 1f136f00..0dd3308c 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -163,12 +163,11 @@ const en: RequiredLocaleType = { Error: "Something went wrong, please try again later.", Prompt: { History: (content: string) => - "This is a summary of the chat history between the AI and the user as a recap: " + - content, + "This is a summary of the chat history as a recap: " + content, Topic: "Please generate a four to five word title summarizing our conversation without any lead-in, punctuation, quotation marks, periods, symbols, or additional text. Remove enclosing quotation marks.", Summarize: - "Summarize our discussion briefly in 200 words or less to use as a prompt for future context.", + "Summarize the discussion briefly in 200 words or less to use as a prompt for future context.", }, }, Copy: { diff --git a/app/store/chat.ts b/app/store/chat.ts index 888ac3a0..4abba0cf 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -45,6 +45,7 @@ export interface ChatSession { stat: ChatStat; lastUpdate: number; lastSummarizeIndex: number; + clearContextIndex?: number; mask: Mask; } @@ -341,7 +342,12 @@ export const useChatStore = create()( getMessagesWithMemory() { const session = get().currentSession(); const modelConfig = session.mask.modelConfig; - const messages = session.messages.filter((msg) => !msg.isError); + + // wont send cleared context messages + const clearedContextMessages = session.messages.slice( + (session.clearContextIndex ?? -1) + 1, + ); + const messages = clearedContextMessages.filter((msg) => !msg.isError); const n = messages.length; const context = session.mask.context.slice(); @@ -362,17 +368,17 @@ export const useChatStore = create()( n - modelConfig.historyMessageCount, ); const longTermMemoryMessageIndex = session.lastSummarizeIndex; - const oldestIndex = Math.max( + const mostRecentIndex = Math.max( shortTermMemoryMessageIndex, longTermMemoryMessageIndex, ); - const threshold = modelConfig.compressMessageLengthThreshold; + const threshold = modelConfig.compressMessageLengthThreshold * 2; // get recent messages as many as possible const reversedRecentMessages = []; for ( let i = n - 1, count = 0; - i >= oldestIndex && count < threshold; + i >= mostRecentIndex && count < threshold; i -= 1 ) { const msg = messages[i]; @@ -410,15 +416,15 @@ export const useChatStore = create()( const session = get().currentSession(); // remove error messages if any - const cleanMessages = session.messages.filter((msg) => !msg.isError); + const messages = session.messages; // should summarize topic after chating more than 50 words const SUMMARIZE_MIN_LEN = 50; if ( session.topic === DEFAULT_TOPIC && - countMessages(cleanMessages) >= SUMMARIZE_MIN_LEN + countMessages(messages) >= SUMMARIZE_MIN_LEN ) { - const topicMessages = cleanMessages.concat( + const topicMessages = messages.concat( createMessage({ role: "user", content: Locale.Store.Prompt.Topic, @@ -440,9 +446,13 @@ export const useChatStore = create()( } const modelConfig = session.mask.modelConfig; - let toBeSummarizedMsgs = cleanMessages.slice( + const summarizeIndex = Math.max( session.lastSummarizeIndex, + session.clearContextIndex ?? 0, ); + let toBeSummarizedMsgs = messages + .filter((msg) => !msg.isError) + .slice(summarizeIndex); const historyMsgLength = countMessages(toBeSummarizedMsgs);