From 9f7aed0b2d2ef60ef8ce853a433e09c576fea77a Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Tue, 15 Sep 2026 17:37:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E6=8A=98?= =?UTF-8?q?=E5=8F=A0=E5=9D=97=E8=A1=A5=E9=BD=90=E8=80=97=E6=97=B6=E4=B8=8E?= =?UTF-8?q?=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 toolCallGroupPresentation.ts 的耗时 / 时间纯函数:toolCallDurationMs、formatToolCallDuration(0.4s / 12.3s / 2m 5s)、turnToolCallDurationMs、formatTurnDuration(42秒 / 4分钟 / 5分钟 45秒)、formatClockTime、turnToolCallTimeLabel - ToolCallGroup:块头右侧显示总用时(min(startedAt) → max(updatedAt))与该回合结束时间(拿得到同回合用户消息时间时显示 发送 → 结束) - ToolCallGroup:展开态每行右侧显示该工具自己的耗时;startedAt 为 0 或时间倒序时不显示耗时(不出现 0s / 负数),running 行显示 执行中 - ToolCallGroup:块与行都带 data-duration-ms(无法计算时为空串),块尾补一行 结束于 HH:mm - ProjectSupervisorView:按回合把用户消息的 updatedAt 传给块,用于 发送 → 结束 时间显示 - 用例:耗时格式化边界(0 / <1s / 整秒 / ≥60s / 时间倒序)、总用时、每行耗时、无时间戳不渲染耗时 - 技术方案文档补耗时 / 时间 / data-* 契约 --- .../ProjectSupervisorView.tsx | 32 ++++ .../project-workspace/ToolCallGroup.tsx | 53 ++++++- .../toolCallGroupPresentation.ts | 123 +++++++++++++++ .../appSurface/project-development.suite.ts | 28 +++- .../tests/appSurface/tool-call-group.suite.ts | 149 ++++++++++++++++++ ...方案】GameAgent对话工具调用卡片-2026-09-14.md | 4 +- 6 files changed, 381 insertions(+), 8 deletions(-) diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index 0d7c44c42..af2336e9a 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -70,6 +70,16 @@ function directCodexTurnMessageId(turnId: string, role: 'user' | 'assistant') { return `direct-codex:${turnId}:${role}`; } +/** 从 `direct-codex::assistant` 反解回合 id;不是这个形状返回 `null`。 */ +function directCodexTurnIdFromAssistantMessageId(messageId: string) { + const prefix = 'direct-codex:'; + const suffix = ':assistant'; + if (!messageId.startsWith(prefix) || !messageId.endsWith(suffix)) { + return null; + } + return messageId.slice(prefix.length, -suffix.length); +} + type RuntimePanelProps = ComponentProps; function directStatusTitle(status: string | null | undefined) { @@ -233,6 +243,7 @@ export function ProjectSupervisorView({ const streamTurnId = activeTurnId?.trim() ?? ''; const toolCallsByAnchor = new Map(); const liveToolCalls: GameCreatorDirectToolCall[] = []; + let liveToolCallTurnId = ''; if (directCodex) { for (const call of toolCalls) { const expected = directCodexTurnMessageId(call.turnId, 'assistant'); @@ -249,10 +260,24 @@ export function ProjectSupervisorView({ continue; } if (streamTurnId && call.turnId === streamTurnId) { + if (!liveToolCallTurnId) { + liveToolCallTurnId = call.turnId; + } liveToolCalls.push(call); } } } + // 该回合用户消息的 `updatedAt`:拿得到就在块头显示「发送 → 结束」,拿不到只显示结束时间。 + const userMessageUpdatedAtForTurn = (turnId: string) => { + if (!turnId) { + return 0; + } + const userId = directCodexTurnMessageId(turnId, 'user'); + return ( + visibleMessages.find((message) => message.messageId === userId) + ?.updatedAt ?? 0 + ); + }; const emptyState = directCodex && visibleMessages.length === 0 && @@ -363,11 +388,17 @@ export function ProjectSupervisorView({ const anchoredToolCalls = message.messageId ? (toolCallsByAnchor.get(message.messageId) ?? []) : []; + const anchoredTurnId = message.messageId + ? directCodexTurnIdFromAssistantMessageId(message.messageId) + : null; return ( {anchoredToolCalls.length > 0 ? ( ) : null} @@ -383,6 +414,7 @@ export function ProjectSupervisorView({ {liveToolCalls.length > 0 ? ( ) : null} diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ToolCallGroup.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ToolCallGroup.tsx index 781c12f94..62fbf8f27 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ToolCallGroup.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ToolCallGroup.tsx @@ -10,26 +10,36 @@ import { useId, useState } from 'react'; import type { GameCreatorDirectToolCall } from '../../app/types'; import { + formatClockTime, + formatToolCallDuration, + formatTurnDuration, + toolCallDurationMs, toolCallGroupSummary, toolCallRowText, + turnToolCallDurationMs, + turnToolCallEndedAt, + turnToolCallTimeLabel, } from './toolCallGroupPresentation'; /** * 一回合的工具调用折叠块(Codex 风格): - * 块头一行汇总,展开态每行一条工具(行可二级展开看命令 / 文件明细 / 输出)。 + * 块头一行汇总 + 该回合总用时 + 结束时间,展开态每行一条工具(行可二级展开看命令 / 文件明细 / 输出)。 * 契约见 `docs/technical/【技术方案】GameAgent对话工具调用卡片-2026-09-14.md`。 * * 无障碍:块头与每一行都是 `