mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-28 23:35:27 +08:00
feat: make browser use opt-in
This commit is contained in:
@@ -61,7 +61,7 @@ let installPromise: Promise<{ success: boolean; message: string }> | null = null
|
|||||||
let lastInstallMessage: string | null = null;
|
let lastInstallMessage: string | null = null;
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: BrowserUseSettings = {
|
const DEFAULT_SETTINGS: BrowserUseSettings = {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getRuntime(): BrowserUseRuntime {
|
function getRuntime(): BrowserUseRuntime {
|
||||||
@@ -77,7 +77,7 @@ function readSettings(): BrowserUseSettings {
|
|||||||
|
|
||||||
const parsed = JSON.parse(raw) as Partial<BrowserUseSettings>;
|
const parsed = JSON.parse(raw) as Partial<BrowserUseSettings>;
|
||||||
return {
|
return {
|
||||||
enabled: parsed.enabled !== false,
|
enabled: parsed.enabled === true,
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.warn('[Browser Use] Failed to read settings:', error?.message || error);
|
console.warn('[Browser Use] Failed to read settings:', error?.message || error);
|
||||||
@@ -87,7 +87,7 @@ function readSettings(): BrowserUseSettings {
|
|||||||
|
|
||||||
function writeSettings(settings: BrowserUseSettings): BrowserUseSettings {
|
function writeSettings(settings: BrowserUseSettings): BrowserUseSettings {
|
||||||
const normalized = {
|
const normalized = {
|
||||||
enabled: settings.enabled !== false,
|
enabled: settings.enabled === true,
|
||||||
};
|
};
|
||||||
|
|
||||||
appConfigDb.set(BROWSER_USE_SETTINGS_KEY, JSON.stringify(normalized));
|
appConfigDb.set(BROWSER_USE_SETTINGS_KEY, JSON.stringify(normalized));
|
||||||
@@ -168,6 +168,14 @@ function runCommand(command: string, args: string[]): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatInstallError(error: unknown): string {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
if (message.includes('sudo') && message.includes('password')) {
|
||||||
|
return 'Installing Chromium system dependencies requires administrator privileges. Run `npx playwright install-deps chromium` on the machine where CloudCLI runs, then try again.';
|
||||||
|
}
|
||||||
|
return message || 'Failed to install Browser Use runtime.';
|
||||||
|
}
|
||||||
|
|
||||||
async function installRuntime(): Promise<{ success: boolean; message: string }> {
|
async function installRuntime(): Promise<{ success: boolean; message: string }> {
|
||||||
if (installPromise) {
|
if (installPromise) {
|
||||||
return installPromise;
|
return installPromise;
|
||||||
@@ -179,13 +187,18 @@ async function installRuntime(): Promise<{ success: boolean; message: string }>
|
|||||||
lastInstallMessage = 'Installing Playwright package...';
|
lastInstallMessage = 'Installing Playwright package...';
|
||||||
await runCommand(npmCommand, ['install', '--no-save', '--no-package-lock', 'playwright']);
|
await runCommand(npmCommand, ['install', '--no-save', '--no-package-lock', 'playwright']);
|
||||||
|
|
||||||
|
if (process.platform === 'linux') {
|
||||||
|
lastInstallMessage = 'Installing Chromium system dependencies...';
|
||||||
|
await runCommand(npmCommand, ['exec', '--', 'playwright', 'install-deps', 'chromium']);
|
||||||
|
}
|
||||||
|
|
||||||
lastInstallMessage = 'Installing Chromium runtime...';
|
lastInstallMessage = 'Installing Chromium runtime...';
|
||||||
await runCommand(npmCommand, ['exec', '--', 'playwright', 'install', 'chromium']);
|
await runCommand(npmCommand, ['exec', '--', 'playwright', 'install', 'chromium']);
|
||||||
|
|
||||||
lastInstallMessage = 'Browser Use runtime installed.';
|
lastInstallMessage = 'Browser Use runtime installed.';
|
||||||
return { success: true, message: lastInstallMessage };
|
return { success: true, message: lastInstallMessage };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastInstallMessage = error instanceof Error ? error.message : 'Failed to install Browser Use runtime.';
|
lastInstallMessage = formatInstallError(error);
|
||||||
return { success: false, message: lastInstallMessage };
|
return { success: false, message: lastInstallMessage };
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -359,7 +372,7 @@ export const browserUseService = {
|
|||||||
const current = readSettings();
|
const current = readSettings();
|
||||||
return writeSettings({
|
return writeSettings({
|
||||||
...current,
|
...current,
|
||||||
enabled: settings.enabled ?? current.enabled,
|
enabled: typeof settings.enabled === 'boolean' ? settings.enabled : current.enabled,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export type MainContentHeaderProps = {
|
|||||||
selectedProject: Project;
|
selectedProject: Project;
|
||||||
selectedSession: ProjectSession | null;
|
selectedSession: ProjectSession | null;
|
||||||
shouldShowTasksTab: boolean;
|
shouldShowTasksTab: boolean;
|
||||||
|
shouldShowBrowserTab: boolean;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
onMenuClick: () => void;
|
onMenuClick: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import ChatInterface from '../../chat/view/ChatInterface';
|
import ChatInterface from '../../chat/view/ChatInterface';
|
||||||
import FileTree from '../../file-tree/view/FileTree';
|
import FileTree from '../../file-tree/view/FileTree';
|
||||||
@@ -11,6 +11,7 @@ import { useTaskMaster } from '../../../contexts/TaskMasterContext';
|
|||||||
import { usePaletteOpsRegister } from '../../../contexts/PaletteOpsContext';
|
import { usePaletteOpsRegister } from '../../../contexts/PaletteOpsContext';
|
||||||
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
||||||
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
||||||
|
import { authenticatedFetch } from '../../../utils/api';
|
||||||
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
||||||
import EditorSidebar from '../../code-editor/view/EditorSidebar';
|
import EditorSidebar from '../../code-editor/view/EditorSidebar';
|
||||||
import type { Project } from '../../../types/app';
|
import type { Project } from '../../../types/app';
|
||||||
@@ -56,8 +57,10 @@ function MainContent({
|
|||||||
|
|
||||||
const { currentProject, setCurrentProject } = useTaskMaster() as TaskMasterContextValue;
|
const { currentProject, setCurrentProject } = useTaskMaster() as TaskMasterContextValue;
|
||||||
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings() as TasksSettingsContextValue;
|
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings() as TasksSettingsContextValue;
|
||||||
|
const [browserUseEnabled, setBrowserUseEnabled] = useState(false);
|
||||||
|
|
||||||
const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled);
|
const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled);
|
||||||
|
const shouldShowBrowserTab = browserUseEnabled;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
editingFile,
|
editingFile,
|
||||||
@@ -91,6 +94,28 @@ function MainContent({
|
|||||||
}
|
}
|
||||||
}, [shouldShowTasksTab, activeTab, setActiveTab]);
|
}, [shouldShowTasksTab, activeTab, setActiveTab]);
|
||||||
|
|
||||||
|
const loadBrowserUseSettings = useCallback(async () => {
|
||||||
|
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));
|
||||||
|
} catch {
|
||||||
|
setBrowserUseEnabled(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadBrowserUseSettings();
|
||||||
|
window.addEventListener('browserUseSettingsChanged', loadBrowserUseSettings);
|
||||||
|
return () => window.removeEventListener('browserUseSettingsChanged', loadBrowserUseSettings);
|
||||||
|
}, [loadBrowserUseSettings]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldShowBrowserTab && activeTab === 'browser') {
|
||||||
|
setActiveTab('chat');
|
||||||
|
}
|
||||||
|
}, [shouldShowBrowserTab, activeTab, setActiveTab]);
|
||||||
|
|
||||||
usePaletteOpsRegister({
|
usePaletteOpsRegister({
|
||||||
openFile: (filePath: string) => {
|
openFile: (filePath: string) => {
|
||||||
setActiveTab('files');
|
setActiveTab('files');
|
||||||
@@ -114,6 +139,7 @@ function MainContent({
|
|||||||
selectedProject={selectedProject}
|
selectedProject={selectedProject}
|
||||||
selectedSession={selectedSession}
|
selectedSession={selectedSession}
|
||||||
shouldShowTasksTab={shouldShowTasksTab}
|
shouldShowTasksTab={shouldShowTasksTab}
|
||||||
|
shouldShowBrowserTab={shouldShowBrowserTab}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
onMenuClick={onMenuClick}
|
onMenuClick={onMenuClick}
|
||||||
/>
|
/>
|
||||||
@@ -172,7 +198,7 @@ function MainContent({
|
|||||||
|
|
||||||
{shouldShowTasksTab && <TaskMasterPanel isVisible={activeTab === 'tasks'} />}
|
{shouldShowTasksTab && <TaskMasterPanel isVisible={activeTab === 'tasks'} />}
|
||||||
|
|
||||||
{activeTab === 'browser' && (
|
{shouldShowBrowserTab && activeTab === 'browser' && (
|
||||||
<div className="h-full overflow-hidden">
|
<div className="h-full overflow-hidden">
|
||||||
<BrowserUsePanel isVisible={activeTab === 'browser'} />
|
<BrowserUsePanel isVisible={activeTab === 'browser'} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export default function MainContentHeader({
|
|||||||
selectedProject,
|
selectedProject,
|
||||||
selectedSession,
|
selectedSession,
|
||||||
shouldShowTasksTab,
|
shouldShowTasksTab,
|
||||||
|
shouldShowBrowserTab,
|
||||||
isMobile,
|
isMobile,
|
||||||
onMenuClick,
|
onMenuClick,
|
||||||
}: MainContentHeaderProps) {
|
}: MainContentHeaderProps) {
|
||||||
@@ -59,6 +60,7 @@ export default function MainContentHeader({
|
|||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
setActiveTab={setActiveTab}
|
setActiveTab={setActiveTab}
|
||||||
shouldShowTasksTab={shouldShowTasksTab}
|
shouldShowTasksTab={shouldShowTasksTab}
|
||||||
|
shouldShowBrowserTab={shouldShowBrowserTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{canScrollRight && (
|
{canScrollRight && (
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type MainContentTabSwitcherProps = {
|
|||||||
activeTab: AppTab;
|
activeTab: AppTab;
|
||||||
setActiveTab: Dispatch<SetStateAction<AppTab>>;
|
setActiveTab: Dispatch<SetStateAction<AppTab>>;
|
||||||
shouldShowTasksTab: boolean;
|
shouldShowTasksTab: boolean;
|
||||||
|
shouldShowBrowserTab: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type BuiltInTab = {
|
type BuiltInTab = {
|
||||||
@@ -35,9 +36,15 @@ const BASE_TABS: BuiltInTab[] = [
|
|||||||
{ kind: 'builtin', id: 'shell', labelKey: 'tabs.shell', icon: Terminal },
|
{ kind: 'builtin', id: 'shell', labelKey: 'tabs.shell', icon: Terminal },
|
||||||
{ kind: 'builtin', id: 'files', labelKey: 'tabs.files', icon: Folder },
|
{ kind: 'builtin', id: 'files', labelKey: 'tabs.files', icon: Folder },
|
||||||
{ kind: 'builtin', id: 'git', labelKey: 'tabs.git', icon: GitBranch },
|
{ kind: 'builtin', id: 'git', labelKey: 'tabs.git', icon: GitBranch },
|
||||||
{ kind: 'builtin', id: 'browser', labelKey: 'tabs.browser', icon: MonitorPlay },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const BROWSER_TAB: BuiltInTab = {
|
||||||
|
kind: 'builtin',
|
||||||
|
id: 'browser',
|
||||||
|
labelKey: 'tabs.browser',
|
||||||
|
icon: MonitorPlay,
|
||||||
|
};
|
||||||
|
|
||||||
const TASKS_TAB: BuiltInTab = {
|
const TASKS_TAB: BuiltInTab = {
|
||||||
kind: 'builtin',
|
kind: 'builtin',
|
||||||
id: 'tasks',
|
id: 'tasks',
|
||||||
@@ -49,11 +56,16 @@ export default function MainContentTabSwitcher({
|
|||||||
activeTab,
|
activeTab,
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
shouldShowTasksTab,
|
shouldShowTasksTab,
|
||||||
|
shouldShowBrowserTab,
|
||||||
}: MainContentTabSwitcherProps) {
|
}: MainContentTabSwitcherProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { plugins } = usePlugins();
|
const { plugins } = usePlugins();
|
||||||
|
|
||||||
const builtInTabs: BuiltInTab[] = shouldShowTasksTab ? [...BASE_TABS, TASKS_TAB] : BASE_TABS;
|
const builtInTabs: BuiltInTab[] = [
|
||||||
|
...BASE_TABS,
|
||||||
|
...(shouldShowBrowserTab ? [BROWSER_TAB] : []),
|
||||||
|
...(shouldShowTasksTab ? [TASKS_TAB] : []),
|
||||||
|
];
|
||||||
|
|
||||||
const pluginTabs: PluginTab[] = plugins
|
const pluginTabs: PluginTab[] = plugins
|
||||||
.filter((p) => p.enabled)
|
.filter((p) => p.enabled)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async function readJson<T>(response: Response): Promise<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function BrowserUseSettingsTab() {
|
export default function BrowserUseSettingsTab() {
|
||||||
const [settings, setSettings] = useState<BrowserUseSettings>({ enabled: true });
|
const [settings, setSettings] = useState<BrowserUseSettings>({ enabled: false });
|
||||||
const [status, setStatus] = useState<BrowserUseStatus | null>(null);
|
const [status, setStatus] = useState<BrowserUseStatus | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
@@ -67,6 +67,7 @@ export default function BrowserUseSettingsTab() {
|
|||||||
});
|
});
|
||||||
const data = await readJson<{ data: { settings: BrowserUseSettings } }>(response);
|
const data = await readJson<{ data: { settings: BrowserUseSettings } }>(response);
|
||||||
setSettings(data.data.settings);
|
setSettings(data.data.settings);
|
||||||
|
window.dispatchEvent(new Event('browserUseSettingsChanged'));
|
||||||
await loadState();
|
await loadState();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to save Browser Use settings');
|
setError(err instanceof Error ? err.message : 'Failed to save Browser Use settings');
|
||||||
|
|||||||
Reference in New Issue
Block a user