Spaces:
Running
Running
File size: 3,914 Bytes
5012205 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
"use client";
import { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import {
ChevronDownIcon,
ChevronUpIcon,
Loader2,
PocketKnife,
CheckCircle,
StopCircle,
Code2,
Terminal,
} from "lucide-react";
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 variants = {
collapsed: {
height: 0,
opacity: 0,
},
expanded: {
height: "auto",
opacity: 1,
},
};
const getStatusIcon = () => {
if (state === "call") {
if (isLatestMessage && status !== "ready") {
return <Loader2 className="animate-spin h-4 w-4 text-muted-foreground" />;
}
return <StopCircle className="h-4 w-4 text-destructive" />;
}
return <CheckCircle size={14} className="text-success" />;
};
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="flex flex-col gap-2 p-4 mb-4 bg-muted/50 rounded-xl border border-border backdrop-blur-sm">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 bg-muted rounded-lg">
<PocketKnife className="h-4 w-4" />
</div>
<div className="flex-1 flex items-center gap-2">
<span className="text-sm font-medium">
{state === "call" ? "Calling" : "Called"}
</span>
<code className="px-2 py-1 text-xs font-mono rounded-md bg-muted text-muted-foreground">
{toolName}
</code>
</div>
<div className="flex items-center gap-2">
{getStatusIcon()}
<button
onClick={() => setIsExpanded(!isExpanded)}
className="p-1 hover:bg-muted rounded-md transition-colors"
>
{isExpanded ? (
<ChevronUpIcon className="h-4 w-4" />
) : (
<ChevronDownIcon className="h-4 w-4" />
)}
</button>
</div>
</div>
<AnimatePresence initial={false}>
{isExpanded && (
<motion.div
initial="collapsed"
animate="expanded"
exit="collapsed"
variants={variants}
transition={{ duration: 0.2 }}
className="space-y-3 pt-2"
>
{!!args && (
<div className="space-y-1.5">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Terminal className="h-3.5 w-3.5" />
<span>Arguments</span>
</div>
<pre className="text-xs font-mono bg-muted p-3 rounded-lg overflow-x-auto">
{formatContent(args)}
</pre>
</div>
)}
{!!result && (
<div className="space-y-1.5">
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Code2 className="h-3.5 w-3.5" />
<span>Result</span>
</div>
<pre className="text-xs font-mono bg-muted p-3 rounded-lg overflow-x-auto max-h-[300px] overflow-y-auto">
{formatContent(result)}
</pre>
</div>
)}
</motion.div>
)}
</AnimatePresence>
</div>
);
} |