From 9b6bdaea323535b191ae0fd841abfdfce443203b Mon Sep 17 00:00:00 2001 From: thzxx Date: Tue, 7 Jul 2026 22:02:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=8A=B6=E6=80=81=E6=8C=87=E7=A4=BA?= =?UTF-8?q?=E5=99=A8=E9=87=8D=E5=8F=A0=E4=B8=8E=20Trace=20Viewer=20?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. StreamingIndicator: 最后一条是 assistant 消息时不显示'正在连接', 避免与 AssistantMessage 的'正在思考'重叠 2. useAgentStream: 跳过 INIT(iteration=0) 的 trace step, 引擎初始化无实际内容不应展示 3. useAgentStream: 状态转换追加到 states 数组而非覆盖 state 字段, 保留完整 THINKING->EXECUTING->OBSERVING 进度 4. TraceStep: header 显示状态进度链(如'思考 -> 执行 -> 观察')而非仅最终状态 5. agent-store: 旧数据兼容, 加载时为缺少 states 的 trace step 补全 --- src/components/chat/StreamingIndicator.tsx | 9 ++-- src/components/trace/TraceStep.tsx | 7 ++- src/hooks/useAgentStream.ts | 52 +++++++++++++--------- src/stores/agent-store.ts | 4 +- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/components/chat/StreamingIndicator.tsx b/src/components/chat/StreamingIndicator.tsx index a3c236c..3edbb47 100644 --- a/src/components/chat/StreamingIndicator.tsx +++ b/src/components/chat/StreamingIndicator.tsx @@ -17,12 +17,9 @@ export function StreamingIndicator(): React.JSX.Element | null { if (!isStreaming) return null; const lastMsg = messages[messages.length - 1]; - // 仅当最后一条是 assistant 消息且有内容时才隐藏 - // 如果最后一条是 user 消息(刚发送,等待 AI 响应),应显示加载指示器 - const isLastAssistant = lastMsg?.role === 'assistant'; - const hasVisibleContent = isLastAssistant && - (!!lastMsg?.content || !!lastMsg?.reasoningContent || !!lastMsg?.toolCalls?.length); - if (hasVisibleContent) return null; + // 最后一条是 assistant 消息时隐藏(即使内容为空,AssistantMessage 会显示"正在思考...") + // 仅当最后一条是 user 消息(刚发送,等待 AI 响应)时显示加载指示器 + if (lastMsg?.role === 'assistant') return null; return ( diff --git a/src/components/trace/TraceStep.tsx b/src/components/trace/TraceStep.tsx index 8a6f686..70dea12 100644 --- a/src/components/trace/TraceStep.tsx +++ b/src/components/trace/TraceStep.tsx @@ -64,13 +64,18 @@ export function TraceStep({ step, isCurrent }: TraceStepProps): React.JSX.Elemen const label = TRACE_STATE_LABELS[step.state] ?? step.state; const duration = step.completedAt ? step.completedAt - step.startedAt : null; + // 构建状态进度链(如 "思考 → 执行 → 观察") + const stateChain = (step.states ?? [step.state]) + .map((s) => TRACE_STATE_LABELS[s] ?? s) + .join(' → '); + return ( {expanded ? : } {isCurrent ? : step.completedAt ? : } #{step.iteration} - {label} + {stateChain} {duration != null && {formatDuration(duration)}} {step.tokenUsage && {formatTokens(step.tokenUsage.totalTokens)} tok} diff --git a/src/hooks/useAgentStream.ts b/src/hooks/useAgentStream.ts index 21fc66a..9f3b8f5 100644 --- a/src/hooks/useAgentStream.ts +++ b/src/hooks/useAgentStream.ts @@ -265,29 +265,41 @@ export function useAgentStream(): void { } } - // --- Trace 步骤管理(每轮迭代一个步骤,状态转换时更新而非新建)--- + // --- Trace 步骤管理(每轮迭代一个步骤,状态转换时追加到 states 数组)--- if (data.state && data.iteration != null) { - const traceSteps = store.traceSteps; - const lastStep = traceSteps[traceSteps.length - 1]; - - // 判断是否需要创建新步骤:同一迭代的首次状态(INIT/THINKING)创建新步骤 - // 后续状态转换(EXECUTING/OBSERVING等)只更新当前步骤的 state 字段 - const isSameIteration = lastStep && lastStep.iteration === data.iteration; - - if (isSameIteration) { - // 同一迭代内的状态转换 → 更新当前步骤状态 - store.updateLastTraceStep({ state: data.state }); + // 跳过 INIT (iteration=0):引擎初始化阶段无实际内容,不创建 trace step + if (data.state === 'INIT' && data.iteration === 0) { + // 仍需更新 Agent 状态映射 } else { - // 新迭代 → 标记上一步完成,创建新步骤 - if (lastStep && !lastStep.completedAt) { - store.updateLastTraceStep({ completedAt: Date.now() }); + const traceSteps = store.traceSteps; + const lastStep = traceSteps[traceSteps.length - 1]; + + // 判断是否需要创建新步骤:同一迭代的首次状态创建新步骤 + // 后续状态转换(EXECUTING/OBSERVING等)追加到 states 数组 + const isSameIteration = lastStep && lastStep.iteration === data.iteration; + + if (isSameIteration) { + // 同一迭代内的状态转换 → 追加状态到 states 数组,更新当前 state + const currentStates = lastStep.states ?? [lastStep.state]; + if (currentStates[currentStates.length - 1] !== data.state) { + store.updateLastTraceStep({ + state: data.state, + states: [...currentStates, data.state], + }); + } + } else { + // 新迭代 → 标记上一步完成,创建新步骤 + if (lastStep && !lastStep.completedAt) { + store.updateLastTraceStep({ completedAt: Date.now() }); + } + store.addTraceStep({ + id: `trace_${data.iteration}_${data.state}_${Date.now()}`, + iteration: data.iteration, + state: data.state, + states: [data.state], + startedAt: Date.now(), + }); } - store.addTraceStep({ - id: `trace_${data.iteration}_${data.state}_${Date.now()}`, - iteration: data.iteration, - state: data.state, - startedAt: Date.now(), - }); } // --- Agent 状态映射 --- diff --git a/src/stores/agent-store.ts b/src/stores/agent-store.ts index b6d1430..cb2ac91 100644 --- a/src/stores/agent-store.ts +++ b/src/stores/agent-store.ts @@ -59,6 +59,7 @@ export interface TraceStep { id: string; iteration: number; state: string; + states: string[]; startedAt: number; completedAt?: number; thought?: string; @@ -163,10 +164,11 @@ export const useAgentStore = create((set, get) => ({ window.metona.sessions.getTrace(id).then((data) => { if (data) { if (data.traceSteps) { - // 兼容旧数据:为缺少 id 的 trace 步骤生成 id + // 兼容旧数据:为缺少 id/states 的 trace 步骤补全 const steps = (data.traceSteps as TraceStep[]).map((t, i) => ({ ...t, id: t.id ?? `trace_legacy_${t.iteration}_${t.state}_${i}`, + states: t.states ?? [t.state], })); set({ traceSteps: steps }); }