import { useCallback, useEffect, useMemo, useState } from 'react'; import { ExternalLink, Globe, MonitorPlay, Navigation, Pause, RefreshCw, Square } from 'lucide-react'; import { Badge, Button } from '../../../shared/view/ui'; import { authenticatedFetch } from '../../../utils/api'; type BrowserUseStatus = { enabled: boolean; available: boolean; runtime: 'cloud' | 'local'; sessionCount: number; mcpRecommended: boolean; message: string; }; type BrowserUseSession = { id: string; runtime: 'cloud' | 'local'; status: 'ready' | 'stopped' | 'unavailable'; url: string | null; title: string | null; screenshotDataUrl: string | null; createdAt: string; updatedAt: string; lastAction: string | null; message: string | 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 [error, setError] = useState(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]); 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); }); return (

Browser Use

{status && ( {status.runtime} )}

Managed Playwright browser sessions with owner-scoped screenshots and navigation.

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" />
{error && (
{error}
)}
{selectedSession?.url || 'No page loaded'}
{selectedSession?.screenshotDataUrl ? ( Browser session screenshot ) : (
{selectedSession?.message || 'Create a browser session to start.'}

This panel shows captured browser screenshots. Interactive agent control should use the guarded Browser Use API.

)}
); }