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);