fix(webui): handle final stream image rewrites

This commit is contained in:
Xubin Ren
2026-05-24 19:43:20 +08:00
parent c9ff64fc0f
commit 8be258212e
6 changed files with 110 additions and 9 deletions
+21 -8
View File
@@ -520,15 +520,28 @@ export function useNanobotStream(
resolveActiveAssistantIndex(next)
?? findStreamingAssistantIndex(next, closedAssistantStreamIdsRef.current)
?? findLatestAssistantAnswerIndex(next);
if (targetIndex !== null) {
const target = next[targetIndex];
next = replaceMessageAt(next, targetIndex, {
...target,
content: finalAnswerText,
isStreaming: true,
});
if (targetIndex !== null) {
const target = next[targetIndex];
next = replaceMessageAt(next, targetIndex, {
...target,
content: finalAnswerText,
isStreaming: true,
});
} else {
const id = crypto.randomUUID();
closedAssistantStreamIdsRef.current.add(id);
next = [
...next,
{
id,
role: "assistant",
content: finalAnswerText,
isStreaming: true,
createdAt: Date.now(),
},
];
}
}
}
if (options?.closeAnswerSegment) closeActiveAssistantStream();
return next;
});
+22
View File
@@ -1298,6 +1298,28 @@ describe("useNanobotStream", () => {
});
});
it("creates an assistant bubble from final stream_end text without prior delta", () => {
const fake = fakeClient();
const { result } = renderHook(() => useNanobotStream("chat-stream-end-only", EMPTY_MESSAGES), {
wrapper: wrap(fake.client),
});
act(() => {
fake.emit("chat-stream-end-only", {
event: "stream_end",
chat_id: "chat-stream-end-only",
text: "![Diagram](/api/media/sig/payload)",
});
});
expect(result.current.messages).toHaveLength(1);
expect(result.current.messages[0]).toMatchObject({
role: "assistant",
content: "![Diagram](/api/media/sig/payload)",
isStreaming: true,
});
});
it("stamps latency on the last assistant bubble from turn_end", () => {
const fake = fakeClient();
const { result } = renderHook(() => useNanobotStream("chat-lat", EMPTY_MESSAGES), {