mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-02 02:22:55 +08:00
feat: add Electron desktop app
This commit is contained in:
@@ -6,11 +6,13 @@ import StandaloneShell from '../../standalone-shell/view/StandaloneShell';
|
||||
import GitPanel from '../../git-panel/view/GitPanel';
|
||||
import PluginTabContent from '../../plugins/view/PluginTabContent';
|
||||
import { BrowserUsePanel } from '../../browser-use';
|
||||
import { ComputerUsePanel } from '../../computer-use';
|
||||
import type { MainContentProps } from '../types/types';
|
||||
import { useTaskMaster } from '../../../contexts/TaskMasterContext';
|
||||
import { usePaletteOpsRegister } from '../../../contexts/PaletteOpsContext';
|
||||
import { useTasksSettings } from '../../../contexts/TasksSettingsContext';
|
||||
import { useUiPreferences } from '../../../hooks/useUiPreferences';
|
||||
import { COMPUTER_USE_MENUS_ENABLED } from '../../../constants/featureFlags';
|
||||
import { authenticatedFetch } from '../../../utils/api';
|
||||
import { useEditorSidebar } from '../../code-editor/hooks/useEditorSidebar';
|
||||
import EditorSidebar from '../../code-editor/view/EditorSidebar';
|
||||
@@ -58,9 +60,11 @@ function MainContent({
|
||||
const { currentProject, setCurrentProject } = useTaskMaster() as TaskMasterContextValue;
|
||||
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings() as TasksSettingsContextValue;
|
||||
const [browserUseEnabled, setBrowserUseEnabled] = useState(false);
|
||||
const [computerUseEnabled, setComputerUseEnabled] = useState<boolean | undefined>(undefined);
|
||||
|
||||
const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled);
|
||||
const shouldShowBrowserTab = browserUseEnabled;
|
||||
const shouldShowComputerTab = COMPUTER_USE_MENUS_ENABLED && computerUseEnabled === true;
|
||||
|
||||
const {
|
||||
editingFile,
|
||||
@@ -116,6 +120,60 @@ function MainContent({
|
||||
}
|
||||
}, [shouldShowBrowserTab, activeTab, setActiveTab]);
|
||||
|
||||
const loadComputerUseSettings = useCallback(async () => {
|
||||
try {
|
||||
const [settingsResponse, statusResponse] = await Promise.allSettled([
|
||||
authenticatedFetch('/api/computer-use/settings'),
|
||||
authenticatedFetch('/api/computer-use/status'),
|
||||
]);
|
||||
const settingsRes = settingsResponse.status === 'fulfilled' ? settingsResponse.value : null;
|
||||
const statusRes = statusResponse.status === 'fulfilled' ? statusResponse.value : null;
|
||||
const readJson = async (response: Response | null) => {
|
||||
if (!response) return null;
|
||||
try {
|
||||
return await response.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const settingsData = await readJson(settingsRes);
|
||||
const statusData = await readJson(statusRes);
|
||||
const runtime = statusData?.data?.runtime;
|
||||
const settingsUsable = Boolean(settingsRes?.ok && settingsData?.success !== false);
|
||||
const statusUsable = Boolean(statusRes?.ok && statusData?.success !== false);
|
||||
const settingsEnabled = Boolean(
|
||||
settingsUsable &&
|
||||
settingsData?.data?.settings?.enabled
|
||||
);
|
||||
const cloudEnabled = Boolean(
|
||||
statusUsable &&
|
||||
runtime === 'cloud' &&
|
||||
statusData?.data?.enabled
|
||||
);
|
||||
if (runtime === 'cloud') {
|
||||
setComputerUseEnabled(cloudEnabled);
|
||||
} else if (settingsUsable) {
|
||||
setComputerUseEnabled(settingsEnabled);
|
||||
} else if (statusUsable) {
|
||||
setComputerUseEnabled(Boolean(statusData?.data?.enabled));
|
||||
}
|
||||
} catch {
|
||||
// Keep the current tab availability on transient status/settings failures.
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void loadComputerUseSettings();
|
||||
window.addEventListener('computerUseSettingsChanged', loadComputerUseSettings);
|
||||
return () => window.removeEventListener('computerUseSettingsChanged', loadComputerUseSettings);
|
||||
}, [loadComputerUseSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldShowComputerTab && activeTab === 'computer') {
|
||||
setActiveTab('chat');
|
||||
}
|
||||
}, [shouldShowComputerTab, activeTab, setActiveTab]);
|
||||
|
||||
usePaletteOpsRegister({
|
||||
openFile: (filePath: string) => {
|
||||
setActiveTab('files');
|
||||
@@ -140,6 +198,7 @@ function MainContent({
|
||||
selectedSession={selectedSession}
|
||||
shouldShowTasksTab={shouldShowTasksTab}
|
||||
shouldShowBrowserTab={shouldShowBrowserTab}
|
||||
shouldShowComputerTab={shouldShowComputerTab}
|
||||
isMobile={isMobile}
|
||||
onMenuClick={onMenuClick}
|
||||
/>
|
||||
@@ -204,6 +263,12 @@ function MainContent({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShowComputerTab && activeTab === 'computer' && (
|
||||
<div className="h-full overflow-hidden">
|
||||
<ComputerUsePanel isVisible={activeTab === 'computer'} onShowSettings={onShowSettings} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab.startsWith('plugin:') && (
|
||||
<div className="h-full overflow-hidden">
|
||||
<PluginTabContent
|
||||
|
||||
@@ -11,6 +11,7 @@ export default function MainContentHeader({
|
||||
selectedSession,
|
||||
shouldShowTasksTab,
|
||||
shouldShowBrowserTab,
|
||||
shouldShowComputerTab,
|
||||
isMobile,
|
||||
onMenuClick,
|
||||
}: MainContentHeaderProps) {
|
||||
@@ -61,6 +62,7 @@ export default function MainContentHeader({
|
||||
setActiveTab={setActiveTab}
|
||||
shouldShowTasksTab={shouldShowTasksTab}
|
||||
shouldShowBrowserTab={shouldShowBrowserTab}
|
||||
shouldShowComputerTab={shouldShowComputerTab}
|
||||
/>
|
||||
</div>
|
||||
{canScrollRight && (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MessageSquare, Terminal, Folder, GitBranch, ClipboardCheck, MonitorPlay, type LucideIcon } from 'lucide-react';
|
||||
import { MessageSquare, Terminal, Folder, GitBranch, ClipboardCheck, MonitorCog, MonitorPlay, type LucideIcon } from 'lucide-react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -12,6 +12,7 @@ type MainContentTabSwitcherProps = {
|
||||
setActiveTab: Dispatch<SetStateAction<AppTab>>;
|
||||
shouldShowTasksTab: boolean;
|
||||
shouldShowBrowserTab: boolean;
|
||||
shouldShowComputerTab: boolean;
|
||||
};
|
||||
|
||||
type BuiltInTab = {
|
||||
@@ -45,6 +46,13 @@ const BROWSER_TAB: BuiltInTab = {
|
||||
icon: MonitorPlay,
|
||||
};
|
||||
|
||||
const COMPUTER_TAB: BuiltInTab = {
|
||||
kind: 'builtin',
|
||||
id: 'computer',
|
||||
labelKey: 'tabs.computer',
|
||||
icon: MonitorCog,
|
||||
};
|
||||
|
||||
const TASKS_TAB: BuiltInTab = {
|
||||
kind: 'builtin',
|
||||
id: 'tasks',
|
||||
@@ -57,6 +65,7 @@ export default function MainContentTabSwitcher({
|
||||
setActiveTab,
|
||||
shouldShowTasksTab,
|
||||
shouldShowBrowserTab,
|
||||
shouldShowComputerTab,
|
||||
}: MainContentTabSwitcherProps) {
|
||||
const { t } = useTranslation();
|
||||
const { plugins } = usePlugins();
|
||||
@@ -64,6 +73,7 @@ export default function MainContentTabSwitcher({
|
||||
const builtInTabs: BuiltInTab[] = [
|
||||
...BASE_TABS,
|
||||
...(shouldShowBrowserTab ? [BROWSER_TAB] : []),
|
||||
...(shouldShowComputerTab ? [COMPUTER_TAB] : []),
|
||||
...(shouldShowTasksTab ? [TASKS_TAB] : []),
|
||||
];
|
||||
|
||||
|
||||
@@ -29,7 +29,11 @@ function getTabTitle(activeTab: AppTab, shouldShowTasksTab: boolean, t: (key: st
|
||||
}
|
||||
|
||||
if (activeTab === 'browser') {
|
||||
return 'Browser';
|
||||
return t('tabs.browser');
|
||||
}
|
||||
|
||||
if (activeTab === 'computer') {
|
||||
return t('tabs.computer');
|
||||
}
|
||||
|
||||
return 'Project';
|
||||
|
||||
Reference in New Issue
Block a user