fix(desktop): 隐藏Hand状态消息 + 过滤LLM工具调用叙述
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
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)
This commit is contained in:
@@ -34,7 +34,6 @@ import { ModelSelector } from './ai/ModelSelector';
|
|||||||
import { isTauriRuntime } from '../lib/tauri-gateway';
|
import { isTauriRuntime } from '../lib/tauri-gateway';
|
||||||
import { SuggestionChips } from './ai/SuggestionChips';
|
import { SuggestionChips } from './ai/SuggestionChips';
|
||||||
import { PipelineResultPreview } from './pipeline/PipelineResultPreview';
|
import { PipelineResultPreview } from './pipeline/PipelineResultPreview';
|
||||||
import { PresentationContainer } from './presentation/PresentationContainer';
|
|
||||||
// TokenMeter temporarily unused — using inline text counter instead
|
// TokenMeter temporarily unused — using inline text counter instead
|
||||||
|
|
||||||
// Default heights for virtualized messages
|
// 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 }) {
|
function MessageBubble({ message, onRetry }: { message: Message; setInput?: (text: string) => void; onRetry?: () => void }) {
|
||||||
if (message.role === 'tool') {
|
if (message.role === 'tool') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Researcher hand results are internal — search results are already in the LLM reply
|
// Hand status/result messages are internal — search results are already in the LLM reply
|
||||||
if (message.role === 'hand' && message.handName === 'researcher') {
|
if (message.role === 'hand') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,15 +744,15 @@ function MessageBubble({ message, onRetry }: { message: Message; setInput?: (tex
|
|||||||
? (isUser
|
? (isUser
|
||||||
? message.content
|
? message.content
|
||||||
: <StreamingText
|
: <StreamingText
|
||||||
content={message.content}
|
content={stripToolNarration(message.content)}
|
||||||
isStreaming={!!message.streaming}
|
isStreaming={!!message.streaming}
|
||||||
className="text-gray-700 dark:text-gray-200"
|
className="text-gray-700 dark:text-gray-200"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: '...'}
|
: '...'}
|
||||||
</div>
|
</div>
|
||||||
{/* Pipeline / Hand result presentation */}
|
{/* Pipeline result presentation */}
|
||||||
{!isUser && (message.role === 'workflow' || message.role === 'hand') && message.workflowResult && typeof message.workflowResult === 'object' && message.workflowResult !== null && (
|
{!isUser && message.role === 'workflow' && message.workflowResult && typeof message.workflowResult === 'object' && message.workflowResult !== null && (
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<PipelineResultPreview
|
<PipelineResultPreview
|
||||||
outputs={message.workflowResult as Record<string, unknown>}
|
outputs={message.workflowResult as Record<string, unknown>}
|
||||||
@@ -737,11 +760,6 @@ function MessageBubble({ message, onRetry }: { message: Message; setInput?: (tex
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isUser && message.role === 'hand' && message.handResult && typeof message.handResult === 'object' && message.handResult !== null && !message.workflowResult && message.handName !== 'researcher' && (
|
|
||||||
<div className="mt-3">
|
|
||||||
<PresentationContainer data={message.handResult} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{message.error && (
|
{message.error && (
|
||||||
<div className="flex items-center gap-2 mt-2">
|
<div className="flex items-center gap-2 mt-2">
|
||||||
<p className="text-xs text-red-500">{message.error}</p>
|
<p className="text-xs text-red-500">{message.error}</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user