Getting Started
Components
- Agent Status
- Agent Template Card
- API Key Card
- Audio Player
- Bar Visualizer
- Channel Status Card
- Chat Composer
- Code Editor
- Condition Builder
- Confirm Dialog
- Conversation
- Conversation Bar
- Dataset Card
- Document Status Badge
- Extraction Strategy Select
- File Icon
- File Upload
- Indexing Progress
- Live Waveform
- Matrix
- Message
- Mic Selector
- Model Diff View
- Model Selector
- Model Type Filter
- Node Catalog
- Option Editor
- Orb
- Output Variables View
- Package Card
- Prompt Editor
- Provider Status Card
- Radio Card
- Resource Card
- Resource Sidebar
- Response
- Run Node List
- Scrub Bar
- Segment Card
- Shimmering Text
- Speech Input
- Token Trend Chart
- Tool Call Card
- Transcript Viewer
- Usage Stat Card
- Variable Binding Editor
- Variable Selector
- Voice Button
- Voice Picker
- Waveform
- Workflow Node Card
- Workspace Selector
Look up customer
SuccessFetched account details before drafting the response.
crm.lookup_customer312 msInput
{
"email": "maya@zgi.ai",
"include": [
"plan",
"open_tickets"
]
}Output
{
"plan": "Pro",
"openTickets": 1,
"renewal": "2026-08-14"
}Schedule follow-up
RunningPreparing a handoff reminder for the success team.
calendar.create_followuprunning"use client"
import { ToolCallCard } from "@/components/ui/tool-call-card"
export function ToolCallCardDemo() {
return (
<div className="w-full max-w-2xl space-y-3">
<ToolCallCard
tool="crm.lookup_customer"
status="success"
title="Look up customer"
description="Fetched account details before drafting the response."
duration="312 ms"
defaultOpen
input={{
email: "maya@zgi.ai",
include: ["plan", "open_tickets"],
}}
output={{
plan: "Pro",
openTickets: 1,
renewal: "2026-08-14",
}}
/>
<ToolCallCard
tool="calendar.create_followup"
status="running"
title="Schedule follow-up"
description="Preparing a handoff reminder for the success team."
duration="running"
input={{ owner: "success", priority: "high" }}
/>
</div>
)
}
Installation
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/tool-call-card.jsonpnpm dlx shadcn@latest add https://ui.zgi.ai/r/tool-call-card.json
yarn dlx shadcn@latest add https://ui.zgi.ai/r/tool-call-card.jsonComponent
"use client"
import * as React from "react"
import {
AlertCircleIcon,
CheckCircle2Icon,
ChevronDownIcon,
Clock3Icon,
CopyIcon,
Loader2Icon,
WrenchIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
export type ToolCallStatus = "queued" | "running" | "success" | "error"
export type ToolCallPayload =
| React.ReactNode
| Record<string, unknown>
| unknown[]
export interface ToolCallCardProps
extends Omit<React.ComponentProps<"div">, "title"> {
tool: string
status: ToolCallStatus
title?: React.ReactNode
description?: React.ReactNode
duration?: React.ReactNode
input?: ToolCallPayload
output?: ToolCallPayload
defaultOpen?: boolean
onCopy?: () => void
}
const statusConfig: Record<
ToolCallStatus,
{
label: string
icon: React.ComponentType<{ className?: string }>
className: string
}
> = {
queued: {
label: "Queued",
icon: Clock3Icon,
className: "border-border bg-muted text-muted-foreground",
},
running: {
label: "Running",
icon: Loader2Icon,
className:
"border-blue-500/20 bg-blue-500/10 text-blue-700 dark:text-blue-300",
},
success: {
label: "Success",
icon: CheckCircle2Icon,
className:
"border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
},
error: {
label: "Error",
icon: AlertCircleIcon,
className: "border-destructive/20 bg-destructive/10 text-destructive",
},
}
function renderPayload(payload: ToolCallPayload | undefined) {
if (payload == null || React.isValidElement(payload)) {
return payload
}
if (typeof payload === "string") {
return payload
}
return JSON.stringify(payload, null, 2)
}
export function ToolCallCard({
tool,
status,
title,
description,
duration,
input,
output,
defaultOpen = false,
onCopy,
className,
...props
}: ToolCallCardProps) {
const [open, setOpen] = React.useState(defaultOpen)
const config = statusConfig[status]
const Icon = config.icon
const hasDetails = input != null || output != null
const renderedInput = renderPayload(input)
const renderedOutput = renderPayload(output)
return (
<Collapsible open={open} onOpenChange={setOpen}>
<div
className={cn(
"border-border bg-card text-card-foreground overflow-hidden rounded-lg border shadow-sm",
className
)}
{...props}
>
<div className="flex items-start gap-3 p-3">
<div className="bg-muted text-muted-foreground flex size-9 shrink-0 items-center justify-center rounded-md">
<WrenchIcon className="size-4" />
</div>
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-2">
<p className="truncate text-sm font-medium">{title ?? tool}</p>
<Badge
variant="outline"
className={cn("h-6 gap-1 rounded-md px-2", config.className)}
>
<Icon
className={cn(
"size-3",
status === "running" && "animate-spin"
)}
/>
{config.label}
</Badge>
</div>
{description ? (
<p className="text-muted-foreground mt-1 line-clamp-2 text-xs leading-5">
{description}
</p>
) : null}
<div className="text-muted-foreground mt-2 flex flex-wrap items-center gap-2 text-xs">
<code className="bg-muted rounded px-1.5 py-0.5 font-mono">
{tool}
</code>
{duration ? <span>{duration}</span> : null}
</div>
</div>
<div className="flex shrink-0 items-center gap-1">
{onCopy ? (
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 rounded-md"
onClick={onCopy}
aria-label="Copy tool call"
>
<CopyIcon className="size-4" />
</Button>
) : null}
{hasDetails ? (
<CollapsibleTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon"
className="size-8 rounded-md"
aria-label="Toggle tool call details"
>
<ChevronDownIcon
className={cn(
"size-4 transition-transform",
open && "rotate-180"
)}
/>
</Button>
</CollapsibleTrigger>
) : null}
</div>
</div>
{hasDetails ? (
<CollapsibleContent>
<div className="border-border grid gap-3 border-t p-3 sm:grid-cols-2">
{renderedInput ? (
<div className="min-w-0 space-y-1.5">
<p className="text-muted-foreground text-xs font-medium">
Input
</p>
<pre className="bg-muted max-h-44 overflow-auto rounded-md p-3 text-xs leading-5">
{renderedInput}
</pre>
</div>
) : null}
{renderedOutput ? (
<div className="min-w-0 space-y-1.5">
<p className="text-muted-foreground text-xs font-medium">
Output
</p>
<pre className="bg-muted max-h-44 overflow-auto rounded-md p-3 text-xs leading-5">
{renderedOutput}
</pre>
</div>
) : null}
</div>
</CollapsibleContent>
) : null}
</div>
</Collapsible>
)
}
Usage
import { ToolCallCard } from "@/components/ui/tool-call-card"
export function Example() {
return (
<ToolCallCard
tool="crm.lookup_customer"
status="success"
title="Look up customer"
duration="312 ms"
input={{ email: "maya@zgi.ai" }}
output={{ plan: "Pro", openTickets: 1 }}
defaultOpen
/>
)
}Props
| Prop | Type | Default |
|---|---|---|
| tool | string | required |
| status | "queued" | "running" | "success" | "error" | required |
| title | ReactNode | tool |
| description | ReactNode | - |
| duration | ReactNode | - |
| input | ReactNode | object | array | - |
| output | ReactNode | object | array | - |
| defaultOpen | boolean | false |
| onCopy | () => void | - |
Deploy and Scale Agents with ZGI
ZGI delivers the infrastructure and developer experience you need to ship reliable audio & agent applications at scale.
Talk to an expert