ReadImageTool.tsx

  1import React, { useState } from "react";
  2import { LLMContent } from "../types";
  3
  4interface ReadImageToolProps {
  5  toolInput?: unknown; // { path: string }
  6  isRunning?: boolean;
  7  toolResult?: LLMContent[];
  8  hasError?: boolean;
  9  executionTime?: string;
 10}
 11
 12function ReadImageTool({
 13  toolInput,
 14  isRunning,
 15  toolResult,
 16  hasError,
 17  executionTime,
 18}: ReadImageToolProps) {
 19  const [isExpanded, setIsExpanded] = useState(true); // Default to expanded
 20
 21  // Extract display info from toolInput
 22  const getPath = (input: unknown): string | undefined => {
 23    if (
 24      typeof input === "object" &&
 25      input !== null &&
 26      "path" in input &&
 27      typeof input.path === "string"
 28    ) {
 29      return input.path;
 30    }
 31    return undefined;
 32  };
 33
 34  const getId = (input: unknown): string | undefined => {
 35    if (
 36      typeof input === "object" &&
 37      input !== null &&
 38      "id" in input &&
 39      typeof input.id === "string"
 40    ) {
 41      return input.id;
 42    }
 43    return undefined;
 44  };
 45
 46  const filename = getPath(toolInput) || getId(toolInput) || "image";
 47
 48  // Build image URL from the base64 data in toolResult
 49  // The read_image tool returns [text description, image content with Data and MediaType]
 50  let imageUrl: string | undefined = undefined;
 51  if (toolResult && toolResult.length >= 2) {
 52    const imageContent = toolResult[1];
 53    if (imageContent?.Data && imageContent?.MediaType) {
 54      imageUrl = `data:${imageContent.MediaType};base64,${imageContent.Data}`;
 55    }
 56  }
 57
 58  const isComplete = !isRunning && toolResult !== undefined;
 59
 60  return (
 61    <div
 62      className="screenshot-tool"
 63      data-testid={isComplete ? "tool-call-completed" : "tool-call-running"}
 64    >
 65      <div className="screenshot-tool-header" onClick={() => setIsExpanded(!isExpanded)}>
 66        <div className="screenshot-tool-summary">
 67          <span className={`screenshot-tool-emoji ${isRunning ? "running" : ""}`}>🖼</span>
 68          <span className="screenshot-tool-filename">{filename}</span>
 69          {isComplete && hasError && <span className="screenshot-tool-error"></span>}
 70          {isComplete && !hasError && <span className="screenshot-tool-success"></span>}
 71        </div>
 72        <button
 73          className="screenshot-tool-toggle"
 74          aria-label={isExpanded ? "Collapse" : "Expand"}
 75          aria-expanded={isExpanded}
 76        >
 77          <svg
 78            width="12"
 79            height="12"
 80            viewBox="0 0 12 12"
 81            fill="none"
 82            xmlns="http://www.w3.org/2000/svg"
 83            style={{
 84              transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
 85              transition: "transform 0.2s",
 86            }}
 87          >
 88            <path
 89              d="M4.5 3L7.5 6L4.5 9"
 90              stroke="currentColor"
 91              strokeWidth="1.5"
 92              strokeLinecap="round"
 93              strokeLinejoin="round"
 94            />
 95          </svg>
 96        </button>
 97      </div>
 98
 99      {isExpanded && (
100        <div className="screenshot-tool-details">
101          {isComplete && !hasError && imageUrl && (
102            <div className="screenshot-tool-section">
103              {executionTime && (
104                <div className="screenshot-tool-label">
105                  <span>Image:</span>
106                  <span className="screenshot-tool-time">{executionTime}</span>
107                </div>
108              )}
109              <div className="screenshot-tool-image-container">
110                <a href={imageUrl} target="_blank" rel="noopener noreferrer">
111                  <img
112                    src={imageUrl}
113                    alt={`Image: ${filename}`}
114                    style={{ maxWidth: "100%", height: "auto" }}
115                  />
116                </a>
117              </div>
118            </div>
119          )}
120
121          {isComplete && hasError && (
122            <div className="screenshot-tool-section">
123              <div className="screenshot-tool-label">
124                <span>Error:</span>
125                {executionTime && <span className="screenshot-tool-time">{executionTime}</span>}
126              </div>
127              <pre className="screenshot-tool-error-message">
128                {toolResult && toolResult[0]?.Text ? toolResult[0].Text : "Image read failed"}
129              </pre>
130            </div>
131          )}
132
133          {isRunning && (
134            <div className="screenshot-tool-section">
135              <div className="screenshot-tool-label">Reading image...</div>
136            </div>
137          )}
138        </div>
139      )}
140    </div>
141  );
142}
143
144export default ReadImageTool;