Files
Jyotisha/frontend/src/lib/chat-message-view.ts
T
Jesse_Chen 6a44c778c3 fix(web): show live agent work progress and fail truncated rectification answers
Rectification dropped tool.activity started events and treated length finishes as completed. Share generation settings with consultation, keep the activity line through streaming, and name multi-domain chart calculation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 12:39:09 +08:00

81 lines
2.4 KiB
TypeScript

import type { AgentExecutionReceipt, PublicActivityPhase, WorkflowReceipt } from "./consultation-agent-events.ts";
export const ACTIVITY_ELAPSED_VISIBLE_AFTER_MS = 8_000;
export const ACTIVITY_COMPLETED_TRAIL_LIMIT = 3;
export type AgentActivityView = Readonly<{
phase: PublicActivityPhase;
label: string;
startedAt?: number;
completedTrail?: string;
}>;
export function activityCompletedTrail(steps: readonly string[]): string | undefined {
if (steps.length === 0) return undefined;
return `已完成:${steps.slice(-ACTIVITY_COMPLETED_TRAIL_LIMIT).join(" · ")}`;
}
export function nextActivityView(
previous: AgentActivityView | undefined,
next: Omit<AgentActivityView, "startedAt">,
now = Date.now(),
): AgentActivityView {
const startedAt = previous?.label === next.label && previous.startedAt ? previous.startedAt : now;
const completedTrail = next.phase === "answer-composition"
? undefined
: next.completedTrail !== undefined
? next.completedTrail || undefined
: previous?.completedTrail;
return {
phase: next.phase,
label: next.label,
startedAt,
...(completedTrail ? { completedTrail } : {}),
};
}
export function activityElapsedLabel(startedAt: number, now: number): string | null {
const elapsedMs = now - startedAt;
if (elapsedMs < ACTIVITY_ELAPSED_VISIBLE_AFTER_MS) return null;
return `已用时 ${Math.floor(elapsedMs / 1000)}`;
}
export type ChatMessage = {
readonly role: "user" | "assistant";
readonly text: string;
readonly techniqueTruth?: string;
readonly agentExecutionReceipt?: AgentExecutionReceipt;
readonly workflowReceipt?: WorkflowReceipt;
};
export type ChatMessageView = ChatMessage & {
readonly renderKey: string;
readonly state: "settled" | "streaming" | "thinking";
readonly activity?: AgentActivityView;
};
export function chatMessageViews(
messages: readonly ChatMessage[],
loading: boolean,
streamingText: string,
activity?: AgentActivityView,
): readonly ChatMessageView[] {
const settled = messages.map((message, index) => ({
...message,
renderKey: `message-${index}`,
state: "settled" as const,
}));
if (!loading || messages.at(-1)?.role === "assistant") return settled;
return [
...settled,
{
role: "assistant",
text: streamingText,
renderKey: `message-${messages.length}`,
state: streamingText ? "streaming" : "thinking",
activity,
},
];
}