Smooth WebUI streaming with state-driven viewport motion (#4696)
This commit is contained in:
@@ -1569,6 +1569,33 @@ describe("useNanobotStream", () => {
|
||||
expect(result.current.messages[0].turnPhase).toBe("user");
|
||||
});
|
||||
|
||||
it("returns the submitted turn identity used by the optimistic row and wire frame", () => {
|
||||
const fake = fakeClient();
|
||||
const { result } = renderHook(
|
||||
() => useNanobotStream("chat-submitted-turn", EMPTY_MESSAGES),
|
||||
{ wrapper: wrap(fake.client) },
|
||||
);
|
||||
|
||||
let submitted: ReturnType<typeof result.current.send> = null;
|
||||
act(() => {
|
||||
submitted = result.current.send("bind the camera");
|
||||
});
|
||||
|
||||
expect(submitted).not.toBeNull();
|
||||
expect(submitted?.sideChannel).toBe(false);
|
||||
expect(result.current.messages[0]).toMatchObject({
|
||||
id: submitted?.userMessageId,
|
||||
turnId: submitted?.turnId,
|
||||
role: "user",
|
||||
});
|
||||
expect(fake.client.sendMessage).toHaveBeenCalledWith(
|
||||
"chat-submitted-turn",
|
||||
"bind the camera",
|
||||
undefined,
|
||||
expect.objectContaining({ turnId: submitted?.turnId }),
|
||||
);
|
||||
});
|
||||
|
||||
it("adds optimistic user file attachments as media", () => {
|
||||
const fake = fakeClient();
|
||||
const { result } = renderHook(() => useNanobotStream("chat-file-send", EMPTY_MESSAGES), {
|
||||
@@ -2097,30 +2124,39 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("stamps latency on the last assistant bubble from turn_end", () => {
|
||||
const fake = fakeClient();
|
||||
const { result } = renderHook(() => useNanobotStream("chat-lat", EMPTY_MESSAGES), {
|
||||
wrapper: wrap(fake.client),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-lat", {
|
||||
event: "delta",
|
||||
chat_id: "chat-lat",
|
||||
text: "Hi",
|
||||
it("stamps completion time and latency on the last assistant bubble from turn_end", () => {
|
||||
const completedAt = Date.UTC(2026, 6, 25, 12, 34, 56);
|
||||
const dateNow = vi.spyOn(Date, "now").mockReturnValue(completedAt);
|
||||
try {
|
||||
const fake = fakeClient();
|
||||
const { result } = renderHook(() => useNanobotStream("chat-lat", EMPTY_MESSAGES), {
|
||||
wrapper: wrap(fake.client),
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-lat", {
|
||||
event: "turn_end",
|
||||
chat_id: "chat-lat",
|
||||
latency_ms: 2400,
|
||||
act(() => {
|
||||
fake.emit("chat-lat", {
|
||||
event: "delta",
|
||||
chat_id: "chat-lat",
|
||||
text: "Hi",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const lastAssistant = [...result.current.messages].reverse().find((m) => m.role === "assistant");
|
||||
expect(lastAssistant?.latencyMs).toBe(2400);
|
||||
act(() => {
|
||||
fake.emit("chat-lat", {
|
||||
event: "turn_end",
|
||||
chat_id: "chat-lat",
|
||||
latency_ms: 2400,
|
||||
});
|
||||
});
|
||||
|
||||
const lastAssistant = [...result.current.messages]
|
||||
.reverse()
|
||||
.find((m) => m.role === "assistant");
|
||||
expect(lastAssistant?.latencyMs).toBe(2400);
|
||||
expect(lastAssistant?.completedAt).toBe(completedAt);
|
||||
} finally {
|
||||
dateNow.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("tracks goal_status running and clears on idle", () => {
|
||||
@@ -2130,6 +2166,7 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
|
||||
expect(result.current.runStartedAt).toBeNull();
|
||||
expect(result.current.isStreaming).toBe(false);
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-g", {
|
||||
@@ -2140,6 +2177,7 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
});
|
||||
expect(result.current.runStartedAt).toBe(1700);
|
||||
expect(result.current.isStreaming).toBe(true);
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-g", {
|
||||
@@ -2149,6 +2187,7 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
});
|
||||
expect(result.current.runStartedAt).toBeNull();
|
||||
expect(result.current.isStreaming).toBe(false);
|
||||
});
|
||||
|
||||
it("clears runStartedAt on turn_end even without idle", () => {
|
||||
@@ -2166,6 +2205,7 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
});
|
||||
expect(result.current.runStartedAt).toBe(1700);
|
||||
expect(result.current.isStreaming).toBe(true);
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-g", {
|
||||
@@ -2174,6 +2214,7 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
});
|
||||
expect(result.current.runStartedAt).toBeNull();
|
||||
expect(result.current.isStreaming).toBe(false);
|
||||
});
|
||||
|
||||
it("restores runStartedAt after switching away and back when goal_status was recorded without a subscriber", () => {
|
||||
@@ -2195,9 +2236,11 @@ describe("useNanobotStream", () => {
|
||||
});
|
||||
});
|
||||
expect(result.current.runStartedAt).toBe(4242);
|
||||
expect(result.current.isStreaming).toBe(true);
|
||||
|
||||
rerender({ chatId: "chat-b" });
|
||||
expect(result.current.runStartedAt).toBeNull();
|
||||
expect(result.current.isStreaming).toBe(false);
|
||||
|
||||
act(() => {
|
||||
fake.emit("chat-a", {
|
||||
@@ -2210,6 +2253,7 @@ describe("useNanobotStream", () => {
|
||||
|
||||
rerender({ chatId: "chat-a" });
|
||||
expect(result.current.runStartedAt).toBe(9001);
|
||||
expect(result.current.isStreaming).toBe(true);
|
||||
});
|
||||
|
||||
it("tracks goal_state per chat and restores after switching sessions", () => {
|
||||
|
||||
Reference in New Issue
Block a user