"use client"
import { Button } from "@/components/ui/button"
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { cn } from "@/lib/utils"
import {
CheckCircle,
ChevronDown,
Loader2,
Settings,
XCircle,
} from "lucide-react"
import { useState } from "react"
export type ToolPart = {
type: string
state:
| "input-streaming"
| "input-available"
| "output-available"
| "output-error"
input?: Record<string, unknown>
output?: Record<string, unknown>
toolCallId?: string
errorText?: string
}
export type ToolProps = {
toolPart: ToolPart
defaultOpen?: boolean
className?: string
}
const Tool = ({ toolPart, defaultOpen = false, className }: ToolProps) => {
const [isOpen, setIsOpen] = useState(defaultOpen)
const { state, input, output, toolCallId } = toolPart
const getStateIcon = () => {
switch (state) {
case "input-streaming":
return <Loader2 className="h-4 w-4 animate-spin text-blue-500" />
case "input-available":
return <Settings className="h-4 w-4 text-orange-500" />
case "output-available":
return <CheckCircle className="h-4 w-4 text-green-500" />
case "output-error":
return <XCircle className="h-4 w-4 text-red-500" />
default:
return <Settings className="text-muted-foreground h-4 w-4" />
}
}
const getStateBadge = () => {
const baseClasses = "px-2 py-1 rounded-full text-xs font-medium"
switch (state) {
case "input-streaming":
return (
<span
className={cn(
baseClasses,
"bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
)}
>
Processing
</span>
)
case "input-available":
return (
<span
className={cn(
baseClasses,
"bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
)}
>
Ready
</span>
)
case "output-available":
return (
<span
className={cn(
baseClasses,
"bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"
)}
>
Completed
</span>
)
case "output-error":
return (
<span
className={cn(
baseClasses,
"bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400"
)}
>
Error
</span>
)
default:
return (
<span
className={cn(
baseClasses,
"bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400"
)}
>
Pending
</span>
)
}
}
const formatValue = (value: unknown): string => {
if (value === null) return "null"
if (value === undefined) return "undefined"
if (typeof value === "string") return value
if (typeof value === "object") {
return JSON.stringify(value, null, 2)
}
return String(value)
}
return (
<div
className={cn(
"border-border mt-3 overflow-hidden rounded-lg border",
className
)}
>
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger asChild>
<Button
variant="ghost"
className="bg-background h-auto w-full justify-between rounded-b-none px-3 py-2 font-normal"
>
<div className="flex items-center gap-2">
{getStateIcon()}
<span className="font-mono text-sm font-medium">
{toolPart.type}
</span>
{getStateBadge()}
</div>
<ChevronDown className={cn("h-4 w-4", isOpen && "rotate-180")} />
</Button>
</CollapsibleTrigger>
<CollapsibleContent
className={cn(
"border-border border-t",
"data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down overflow-hidden"
)}
>
<div className="bg-background space-y-3 p-3">
{input && Object.keys(input).length > 0 && (
<div>
<h4 className="text-muted-foreground mb-2 text-sm font-medium">
Input
</h4>
<div className="bg-background rounded border p-2 font-mono text-sm">
{Object.entries(input).map(([key, value]) => (
<div key={key} className="mb-1">
<span className="text-muted-foreground">{key}:</span>{" "}
<span>{formatValue(value)}</span>
</div>
))}
</div>
</div>
)}
{output && (
<div>
<h4 className="text-muted-foreground mb-2 text-sm font-medium">
Output
</h4>
<div className="bg-background max-h-60 overflow-auto rounded border p-2 font-mono text-sm">
<pre className="whitespace-pre-wrap">
{formatValue(output)}
</pre>
</div>
</div>
)}
{state === "output-error" && toolPart.errorText && (
<div>
<h4 className="mb-2 text-sm font-medium text-red-500">Error</h4>
<div className="bg-background rounded border border-red-200 p-2 text-sm dark:border-red-950 dark:bg-red-900/20">
{toolPart.errorText}
</div>
</div>
)}
{state === "input-streaming" && (
<div className="text-muted-foreground text-sm">
Processing tool call...
</div>
)}
{toolCallId && (
<div className="text-muted-foreground border-t border-blue-200 pt-2 text-xs">
<span className="font-mono">Call ID: {toolCallId}</span>
</div>
)}
</div>
</CollapsibleContent>
</Collapsible>
</div>
)
}
export { Tool }