fix(webui): preserve activity duration on replay

This commit is contained in:
Xubin Ren
2026-05-24 19:43:20 +08:00
parent 8fedee276b
commit c4e2fcaf0c
13 changed files with 126 additions and 3 deletions
@@ -340,6 +340,44 @@ describe("AgentActivityCluster", () => {
}
});
it("uses persisted turn latency for completed history instead of replay timestamps", () => {
render(
<AgentActivityCluster
messages={[{
id: "r-history",
role: "assistant",
content: "",
reasoning: "historical thought",
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow
turnLatencyMs={12_400}
/>,
);
expect(screen.getByText("Thought for 12s")).toBeInTheDocument();
});
it("omits the duration when completed history has no reliable timing", () => {
render(
<AgentActivityCluster
messages={[{
id: "r-old-history",
role: "assistant",
content: "",
reasoning: "old historical thought",
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow
/>,
);
expect(screen.getByText("Thought")).toBeInTheDocument();
expect(screen.queryByText("Thought for 0s")).not.toBeInTheDocument();
});
it("renders file edit totals and a compact expanded file list", async () => {
const restoreMotion = installReducedMotion();
try {
+36
View File
@@ -226,6 +226,7 @@ describe("ThreadMessages", () => {
content: "final answer",
reasoning: "summarize results",
reasoningStreaming: false,
latencyMs: 9_200,
createdAt: 3,
},
];
@@ -239,6 +240,7 @@ describe("ThreadMessages", () => {
"t1",
"a1-reasoning",
]);
expect(units[0].type === "cluster" ? units[0].messages.at(-1)?.latencyMs : undefined).toBe(9_200);
expect(units[1]).toMatchObject({
type: "single",
message: {
@@ -252,9 +254,43 @@ describe("ThreadMessages", () => {
render(<ThreadMessages messages={messages} isStreaming={false} />);
expect(screen.queryByRole("button", { name: /^thinking$/i })).not.toBeInTheDocument();
expect(screen.getByText("Thought for 9s")).toBeInTheDocument();
expect(screen.getByText("final answer")).toBeInTheDocument();
});
it("passes assistant turn latency to the preceding completed activity cluster", () => {
const messages: UIMessage[] = [
{
id: "r1",
role: "assistant",
content: "",
reasoning: "search plan",
reasoningStreaming: false,
createdAt: 1,
},
{
id: "t1",
role: "tool",
kind: "trace",
content: "web_search()",
traces: ["web_search()"],
createdAt: 1,
},
{
id: "a1",
role: "assistant",
content: "final answer",
latencyMs: 14_800,
createdAt: 1,
},
];
render(<ThreadMessages messages={messages} isStreaming={false} />);
expect(screen.getByText("Thought for 15s")).toBeInTheDocument();
expect(screen.queryByText("Thought for 0s")).not.toBeInTheDocument();
});
it("shows copy only on the last assistant slice before the next user turn", () => {
const messages: UIMessage[] = [
{