Files
Jyotisha/frontend/src/lib/chat-message-view.ts
T
Jesse_Chen 4e247c112e fix(web): keep thinking off the spoken consult and rectification answer
Enumerate evidence kinds so education cannot be proposed as a kind, and stream Chinese thinking on a separate channel that collapses when the reply arrives.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 21:30:55 +08:00

84 lines
2.5 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 thinkingText?: 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,
thinkingText?: string,
): 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,
thinkingText,
renderKey: `message-${messages.length}`,
state: streamingText ? "streaming" : "thinking",
activity,
},
];
}