feat(browser-use): label Camoufox mode as co-browse

This commit is contained in:
Simos Mikelatos
2026-07-09 20:23:31 +00:00
parent 1dfca21558
commit b389ecb24b
9 changed files with 49 additions and 13 deletions

View File

@@ -161,7 +161,7 @@ function getStatusDot(status: BrowserUseSession['status']): string {
}
function getEngineLabel(backend?: BrowserUseStatus['backend'] | BrowserUseSession['backend']): string {
return backend === 'camoufox-vnc' ? 'Visible browser' : 'Playwright';
return backend === 'camoufox-vnc' ? 'Co-browse' : 'Playwright';
}
const PROMPTS = [
@@ -207,6 +207,7 @@ export default function BrowserUsePanel({ isVisible, projectId, onShowSettings }
const isBackgroundRefreshing = isRefreshing && !isInitialLoading;
const needsBrowserBinaries = Boolean(status?.enabled && !status.available);
const usesLocalWindowViewer = status?.viewerMode === 'window';
const panelTitle = status?.browserBackend === 'camoufox-vnc' ? 'Co-browse' : 'Browser';
const runtimeLabel = isInitialLoading
? 'Loading'
: !status?.enabled
@@ -491,7 +492,7 @@ export default function BrowserUsePanel({ isVisible, projectId, onShowSettings }
<div className="min-w-0">
<div className="flex items-center gap-2">
<MonitorPlay className="h-4 w-4 text-primary" />
<h3 className="text-sm font-semibold text-foreground">Browser</h3>
<h3 className="text-sm font-semibold text-foreground">{panelTitle}</h3>
<Badge variant="outline" className={cn('text-[10px]', getRuntimeTone(status, isInstalling))}>
{runtimeLabel}
</Badge>
@@ -499,7 +500,11 @@ export default function BrowserUsePanel({ isVisible, projectId, onShowSettings }
{getEngineLabel(status?.backend)}
</Badge>
</div>
<p className="mt-0.5 text-xs text-muted-foreground">Watch and manage browser sessions agents use to test real websites.</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{status?.browserBackend === 'camoufox-vnc'
? 'Watch and manage co-browse sessions agents use to test real websites.'
: 'Watch and manage browser sessions agents use to test real websites.'}
</p>
{isBackgroundRefreshing && (
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground">
<RefreshCw className="h-3 w-3 animate-spin" />

View File

@@ -66,6 +66,7 @@ export type MainContentHeaderProps = {
selectedSession: ProjectSession | null;
shouldShowTasksTab: boolean;
shouldShowBrowserTab: boolean;
shouldLabelBrowserTabAsCobrowse: boolean;
isMobile: boolean;
onMenuClick: () => void;
};

View File

@@ -59,6 +59,7 @@ function MainContent({
const { currentProject, setCurrentProject } = useTaskMaster() as TaskMasterContextValue;
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings() as TasksSettingsContextValue;
const [browserUseEnabled, setBrowserUseEnabled] = useState(false);
const [shouldLabelBrowserTabAsCobrowse, setShouldLabelBrowserTabAsCobrowse] = useState(false);
const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled);
const shouldShowBrowserTab = browserUseEnabled;
@@ -103,9 +104,13 @@ function MainContent({
try {
const response = await authenticatedFetch('/api/browser-use/settings');
const data = await response.json();
setBrowserUseEnabled(Boolean(response.ok && data?.success !== false && data?.data?.settings?.enabled));
const settings = data?.data?.settings;
const enabled = Boolean(response.ok && data?.success !== false && settings?.enabled);
setBrowserUseEnabled(enabled);
setShouldLabelBrowserTabAsCobrowse(enabled && settings?.browserBackend === 'camoufox-vnc');
} catch {
setBrowserUseEnabled(false);
setShouldLabelBrowserTabAsCobrowse(false);
}
}, []);
@@ -149,6 +154,7 @@ function MainContent({
selectedSession={selectedSession}
shouldShowTasksTab={shouldShowTasksTab}
shouldShowBrowserTab={shouldShowBrowserTab}
shouldLabelBrowserTabAsCobrowse={shouldLabelBrowserTabAsCobrowse}
isMobile={isMobile}
onMenuClick={onMenuClick}
/>

View File

@@ -11,6 +11,7 @@ export default function MainContentHeader({
selectedSession,
shouldShowTasksTab,
shouldShowBrowserTab,
shouldLabelBrowserTabAsCobrowse,
isMobile,
onMenuClick,
}: MainContentHeaderProps) {
@@ -44,6 +45,7 @@ export default function MainContentHeader({
selectedProject={selectedProject}
selectedSession={selectedSession}
shouldShowTasksTab={shouldShowTasksTab}
shouldLabelBrowserTabAsCobrowse={shouldLabelBrowserTabAsCobrowse}
/>
</div>
@@ -61,6 +63,7 @@ export default function MainContentHeader({
setActiveTab={setActiveTab}
shouldShowTasksTab={shouldShowTasksTab}
shouldShowBrowserTab={shouldShowBrowserTab}
shouldLabelBrowserTabAsCobrowse={shouldLabelBrowserTabAsCobrowse}
/>
</div>
{canScrollRight && (

View File

@@ -12,6 +12,7 @@ type MainContentTabSwitcherProps = {
setActiveTab: Dispatch<SetStateAction<AppTab>>;
shouldShowTasksTab: boolean;
shouldShowBrowserTab: boolean;
shouldLabelBrowserTabAsCobrowse: boolean;
};
type BuiltInTab = {
@@ -57,9 +58,11 @@ export default function MainContentTabSwitcher({
setActiveTab,
shouldShowTasksTab,
shouldShowBrowserTab,
shouldLabelBrowserTabAsCobrowse,
}: MainContentTabSwitcherProps) {
const { t } = useTranslation();
const { plugins } = usePlugins();
const coBrowseLabel = t('tabs.coBrowse', { defaultValue: 'Co-browse' });
const builtInTabs: BuiltInTab[] = [
...BASE_TABS,
@@ -83,7 +86,11 @@ export default function MainContentTabSwitcher({
<PillBar>
{tabs.map((tab) => {
const isActive = tab.id === activeTab;
const displayLabel = tab.kind === 'builtin' ? t(tab.labelKey) : tab.label;
const displayLabel = tab.kind === 'plugin'
? tab.label
: tab.id === 'browser' && shouldLabelBrowserTabAsCobrowse
? coBrowseLabel
: t(tab.labelKey);
return (
<Tooltip key={tab.id} content={displayLabel} position="bottom">

View File

@@ -9,9 +9,17 @@ type MainContentTitleProps = {
selectedProject: Project;
selectedSession: ProjectSession | null;
shouldShowTasksTab: boolean;
shouldLabelBrowserTabAsCobrowse: boolean;
};
function getTabTitle(activeTab: AppTab, shouldShowTasksTab: boolean, t: (key: string) => string, pluginDisplayName?: string) {
function getTabTitle(
activeTab: AppTab,
shouldShowTasksTab: boolean,
shouldLabelBrowserTabAsCobrowse: boolean,
t: (key: string) => string,
coBrowseLabel: string,
pluginDisplayName?: string,
) {
if (activeTab.startsWith('plugin:') && pluginDisplayName) {
return pluginDisplayName;
}
@@ -29,6 +37,9 @@ function getTabTitle(activeTab: AppTab, shouldShowTasksTab: boolean, t: (key: st
}
if (activeTab === 'browser') {
if (shouldLabelBrowserTabAsCobrowse) {
return coBrowseLabel;
}
return t('tabs.browser');
}
@@ -48,9 +59,11 @@ export default function MainContentTitle({
selectedProject,
selectedSession,
shouldShowTasksTab,
shouldLabelBrowserTabAsCobrowse,
}: MainContentTitleProps) {
const { t } = useTranslation();
const { plugins } = usePlugins();
const coBrowseLabel = t('tabs.coBrowse', { defaultValue: 'Co-browse' });
const pluginDisplayName = activeTab.startsWith('plugin:')
? plugins.find((p) => p.name === activeTab.replace('plugin:', ''))?.displayName
@@ -91,7 +104,7 @@ export default function MainContentTitle({
) : (
<div className="min-w-0">
<h2 className="text-sm font-semibold leading-tight text-foreground">
{getTabTitle(activeTab, shouldShowTasksTab, t, pluginDisplayName)}
{getTabTitle(activeTab, shouldShowTasksTab, shouldLabelBrowserTabAsCobrowse, t, coBrowseLabel, pluginDisplayName)}
</h2>
<div className="truncate text-[11px] leading-tight text-muted-foreground">{selectedProject.displayName}</div>
</div>

View File

@@ -33,7 +33,7 @@ export const SETTINGS_MAIN_TABS: SettingsMainTabMeta[] = [
{ id: 'git', label: 'Git', keywords: 'git github commits', icon: GitBranch },
{ id: 'api', label: 'API Tokens', keywords: 'api tokens auth keys', icon: KeyRound },
{ id: 'tasks', label: 'Tasks', keywords: 'tasks taskmaster', icon: ListChecks },
{ id: 'browser', label: 'Browser', keywords: 'browser playwright chromium automation', icon: MonitorPlay },
{ id: 'browser', label: 'Browser', keywords: 'browser co-browse cobrowse playwright chromium camoufox automation', icon: MonitorPlay },
{ id: 'notifications', label: 'Notifications', keywords: 'notifications alerts push', icon: Bell },
{ id: 'plugins', label: 'Plugins', keywords: 'plugins extensions integrations', icon: Plug },
{ id: 'about', label: 'About', keywords: 'about version info', icon: Info },

View File

@@ -133,7 +133,7 @@ export default function BrowserUseSettingsTab() {
const selectedBackend = settings?.browserBackend || 'playwright';
const effectiveBackend = status?.backend || 'playwright';
const usesLocalWindowViewer = status?.viewerMode === 'window';
const camoufoxLabel = usesLocalWindowViewer ? 'Camoufox (visible window)' : 'Camoufox + noVNC';
const camoufoxRuntimeLabel = usesLocalWindowViewer ? 'Camoufox visible window' : 'Camoufox + noVNC';
const needsBrowserBinaries = Boolean(browserEnabled && status && !status.available);
const runtimeLabel = (installed?: boolean) => {
if (isStatusLoading && !status) {
@@ -208,10 +208,10 @@ export default function BrowserUseSettingsTab() {
},
{
value: 'camoufox-vnc' as const,
label: camoufoxLabel,
label: 'Co-browse',
description: usesLocalWindowViewer
? 'Best when a person may need to log in or approve a step. The browser opens as a window on the machine running CloudCLI.'
: 'Best when a person may need to log in, approve a step, or watch the browser session live.',
? 'Use Camoufox for co-browse sessions where a person may need to log in or approve a step on the machine running CloudCLI.'
: 'Use Camoufox for live co-browse sessions where a person may need to watch, log in, or approve a step.',
icon: Eye,
},
]).map((option) => {
@@ -293,7 +293,7 @@ export default function BrowserUseSettingsTab() {
<div className="space-y-4 px-4 py-4">
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
<span className="rounded-md border border-border px-2 py-1">
Backend: {effectiveBackend === 'camoufox-vnc' ? camoufoxLabel : 'Playwright'}
Backend: {effectiveBackend === 'camoufox-vnc' ? `Co-browse (${camoufoxRuntimeLabel})` : 'Playwright'}
</span>
<span className="rounded-md border border-border px-2 py-1">
Playwright: {runtimeLabel(status?.playwrightInstalled)}

View File

@@ -24,6 +24,7 @@
"git": "Source Control",
"tasks": "Tasks",
"browser": "Browser",
"coBrowse": "Co-browse",
"computer": "Computer"
},
"status": {