fix(webui): correct activity duration display
This commit is contained in:
@@ -245,19 +245,32 @@ export function AgentActivityCluster({
|
||||
const singleFileTooltipPath = fileCount === 1 ? primaryFileTooltipPath : undefined;
|
||||
const hasVisibleActivity = reasoningSteps > 0 || toolCalls > 0 || cliCount > 0 || mcpCount > 0 || fileCount > 0;
|
||||
const hasOnlyFileActivity = fileCount > 0 && messages.every(messageHasOnlyFileActivity);
|
||||
const hasNonReasoningActivity = toolCalls > 0 || cliCount > 0 || mcpCount > 0 || fileCount > 0;
|
||||
const durationMs = activityDurationMs(messages, isTurnStreaming, now, turnLatencyMs);
|
||||
const activityDuration = formatActivityDuration(durationMs);
|
||||
const thoughtLabel = isTurnStreaming
|
||||
? t("message.activityThinkingFor", {
|
||||
duration: activityDuration,
|
||||
defaultValue: "Thinking for {{duration}}",
|
||||
})
|
||||
: durationMs <= 0
|
||||
? t("message.activityThought", { defaultValue: "Thought" })
|
||||
: t("message.activityThoughtFor", {
|
||||
duration: activityDuration,
|
||||
defaultValue: "Thought for {{duration}}",
|
||||
});
|
||||
const thoughtLabel = hasNonReasoningActivity
|
||||
? isTurnStreaming
|
||||
? t("message.activityWorkingFor", {
|
||||
duration: activityDuration,
|
||||
defaultValue: "Working for {{duration}}",
|
||||
})
|
||||
: durationMs <= 0
|
||||
? t("message.activityWorked", { defaultValue: "Worked" })
|
||||
: t("message.activityWorkedFor", {
|
||||
duration: activityDuration,
|
||||
defaultValue: "Worked for {{duration}}",
|
||||
})
|
||||
: isTurnStreaming
|
||||
? t("message.activityThinkingFor", {
|
||||
duration: activityDuration,
|
||||
defaultValue: "Thinking for {{duration}}",
|
||||
})
|
||||
: durationMs <= 0
|
||||
? t("message.activityThought", { defaultValue: "Thought" })
|
||||
: t("message.activityThoughtFor", {
|
||||
duration: activityDuration,
|
||||
defaultValue: "Thought for {{duration}}",
|
||||
});
|
||||
|
||||
const fileActivitySummary = fileCount > 0
|
||||
? hasPendingFileEdit && !singleFilePath
|
||||
|
||||
@@ -987,6 +987,9 @@
|
||||
"activityThinkingFor": "Thinking for {{duration}}",
|
||||
"activityThought": "Thought",
|
||||
"activityThoughtFor": "Thought for {{duration}}",
|
||||
"activityWorkingFor": "Working for {{duration}}",
|
||||
"activityWorked": "Worked",
|
||||
"activityWorkedFor": "Worked for {{duration}}",
|
||||
"cliActivityRunningOne": "Using @{{name}}",
|
||||
"cliActivityRanOne": "Used @{{name}}",
|
||||
"cliActivityFailedOne": "Failed @{{name}}",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "Pensando durante {{duration}}",
|
||||
"activityThought": "Pensamiento completado",
|
||||
"activityThoughtFor": "Pensó durante {{duration}}",
|
||||
"activityWorkingFor": "Trabajando durante {{duration}}",
|
||||
"activityWorked": "Trabajo completado",
|
||||
"activityWorkedFor": "Trabajó durante {{duration}}",
|
||||
"cliActivityRunningOne": "Usando @{{name}}",
|
||||
"cliActivityRanOne": "Usó @{{name}}",
|
||||
"cliActivityFailedOne": "Falló @{{name}}",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "Réflexion pendant {{duration}}",
|
||||
"activityThought": "Réflexion terminée",
|
||||
"activityThoughtFor": "Réflexion terminée en {{duration}}",
|
||||
"activityWorkingFor": "Travail en cours depuis {{duration}}",
|
||||
"activityWorked": "Travail terminé",
|
||||
"activityWorkedFor": "Travail terminé en {{duration}}",
|
||||
"cliActivityRunningOne": "Utilisation de @{{name}}",
|
||||
"cliActivityRanOne": "@{{name}} utilisé",
|
||||
"cliActivityFailedOne": "Échec de @{{name}}",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "Berpikir selama {{duration}}",
|
||||
"activityThought": "Selesai berpikir",
|
||||
"activityThoughtFor": "Selesai berpikir dalam {{duration}}",
|
||||
"activityWorkingFor": "Memproses selama {{duration}}",
|
||||
"activityWorked": "Selesai memproses",
|
||||
"activityWorkedFor": "Diproses selama {{duration}}",
|
||||
"cliActivityRunningOne": "Menggunakan @{{name}}",
|
||||
"cliActivityRanOne": "Menggunakan @{{name}} selesai",
|
||||
"cliActivityFailedOne": "@{{name}} gagal",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "{{duration}}考えています",
|
||||
"activityThought": "思考しました",
|
||||
"activityThoughtFor": "{{duration}}考えました",
|
||||
"activityWorkingFor": "{{duration}}作業中",
|
||||
"activityWorked": "作業しました",
|
||||
"activityWorkedFor": "{{duration}}作業しました",
|
||||
"cliActivityRunningOne": "@{{name}} を使用中",
|
||||
"cliActivityRanOne": "@{{name}} を使用しました",
|
||||
"cliActivityFailedOne": "@{{name}} が失敗しました",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "{{duration}} 동안 생각 중",
|
||||
"activityThought": "생각함",
|
||||
"activityThoughtFor": "{{duration}} 동안 생각함",
|
||||
"activityWorkingFor": "{{duration}} 동안 작업 중",
|
||||
"activityWorked": "작업함",
|
||||
"activityWorkedFor": "{{duration}} 동안 작업함",
|
||||
"cliActivityRunningOne": "@{{name}} 사용 중",
|
||||
"cliActivityRanOne": "@{{name}} 사용함",
|
||||
"cliActivityFailedOne": "@{{name}} 실패",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "Đang suy nghĩ trong {{duration}}",
|
||||
"activityThought": "Đã suy nghĩ",
|
||||
"activityThoughtFor": "Đã suy nghĩ trong {{duration}}",
|
||||
"activityWorkingFor": "Đang xử lý trong {{duration}}",
|
||||
"activityWorked": "Đã xử lý",
|
||||
"activityWorkedFor": "Đã xử lý trong {{duration}}",
|
||||
"cliActivityRunningOne": "Đang dùng @{{name}}",
|
||||
"cliActivityRanOne": "Đã dùng @{{name}}",
|
||||
"cliActivityFailedOne": "@{{name}} thất bại",
|
||||
|
||||
@@ -987,6 +987,9 @@
|
||||
"activityThinkingFor": "思考中 {{duration}}",
|
||||
"activityThought": "已思考",
|
||||
"activityThoughtFor": "思考了 {{duration}}",
|
||||
"activityWorkingFor": "处理中 {{duration}}",
|
||||
"activityWorked": "已处理",
|
||||
"activityWorkedFor": "处理了 {{duration}}",
|
||||
"cliActivityRunningOne": "正在使用 @{{name}}",
|
||||
"cliActivityRanOne": "已使用 @{{name}}",
|
||||
"cliActivityFailedOne": "使用 @{{name}} 失败",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"activityThinkingFor": "思考中,已 {{duration}}",
|
||||
"activityThought": "已思考",
|
||||
"activityThoughtFor": "已思考 {{duration}}",
|
||||
"activityWorkingFor": "處理中,已 {{duration}}",
|
||||
"activityWorked": "已處理",
|
||||
"activityWorkedFor": "已處理 {{duration}}",
|
||||
"cliActivityRunningOne": "正在使用 @{{name}}",
|
||||
"cliActivityRanOne": "已使用 @{{name}}",
|
||||
"cliActivityFailedOne": "@{{name}} 失敗",
|
||||
|
||||
@@ -308,14 +308,14 @@ function activityItemsForMessage(message: UIMessage): ActivityItem[] {
|
||||
}
|
||||
|
||||
function activityTurnLatencyMs(activityMessages: UIMessage[], visibleMessages: UIMessage[]): number | undefined {
|
||||
for (let i = activityMessages.length - 1; i >= 0; i -= 1) {
|
||||
const latency = activityMessages[i].latencyMs;
|
||||
if (isValidLatency(latency)) return latency;
|
||||
}
|
||||
for (let i = visibleMessages.length - 1; i >= 0; i -= 1) {
|
||||
const latency = visibleMessages[i].latencyMs;
|
||||
if (isValidLatency(latency)) return latency;
|
||||
}
|
||||
for (let i = activityMessages.length - 1; i >= 0; i -= 1) {
|
||||
const latency = activityMessages[i].latencyMs;
|
||||
if (isValidLatency(latency)) return latency;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -359,6 +359,20 @@ describe("AgentActivityCluster", () => {
|
||||
expect(screen.getByText("Thought for 12s")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("labels mixed tool activity as work instead of thought", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={activityMessages()}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow
|
||||
turnLatencyMs={12_400}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Worked for 12s")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Thought for 12s")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("omits the duration when completed history has no reliable timing", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
|
||||
@@ -308,10 +308,47 @@ 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("Worked for 9s")).toBeInTheDocument();
|
||||
expect(screen.getByText("final answer")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses final turn latency when an earlier reasoning segment has its own latency", () => {
|
||||
const messages: UIMessage[] = [
|
||||
{
|
||||
id: "r1",
|
||||
role: "assistant",
|
||||
content: "",
|
||||
reasoning: "plan",
|
||||
reasoningStreaming: false,
|
||||
latencyMs: 3_000,
|
||||
createdAt: 1,
|
||||
},
|
||||
{
|
||||
id: "t1",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: "shell()",
|
||||
traces: ["shell()"],
|
||||
createdAt: 2,
|
||||
},
|
||||
{
|
||||
id: "a1",
|
||||
role: "assistant",
|
||||
content: "done",
|
||||
latencyMs: 20_000,
|
||||
createdAt: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const units = buildDisplayUnits(messages);
|
||||
|
||||
expect(units[0].type === "activity" ? units[0].turnLatencyMs : undefined).toBe(20_000);
|
||||
|
||||
render(<ThreadMessages messages={messages} isStreaming={false} />);
|
||||
expect(screen.getByText("Worked for 20s")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Worked for 3s")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps late activity after the live assistant answer while streaming", () => {
|
||||
const messages: UIMessage[] = [
|
||||
{
|
||||
@@ -626,8 +663,8 @@ describe("ThreadMessages", () => {
|
||||
|
||||
render(<ThreadMessages messages={messages} isStreaming={false} />);
|
||||
|
||||
expect(screen.getByText("Thought for 15s")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Thought for 0s")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Worked for 15s")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Worked for 0s")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows copy only on the last assistant slice before the next user turn", () => {
|
||||
|
||||
Reference in New Issue
Block a user