"use client" import { memo, useMemo, useState } from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import type { LiveMessage } from "@/contexts/acp-connections-context" import type { PlanEntryInfo } from "@/lib/types" import { cn } from "@/lib/utils" import { CheckCircle2Icon, ChevronDownIcon, ChevronUpIcon, CircleDashedIcon, ListTodoIcon, Loader2Icon, } from "lucide-react" interface AgentPlanOverlayProps { message?: LiveMessage | null entries?: PlanEntryInfo[] | null planKey?: string | null visible?: boolean defaultExpanded?: boolean } function getLatestPlanEntries(message: LiveMessage | null): PlanEntryInfo[] { if (!message) return [] for (let i = message.content.length - 1; i >= 0; i -= 1) { const block = message.content[i] if (block.type === "plan") { return block.entries } } return [] } function getStatusLabel(status: string): string { switch (status) { case "completed": return "Completed" case "in_progress": return "In Progress" case "pending": return "Pending" default: return "Unknown" } } function getPriorityLabel(priority: string): string { switch (priority) { case "high": return "High" case "medium": return "Medium" case "low": return "Low" default: return "Unknown" } } function getPriorityClassName(priority: string): string { switch (priority) { case "high": return "text-red-700 bg-red-500/10 border-red-500/20 dark:text-red-300" case "medium": return "text-amber-700 bg-amber-500/10 border-amber-500/20 dark:text-amber-300" case "low": return "text-slate-700 bg-slate-500/10 border-slate-500/20 dark:text-slate-300" default: return "text-muted-foreground" } } function StatusIcon({ status }: { status: string }) { if (status === "completed") { return } if (status === "in_progress") { return } return } export const AgentPlanOverlay = memo(function AgentPlanOverlay({ message, entries, planKey, visible = true, defaultExpanded = true, }: AgentPlanOverlayProps) { const liveEntries = useMemo( () => getLatestPlanEntries(message ?? null), [message] ) const resolvedEntries = useMemo( () => (liveEntries.length > 0 ? liveEntries : (entries ?? [])), [liveEntries, entries] ) const hasPlan = visible && resolvedEntries.length > 0 const fallbackPlanKey = useMemo(() => { if (resolvedEntries.length === 0) return null return resolvedEntries .map((entry) => `${entry.status}:${entry.priority}:${entry.content}`) .join("|") }, [resolvedEntries]) const currentPlanKey = planKey ?? message?.id ?? fallbackPlanKey const completedCount = useMemo( () => resolvedEntries.filter((entry) => entry.status === "completed").length, [resolvedEntries] ) const hasIncompleteEntries = completedCount < resolvedEntries.length const resolvedDefaultExpanded = defaultExpanded && hasIncompleteEntries const currentPlanStateKey = currentPlanKey ?? "__plan__default__" const [collapsedByPlanKey, setCollapsedByPlanKey] = useState< Record >({}) const isExpanded = !( collapsedByPlanKey[currentPlanStateKey] ?? !resolvedDefaultExpanded ) if (!hasPlan) { return null } if (!isExpanded) { return ( setCollapsedByPlanKey((prev) => ({ ...prev, [currentPlanStateKey]: false, })) } > Plan {completedCount}/{resolvedEntries.length} ) } return ( Agent Plan {completedCount}/{resolvedEntries.length} setCollapsedByPlanKey((prev) => ({ ...prev, [currentPlanStateKey]: true, })) } > {resolvedEntries.map((entry, index) => ( {entry.content} {getStatusLabel(entry.status)} {getPriorityLabel(entry.priority)} ))} ) })
{entry.content}