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, 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, }, ]; }