From 46fee4b2c8520287cfb5a5d0ff8ed21bc66aafae Mon Sep 17 00:00:00 2001 From: iven Date: Wed, 22 Apr 2026 13:17:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20=E9=9A=90=E8=97=8FHand=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=B6=88=E6=81=AF=20+=20=E8=BF=87=E6=BB=A4LLM?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E5=8F=99=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 所有 role=hand 的消息不再显示 (不仅仅是 researcher) - "Hand: hand_researcher - running" 不再出现 - Hand 错误 JSON 不再显示 - 移除未使用的 PresentationContainer import 2. 添加 stripToolNarration() 过滤 LLM 推理文本 - 英文: "Now let me...", "I need to...", "I keep getting..." - 中文: "让我执行...", "让我尝试使用...", "好的,让我为您..." - 保留实际有用内容,仅过滤工具调用叙述 验证: tsc --noEmit 零错误, vitest 343 pass (1 pre-existing fail) --- desktop/src/components/ChatArea.tsx | 40 +++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/desktop/src/components/ChatArea.tsx b/desktop/src/components/ChatArea.tsx index 0cf7cb2..b601a4a 100644 --- a/desktop/src/components/ChatArea.tsx +++ b/desktop/src/components/ChatArea.tsx @@ -34,7 +34,6 @@ import { ModelSelector } from './ai/ModelSelector'; import { isTauriRuntime } from '../lib/tauri-gateway'; import { SuggestionChips } from './ai/SuggestionChips'; import { PipelineResultPreview } from './pipeline/PipelineResultPreview'; -import { PresentationContainer } from './presentation/PresentationContainer'; // TokenMeter temporarily unused — using inline text counter instead // Default heights for virtualized messages @@ -637,12 +636,36 @@ export function ChatArea({ compact, onOpenDetail }: { compact?: boolean; onOpenD ); } +/** + * Strip LLM tool-usage narration from response content. + * When the LLM calls tools (search, fetch, etc.), it often narrates its reasoning + * in English ("Now let me execute...", "I need to provide...", "I keep getting errors...") + * and Chinese ("让我执行...", "让我尝试..."). These are internal thoughts, not user-facing content. + */ +function stripToolNarration(content: string): string { + const sentences = content.split(/(?<=[。!?.!?])\s*/); + const filtered = sentences.filter(s => { + const t = s.trim(); + if (!t) return false; + // English narration patterns + if (/^(?:Now )?[Ll]et me\s/i.test(t)) return false; + if (/^I\s+(?:need to|keep getting|should|will try|have to|can try|must)\s/i.test(t)) return false; + if (/^The hand_researcher\s/i.test(t)) return false; + // Chinese narration patterns + if (/^让我(?:执行|尝试|使用|进一步|调用|运行)/.test(t)) return false; + if (/^好的,让我为您/.test(t)) return false; + return true; + }); + const result = filtered.join(' ').replace(/\s{2,}/g, ' ').trim(); + return result || content; // Fallback: if everything was stripped, show original +} + function MessageBubble({ message, onRetry }: { message: Message; setInput?: (text: string) => void; onRetry?: () => void }) { if (message.role === 'tool') { return null; } - // Researcher hand results are internal — search results are already in the LLM reply - if (message.role === 'hand' && message.handName === 'researcher') { + // Hand status/result messages are internal — search results are already in the LLM reply + if (message.role === 'hand') { return null; } @@ -721,15 +744,15 @@ function MessageBubble({ message, onRetry }: { message: Message; setInput?: (tex ? (isUser ? message.content : ) : '...'} - {/* Pipeline / Hand result presentation */} - {!isUser && (message.role === 'workflow' || message.role === 'hand') && message.workflowResult && typeof message.workflowResult === 'object' && message.workflowResult !== null && ( + {/* Pipeline result presentation */} + {!isUser && message.role === 'workflow' && message.workflowResult && typeof message.workflowResult === 'object' && message.workflowResult !== null && (
} @@ -737,11 +760,6 @@ function MessageBubble({ message, onRetry }: { message: Message; setInput?: (tex />
)} - {!isUser && message.role === 'hand' && message.handResult && typeof message.handResult === 'object' && message.handResult !== null && !message.workflowResult && message.handName !== 'researcher' && ( -
- -
- )} {message.error && (

{message.error}