From 78dd2d4258973506046f7bd2cf846dafcf87bb5e Mon Sep 17 00:00:00 2001 From: Hk-Gosuto Date: Mon, 3 Mar 2025 15:33:19 +0800 Subject: [PATCH] feat: thinking style optimize --- app/client/api.ts | 2 + app/components/chat.tsx | 2 + app/components/thinking-content.module.scss | 106 ++++++++++++++++++++ app/components/thinking-content.tsx | 67 +++++++++++++ app/locales/cn.ts | 3 + app/locales/en.ts | 3 + app/store/chat.ts | 9 ++ app/utils/chat.ts | 37 ++++--- 8 files changed, 216 insertions(+), 13 deletions(-) create mode 100644 app/components/thinking-content.module.scss create mode 100644 app/components/thinking-content.tsx diff --git a/app/client/api.ts b/app/client/api.ts index 1b265615c..ba592e5d9 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -45,6 +45,7 @@ export interface MultimodalContent { export interface RequestMessage { role: MessageRole; content: string | MultimodalContent[]; + reasoningContent?: string; fileInfos?: FileInfo[]; webSearchReferences?: TavilySearchResponse; } @@ -93,6 +94,7 @@ export interface ChatOptions { onToolUpdate?: (toolName: string, toolInput: string) => void; onUpdate?: (message: string, chunk: string) => void; + onReasoningUpdate?: (message: string, chunk: string) => void; onFinish: (message: string, responseRes: Response) => void; onError?: (err: Error) => void; onController?: (controller: AbortController) => void; diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 00f724b5c..79502c504 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -147,6 +147,7 @@ import { WebTranscriptionApi, } from "../utils/speech"; import { FileInfo } from "../client/platforms/utils"; +import { ThinkingContent } from "./thinking-content"; const ttsPlayer = createTTSPlayer(); @@ -2151,6 +2152,7 @@ function _Chat() { ))} )} + {!isUser && }
(null); + + const thinkingContent = message.reasoningContent; + const isThinking = + message.streaming && thinkingContent && thinkingContent.length > 0; + + // Auto-scroll to bottom of thinking container + useEffect(() => { + if (isThinking && thinkingContentRef.current) { + requestAnimationFrame(() => { + if (thinkingContentRef.current) { + thinkingContentRef.current.scrollTop = + thinkingContentRef.current.scrollHeight; + } + }); + } + }, [thinkingContent, isThinking, expanded]); + + if (!thinkingContent) return null; + + return ( +
+
+
+ {Locale.Chat.Thinking.Title} +
+
setExpanded(!expanded)} + > + {expanded ? : } +
+
+
+ {!expanded &&
} +
+
+ {thinkingContent} +
+
+ {!expanded &&
} +
+
+ ); +} diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 2ab7ddbea..0777d8d31 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -23,6 +23,9 @@ const cn = { }, Chat: { SubTitle: (count: number) => `共 ${count} 条对话`, + Thinking: { + Title: "深度思考", + }, EditMessage: { Title: "编辑消息记录", Topic: { diff --git a/app/locales/en.ts b/app/locales/en.ts index 53fa57612..ece4dc2de 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -25,6 +25,9 @@ const en: LocaleType = { }, Chat: { SubTitle: (count: number) => `${count} messages`, + Thinking: { + Title: "Thinking", + }, EditMessage: { Title: "Edit All Messages", Topic: { diff --git a/app/store/chat.ts b/app/store/chat.ts index db4220b48..08423e9f1 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -562,6 +562,15 @@ export const useChatStore = createPersistStore( session.messages = session.messages.concat(); }); }, + onReasoningUpdate(message) { + botMessage.streaming = true; + if (message) { + botMessage.reasoningContent = message; + } + get().updateTargetSession(session, (session) => { + session.messages = session.messages.concat(); + }); + }, onFinish(message) { botMessage.streaming = false; if (message) { diff --git a/app/utils/chat.ts b/app/utils/chat.ts index 884385e09..b27681b5a 100644 --- a/app/utils/chat.ts +++ b/app/utils/chat.ts @@ -388,6 +388,8 @@ export function streamWithThink( ) { let responseText = ""; let remainText = ""; + let reasoningResponseText = ""; + let reasoningRemainText = ""; let finished = false; let running = false; let runTools: any[] = []; @@ -414,6 +416,19 @@ export function streamWithThink( options.onUpdate?.(responseText, fetchText); } + if (reasoningRemainText.length > 0) { + const fetchCount = Math.max( + 1, + Math.round(reasoningRemainText.length / 60), + ); + const fetchText = reasoningRemainText.slice(0, fetchCount); + reasoningResponseText += fetchText; + // 删除空行 + reasoningResponseText = reasoningResponseText.replace(/^\s*\n/gm, ""); + reasoningRemainText = reasoningRemainText.slice(fetchCount); + options.onReasoningUpdate?.(reasoningResponseText, fetchText); + } + requestAnimationFrame(animateResponseText); } @@ -582,28 +597,24 @@ export function streamWithThink( if (!isInThinkingMode || isThinkingChanged) { // If this is a new thinking block or mode changed, add prefix isInThinkingMode = true; - if (remainText.length > 0) { - remainText += "\n"; - } - remainText += "> " + chunk.content; + // if (remainText.length > 0) { + // remainText += "\n"; + // } + // Add thinking prefix with timestamp + // const timestamp = new Date().toISOString().substr(11, 8); // HH:MM:SS format + // remainText += `> [${timestamp}] ` + chunk.content; + reasoningRemainText += chunk.content; } else { // Handle newlines in thinking content - if (chunk.content.includes("\n\n")) { - const lines = chunk.content.split("\n\n"); - remainText += lines.join("\n\n> "); - } else { - remainText += chunk.content; - } + reasoningRemainText += chunk.content; } } else { // If in normal mode if (isInThinkingMode || isThinkingChanged) { // If switching from thinking mode to normal mode isInThinkingMode = false; - remainText += "\n\n" + chunk.content; - } else { - remainText += chunk.content; } + remainText += chunk.content; } } catch (e) { console.error("[Request] parse error", text, msg, e);