Tool Result
An execution disclosure for running, successful, or failed tools with optional raw output.
Usage
import { ToolResult } from "@/components/ai/tool-result"
export function Example() {
return <ToolResult name="Search files" summary="Found 12 matches" output="…" />
}
Install
pnpm dlx shadcn@latest add @pastaui/tool-resultnpx shadcn@latest add @pastaui/tool-resultyarn dlx shadcn@latest add @pastaui/tool-resultbunx --bun shadcn@latest add @pastaui/tool-resultSource
import { CheckCircleIcon as CheckCircle2Icon } from '@solar-icons/react/linear/check-circle'
import { AltArrowDownIcon as ChevronDownIcon } from '@solar-icons/react/linear/alt-arrow-down'
import { DangerCircleIcon as CircleAlertIcon } from '@solar-icons/react/linear/danger-circle'
import { LoaderIcon as LoaderCircleIcon } from '@solar-icons/react/linear/loader'
import { ToolboxIcon as WrenchIcon } from '@solar-icons/react/linear/toolbox'
import * as React from 'react'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import { cn } from 'cn'
interface ToolResultProps extends React.ComponentProps<typeof Collapsible> {
duration?: string
name: string
output?: React.ReactNode
status?: 'complete' | 'error' | 'running'
summary: string
}
function ToolResult({
className,
defaultOpen = false,
duration,
name,
output,
status = 'complete',
summary,
...props
}: ToolResultProps) {
return (
<Collapsible
data-slot="tool-result"
className={cn(
'rounded-[16px] bg-card font-sans text-sm font-[450] tracking-[-0.05px] text-muted-foreground shadow-card',
className,
)}
defaultOpen={defaultOpen}
{...props}
>
<CollapsibleTrigger
className="group flex w-full items-center gap-3 p-3 text-left"
render={<button aria-label={`Toggle ${name} result`} type="button" />}
>
<ToolStatus status={status} />
<div className="min-w-0 flex-1">
<p className="truncate card-heading">{name}</p>
<p className="truncate text-xs leading-5 text-muted-foreground">{summary}</p>
</div>
{duration && <span className="text-xs leading-5 text-muted-foreground">{duration}</span>}
{output && (
<ChevronDownIcon className="size-4 text-muted-foreground transition-[rotate] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] group-data-panel-open:rotate-180 motion-reduce:transition-none" />
)}
</CollapsibleTrigger>
{output && (
<CollapsibleContent className="h-(--collapsible-panel-height) overflow-hidden transition-[height,opacity] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] data-ending-style:h-0 data-ending-style:opacity-0 data-starting-style:h-0 data-starting-style:opacity-0 motion-reduce:transition-opacity">
<div className="border-t border-foreground/8 p-3 font-mono text-xs leading-5 text-muted-foreground">
{output}
</div>
</CollapsibleContent>
)}
</Collapsible>
)
}
const statusEnterClass =
'transition-[opacity,scale] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] starting:scale-90 starting:opacity-0 motion-reduce:starting:scale-100'
function ToolStatus({ status }: Pick<ToolResultProps, 'status'>) {
if (status === 'running')
return (
<LoaderCircleIcon
aria-label="Running"
className="size-4 animate-spin motion-reduce:animate-none"
/>
)
if (status === 'error')
return (
<CircleAlertIcon
aria-label="Failed"
className={cn('size-4 text-destructive', statusEnterClass)}
/>
)
return (
<CheckCircle2Icon
aria-label="Complete"
className={cn('size-4 text-emerald-600', statusEnterClass)}
/>
)
}
function ToolResultIcon() {
return <WrenchIcon className="size-4" />
}
export { ToolResult, ToolResultIcon }
export type { ToolResultProps }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| status | complete | error | running | = complete | Sets the execution state. |
| summary | string | = — | Explains the result in one line. |
| output | ReactNode | = — | Adds expandable raw output. |