File size: 4,765 Bytes
250bf31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronDown, Globe, Loader2, CheckCircle2, XCircle, Search } from "lucide-react";
import { cn } from "@/lib/utils";

interface ToolInvocationProps {
    toolName: string;
    args: Record<string, unknown>;
    result?: string;
    status: "pending" | "running" | "complete" | "error";
}

export function ToolInvocation({
    toolName,
    args,
    result,
    status,
}: ToolInvocationProps) {
    const [isExpanded, setIsExpanded] = useState(status === "running");

    const getStatusIndicator = () => {
        switch (status) {
            case "pending":
            case "running":
                return (
                    <div className="relative">
                        <div className="w-5 h-5 rounded-full border-2 border-blue-500/30 border-t-blue-500 animate-spin" />
                    </div>
                );
            case "complete":
                return (
                    <div className="w-5 h-5 rounded-full bg-green-500/10 flex items-center justify-center">
                        <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />
                    </div>
                );
            case "error":
                return (
                    <div className="w-5 h-5 rounded-full bg-red-500/10 flex items-center justify-center">
                        <XCircle className="w-3.5 h-3.5 text-red-500" />
                    </div>
                );
        }
    };

    const getDisplayInfo = () => {
        if (toolName === "web_search") {
            const query = args.query as string;
            return {
                icon: <Globe className="w-4 h-4" />,
                title: "Web Search",
                subtitle: query,
            };
        }
        return {
            icon: <Search className="w-4 h-4" />,
            title: toolName,
            subtitle: JSON.stringify(args),
        };
    };

    const info = getDisplayInfo();

    return (
        <div className={cn(
            "mb-3 rounded-xl border overflow-hidden transition-all duration-200",
            status === "running"
                ? "border-blue-500/30 bg-blue-500/5"
                : status === "error"
                    ? "border-red-500/30 bg-red-500/5"
                    : "border-border bg-muted/30"
        )}>
            <button
                onClick={() => setIsExpanded(!isExpanded)}
                className="w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-accent/30 transition-colors"
            >
                {getStatusIndicator()}

                <div className={cn(
                    "w-8 h-8 rounded-lg flex items-center justify-center",
                    status === "running" ? "bg-blue-500/10 text-blue-500" : "bg-muted text-muted-foreground"
                )}>
                    {info.icon}
                </div>

                <div className="flex-1 min-w-0">
                    <p className="text-sm font-medium">{info.title}</p>
                    <p className="text-xs text-muted-foreground truncate">{info.subtitle}</p>
                </div>

                <ChevronDown
                    className={cn(
                        "w-4 h-4 text-muted-foreground transition-transform duration-200 shrink-0",
                        isExpanded && "rotate-180"
                    )}
                />
            </button>

            <AnimatePresence initial={false}>
                {isExpanded && (
                    <motion.div
                        initial={{ height: 0 }}
                        animate={{ height: "auto" }}
                        exit={{ height: 0 }}
                        transition={{ duration: 0.2, ease: "easeInOut" }}
                        className="overflow-hidden"
                    >
                        <div className="px-4 pb-4 pt-2 border-t border-border/50">
                            {result && (
                                <div className="text-sm text-muted-foreground max-h-48 overflow-y-auto whitespace-pre-wrap rounded-lg bg-background/50 p-3">
                                    {result}
                                </div>
                            )}
                            {!result && status === "running" && (
                                <div className="flex items-center gap-2 text-sm text-blue-500">
                                    <Loader2 className="w-4 h-4 animate-spin" />
                                    <span>Fetching results...</span>
                                </div>
                            )}
                        </div>
                    </motion.div>
                )}
            </AnimatePresence>
        </div>
    );
}