Spaces:
Running
Running
"use client"; | |
import { useState } from "react"; | |
import { | |
ChevronDownIcon, | |
ChevronUpIcon, | |
Loader2, | |
CheckCircle2, | |
TerminalSquare, | |
Code, | |
ArrowRight, | |
Circle, | |
} from "lucide-react"; | |
import { cn } from "@/lib/utils"; | |
interface ToolInvocationProps { | |
toolName: string; | |
state: string; | |
args: any; | |
result: any; | |
isLatestMessage: boolean; | |
status: string; | |
} | |
export function ToolInvocation({ | |
toolName, | |
state, | |
args, | |
result, | |
isLatestMessage, | |
status, | |
}: ToolInvocationProps) { | |
const [isExpanded, setIsExpanded] = useState(false); | |
const getStatusIcon = () => { | |
if (state === "call") { | |
if (isLatestMessage && status !== "ready") { | |
return <Loader2 className="animate-spin h-3.5 w-3.5 text-primary/70" />; | |
} | |
return <Circle className="h-3.5 w-3.5 fill-muted-foreground/10 text-muted-foreground/70" />; | |
} | |
return <CheckCircle2 size={14} className="text-primary/90" />; | |
}; | |
const getStatusClass = () => { | |
if (state === "call") { | |
if (isLatestMessage && status !== "ready") { | |
return "text-primary"; | |
} | |
return "text-muted-foreground"; | |
} | |
return "text-primary"; | |
}; | |
const formatContent = (content: any): string => { | |
try { | |
if (typeof content === "string") { | |
try { | |
const parsed = JSON.parse(content); | |
return JSON.stringify(parsed, null, 2); | |
} catch { | |
return content; | |
} | |
} | |
return JSON.stringify(content, null, 2); | |
} catch { | |
return String(content); | |
} | |
}; | |
return ( | |
<div className={cn( | |
"flex flex-col mb-2 rounded-md border border-border/50 overflow-hidden", | |
"bg-gradient-to-b from-background to-muted/30 backdrop-blur-sm", | |
"transition-all duration-200 hover:border-border/80 group" | |
)}> | |
<div | |
className={cn( | |
"flex items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors", | |
"hover:bg-muted/20" | |
)} | |
onClick={() => setIsExpanded(!isExpanded)} | |
> | |
<div className="flex items-center justify-center rounded-full w-5 h-5 bg-primary/5 text-primary"> | |
<TerminalSquare className="h-3.5 w-3.5" /> | |
</div> | |
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground flex-1"> | |
<span className="text-foreground font-semibold tracking-tight">{toolName}</span> | |
<ArrowRight className="h-3 w-3 text-muted-foreground/50" /> | |
<span className={cn("font-medium", getStatusClass())}> | |
{state === "call" ? (isLatestMessage && status !== "ready" ? "Running" : "Waiting") : "Completed"} | |
</span> | |
</div> | |
<div className="flex items-center gap-2 opacity-70 group-hover:opacity-100 transition-opacity"> | |
{getStatusIcon()} | |
<div className="bg-muted/30 rounded-full p-0.5 border border-border/30"> | |
{isExpanded ? ( | |
<ChevronUpIcon className="h-3 w-3 text-foreground/70" /> | |
) : ( | |
<ChevronDownIcon className="h-3 w-3 text-foreground/70" /> | |
)} | |
</div> | |
</div> | |
</div> | |
{isExpanded && ( | |
<div className="space-y-2 px-3 pb-3"> | |
{!!args && ( | |
<div className="space-y-1.5"> | |
<div className="flex items-center gap-1.5 text-xs text-muted-foreground/70 pt-1.5"> | |
<Code className="h-3 w-3" /> | |
<span className="font-medium">Arguments</span> | |
</div> | |
<pre className={cn( | |
"text-xs font-mono p-2.5 rounded-md overflow-x-auto", | |
"border border-border/40 bg-muted/10" | |
)}> | |
{formatContent(args)} | |
</pre> | |
</div> | |
)} | |
{!!result && ( | |
<div className="space-y-1.5"> | |
<div className="flex items-center gap-1.5 text-xs text-muted-foreground/70"> | |
<ArrowRight className="h-3 w-3" /> | |
<span className="font-medium">Result</span> | |
</div> | |
<pre className={cn( | |
"text-xs font-mono p-2.5 rounded-md overflow-x-auto max-h-[300px] overflow-y-auto", | |
"border border-border/40 bg-muted/10" | |
)}> | |
{formatContent(result)} | |
</pre> | |
</div> | |
)} | |
</div> | |
)} | |
</div> | |
); | |
} |