From 496a895e8a61d4130c2506d3c8f837ffb8a441d8 Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Wed, 17 Jun 2026 17:39:55 +0000 Subject: [PATCH] feat(browser-use): refine monitoring panel ux --- .../browser-use/view/BrowserUsePanel.tsx | 419 +++++++++++------- 1 file changed, 249 insertions(+), 170 deletions(-) diff --git a/src/components/browser-use/view/BrowserUsePanel.tsx b/src/components/browser-use/view/BrowserUsePanel.tsx index 5a7825ba..03331024 100644 --- a/src/components/browser-use/view/BrowserUsePanel.tsx +++ b/src/components/browser-use/view/BrowserUsePanel.tsx @@ -1,6 +1,22 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Bot, Clock3, Download, Expand, ExternalLink, Loader2, MonitorPlay, RefreshCw, Settings, Square, Trash2, X } from 'lucide-react'; +import { + Activity, + Bot, + Clock3, + Download, + Expand, + ExternalLink, + Globe2, + 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'; @@ -51,14 +67,10 @@ async function readJson(response: Response): Promise { } function formatRelativeTime(value: string | null): string { - if (!value) { - return 'Never'; - } + if (!value) return 'Never'; const timestamp = Date.parse(value); - if (!Number.isFinite(timestamp)) { - return 'Unknown'; - } + if (!Number.isFinite(timestamp)) return 'Unknown'; const elapsedSeconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000)); if (elapsedSeconds < 10) return 'Just now'; @@ -71,9 +83,7 @@ function formatRelativeTime(value: string | null): string { } function getDomain(url: string | null): string { - if (!url) { - return 'No page loaded'; - } + if (!url) return 'No page loaded'; try { return new URL(url).hostname; @@ -83,9 +93,7 @@ function getDomain(url: string | null): string { } function formatAction(action: string | null): string { - if (!action) { - return 'Waiting'; - } + if (!action) return 'Waiting'; return action.replace(/_/g, ' ').replace(/:/g, ': '); } @@ -99,9 +107,16 @@ function getStatusTone(status: BrowserUseSession['status']): string { return 'border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300'; } +function getRuntimeTone(status: BrowserUseStatus | null, installing: boolean): string { + if (!status?.enabled) return 'border-border bg-muted text-muted-foreground'; + if (status.available) return 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300'; + if (status.installInProgress || installing) return 'border-blue-500/30 bg-blue-500/10 text-blue-700 dark:text-blue-300'; + return 'border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300'; +} + const PROMPTS = [ - 'Use Browser Use to open the staging checkout flow, try the main path, and summarize anything that looks broken.', - 'Use Browser Use to inspect the page at , capture what changed after each click, and report UI issues with screenshots.', + 'Use Browser Use to inspect the checkout flow and report any broken UI states.', + 'Open with Browser Use, interact with the page, and summarize what changed after each step.', ]; export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUsePanelProps) { @@ -119,6 +134,23 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs [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 { @@ -185,59 +217,105 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs } }); - const needsBrowserBinaries = Boolean(status?.enabled && (!status.playwrightInstalled || !status.chromiumInstalled)); - const activeSessions = sessions.filter((session) => session.status === 'ready'); - const inactiveSessions = sessions.filter((session) => session.status !== 'ready'); - const statusLabel = !status?.enabled - ? 'Disabled' - : status.available - ? 'Ready' - : status.installInProgress || isInstalling - ? 'Installing' - : 'Setup required'; + const renderSessionItem = (session: BrowserUseSession) => { + const isSelected = selectedSession?.id === session.id; + return ( + + ); + }; - const cursorStyle = selectedSession?.cursor && selectedSession.viewport - ? { - left: `${(selectedSession.cursor.x / selectedSession.viewport.width) * 100}%`, - top: `${(selectedSession.cursor.y / selectedSession.viewport.height) * 100}%`, - } - : null; + const renderEmptyState = () => ( +
+
+
+
+ +
+
+
+ {status?.enabled ? 'No browser sessions yet' : 'Browser Use is disabled'} +
+

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

+
+
- const renderSessionItem = (session: BrowserUseSession) => ( - +
+ )} + +
+ {PROMPTS.map((prompt) => ( +
+
+ + Prompt +
+

{prompt}

+
+ ))} +
-
{session.url || session.message || session.id}
-
- - {formatRelativeTime(session.updatedAt)} - {session.lastAction && - {formatAction(session.lastAction)}} -
- + ); const renderBrowserSurface = (fullscreen = false) => ( -
+
{selectedSession?.screenshotDataUrl ? (
Browser session screenshot {cursorStyle && (
@@ -245,14 +323,10 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs )}
) : ( -
- -
- {selectedSession?.message || 'No browser screenshot yet.'} -
-

- Agent-created browser sessions appear here after the agent starts using Browser Use. -

+
+ +
{selectedSession?.message || 'Waiting for screenshot'}
+

The next agent browser snapshot will render here.

)}
@@ -260,16 +334,16 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs return (
-
+

Browser Use

- {statusLabel} + + {runtimeLabel} +
-

- Watch browser sessions created by AI agents and stop them when needed. -

+

Monitor browser sessions opened by AI agents.

{onShowSettings && ( @@ -293,124 +367,129 @@ export default function BrowserUsePanel({ isVisible, onShowSettings }: BrowserUs title="Refresh browser sessions" aria-label="Refresh browser sessions" > - +
-
-