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;