"use client"; import { CheckIcon, ChevronDownIcon, XCircleIcon } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { NestedScroll } from "@/components/assistant-ui/nested-scroll"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Separator } from "@/components/ui/separator"; import { Spinner } from "@/components/ui/spinner"; import { getToolDisplayName } from "@/contracts/enums/toolIcons"; import { cn } from "@/lib/utils"; import type { TimelineToolComponent } from "../types"; import { ToolCardRevertButton } from "./revert-button"; /** * Best-effort error/cancellation reason from a tool result. Used as * the card subtitle when ``status`` is "error" or "cancelled". Returns * ``null`` if no usable text can be extracted. * * Tries: plain string → ``result.error`` → ``result.message`` → * stringified result. Per-tool components own richer error UIs; this * is the generic fallback's coarse summary. */ function deriveResultMessage(result: unknown): string | null { if (result == null) return null; if (typeof result === "string") return result; if (typeof result !== "object") return null; const r = result as { error?: unknown; message?: unknown }; if (typeof r.error === "string") return r.error; if (typeof r.message === "string") return r.message; try { return JSON.stringify(result); } catch { return null; } } /** * Compact tool-call card. Used by ``FallbackToolBody`` for unregistered * tools whose result is not an HITL interrupt. * * shadcn composition note: ``Card`` is used as a visual frame WITHOUT * ``CardHeader``/``CardContent`` — the full composition's ``p-6`` * doesn't fit a compact collapsible header that IS the trigger. * * Per-card expansion auto-syncs to ``isRunning`` (auto-expand on * stream start, auto-collapse on completion); manual toggle takes over * once streaming ends. */ export const DefaultFallbackCard: TimelineToolComponent = ({ toolCallId, toolName, argsText, result, status, langchainToolCallId, }) => { const isCancelled = status === "cancelled"; const isError = status === "error"; const isRunning = status === "running"; const [isExpanded, setIsExpanded] = useState(isRunning); useEffect(() => { setIsExpanded(isRunning); }, [isRunning]); const serializedResult = useMemo( () => result !== undefined && typeof result !== "string" ? JSON.stringify(result, null, 2) : null, [result] ); const subtitle = useMemo( () => (isError || isCancelled ? deriveResultMessage(result) : null), [isError, isCancelled, result] ); const displayName = getToolDisplayName(toolName); return ( { if (isRunning) return; setIsExpanded(next); }} >
{(argsText || isRunning) && (

Inputs

{argsText ? (
											{argsText}
										
) : (

Waiting for input…

)}
)} {!isCancelled && result !== undefined && ( <>

Result

											{typeof result === "string" ? result : serializedResult}
										
)}
); };