feat: tool_call_request/result 拆分为独立 role:tool 消息气泡
- 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 形式
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import type { Message } from '../types/protocol'
|
import type { Message } from '../types/protocol'
|
||||||
import SegmentRenderer from './SegmentRenderer'
|
import SegmentRenderer from './SegmentRenderer'
|
||||||
import { User, Bot, Settings } from 'lucide-react'
|
import { User, Bot, Settings, Wrench } from 'lucide-react'
|
||||||
|
|
||||||
const roleConfig = {
|
const roleConfig = {
|
||||||
system: { icon: Settings, label: 'SYSTEM', align: 'left', bg: 'bg-gray-100', text: 'text-gray-500' },
|
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' },
|
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' },
|
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 {
|
interface MessageBubbleProps {
|
||||||
@@ -27,7 +28,7 @@ export default function MessageBubble({ message, varMap = {} }: MessageBubblePro
|
|||||||
{/* Role header */}
|
{/* Role header */}
|
||||||
<div className={`flex items-center gap-1.5 mb-2 ${cfg.text}`}>
|
<div className={`flex items-center gap-1.5 mb-2 ${cfg.text}`}>
|
||||||
<Icon size={14} />
|
<Icon size={14} />
|
||||||
<span className="text-[10px] font-bold tracking-widest uppercase">
|
<span className="text-xs font-bold">
|
||||||
{cfg.label}
|
{cfg.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import type { Message, StaticVarSegment } from '../types/protocol'
|
import type { Message, Segment, StaticVarSegment } from '../types/protocol'
|
||||||
import SessionBar from './SessionBar'
|
import SessionBar from './SessionBar'
|
||||||
import MessageBubble from './MessageBubble'
|
import MessageBubble from './MessageBubble'
|
||||||
|
|
||||||
@@ -41,13 +41,82 @@ function extractSessionVars(messages: Message[]): {
|
|||||||
return { variables, varMap, cleanedMessages }
|
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) {
|
export default function MessageList({ messages }: MessageListProps) {
|
||||||
const { variables, varMap, cleanedMessages } = useMemo(
|
const { variables, varMap, cleanedMessages } = useMemo(
|
||||||
() => extractSessionVars(messages),
|
() => extractSessionVars(messages),
|
||||||
[messages]
|
[messages]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (cleanedMessages.length === 0) {
|
const splitMessages = useMemo(
|
||||||
|
() => extractToolMessages(cleanedMessages),
|
||||||
|
[cleanedMessages]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (splitMessages.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex items-center justify-center text-gray-300 text-sm">
|
<div className="flex-1 flex items-center justify-center text-gray-300 text-sm">
|
||||||
选择一个 Demo 场景开始
|
选择一个 Demo 场景开始
|
||||||
@@ -62,7 +131,7 @@ export default function MessageList({ messages }: MessageListProps) {
|
|||||||
|
|
||||||
{/* 消息列表 */}
|
{/* 消息列表 */}
|
||||||
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-1">
|
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-1">
|
||||||
{cleanedMessages.map((msg) => (
|
{splitMessages.map((msg) => (
|
||||||
<MessageBubble key={msg.id} message={msg} varMap={varMap} />
|
<MessageBubble key={msg.id} message={msg} varMap={varMap} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+6
-5
@@ -883,15 +883,16 @@ const demoE: PromptEnvelope = {
|
|||||||
],
|
],
|
||||||
timestamp: now - 280000,
|
timestamp: now - 280000,
|
||||||
},
|
},
|
||||||
// --- Skill 触发:指令以 system 消息形式追加到对话 ---
|
// --- Skill 触发:指令以 tool_call_result 形式追加到对话 ---
|
||||||
// (而非合并进原有的 System Prompt segment)
|
|
||||||
{
|
{
|
||||||
id: 'e-4',
|
id: 'e-4',
|
||||||
role: 'system',
|
role: 'assistant',
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
kind: 'system_prompt',
|
kind: 'tool_call_result',
|
||||||
content: `[/deep-research 已触发]
|
toolName: 'run_skill',
|
||||||
|
success: true,
|
||||||
|
result: `[/deep-research 已触发]
|
||||||
|
|
||||||
深度研究工作流程:
|
深度研究工作流程:
|
||||||
1. 分析用户问题,拆解为 3-5 个子问题
|
1. 分析用户问题,拆解为 3-5 个子问题
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface PromptEnvelope {
|
|||||||
// --- 单条消息 ---
|
// --- 单条消息 ---
|
||||||
export interface Message {
|
export interface Message {
|
||||||
id: string
|
id: string
|
||||||
role: 'system' | 'user' | 'assistant'
|
role: 'system' | 'user' | 'assistant' | 'tool'
|
||||||
segments: Segment[]
|
segments: Segment[]
|
||||||
timestamp: number
|
timestamp: number
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user