import React, { useMemo } from "react"; import { Globe, MonitorPlay, ExternalLink, CheckCircle, AlertTriangle, CircleDashed } from "lucide-react"; import { ToolViewProps } from "./types"; import { extractBrowserUrl, extractBrowserOperation, formatTimestamp, getToolTitle } from "./utils"; import { ApiMessageType } from '@/components/thread/types'; import { safeJsonParse } from '@/components/thread/utils'; import { cn } from "@/lib/utils"; export function BrowserToolView({ name = "browser-operation", assistantContent, toolContent, assistantTimestamp, toolTimestamp, isSuccess = true, isStreaming = false, project, agentStatus = 'idle', messages = [], currentIndex = 0, totalCalls = 1 }: ToolViewProps) { const url = extractBrowserUrl(assistantContent); const operation = extractBrowserOperation(name); const toolTitle = getToolTitle(name); // --- message_id Extraction Logic --- let browserStateMessageId: string | undefined; try { // 1. Parse the top-level JSON const topLevelParsed = safeJsonParse<{ content?: string }>(toolContent, {}); const innerContentString = topLevelParsed?.content; if (innerContentString && typeof innerContentString === 'string') { // 2. Extract the output='...' string using regex const outputMatch = innerContentString.match(/\boutput='(.*?)'(?=\s*\))/); const outputString = outputMatch ? outputMatch[1] : null; if (outputString) { // 3. Unescape the JSON string (basic unescaping for \n and \") const unescapedOutput = outputString.replace(/\\n/g, '\n').replace(/\\"/g, '"'); // 4. Parse the unescaped JSON to get message_id const finalParsedOutput = safeJsonParse<{ message_id?: string }>(unescapedOutput, {}); browserStateMessageId = finalParsedOutput?.message_id; } } } catch (error) { console.error("[BrowserToolView] Error parsing tool content for message_id:", error); } // Find the browser_state message and extract the screenshot let screenshotBase64: string | null = null; if (browserStateMessageId && messages.length > 0) { const browserStateMessage = messages.find(msg => (msg.type as string) === 'browser_state' && msg.message_id === browserStateMessageId ); if (browserStateMessage) { const browserStateContent = safeJsonParse<{ screenshot_base64?: string }>(browserStateMessage.content, {}); screenshotBase64 = browserStateContent?.screenshot_base64 || null; } } // Check if we have a VNC preview URL from the project const vncPreviewUrl = project?.sandbox?.vnc_preview ? `${project.sandbox.vnc_preview}/vnc_lite.html?password=${project?.sandbox?.pass}&autoconnect=true&scale=local&width=1024&height=768` : undefined; const isRunning = isStreaming || agentStatus === 'running'; const isLastToolCall = currentIndex === (totalCalls - 1); // Memoize the VNC iframe to prevent reconnections on re-renders const vncIframe = useMemo(() => { if (!vncPreviewUrl) return null; console.log("[BrowserToolView] Creating memoized VNC iframe with URL:", vncPreviewUrl); return ( ); }, [vncPreviewUrl]); // Only recreate if the URL changes return (
No Browser State image found