1import React, { useState } from "react";
2import { LLMContent } from "../types";
3
4interface ScreenshotToolProps {
5 // For tool_use (pending state)
6 toolInput?: unknown;
7 isRunning?: boolean;
8
9 // For tool_result (completed state)
10 toolResult?: LLMContent[];
11 hasError?: boolean;
12 executionTime?: string;
13 display?: unknown; // Display data from the tool_result Content
14}
15
16function ScreenshotTool({
17 toolInput,
18 isRunning,
19 toolResult,
20 hasError,
21 executionTime,
22 display,
23}: ScreenshotToolProps) {
24 const [isExpanded, setIsExpanded] = useState(true); // Default to expanded
25
26 // Extract display info from toolInput
27 const getPath = (input: unknown): string | undefined => {
28 if (
29 typeof input === "object" &&
30 input !== null &&
31 "path" in input &&
32 typeof input.path === "string"
33 ) {
34 return input.path;
35 }
36 return undefined;
37 };
38
39 const getId = (input: unknown): string | undefined => {
40 if (
41 typeof input === "object" &&
42 input !== null &&
43 "id" in input &&
44 typeof input.id === "string"
45 ) {
46 return input.id;
47 }
48 return undefined;
49 };
50
51 const getSelector = (input: unknown): string | undefined => {
52 if (
53 typeof input === "object" &&
54 input !== null &&
55 "selector" in input &&
56 typeof input.selector === "string"
57 ) {
58 return input.selector;
59 }
60 return undefined;
61 };
62
63 const filename = getPath(toolInput) || getId(toolInput) || getSelector(toolInput) || "screenshot";
64
65 // Use display data passed as prop (from tool_result Content.Display)
66 const displayData = display;
67
68 // Construct image URL
69 let imageUrl: string | undefined = undefined;
70 if (displayData && typeof displayData === "object" && displayData !== null) {
71 const url =
72 "url" in displayData && typeof displayData.url === "string" ? displayData.url : undefined;
73 const path =
74 "path" in displayData && typeof displayData.path === "string" ? displayData.path : undefined;
75 const id =
76 "id" in displayData && typeof displayData.id === "string" ? displayData.id : undefined;
77
78 imageUrl =
79 url ||
80 (path
81 ? `/api/read?path=${encodeURIComponent(path)}`
82 : id
83 ? `/api/read?path=${encodeURIComponent(id)}`
84 : undefined);
85 }
86
87 const isComplete = !isRunning && toolResult !== undefined;
88
89 return (
90 <div
91 className="screenshot-tool"
92 data-testid={isComplete ? "tool-call-completed" : "tool-call-running"}
93 >
94 <div className="screenshot-tool-header" onClick={() => setIsExpanded(!isExpanded)}>
95 <div className="screenshot-tool-summary">
96 <span className={`screenshot-tool-emoji ${isRunning ? "running" : ""}`}>📷</span>
97 <span className="screenshot-tool-filename">{filename}</span>
98 {isComplete && hasError && <span className="screenshot-tool-error">✗</span>}
99 {isComplete && !hasError && <span className="screenshot-tool-success">✓</span>}
100 </div>
101 <button
102 className="screenshot-tool-toggle"
103 aria-label={isExpanded ? "Collapse" : "Expand"}
104 aria-expanded={isExpanded}
105 >
106 <svg
107 width="12"
108 height="12"
109 viewBox="0 0 12 12"
110 fill="none"
111 xmlns="http://www.w3.org/2000/svg"
112 style={{
113 transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
114 transition: "transform 0.2s",
115 }}
116 >
117 <path
118 d="M4.5 3L7.5 6L4.5 9"
119 stroke="currentColor"
120 strokeWidth="1.5"
121 strokeLinecap="round"
122 strokeLinejoin="round"
123 />
124 </svg>
125 </button>
126 </div>
127
128 {isExpanded && (
129 <div className="screenshot-tool-details">
130 {isComplete && !hasError && imageUrl && (
131 <div className="screenshot-tool-section">
132 {executionTime && (
133 <div className="screenshot-tool-label">
134 <span>Screenshot:</span>
135 <span className="screenshot-tool-time">{executionTime}</span>
136 </div>
137 )}
138 <div className="screenshot-tool-image-container">
139 <a href={imageUrl} target="_blank" rel="noopener noreferrer">
140 <img
141 src={imageUrl}
142 alt={`Screenshot: ${filename}`}
143 style={{ maxWidth: "100%", height: "auto" }}
144 />
145 </a>
146 </div>
147 </div>
148 )}
149
150 {isComplete && hasError && (
151 <div className="screenshot-tool-section">
152 <div className="screenshot-tool-label">
153 <span>Error:</span>
154 {executionTime && <span className="screenshot-tool-time">{executionTime}</span>}
155 </div>
156 <pre className="screenshot-tool-error-message">
157 {toolResult && toolResult[0]?.Text
158 ? toolResult[0].Text
159 : "Screenshot capture failed"}
160 </pre>
161 </div>
162 )}
163
164 {isRunning && (
165 <div className="screenshot-tool-section">
166 <div className="screenshot-tool-label">Capturing screenshot...</div>
167 </div>
168 )}
169 </div>
170 )}
171 </div>
172 );
173}
174
175export default ScreenshotTool;