1import React, { useState } from "react";
2import { LLMContent } from "../types";
3
4interface GenericToolProps {
5 toolName: string;
6
7 // For tool_use (pending state)
8 toolInput?: unknown;
9 isRunning?: boolean;
10
11 // For tool_result (completed state)
12 toolResult?: LLMContent[];
13 hasError?: boolean;
14 executionTime?: string;
15}
16
17function GenericTool({
18 toolName,
19 toolInput,
20 isRunning,
21 toolResult,
22 hasError,
23 executionTime,
24}: GenericToolProps) {
25 const [isExpanded, setIsExpanded] = useState(false);
26
27 // Format data for display
28 const formatData = (data: unknown): string => {
29 if (data === undefined || data === null) return "";
30 if (typeof data === "string") return data;
31 try {
32 return JSON.stringify(data, null, 2);
33 } catch {
34 return String(data);
35 }
36 };
37
38 // Extract output from toolResult
39 const output =
40 toolResult && toolResult.length > 0
41 ? toolResult.map((result) => result.Text || formatData(result)).join("\n")
42 : "";
43
44 const isComplete = !isRunning && toolResult !== undefined;
45
46 return (
47 <div className="tool" data-testid={isComplete ? "tool-call-completed" : "tool-call-running"}>
48 <div className="tool-header" onClick={() => setIsExpanded(!isExpanded)}>
49 <div className="tool-summary">
50 <span className={`tool-emoji ${isRunning ? "running" : ""}`}>⚙️</span>
51 <span className="tool-command">{toolName}</span>
52 {isComplete && hasError && <span className="tool-error">✗</span>}
53 {isComplete && !hasError && <span className="tool-success">✓</span>}
54 </div>
55 <button
56 className="tool-toggle"
57 aria-label={isExpanded ? "Collapse" : "Expand"}
58 aria-expanded={isExpanded}
59 >
60 <svg
61 width="12"
62 height="12"
63 viewBox="0 0 12 12"
64 fill="none"
65 xmlns="http://www.w3.org/2000/svg"
66 style={{
67 transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
68 transition: "transform 0.2s",
69 }}
70 >
71 <path
72 d="M4.5 3L7.5 6L4.5 9"
73 stroke="currentColor"
74 strokeWidth="1.5"
75 strokeLinecap="round"
76 strokeLinejoin="round"
77 />
78 </svg>
79 </button>
80 </div>
81
82 {isExpanded && (
83 <div className="tool-details">
84 {toolInput !== undefined && (
85 <div className="tool-section">
86 <div className="tool-label">Input:</div>
87 <pre className="tool-code">{formatData(toolInput)}</pre>
88 </div>
89 )}
90
91 {isRunning && (
92 <div className="tool-section">
93 <div className="tool-label">Status:</div>
94 <div className="tool-running-text">running...</div>
95 </div>
96 )}
97
98 {isComplete && (
99 <div className="tool-section">
100 <div className="tool-label">
101 Output{hasError ? " (Error)" : ""}:
102 {executionTime && <span className="tool-time">{executionTime}</span>}
103 </div>
104 <pre className={`tool-code ${hasError ? "error" : ""}`}>
105 {output || "(no output)"}
106 </pre>
107 </div>
108 )}
109 </div>
110 )}
111 </div>
112 );
113}
114
115export default GenericTool;