diff --git a/web/hwlab-cloud-web/src/stores/workbench.ts b/web/hwlab-cloud-web/src/stores/workbench.ts index e29f7034..62e8bcf3 100644 --- a/web/hwlab-cloud-web/src/stores/workbench.ts +++ b/web/hwlab-cloud-web/src/stores/workbench.ts @@ -1418,18 +1418,36 @@ function nonBlockingProjection(projection: ProjectionDiagnostic | null): Project const turnId = firstNonEmptyString(lifecycle.turnId, canonicalTraceId); const userMessageId = firstNonEmptyString(lifecycle.userMessageId); const assistantMessageId = firstNonEmptyString(lifecycle.assistantMessageId); + const admissionTiming = optimisticAdmissionTimingPatch(lifecycle); if (!turnId && !userMessageId && !assistantMessageId && canonicalTraceId === traceId) return canonicalTraceId; updateActiveMessages((source) => source.map((message) => { if (message.traceId !== traceId) return message; const runnerTrace = message.runnerTrace ? { ...message.runnerTrace, traceId: canonicalTraceId } : message.runnerTrace; const base = { ...message, traceId: canonicalTraceId, runnerTrace, updatedAt: new Date().toISOString() }; if (message.role === "user" && userMessageId) return { ...base, id: userMessageId, messageId: userMessageId, turnId }; - if (message.role === "agent" && assistantMessageId) return { ...base, id: assistantMessageId, messageId: assistantMessageId, turnId }; + if (message.role === "agent" && assistantMessageId) return { ...base, ...admissionTiming, id: assistantMessageId, messageId: assistantMessageId, turnId }; return turnId ? { ...base, turnId } : base; })); return canonicalTraceId; } + function optimisticAdmissionTimingPatch(lifecycle: AgentChatResponse): Partial { + const patch = messageTimingPatch(lifecycle); + if (patch.timing?.startedAt && patch.timing?.lastEventAt) return patch; + const record = lifecycle as Record; + const admittedAt = firstNonEmptyString(record.startedAt, record.createdAt, record.updatedAt); + if (!admittedAt) return patch; + const timing = { + ...(patch.timing ?? {}), + startedAt: patch.timing?.startedAt ?? admittedAt, + lastEventAt: patch.timing?.lastEventAt ?? admittedAt, + finishedAt: null, + durationMs: null, + valuesRedacted: true, + } as WorkbenchTurnTimingProjection; + return { ...patch, timing, startedAt: timing.startedAt ?? null, lastEventAt: timing.lastEventAt ?? null, finishedAt: null, durationMs: null }; + } + async function clearActiveTrace(traceId: string, reason: string): Promise { if (currentRequest.value?.traceId === traceId) currentRequest.value = null; }