diff --git a/apps/web/tests/snapshots/client-ui-perf-panel/panel.expected.md b/apps/web/tests/snapshots/client-ui-perf-panel/panel.expected.md index 9db08b1afd..5a9e1a214f 100644 --- a/apps/web/tests/snapshots/client-ui-perf-panel/panel.expected.md +++ b/apps/web/tests/snapshots/client-ui-perf-panel/panel.expected.md @@ -1,6 +1,6 @@ - table "Performance": - rowgroup: - - row "Turn / step Trigger Execution Context Output Cache hit First token Model time Output speed (tok/s) Tool time Total time": + - row "Turn / step Trigger Execution Context Output Cache hit First token Model time Output speed Tool time Total time": - columnheader "Turn / step" - columnheader "Trigger" - columnheader "Execution" @@ -9,7 +9,7 @@ - columnheader "Cache hit" - columnheader "First token" - columnheader "Model time" - - columnheader "Output speed (tok/s)" + - columnheader "Output speed" - columnheader "Tool time" - columnheader "Total time" - rowgroup: diff --git a/packages/experimental/client-ui-perf/README.i18n.yaml b/packages/experimental/client-ui-perf/README.i18n.yaml index de167e5e27..8315fe2708 100644 --- a/packages/experimental/client-ui-perf/README.i18n.yaml +++ b/packages/experimental/client-ui-perf/README.i18n.yaml @@ -9,8 +9,8 @@ en: 092989f386525568 zh: 4a6276cfc268c851 /deepseek-ai-dsh-experimental-client-ui-perf/summary: - en: 284607c5791a44d9 - zh: a231eac33a8186a1 + en: 3f121ba393635463 + zh: bfd775b20cf080c3 /deepseek-ai-dsh-experimental-client-ui-perf/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb diff --git a/packages/experimental/client-ui-perf/README.md b/packages/experimental/client-ui-perf/README.md index 9e0a833cad..835b3fd829 100644 --- a/packages/experimental/client-ui-perf/README.md +++ b/packages/experimental/client-ui-perf/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Adds a Performance tab beside Chat and Trajectory. Each turn is one row; expanding it lists its steps with context tokens, output tokens, cache hit rate, first-token latency, model time, output speed, tool time, and a total-time bar split into first token, generation, tools, and the rest. A running step shows elapsed time instead of a blank. Scrolling to the top pulls earlier turns through the same Session paging the other views use, with a `Load earlier` fallback button, an explicit retry state for a page that failed or did not advance, and an end-of-history marker. When a step has no tool calls, its answer label is followed by the first line of the model reply, with the full text on hover. The Trigger column classifies what opened the turn: a human prompt shows its text, while a team/agent message, subagent settlement, goal round, scheduled/webhook/job run, or runtime/time context shows a localized trigger badge plus the triggering text; a team or agent message also names its sender in the badge. A prompt that attached images shows an image-count pill in the same style as the trigger badge. Hovering or keyboard-focusing the pill opens a small preview strip, and clicking a preview loads that image in the shared image viewer; an image-only prompt shows the pill instead of an empty cell. The panel reads Session events and the prompt's durable images; it registers no tool, prompt section, or service. +Adds a Performance tab beside Chat and Trajectory. Each turn is one row; expanding it lists its steps with context tokens, output tokens, cache hit rate, first-token latency, model time, output speed, tool time, and a total-time bar split into first token, generation, tools, and the rest. A running step shows elapsed time instead of a blank. Scrolling to the top pulls earlier turns through the same Session paging the other views use, with a `Load earlier` fallback button, an explicit retry state for a page that failed or did not advance, and an end-of-history marker. When a step has no tool calls, its answer label is followed by the first line of the model reply, with the full text on hover. The Trigger column classifies what opened the turn: a human prompt shows its text, while a team/agent message, subagent settlement, goal round, scheduled/webhook/job run, or runtime/time context shows a localized trigger badge plus the triggering text; a team or agent message names its member in the badge (`团队消息 · hfai-deploy`) and omits opaque session ids, while a subagent settlement shows its tag and the settled report with the transport framing stripped. A prompt that attached images shows an image-count pill in the same style as the trigger badge. Hovering or keyboard-focusing the pill opens a small preview strip, and clicking a preview loads that image in the shared image viewer; an image-only prompt shows the pill instead of an empty cell. The panel reads Session events and the prompt's durable images; it registers no tool, prompt section, or service. ## Table of Contents diff --git a/packages/experimental/client-ui-perf/README.zh.md b/packages/experimental/client-ui-perf/README.zh.md index 933d0c0ac5..65c80edca4 100644 --- a/packages/experimental/client-ui-perf/README.zh.md +++ b/packages/experimental/client-ui-perf/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -在「对话」和「轨迹」旁边加一个「性能」页签。每一轮一行,展开后列出各步的上下文词数、输出词数、缓存命中率、首 token 延迟、模型用时、输出速度、工具调用用时,以及一条按首 token、生成、工具调用、其余四段拆开的总用时条。还在跑的步显示已用时间,而不是留空。「触发」列会说明这一轮由什么触发:人写的提问显示原文,团队/智能体消息、子代理完成、目标轮次、定时/Webhook/后台任务或上下文唤醒显示本地化触发标签加触发文本;团队或智能体消息还会在标签里写明发送方。带图的提问在「触发」列显示与触发标签同样式的张数标签;悬停或键盘聚焦标签会浮出小图预览,点击预览用共用看图器加载该图,只有图片的提问显示标签而不是空单元格。页签只读会话事件和提问里的持久图片,不注册工具、提示词段落或服务。 +在「对话」和「轨迹」旁边加一个「性能」页签。每一轮一行,展开后列出各步的上下文词数、输出词数、缓存命中率、首 token 延迟、模型用时、输出速度、工具调用用时,以及一条按首 token、生成、工具调用、其余四段拆开的总用时条。还在跑的步显示已用时间,而不是留空。「触发」列会说明这一轮由什么触发:人写的提问显示原文,团队/智能体消息、子代理完成、目标轮次、定时/Webhook/后台任务或上下文唤醒显示本地化触发标签加触发文本;团队或智能体消息还会在标签里写明成员(如「团队消息 · hfai-deploy」),无法读懂的会话 UUID 不显示;子代理完成同样显示标签,正文去掉传输前缀后截断展示。带图的提问在「触发」列显示与触发标签同样式的张数标签;悬停或键盘聚焦标签会浮出小图预览,点击预览用共用看图器加载该图,只有图片的提问显示标签而不是空单元格。页签只读会话事件和提问里的持久图片,不注册工具、提示词段落或服务。 ## 目录 diff --git a/packages/experimental/client-ui-perf/src/client/PerfView.tsx b/packages/experimental/client-ui-perf/src/client/PerfView.tsx index 1919df4a50..3a9cb3391e 100644 --- a/packages/experimental/client-ui-perf/src/client/PerfView.tsx +++ b/packages/experimental/client-ui-perf/src/client/PerfView.tsx @@ -1053,19 +1053,20 @@ function PromptCell({ turn, t, className, loadImage }: PromptCellProps): ReactEl : const source = turn.promptSource if (source !== null && source !== 'user') { - // Automated turns explain what woke them; team messages also name the sender. - const base = triggerLabel(source, t) + // Automated turns show the wake tag, the named sender when known, and the + // body with its transport framing removed. + const raw = turn.prompt ?? '' + const base = triggerLabelOf(raw, source, t) const sender = turn.promptSender const label = sender === null ? base : `${base} · ${sender}` - const raw = turn.prompt ?? '' - const text = sender === null - ? raw - : raw.replace(/^(?:Team message from [^:]+:|Agent \S+ sent a message:)\s*/u, '') + const text = triggerBodyOf(raw) return ( - {label} - {pill} - {text === '' ? null : {text}} + + {label} + {pill} + {text === '' ? null : {text}} + ) } @@ -1086,6 +1087,26 @@ function PromptCell({ turn, t, className, loadImage }: PromptCellProps): ReactEl ) } +const TEAM_FRAME = /^Team message from /u +const AGENT_FRAME = /^Agent \S+ sent a message:/u +const SUBAGENT_FRAME = /^Background subagent \S+ finished/u + +/** Label of the concrete wake frame, falling back to the producer source kind. */ +function triggerLabelOf(text: string, source: string, t: PerfTranslate): string { + if (TEAM_FRAME.test(text)) return t('trigger.team-message') + if (source === 'subagent-settled' && SUBAGENT_FRAME.test(text)) return t('trigger.subagent-settled') + if (source === 'agent-message' && AGENT_FRAME.test(text)) return t('trigger.agent-message') + return triggerLabel(source, t) +} + +/** Remove the transport framing so only the message body is previewed. */ +function triggerBodyOf(text: string): string { + return text + .replace(/^Team message from [^:]+:\s*/u, '') + .replace(/^Agent \S+ sent a message:\s*/u, '') + .replace(/^Background subagent \S+ finished\s*/u, '') +} + /** Localized label for one automated turn trigger. */ function triggerLabel(source: string, t: PerfTranslate): string { switch (source) { diff --git a/packages/experimental/client-ui-perf/src/client/locales.ts b/packages/experimental/client-ui-perf/src/client/locales.ts index c0a7a5adb7..4817eef44c 100644 --- a/packages/experimental/client-ui-perf/src/client/locales.ts +++ b/packages/experimental/client-ui-perf/src/client/locales.ts @@ -10,8 +10,8 @@ export const zh = { 'column.scope.title': '轮次行是该轮的合计;点一行展开或折叠它的每一步', 'column.prompt': '触发', 'column.prompt.title': '这一轮是由什么触发的;人写的提示词显示正文,自动触发显示来源标签', - 'trigger.agent-message': '收到任务消息', - 'trigger.team-message': '收到团队消息', + 'trigger.agent-message': '智能体消息', + 'trigger.team-message': '团队消息', 'trigger.schedule': '定时触发', 'trigger.webhook': 'Webhook', 'trigger.tool-jobs': '后台任务', @@ -145,7 +145,7 @@ export const en: Record = { 'column.scope.title': 'A turn row holds the turn total; click a row to expand or collapse its steps', 'column.prompt': 'Trigger', 'column.prompt.title': 'What opened this turn: a human prompt shows its text, automation shows a source badge', - 'trigger.agent-message': 'Task message', + 'trigger.agent-message': 'Agent message', 'trigger.team-message': 'Team message', 'trigger.schedule': 'Scheduled', 'trigger.webhook': 'Webhook', @@ -267,8 +267,8 @@ export const ja: Record = { 'column.scope.title': 'ターン行はそのターンの合計です。行をクリックすると各ステップを展開または折りたたみます', 'column.prompt': 'トリガー', 'column.prompt.title': 'このターンを開始したものを表示します。人間のプロンプトは本文、自動トリガーはタグを表示します', - 'trigger.agent-message': 'タスクメッセージ受信', - 'trigger.team-message': 'チームメッセージ受信', + 'trigger.agent-message': 'エージェントメッセージ', + 'trigger.team-message': 'チームメッセージ', 'trigger.schedule': 'スケジュール実行', 'trigger.webhook': 'Webhook', 'trigger.tool-jobs': 'バックグラウンドジョブ', diff --git a/packages/experimental/client-ui-perf/src/client/perf-fold.ts b/packages/experimental/client-ui-perf/src/client/perf-fold.ts index f52f0b5c82..751ae28281 100644 --- a/packages/experimental/client-ui-perf/src/client/perf-fold.ts +++ b/packages/experimental/client-ui-perf/src/client/perf-fold.ts @@ -256,6 +256,7 @@ function isTokenDelta(chunk: StreamChunk): boolean { const TEAM_MESSAGE_PREFIX = /^Team message from ([^:]+):/u const AGENT_MESSAGE_PREFIX = /^Agent (\S+) sent a message:/u +const OPAQUE_SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu /** Sender agent name of a teammate/agent notification, parsed from its body prefix. */ export function triggerSenderOf(message: UserMessage): string | null { @@ -269,7 +270,9 @@ export function triggerSenderOf(message: UserMessage): string | null { /* v8 ignore next -- a matched capture group is always present. */ if (team !== null) return team[1]?.trim() ?? null const agent = AGENT_MESSAGE_PREFIX.exec(text) - return agent?.[1] ?? null + const id = agent?.[1] ?? null + // A raw session UUID is not a readable member name; keep the badge generic. + return id !== null && OPAQUE_SESSION_ID.test(id) ? null : id } /** Answer characters kept for the one-line Did preview. */ diff --git a/packages/experimental/client-ui-perf/src/client/perf.module.css b/packages/experimental/client-ui-perf/src/client/perf.module.css index 4a1fd90953..a1670b5268 100644 --- a/packages/experimental/client-ui-perf/src/client/perf.module.css +++ b/packages/experimental/client-ui-perf/src/client/perf.module.css @@ -237,7 +237,24 @@ } /* Did column: tool names, hints, and output sizes. */ +.promptClip { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} + +.promptClip .promptText { + flex: 1; + min-width: 0; + max-width: none; +} + .triggerPill { + flex: none; + max-width: 70%; + overflow: hidden; + text-overflow: ellipsis; margin-right: 4px; padding: 0 4px; border: 0.5px solid var(--dsw-alias-border-l2); diff --git a/packages/experimental/client-ui-perf/tests/perf-fold.client.spec.ts b/packages/experimental/client-ui-perf/tests/perf-fold.client.spec.ts index d4c0833dda..3abac3d09c 100644 --- a/packages/experimental/client-ui-perf/tests/perf-fold.client.spec.ts +++ b/packages/experimental/client-ui-perf/tests/perf-fold.client.spec.ts @@ -734,6 +734,10 @@ describe('trigger sender helper', () => { expect(triggerSenderOf(userMessage([textBlock('Agent abc-123 sent a message: hi')], { kind: 'agent-message', }))).toBe('abc-123') + // Raw session UUIDs are not member names. + expect(triggerSenderOf(userMessage([textBlock('Agent 52224ac7-b02b-4d25-8c6e-123456789abc sent a message: hi')], { + kind: 'agent-message', + }))).toBeNull() }) it('returns nothing without a framed sender', () => { diff --git a/packages/experimental/client-ui-perf/tests/perf-view.client.spec.tsx b/packages/experimental/client-ui-perf/tests/perf-view.client.spec.tsx index cea8b5fee4..46ac88bc12 100644 --- a/packages/experimental/client-ui-perf/tests/perf-view.client.spec.tsx +++ b/packages/experimental/client-ui-perf/tests/perf-view.client.spec.tsx @@ -597,6 +597,37 @@ describe('turn rows', () => { expect(cell.getAttribute('title')).toBe('Team message from hfai-deploy: P1 已 rebase 完成') }) + it('shows the frame label and body without opaque session ids', () => { + mount({ + turns: [ + turn({ + turn: 1, + prompt: 'Agent 52224ac7-b02b-4d25-8c6e-123456789abc sent a message: 请检查构建', + promptSource: 'agent-message', + }), + turn({ + turn: 2, + prompt: 'Background subagent 10a0dbaa-08dd-4e3f-838d-d6f84b36babe finished and will do no further work.', + promptSource: 'subagent-settled', + }), + turn({ turn: 3, prompt: 'Team message from stack-rebase: P2 已合并', promptSource: 'team-message', promptSender: 'stack-rebase' }), + ], + stepCount: 3, + }) + const first = cellsOf(rowWithPrefix(say('turn.label', { turn: 1 })))[1] as HTMLTableCellElement + expect(first.textContent).toContain(say('trigger.agent-message')) + expect(first.textContent).not.toContain('52224ac7') + expect(first.textContent).toContain('请检查构建') + const second = cellsOf(rowWithPrefix(say('turn.label', { turn: 2 })))[1] as HTMLTableCellElement + expect(second.textContent).toContain(say('trigger.subagent-settled')) + expect(second.textContent).not.toContain('10a0dbaa') + expect(second.textContent).toContain('and will do no further work.') + const third = cellsOf(rowWithPrefix(say('turn.label', { turn: 3 })))[1] as HTMLTableCellElement + expect(third.textContent).toContain(`${say('trigger.team-message')} · stack-rebase`) + expect(third.textContent).toContain('P2 已合并') + expect(third.textContent).not.toContain('Team message from') + }) + it('summarizes turn tools with pills, overflow, errors, and output size', () => { mount({ turns: [turn({