import { ChevronDownIcon } from "lucide-react"; import { use, type ReactNode } from "react"; import { formatDuration, type WorkLogEntry } from "../../session-logic"; import { cn } from "~/lib/utils"; import { WorkLogList } from "./WorkLog"; import { TimelineRowCtx, type TimelineRow, type TimelineWorkEntry } from "./timelineRowContext"; import { ToolActivityIconView } from "./TimelineToolActivityIcon"; import { workEntryIconName } from "./TimelineWorkEntryRow.logic"; import { SimpleWorkEntryRow } from "./TimelineWorkEntryRow"; import { liveToolCardTail, toolGroupStats } from "./ToolCallCard.logic"; import { TimelineRowTimestamp } from "./TimelineRowParts"; const LIVE_TAIL_ROWS = 4; const STACK_LIMIT = 4; const cardFrame = "border-border bg-foreground/[0.025]"; const toolCardClassName = { whole: cn(cardFrame, "mt-1.5 rounded-xl border"), top: cn(cardFrame, "mt-1.5 rounded-t-xl border border-b-0"), bottom: cn(cardFrame, "mb-2 rounded-b-xl border border-t-0"), }; function entryIconKey(entry: TimelineWorkEntry) { const icon = entry.toolIcon ?? entry.toolSource?.icon; if (!icon) return workEntryIconName(entry); return icon._tag === "website" ? `website:${icon.pageUrl}` : icon._tag === "themed-logo" ? `logo:${icon.logoUrl}` : `app:${JSON.stringify(icon.app)}`; } /** Overlapping icons for the distinct tools in a group, first use first. */ function ToolStack({ entries }: { entries: ReadonlyArray }) { const seen = new Map(); for (const entry of entries) { if (entry.itemType === "reasoning") continue; const key = entryIconKey(entry); if (!seen.has(key)) seen.set(key, entry); if (seen.size === STACK_LIMIT) break; } return ( {[...seen].map(([key, entry]) => ( ))} ); } function Chevron({ open }: { open: boolean }) { return ( ); } function CardHeader(props: { entries: ReadonlyArray; label: ReactNode; accessibleLabel: string; failedCount: number; trailing?: ReactNode; expanded: boolean; className: string; /** Absent for a card with nothing more to open; it keeps the chevron's space. */ onToggle?: () => void; }) { const Root = props.onToggle ? "button" : "div"; return ( {props.label} {props.failedCount > 0 ? ( {props.failedCount} failed ) : null} {props.trailing} {/* Lines the header time up with the row durations, left of the status column. */} {props.onToggle ? ( ) : ( )} ); } /** The newest calls and thoughts. Rows only append, so the card grows to 4 rows and stops. */ function ToolCardTail({ entries }: { entries: ReadonlyArray }) { const { workspaceRoot } = use(TimelineRowCtx); return (
{entries.map((entry) => ( ))}
); } function toolNoun(count: number) { return count === 1 ? "tool" : "tools"; } /** The working run's calls in one card, so progress is visible without a click. */ export function LiveToolCard({ row }: { row: Extract }) { const ctx = use(TimelineRowCtx); const stats = toolGroupStats(row.groupedEntries); const tail = liveToolCardTail(row.groupedEntries, LIVE_TAIL_ROWS); const hiddenCount = row.expanded ? 0 : stats.toolCount - tail.filter((entry) => entry.itemType !== "reasoning").length; const label = `Using ${stats.toolCount} ${toolNoun(stats.toolCount)}`; return (
0 ? `${stats.failedCount} failed` : null, hiddenCount > 0 ? `${hiddenCount} earlier ${hiddenCount === 1 ? "call" : "calls"} hidden` : null, ] .filter(Boolean) .join(", ")} failedCount={stats.failedCount} trailing={hiddenCount > 0 ? `+${hiddenCount} earlier` : null} expanded={row.expanded} className="rounded-t-xl" onToggle={() => ctx.onToggleWorkGroup(row.groupId, row.id)} /> {row.expanded ? null : }
); } /** Finished tools: one quiet line that opens into the card. */ export function ToolGroupLine({ row }: { row: Extract }) { const ctx = use(TimelineRowCtx); const stats = toolGroupStats(row.groupedEntries); const duration = stats.durationMs !== null ? formatDuration(stats.durationMs) : null; return (
{row.summary} Used {stats.toolCount} {toolNoun(stats.toolCount)} } accessibleLabel={[ row.summary, stats.failedCount > 0 ? `${stats.failedCount} failed` : null, duration, ] .filter(Boolean) .join(", ")} failedCount={stats.failedCount} trailing={ <> {duration} } expanded={row.expanded} className={row.expanded ? "rounded-t-xl" : "rounded-md"} onToggle={() => ctx.onToggleWorkGroup(row.groupId, row.id)} />
); } /** Bottom of an open card: the full list of calls. */ export function ToolCardBody({ children }: { children: ReactNode }) { return (
{children}
); } export function groupHasTools(entries: ReadonlyArray) { return toolGroupStats(entries).toolCount > 0; }