"use client"; import { memo, useCallback, useRef, useState } from "react"; import { AlertCircleIcon, CheckIcon, ChevronDownIcon, LoaderIcon, XCircleIcon, } from "lucide-react"; import { useScrollLock, type ToolCallMessagePartStatus, type ToolCallMessagePartComponent, } from "@assistant-ui/react"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; const ANIMATION_DURATION = 200; export type ToolFallbackRootProps = Omit< React.ComponentProps, "open" | "onOpenChange" > & { open?: boolean; onOpenChange?: (open: boolean) => void; defaultOpen?: boolean; }; function ToolFallbackRoot({ className, open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen = false, children, ...props }: ToolFallbackRootProps) { const collapsibleRef = useRef(null); const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen); const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION); const isControlled = controlledOpen !== undefined; const isOpen = isControlled ? controlledOpen : uncontrolledOpen; const handleOpenChange = useCallback( (open: boolean) => { if (!open) { lockScroll(); } if (!isControlled) { setUncontrolledOpen(open); } controlledOnOpenChange?.(open); }, [lockScroll, isControlled, controlledOnOpenChange], ); return ( {children} ); } type ToolStatus = ToolCallMessagePartStatus["type"]; const statusIconMap: Record = { running: LoaderIcon, complete: CheckIcon, incomplete: XCircleIcon, "requires-action": AlertCircleIcon, }; function ToolFallbackTrigger({ toolName, status, className, ...props }: React.ComponentProps & { toolName: string; status?: ToolCallMessagePartStatus; }) { const statusType = status?.type ?? "complete"; const isRunning = statusType === "running"; const isCancelled = status?.type === "incomplete" && status.reason === "cancelled"; const Icon = statusIconMap[statusType]; const label = isCancelled ? "Cancelled tool" : "Used tool"; return ( {label}: {toolName} {isRunning && ( {label}: {toolName} )} ); } function ToolFallbackContent({ className, children, ...props }: React.ComponentProps) { return (
{children}
); } function ToolFallbackArgs({ argsText, className, ...props }: React.ComponentProps<"div"> & { argsText?: string; }) { if (!argsText) return null; return (
        {argsText}
      
); } function ToolFallbackResult({ result, className, ...props }: React.ComponentProps<"div"> & { result?: unknown; }) { if (result === undefined) return null; return (

Result:

        {typeof result === "string" ? result : JSON.stringify(result, null, 2)}
      
); } function ToolFallbackError({ status, className, ...props }: React.ComponentProps<"div"> & { status?: ToolCallMessagePartStatus; }) { if (status?.type !== "incomplete") return null; const error = status.error; const errorText = error ? typeof error === "string" ? error : JSON.stringify(error) : null; if (!errorText) return null; const isCancelled = status.reason === "cancelled"; const headerText = isCancelled ? "Cancelled reason:" : "Error:"; return (

{headerText}

{errorText}

); } const ToolFallbackImpl: ToolCallMessagePartComponent = ({ toolName, argsText, result, status, }) => { const isCancelled = status?.type === "incomplete" && status.reason === "cancelled"; return ( {!isCancelled && } ); }; const ToolFallback = memo( ToolFallbackImpl, ) as unknown as ToolCallMessagePartComponent & { Root: typeof ToolFallbackRoot; Trigger: typeof ToolFallbackTrigger; Content: typeof ToolFallbackContent; Args: typeof ToolFallbackArgs; Result: typeof ToolFallbackResult; Error: typeof ToolFallbackError; }; ToolFallback.displayName = "ToolFallback"; ToolFallback.Root = ToolFallbackRoot; ToolFallback.Trigger = ToolFallbackTrigger; ToolFallback.Content = ToolFallbackContent; ToolFallback.Args = ToolFallbackArgs; ToolFallback.Result = ToolFallbackResult; ToolFallback.Error = ToolFallbackError; export { ToolFallback, ToolFallbackRoot, ToolFallbackTrigger, ToolFallbackContent, ToolFallbackArgs, ToolFallbackResult, ToolFallbackError, };