import { TaskChatExpansionState, useTaskChatExpansion } from "./expansion-state"; import { useContext, useEffect, type ReactNode } from "react"; import { Brain, ChevronRight, CircleEllipsis } from "lucide-react"; import { cn } from "@/lib/utils"; import { MarkdownBody } from "@/components/MarkdownBody"; import type { TaskChatActivityPhaseItem } from "./task-chat-model"; import { protocolActivityPresentation } from "./task-chat-activity-presentation"; import { toolTaxonomy, type ToolIcon } from "./tool-taxonomy"; function representativeIcon(item: TaskChatActivityPhaseItem): ToolIcon { // Prefer a concrete operation over bookkeeping/reasoning. A phase can own // many different rows, but its collapsed summary needs one calm visual cue. for (const child of item.items) { if (child.kind === "tool") { return toolTaxonomy(child.rawName ?? child.name).icon; } if (child.kind === "protocol") { const presentation = protocolActivityPresentation(child); if (presentation) return presentation.icon; } } if (item.items.some((child) => child.kind === "thinking")) return Brain; return CircleEllipsis; } export function TaskChatActivityPhase({ item, renderChild, renderChildren, autoOpen = true, defaultOpen = false, childrenClassName, showChildRail = false, appearance = "classic", }: { item: TaskChatActivityPhaseItem; renderChild: (child: TaskChatActivityPhaseItem["items"][number]) => ReactNode; renderChildren?: (children: TaskChatActivityPhaseItem["items"]) => ReactNode; autoOpen?: boolean; /** Keep useful historical content visible in an in-flight transcript. */ defaultOpen?: boolean; childrenClassName?: string; /** Draws a nested-activity rail aligned beneath the summary disclosure. */ showChildRail?: boolean; /** Codex-style summary treatment used only by the new Paperclip task UI. */ appearance?: "classic" | "runner"; }) { const shouldAutoOpen = defaultOpen || (autoOpen && (item.active || item.items.some( (child) => (child.kind === "thinking" && child.streaming) || (child.kind === "tool" && child.status === "in_progress") || (child.kind === "protocol" && child.surface === "provider_activity" && child.status === "running") || (child.kind === "protocol" && child.surface === "workspace_change" && !child.complete) || (child.kind === "protocol" && child.surface === "runtime_request" && child.status === "pending"), ))); const [open, setOpen] = useTaskChatExpansion(item.id, shouldAutoOpen); const expansionMemory = useContext(TaskChatExpansionState); useEffect(() => { if (shouldAutoOpen && !expansionMemory?.has(item.id)) setOpen(true); }, [shouldAutoOpen, expansionMemory, item.id, setOpen]); const expandable = item.items.length > 0; const runnerAppearance = appearance === "runner"; const SummaryIcon = runnerAppearance ? representativeIcon(item) : null; return (