Sabil UI

Search documentation

Search for a command to run...

Components

Tool Call

A card for one tool call: its name, live status, input and output.

Input
{ "query": "calendar dropdown", "limit": 3 }
Output
[
  { "title": "Calendar", "href": "/docs/components/calendar" },
  { "title": "Date Picker", "href": "/docs/components/date-picker" }
]

Installation#

  1. Install the dependencies

    npm install cn lucide-react radix-ui
  2. Copy the source into your project

    components/ui/tool-call.tsx
    "use client"import * as React from "react"import { cn } from "cn"import { CheckIcon, ChevronDownIcon, CircleIcon, WrenchIcon, XIcon } from "lucide-react"import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"import { Spinner } from "@/components/ui/spinner"type ToolCallStatus = "pending" | "running" | "done" | "error"const statusLabel: Record<ToolCallStatus, string> = {  pending: "Pending",  running: "Running",  done: "Done",  error: "Error",}function ToolCall({ className, ...props }: React.ComponentProps<typeof Collapsible>) {  return (    <Collapsible      data-slot="tool-call"      className={cn("w-full min-w-0 overflow-hidden rounded-xl border bg-card text-sm", className)}      {...props}    />  )}function StatusIcon({ status }: { status: ToolCallStatus }) {  if (status === "running") return <Spinner className="size-3.5" />  if (status === "done") return <CheckIcon className="size-3.5" />  if (status === "error") return <XIcon className="size-3.5" />  return <CircleIcon className="size-3" />}/** Tool name on the left, status on the right. Click to show input and output. */function ToolCallHeader({  name,  status = "done",  icon,  className,  ...props}: Omit<React.ComponentProps<typeof CollapsibleTrigger>, "children"> & {  name: React.ReactNode  status?: ToolCallStatus  icon?: React.ReactNode}) {  return (    <CollapsibleTrigger      data-slot="tool-call-header"      data-status={status}      className={cn(        "group/tool-call flex h-11 w-full items-center gap-2.5 px-3.5 text-left outline-none transition-colors duration-150 hover:bg-accent focus-visible:bg-accent",        className      )}      {...props}    >      <span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground [&_svg:not([class*='size-'])]:size-3.5">        {icon ?? <WrenchIcon />}      </span>      <span className="min-w-0 flex-1 truncate font-mono text-[13px] font-medium">{name}</span>      <span        className={cn(          "flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium",          status === "pending" && "bg-muted text-muted-foreground",          status === "running" && "bg-primary/10 text-primary",          status === "done" && "bg-muted text-foreground/80",          status === "error" && "bg-destructive/10 text-destructive"        )}      >        <StatusIcon status={status} />        {statusLabel[status]}      </span>      <ChevronDownIcon        aria-hidden="true"        className="size-4 shrink-0 text-muted-foreground transition-transform duration-200 ease-out group-aria-expanded/tool-call:rotate-180"      />    </CollapsibleTrigger>  )}function ToolCallContent({  className,  children,  ...props}: React.ComponentProps<typeof CollapsibleContent>) {  return (    <CollapsibleContent      data-slot="tool-call-content"      className="overflow-hidden duration-200 [--radix-accordion-content-height:var(--radix-collapsible-content-height)] data-open:animate-accordion-down data-closed:animate-accordion-up"      {...props}    >      <div className={cn("flex flex-col gap-3 border-t p-3.5", className)}>{children}</div>    </CollapsibleContent>  )}/** A labelled block of data, e.g. the tool's input or its result. */function ToolCallSection({  label,  className,  children,  ...props}: React.ComponentProps<"div"> & { label: React.ReactNode }) {  return (    <div data-slot="tool-call-section" className={cn("flex min-w-0 flex-col gap-1.5", className)} {...props}>      <span className="text-xs font-medium text-muted-foreground">{label}</span>      <pre className="max-h-56 overflow-auto rounded-lg bg-muted/60 px-3 py-2.5 font-mono text-xs leading-relaxed whitespace-pre-wrap">        {children}      </pre>    </div>  )}export { ToolCall, ToolCallHeader, ToolCallContent, ToolCallSection }export type { ToolCallStatus }
    components/ui/collapsible.tsx
    "use client"import { Collapsible as CollapsiblePrimitive } from "radix-ui"function Collapsible({  ...props}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {  return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />}function CollapsibleTrigger({  ...props}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {  return (    <CollapsiblePrimitive.CollapsibleTrigger      data-slot="collapsible-trigger"      {...props}    />  )}function CollapsibleContent({  ...props}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {  return (    <CollapsiblePrimitive.CollapsibleContent      data-slot="collapsible-content"      {...props}    />  )}export { Collapsible, CollapsibleTrigger, CollapsibleContent }
    components/ui/spinner.tsx
    import { cn } from "cn"import { Loader2Icon } from "lucide-react"function Spinner({ className, ...props }: React.ComponentProps<"svg">) {  return (    <Loader2Icon data-slot="spinner" role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />  )}export { Spinner }
  3. Make sure the foundation is in place

    Components read their colors, radius and surfaces from the tokens in app/globals.css. See Theming.

Usage#

import {  ToolCall,  ToolCallHeader,  ToolCallContent,  ToolCallSection,} from "@/components/ui/tool-call"

Examples#

Status