diff --git a/scripts/web-live-dom-probe.mjs b/scripts/web-live-dom-probe.mjs index 2cea5db5..547219e6 100644 --- a/scripts/web-live-dom-probe.mjs +++ b/scripts/web-live-dom-probe.mjs @@ -1,5 +1,5 @@ #!/usr/bin/env node -// SPEC: PJ2026-010401 Web工作台 - Workbench 可见性与性能探针 +// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0; PJ2026-010401 Web工作台 - Workbench 可见性与性能探针 import { createHash } from "node:crypto"; import { spawn } from "node:child_process"; import fs from "node:fs"; @@ -936,7 +936,27 @@ async function collectTraceTimelineSample(page, args, index) { const trace = latestAgent?.querySelector(".trace-timeline") ?? null; const details = trace?.querySelector("details") ?? null; const rows = latestAgent ? [...latestAgent.querySelectorAll(".trace-render-row")] : []; + const rowPreview = (row) => row?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 240) ?? null; + const rowSummary = (row) => row ? { + rowId: row.getAttribute("data-row-id") ?? null, + kind: row.getAttribute("data-row-kind") ?? null, + terminal: row.getAttribute("data-terminal") === "true", + status: row.getAttribute("data-event-status") ?? null, + preview: rowPreview(row) + } : null; + const isUserVisibleOutputRow = (row) => { + const rowId = row.getAttribute("data-row-id") ?? ""; + const kind = row.getAttribute("data-row-kind") ?? ""; + const text = row.textContent?.replace(/\s+/gu, " ").trim() ?? ""; + if (kind === "tool" || rowId.startsWith("tool:")) return true; + if (rowId.startsWith("trace-final-response:")) return true; + if (/助手(?:最终)?消息|assistant (?:final )?message|final response/iu.test(text)) return true; + if (row.getAttribute("data-terminal") === "true" && row.querySelector(".trace-row-body")?.textContent?.trim()) return true; + return false; + }; const latestRow = rows.at(-1) ?? null; + const userVisibleRows = rows.filter(isUserVisibleOutputRow); + const latestUserVisibleRow = userVisibleRows.at(-1) ?? null; const empty = latestAgent?.querySelector(".trace-empty") ?? null; const finalResponse = latestAgent?.querySelector(".message-final-response") ?? null; return { @@ -949,7 +969,11 @@ async function collectTraceTimelineSample(page, args, index) { traceOpen: details instanceof HTMLDetailsElement ? details.open : null, rowCount: rows.length, emptyLabel: empty?.textContent?.replace(/\s+/gu, " ").trim() ?? null, - latestRowPreview: latestRow?.textContent?.replace(/\s+/gu, " ").trim().slice(0, 240) ?? null, + latestRowPreview: rowPreview(latestRow), + latestRow: rowSummary(latestRow), + userVisibleRowCount: userVisibleRows.length, + latestUserVisibleRowPreview: rowPreview(latestUserVisibleRow), + latestUserVisibleRow: rowSummary(latestUserVisibleRow), finalTextChars: finalResponse?.textContent?.trim().length ?? 0 }; }, index); @@ -1057,6 +1081,7 @@ function compactSessionState(state) { function summarizeTraceRun(samples, dom, sessionSummary) { const firstDomRow = samples.find((sample) => Number(sample.rowCount ?? 0) > 0) ?? null; + const firstUserVisibleRow = samples.find((sample) => Number(sample.userVisibleRowCount ?? 0) > 0) ?? null; const firstRestEvent = samples.find((sample) => Number(sample.traceFetch?.eventCount ?? 0) > 0) ?? null; const last = samples.at(-1) ?? null; const failedFetches = samples.filter((sample) => sample.traceFetch && sample.traceFetch.ok !== true); @@ -1069,8 +1094,12 @@ function summarizeTraceRun(samples, dom, sessionSummary) { finalAgentStatus: last?.agentStatus ?? null, finalTraceStatus: last?.traceStatus ?? null, finalDomRowCount: last?.rowCount ?? null, + finalUserVisibleDomRowCount: last?.userVisibleRowCount ?? null, firstDomRowVisibleAt: firstDomRow?.sampledAt ?? null, firstDomRowPreview: firstDomRow?.latestRowPreview ?? null, + firstUserVisibleRowAt: firstUserVisibleRow?.sampledAt ?? null, + firstUserVisibleRowPreview: firstUserVisibleRow?.latestUserVisibleRowPreview ?? null, + firstUserVisibleRow: firstUserVisibleRow?.latestUserVisibleRow ?? null, firstRestEventVisibleAt: firstRestEvent?.sampledAt ?? null, firstRestEventCount: firstRestEvent?.traceFetch?.eventCount ?? null, restTraceOk: samples.some((sample) => sample.traceFetch?.ok === true), @@ -1087,8 +1116,10 @@ function summarizeProbePerformance(actions, startedAt, samples, traceSummary) { startedAt, submittedAt, firstDomTraceRowAt: traceSummary?.firstDomRowVisibleAt ?? null, + firstUserVisibleTraceRowAt: traceSummary?.firstUserVisibleRowAt ?? null, firstRestTraceEventAt: traceSummary?.firstRestEventVisibleAt ?? null, submitToFirstDomTraceRowMs: submittedAt && traceSummary?.firstDomRowVisibleAt ? Math.max(0, new Date(traceSummary.firstDomRowVisibleAt).getTime() - new Date(submittedAt).getTime()) : null, + submitToFirstUserVisibleTraceRowMs: submittedAt && traceSummary?.firstUserVisibleRowAt ? Math.max(0, new Date(traceSummary.firstUserVisibleRowAt).getTime() - new Date(submittedAt).getTime()) : null, submitToFirstRestTraceEventMs: submittedAt && traceSummary?.firstRestEventVisibleAt ? Math.max(0, new Date(traceSummary.firstRestEventVisibleAt).getTime() - new Date(submittedAt).getTime()) : null, totalSampleWindowMs: samples.length >= 2 ? Math.max(0, new Date(samples.at(-1).sampledAt).getTime() - new Date(samples[0].sampledAt).getTime()) : 0 }; diff --git a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue index 9834f27e..ede8b71b 100644 --- a/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue +++ b/web/hwlab-cloud-web/src/components/agent/TraceTimeline.vue @@ -120,7 +120,7 @@ function terminalTraceEmptyLabel(status: unknown): string {
{{ row.header }}
diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts
index 4870d994..74602e05 100644
--- a/web/hwlab-cloud-web/src/stores/workbench.ts
+++ b/web/hwlab-cloud-web/src/stores/workbench.ts
@@ -370,7 +370,48 @@ export const useWorkbenchStore = defineStore("workbench", () => {
async function waitForPendingSessionCreate(conversationId: string): Promise