ChangeDirTool.tsx

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