ThinkTool.tsx

 1import React, { useState } from "react";
 2import { LLMContent } from "../types";
 3
 4interface ThinkToolProps {
 5  // For tool_use (pending state)
 6  toolInput?: unknown; // { thoughts: string }
 7  isRunning?: boolean;
 8
 9  // For tool_result (completed state)
10  toolResult?: LLMContent[];
11  hasError?: boolean;
12  executionTime?: string;
13}
14
15function ThinkTool({ toolInput, isRunning, toolResult, hasError, executionTime }: ThinkToolProps) {
16  const [isExpanded, setIsExpanded] = useState(false);
17
18  // Extract thoughts from toolInput
19  const thoughts =
20    typeof toolInput === "object" &&
21    toolInput !== null &&
22    "thoughts" in toolInput &&
23    typeof toolInput.thoughts === "string"
24      ? toolInput.thoughts
25      : typeof toolInput === "string"
26        ? toolInput
27        : "";
28
29  // Truncate thoughts for display - get first 50 chars
30  const truncateThoughts = (text: string, maxLen: number = 50) => {
31    if (!text) return "";
32    if (text.length <= maxLen) return text;
33    return text.substring(0, maxLen) + "...";
34  };
35
36  const displayThoughts = truncateThoughts(thoughts);
37  const isComplete = !isRunning && toolResult !== undefined;
38
39  return (
40    <div className="tool" data-testid={isComplete ? "tool-call-completed" : "tool-call-running"}>
41      <div className="tool-header" onClick={() => setIsExpanded(!isExpanded)}>
42        <div className="tool-summary">
43          <span className={`tool-emoji ${isRunning ? "running" : ""}`}>💭</span>
44          <span className="tool-command">
45            {displayThoughts || (isRunning ? "thinking..." : "thinking...")}
46          </span>
47          {isComplete && hasError && <span className="tool-error"></span>}
48          {isComplete && !hasError && <span className="tool-success"></span>}
49        </div>
50        <button
51          className="tool-toggle"
52          aria-label={isExpanded ? "Collapse" : "Expand"}
53          aria-expanded={isExpanded}
54        >
55          <svg
56            width="12"
57            height="12"
58            viewBox="0 0 12 12"
59            fill="none"
60            xmlns="http://www.w3.org/2000/svg"
61            style={{
62              transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
63              transition: "transform 0.2s",
64            }}
65          >
66            <path
67              d="M4.5 3L7.5 6L4.5 9"
68              stroke="currentColor"
69              strokeWidth="1.5"
70              strokeLinecap="round"
71              strokeLinejoin="round"
72            />
73          </svg>
74        </button>
75      </div>
76
77      {isExpanded && (
78        <div className="tool-details">
79          <div className="tool-section">
80            <div className="tool-label">
81              Thoughts:
82              {executionTime && <span className="tool-time">{executionTime}</span>}
83            </div>
84            <div className={`tool-code ${hasError ? "error" : ""}`}>
85              {thoughts || "(no thoughts)"}
86            </div>
87          </div>
88        </div>
89      )}
90    </div>
91  );
92}
93
94export default ThinkTool;