import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent, type MouseEvent } from 'react'; import { Bot, Download, Expand, ExternalLink, Globe, Loader2, MonitorPlay, Navigation, RefreshCw, Share2, Square, Trash2, X } from 'lucide-react'; import { Badge, Button } from '../../../shared/view/ui'; import { authenticatedFetch } from '../../../utils/api'; type BrowserUseStatus = { enabled: boolean; available: boolean; playwrightInstalled: boolean; chromiumInstalled: boolean; installInProgress: boolean; sessionCount: number; agentToolsEnabled: boolean; mcpRecommended: boolean; 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; agentAccessEnabled: boolean; createdBy: 'user' | 'agent'; profileName: string | null; viewport: { width: number; height: number; } | null; cursor: { x: number; y: number; actor: 'agent' | 'user'; } | null; }; type BrowserUsePanelProps = { isVisible: boolean; }; 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; } export default function BrowserUsePanel({ isVisible }: BrowserUsePanelProps) { const [status, setStatus] = useState(null); const [sessions, setSessions] = useState([]); const [selectedSessionId, setSelectedSessionId] = useState(null); const [targetUrl, setTargetUrl] = useState('https://example.com'); const [isBusy, setIsBusy] = useState(false); const [isInstalling, setIsInstalling] = useState(false); const [isFullscreen, setIsFullscreen] = useState(false); const [error, setError] = useState(null); const viewerRef = useRef(null); const selectedSession = useMemo( () => sessions.find((session) => session.id === selectedSessionId) || sessions[0] || null, [selectedSessionId, sessions], ); const refresh = useCallback(async () => { 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); setStatus(statusData.data); setSessions(sessionsData.data.sessions); setSelectedSessionId((current) => ( current && sessionsData.data.sessions.some((session) => session.id === current) ? current : sessionsData.data.sessions[0]?.id || null )); }, []); useEffect(() => { if (!isVisible) return; void refresh().catch((err) => setError(err instanceof Error ? err.message : 'Failed to load Browser Use')); }, [isVisible, refresh]); useEffect(() => { if (!selectedSession?.url) return; setTargetUrl(selectedSession.url); }, [selectedSession?.id, selectedSession?.url]); const runAction = useCallback(async (action: () => Promise) => { setIsBusy(true); setError(null); try { await action(); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : 'Browser Use action failed'); } finally { setIsBusy(false); } }, [refresh]); const createSession = () => runAction(async () => { const response = await authenticatedFetch('/api/browser-use/sessions', { method: 'POST' }); const data = await readJson<{ data: { session: BrowserUseSession } }>(response); setSelectedSessionId(data.data.session.id); }); const navigate = () => runAction(async () => { if (!selectedSession) { throw new Error('Create a browser session first.'); } const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/navigate`, { method: 'POST', body: JSON.stringify({ url: targetUrl }), }); await readJson(response); }); 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 grantAgentAccess = () => runAction(async () => { if (!selectedSession) return; const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/agent-access/grant`, { method: 'POST' }); await readJson(response); }); const revokeAgentAccess = () => runAction(async () => { if (!selectedSession) return; const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/agent-access/revoke`, { method: 'POST' }); await readJson(response); }); 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 clickViewer = useCallback((event: MouseEvent) => { if (!selectedSession || selectedSession.status !== 'ready' || !selectedSession.viewport) { return; } viewerRef.current?.focus(); const bounds = event.currentTarget.getBoundingClientRect(); const scaleX = selectedSession.viewport.width / bounds.width; const scaleY = selectedSession.viewport.height / bounds.height; const x = Math.round((event.clientX - bounds.left) * scaleX); const y = Math.round((event.clientY - bounds.top) * scaleY); void runAction(async () => { const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/click`, { method: 'POST', body: JSON.stringify({ x, y }), }); await readJson(response); }); }, [runAction, selectedSession]); const keyForEvent = useCallback((event: KeyboardEvent) => { if (event.key === ' ') return 'Space'; return event.key; }, []); const pressViewerKey = useCallback((event: KeyboardEvent) => { if (!selectedSession || selectedSession.status !== 'ready') { return; } const ignoredKeys = new Set(['Shift', 'Control', 'Alt', 'Meta', 'CapsLock']); if (ignoredKeys.has(event.key)) { return; } event.preventDefault(); const key = keyForEvent(event); void runAction(async () => { const response = await authenticatedFetch(`/api/browser-use/sessions/${selectedSession.id}/press-key`, { method: 'POST', body: JSON.stringify({ key }), }); await readJson(response); }); }, [keyForEvent, runAction, selectedSession]); const needsBrowserBinaries = Boolean(status?.enabled && (!status.playwrightInstalled || !status.chromiumInstalled)); const cursorStyle = selectedSession?.cursor && selectedSession.viewport ? { left: `${(selectedSession.cursor.x / selectedSession.viewport.width) * 100}%`, top: `${(selectedSession.cursor.y / selectedSession.viewport.height) * 100}%`, } : null; const renderBrowserSurface = (fullscreen = false) => (
{selectedSession?.screenshotDataUrl ? (
Browser session screenshot {cursorStyle && (
)}
) : (
{selectedSession?.message || 'Create a browser session to start.'}

Install browser binaries from this panel or enable Browser Use from Settings.

)}
); return (

Browser Use

Create browser sessions, watch agent activity, and decide which sessions agents may control.

Guide
setTargetUrl(event.target.value)} className="h-9 min-w-[220px] flex-1 rounded-md border border-input bg-background px-3 text-sm outline-none focus:ring-1 focus:ring-ring" placeholder="https://example.com" /> {selectedSession?.agentAccessEnabled ? ( ) : ( )}
{error && (
{error}
)}
{selectedSession?.url || 'No page loaded'} {selectedSession?.agentAccessEnabled && ( Agent access active )}
{renderBrowserSurface()}
{isFullscreen && selectedSession && (
{selectedSession.title || selectedSession.url || 'Browser session'}
{renderBrowserSurface(true)}
)}
); }