Smooth WebUI streaming with state-driven viewport motion (#4696)

This commit is contained in:
chengyongru
2026-07-26 00:18:24 +08:00
committed by GitHub
parent 9a7debcb48
commit b0ef759e2c
28 changed files with 3202 additions and 670 deletions
+40 -1
View File
@@ -24,6 +24,44 @@ export function normalizeLegacyLongTaskMessages(messages: UIMessage[]): UIMessag
});
}
/**
* Replay timestamps an assistant row when its first output is recorded, while
* latency covers the whole turn. Derive the end from the matching user start
* so the displayed time cannot double-count the pre-output interval.
*/
function deriveAssistantCompletionTimes(messages: UIMessage[]): UIMessage[] {
const userStartedAtByTurn = new Map<string, number>();
let latestUserStartedAt: number | undefined;
return messages.map((message) => {
if (message.role === "user") {
if (Number.isFinite(message.createdAt)) {
latestUserStartedAt = message.createdAt;
if (message.turnId) userStartedAtByTurn.set(message.turnId, message.createdAt);
}
return message;
}
if (
message.role !== "assistant"
|| message.kind === "trace"
|| message.completedAt !== undefined
|| message.latencyMs === undefined
|| !Number.isFinite(message.latencyMs)
|| message.latencyMs < 0
) {
return message;
}
const startedAt = message.turnId
? userStartedAtByTurn.get(message.turnId)
: message.source
? undefined
: latestUserStartedAt;
if (startedAt === undefined) return message;
return { ...message, completedAt: startedAt + message.latencyMs };
});
}
export function projectWebuiThreadMessages(messages: UIMessage[]): UIMessage[] {
const normalized = scrubSubagentUiMessages(normalizeLegacyLongTaskMessages(messages));
const hiddenTurns = new Set(normalized.flatMap((message) => (
@@ -31,10 +69,11 @@ export function projectWebuiThreadMessages(messages: UIMessage[]): UIMessage[] {
? [message.turnId]
: []
)));
return normalized.filter((message) => (
const visible = normalized.filter((message) => (
!isSystemCommandTurnId(message.turnId)
&& (!message.turnId || !hiddenTurns.has(message.turnId))
&& !(message.role === "user" && isModelCommandText(message.content))
&& !(message.role === "assistant" && isModelCommandResponseText(message.content))
));
return deriveAssistantCompletionTimes(visible);
}