import type { AgentActivityTraceItem } from "./agent-activity-trace.ts"; import type { AgentExecutionReceipt, PublicActivityPhase, WorkflowReceipt } from "./consultation-agent-events.ts"; import type { PublicThinkingSection } from "./consultation-thinking-plan.ts"; import { consultationTimelineFromSettled, type ConsultationTimelineRow, } from "./consultation-run-timeline.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 activityCompletedSteps(trail: string | undefined): string[] { if (!trail) return []; const body = trail.replace(/^已完成:/, "").trim(); if (!body) return []; return body.split(" · ").map((step) => step.trim()).filter(Boolean); } 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 thinkingSections?: readonly PublicThinkingSection[]; readonly techniqueTruth?: string; readonly agentExecutionReceipt?: AgentExecutionReceipt; readonly workflowReceipt?: WorkflowReceipt; }; export type ChatMessageView = ChatMessage & { readonly renderKey: string; readonly state: "settled" | "streaming" | "thinking"; readonly activity?: AgentActivityView; readonly activityTrace?: readonly AgentActivityTraceItem[]; readonly timeline?: readonly ConsultationTimelineRow[]; }; export function chatMessageViews( messages: readonly ChatMessage[], loading: boolean, streamingText: string, activity?: AgentActivityView, thinkingText?: string, thinkingSections?: readonly PublicThinkingSection[], timeline?: readonly ConsultationTimelineRow[], ): readonly ChatMessageView[] { const settled = messages.map((message, index) => ({ ...message, renderKey: `message-${index}`, state: "settled" as const, ...(message.role === "assistant" ? { timeline: consultationTimelineFromSettled(message) } : {}), })); if (!loading || messages.at(-1)?.role === "assistant") return settled; return [ ...settled, { role: "assistant", text: streamingText, thinkingText, thinkingSections, timeline: timeline ?? [], renderKey: `message-${messages.length}`, state: streamingText ? "streaming" : "thinking", activity, }, ]; }