From a4b06c72849c910a24e7f538d2c3d26b5114c34d Mon Sep 17 00:00:00 2001 From: carry Date: Sun, 7 Jun 2026 15:18:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20tool=5Fcall=5Frequest/result=20?= =?UTF-8?q?=E6=8B=86=E5=88=86=E4=B8=BA=E7=8B=AC=E7=AB=8B=20role:tool=20?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=B0=94=E6=B3=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - protocol.ts: Message.role 增加 'tool' - MessageBubble: 新增 tool 角色配置(琥珀色,小写 label 统一) - MessageList: 新增 extractToolMessages(),将 tool segment 从 assistant/user 消息中拆分出来作为独立 tool 角色消息 - role label 改为小写(system/user/assistant/tool),字号 text-xs - demos.ts: e-4 改为 tool_call_result 形式 --- src/components/MessageBubble.tsx | 11 ++--- src/components/MessageList.tsx | 75 ++++++++++++++++++++++++++++++-- src/data/demos.ts | 11 ++--- src/types/protocol.ts | 2 +- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/components/MessageBubble.tsx b/src/components/MessageBubble.tsx index 150a11f..e542c18 100644 --- a/src/components/MessageBubble.tsx +++ b/src/components/MessageBubble.tsx @@ -1,11 +1,12 @@ import type { Message } from '../types/protocol' import SegmentRenderer from './SegmentRenderer' -import { User, Bot, Settings } from 'lucide-react' +import { User, Bot, Settings, Wrench } from 'lucide-react' const roleConfig = { - system: { icon: Settings, label: 'SYSTEM', align: 'left', bg: 'bg-gray-100', text: 'text-gray-500' }, - user: { icon: User, label: 'YOU', align: 'right', bg: 'bg-blue-50 border-blue-100', text: 'text-blue-600' }, - assistant: { icon: Bot, label: 'ASSISTANT', align: 'left', bg: 'bg-white border-gray-100', text: 'text-green-600' }, + system: { icon: Settings, label: 'system', align: 'left', bg: 'bg-gray-100', text: 'text-gray-500' }, + user: { icon: User, label: 'user', align: 'right', bg: 'bg-blue-50 border-blue-100', text: 'text-blue-600' }, + assistant: { icon: Bot, label: 'assistant', align: 'left', bg: 'bg-white border-gray-100', text: 'text-green-600' }, + tool: { icon: Wrench, label: 'tool', align: 'left', bg: 'bg-amber-50 border-amber-200', text: 'text-amber-700' }, } interface MessageBubbleProps { @@ -27,7 +28,7 @@ export default function MessageBubble({ message, varMap = {} }: MessageBubblePro {/* Role header */}
- + {cfg.label}
diff --git a/src/components/MessageList.tsx b/src/components/MessageList.tsx index a425105..60371a8 100644 --- a/src/components/MessageList.tsx +++ b/src/components/MessageList.tsx @@ -1,5 +1,5 @@ import { useMemo } from 'react' -import type { Message, StaticVarSegment } from '../types/protocol' +import type { Message, Segment, StaticVarSegment } from '../types/protocol' import SessionBar from './SessionBar' import MessageBubble from './MessageBubble' @@ -41,13 +41,82 @@ function extractSessionVars(messages: Message[]): { return { variables, varMap, cleanedMessages } } +/** + * 将消息中的 tool_call_request / tool_call_result segment + * 拆分为独立的 role: "tool" 消息,与非 tool segment 交替排列。 + * + * 例如一条 assistant 消息包含 [text, tool_call, tool_result, text] + * 会拆分为: + * assistant [text] → tool [tool_call] → tool [tool_result] → assistant [text] + */ +function extractToolMessages(messages: Message[]): Message[] { + const result: Message[] = [] + + for (const msg of messages) { + // 将 segments 按 "是否为 tool segment" 分组为连续的 run + const runs: Segment[][] = [] + let currentRun: Segment[] = [] + let currentIsTool: boolean | null = null + + for (const seg of msg.segments) { + const isTool = + seg.kind === 'tool_call_request' || seg.kind === 'tool_call_result' + if (currentIsTool === null) { + currentIsTool = isTool + currentRun.push(seg) + } else if (isTool === currentIsTool) { + currentRun.push(seg) + } else { + runs.push(currentRun) + currentRun = [seg] + currentIsTool = isTool + } + } + if (currentRun.length > 0) { + runs.push(currentRun) + } + + // 每个 run 生成一条独立消息 + let counter = 0 + for (const run of runs) { + if (run.length === 0) continue + const isTool = + run[0].kind === 'tool_call_request' || + run[0].kind === 'tool_call_result' + + if (isTool) { + counter++ + result.push({ + id: `${msg.id}-tool-${counter}`, + role: 'tool', + segments: run, + timestamp: msg.timestamp, + }) + } else { + result.push({ + ...msg, + id: counter > 0 ? `${msg.id}-text-${counter}` : msg.id, + segments: run, + }) + } + } + } + + return result +} + export default function MessageList({ messages }: MessageListProps) { const { variables, varMap, cleanedMessages } = useMemo( () => extractSessionVars(messages), [messages] ) - if (cleanedMessages.length === 0) { + const splitMessages = useMemo( + () => extractToolMessages(cleanedMessages), + [cleanedMessages] + ) + + if (splitMessages.length === 0) { return (
选择一个 Demo 场景开始 @@ -62,7 +131,7 @@ export default function MessageList({ messages }: MessageListProps) { {/* 消息列表 */}
- {cleanedMessages.map((msg) => ( + {splitMessages.map((msg) => ( ))}
diff --git a/src/data/demos.ts b/src/data/demos.ts index 43525c5..758e2d0 100644 --- a/src/data/demos.ts +++ b/src/data/demos.ts @@ -883,15 +883,16 @@ const demoE: PromptEnvelope = { ], timestamp: now - 280000, }, - // --- Skill 触发:指令以 system 消息形式追加到对话 --- - // (而非合并进原有的 System Prompt segment) + // --- Skill 触发:指令以 tool_call_result 形式追加到对话 --- { id: 'e-4', - role: 'system', + role: 'assistant', segments: [ { - kind: 'system_prompt', - content: `[/deep-research 已触发] + kind: 'tool_call_result', + toolName: 'run_skill', + success: true, + result: `[/deep-research 已触发] 深度研究工作流程: 1. 分析用户问题,拆解为 3-5 个子问题 diff --git a/src/types/protocol.ts b/src/types/protocol.ts index 2b46c42..4d871cb 100644 --- a/src/types/protocol.ts +++ b/src/types/protocol.ts @@ -12,7 +12,7 @@ export interface PromptEnvelope { // --- 单条消息 --- export interface Message { id: string - role: 'system' | 'user' | 'assistant' + role: 'system' | 'user' | 'assistant' | 'tool' segments: Segment[] timestamp: number }