import { useCallback, useEffect, useMemo, useState } from 'react'; import { Bot, Clock3, Download, Expand, ExternalLink, Loader2, MonitorPlay, RefreshCw, Settings, Square, Trash2, X, } from 'lucide-react'; import { cn } from '../../../lib/utils'; import { Badge, Button } from '../../../shared/view/ui'; import { authenticatedFetch } from '../../../utils/api'; import type { SettingsMainTab } from '../../settings/types/types'; type BrowserUseStatus = { enabled: boolean; available: boolean; playwrightInstalled: boolean; chromiumInstalled: boolean; installInProgress: boolean; sessionCount: number; message: string; }; type BrowserUseSession = { id: string; status: 'ready' | 'stopped' | 'unavailable'; url: string | null; title: string | null; screenshotDataUrl: string | null; createdAt: string; updatedAt: string; lastAction: string | null; message: string | null; createdBy: 'agent'; profileName: string | null; viewport: { width: number; height: number; } | null; cursor: { x: number; y: number; actor: 'agent'; } | null; }; type BrowserUsePanelProps = { isVisible: boolean; onShowSettings?: (tab?: SettingsMainTab) => void; }; async function readJson(response: Response): Promise { const data = await response.json(); if (!response.ok || data.success === false) { throw new Error(data.error || data.details || `Request failed (${response.status})`); } return data as T; } function formatRelativeTime(value: string | null): string { if (!value) return 'Never'; const timestamp = Date.parse(value); if (!Number.isFinite(timestamp)) return 'Unknown'; const elapsedSeconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000)); if (elapsedSeconds < 10) return 'Just now'; if (elapsedSeconds < 60) return `${elapsedSeconds}s ago`; const elapsedMinutes = Math.round(elapsedSeconds / 60); if (elapsedMinutes < 60) return `${elapsedMinutes}m ago`; const elapsedHours = Math.round(elapsedMinutes / 60); if (elapsedHours < 24) return `${elapsedHours}h ago`; return `${Math.round(elapsedHours / 24)}d ago`; } function getDomain(url: string | null): string { if (!url) return 'No page loaded'; try { return new URL(url).hostname; } catch { return url; } } function formatAction(action: string | null): string { if (!action) return 'Waiting'; return action.replace(/_/g, ' ').replace(/:/g, ': '); } function getStatusTone(status: BrowserUseSession['status']): string { if (status === 'ready') { return 'border-primary/30 bg-primary/5 text-foreground'; } if (status === 'stopped') { return 'border-border bg-muted text-muted-foreground'; } return 'border-border bg-background text-muted-foreground'; } function getRuntimeTone(status: BrowserUseStatus | null, installing: boolean): string { if (!status?.enabled) return 'border-border bg-muted text-muted-foreground'; if (status.available) return 'border-primary/30 bg-primary/5 text-foreground'; if (status.installInProgress || installing) return 'border-primary/30 bg-primary/5 text-foreground'; return 'border-border bg-background text-muted-foreground'; } function getStatusDot(status: BrowserUseSession['status']): string { if (status === 'ready') return 'bg-primary'; if (status === 'stopped') return 'bg-muted-foreground/50'; return 'bg-border'; } const PROMPTS = [ 'Use Browser to inspect the checkout flow and report any broken UI states.', 'Open with Browser, interact with the page, and summarize what changed after each step.', ]; export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUsePanelProps) { const [status, setStatus] = useState(null); const [sessions, setSessions] = useState([]); const [selectedSessionId, setSelectedSessionId] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); const [isBusy, setIsBusy] = useState(false); const [isInstalling, setIsInstalling] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [error, setError] = useState(null); const selectedSession = useMemo( () => sessions.find((session) => session.id === selectedSessionId) || sessions[0] || null, [selectedSessionId, sessions], ); const activeSessions = sessions.filter((session) => session.status === 'ready'); const needsBrowserBinaries = Boolean(status?.enabled && (!status.playwrightInstalled || !status.chromiumInstalled)); const runtimeLabel = !status?.enabled ? 'Disabled' : status.available ? 'Ready' : status.installInProgress || isInstalling ? 'Installing' : 'Setup required'; const cursorStyle = selectedSession?.cursor && selectedSession.viewport ? { left: `${(selectedSession.cursor.x / selectedSession.viewport.width) * 100}%`, top: `${(selectedSession.cursor.y / selectedSession.viewport.height) * 100}%`, } : null; const refresh = useCallback(async () => { setIsRefreshing(true); try { const [statusResponse, sessionsResponse] = await Promise.all([ authenticatedFetch('/api/browser-use/status'), authenticatedFetch('/api/browser-use/sessions'), ]); const statusData = await readJson<{ data: BrowserUseStatus }>(statusResponse); const sessionsData = await readJson<{ data: { sessions: BrowserUseSession[] } }>(sessionsResponse); const nextSessions = sessionsData.data.sessions; setStatus(statusData.data); setSessions(nextSessions); setSelectedSessionId((current) => ( current && nextSessions.some((session) => session.id === current) ? current : nextSessions[0]?.id || null )); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load Browser'); } finally { setIsRefreshing(false); } }, []); useEffect(() => { if (!isVisible) return; void refresh(); }, [isVisible, refresh]); const runAction = useCallback(async (action: () => Promise) => { setIsBusy(true); setError(null); try { await action(); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : 'Browser action failed'); } finally { setIsBusy(false); } }, [refresh]); const stopSession = () => runAction(async () => { if (!selectedSession) return; const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/stop`, { method: 'POST' }); await readJson(response); }); const deleteSession = () => runAction(async () => { if (!selectedSession) return; const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}`, { method: 'DELETE' }); await readJson(response); setIsFullscreen(false); }); const installBrowserBinaries = () => runAction(async () => { setIsInstalling(true); try { const response = await authenticatedFetch('/api/browser-use/runtime/install', { method: 'POST' }); await readJson(response); } finally { setIsInstalling(false); } }); const renderSessionItem = (session: BrowserUseSession) => { const isSelected = selectedSession?.id === session.id; return ( ); }; const renderEmptyState = () => (
{status?.enabled ? 'No browser sessions yet' : 'Browser is disabled'}

{status?.enabled ? 'Agent browser sessions appear here while an AI task is using Browser.' : 'Enable Browser in settings to let agents open monitored browser sessions.'}

{needsBrowserBinaries && (
Runtime setup required

{status?.message}

)}
{PROMPTS.map((prompt) => (
Prompt

{prompt}

))}
); const renderBrowserSurface = (fullscreen = false) => (
{selectedSession?.screenshotDataUrl ? (
Browser session screenshot {cursorStyle && (
)}
) : (
{selectedSession?.message || 'Waiting for screenshot'}

The next agent browser snapshot will render here.

)}
); return (

Browser

{runtimeLabel}

Monitor browser sessions opened by AI agents.

{onShowSettings && ( )}
{error && (
{error}
)} {sessions.length > 0 && (
{sessions.map((session) => ( ))}
)}
{activeSessions.length} active / {sessions.length} total
Updated {formatRelativeTime(selectedSession?.updatedAt || null)}
{sessions.length === 0 ? ( renderEmptyState() ) : (
{selectedSession?.status || 'empty'}
{selectedSession?.title || getDomain(selectedSession?.url || null)}
{selectedSession?.url || 'No page loaded'}
{formatAction(selectedSession?.lastAction || null)}
{renderBrowserSurface()}
)}
{isFullscreen && selectedSession && (
{selectedSession.title || selectedSession.url || 'Browser session'}
{renderBrowserSurface(true)}
)}
); }