import { AppShell, Center, Loader, Stack, Text, useMantineColorScheme } from '@mantine/core'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { BackendCrashModal } from '@/components/App/BackendCrashModal'; import { EjectConfirmModal } from '@/components/App/EjectConfirmModal'; import { ErrorBoundary } from '@/components/App/ErrorBoundary'; import { AppRouter } from '@/components/App/Router'; import { StatusBar } from '@/components/App/StatusBar'; import { TitleBar } from '@/components/App/TitleBar'; import { UpdateAvailableModal } from '@/components/App/UpdateAvailableModal'; import { NotepadContainer } from '@/components/Notepad/Container'; import { SERVER_READY_DELAY_MS, STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants'; import { useUpdateChecker } from '@/hooks/useUpdateChecker'; import { useKoboldBackendsStore } from '@/stores/koboldBackends'; import { useLaunchConfigStore } from '@/stores/launchConfig'; import { usePreferencesStore } from '@/stores/preferences'; import type { InterfaceTab, Screen } from '@/types'; import type { DownloadItem } from '@/types/electron'; import type { KoboldCrashInfo } from '@/types/ipc'; import { getDefaultInterfaceTab } from '@/utils/interface'; export const App = () => { const [currentScreen, setCurrentScreen] = useState(null); const [hasInitialized, setHasInitialized] = useState(false); const [activeInterfaceTab, setActiveInterfaceTab] = useState('terminal'); const [isServerReady, setIsServerReady] = useState(false); const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false); const [crashInfo, setCrashInfo] = useState(null); const isInterfaceScreen = currentScreen === 'interface'; const { resolvedColorScheme: appColorScheme, systemMonitoringEnabled, frontendPreference, imageGenerationFrontendPreference, } = usePreferencesStore(); const { setColorScheme } = useMantineColorScheme(); const { model, sdmodel, isTextMode, isImageGenerationMode } = useLaunchConfigStore(); const defaultInterfaceTab = useMemo( () => getDefaultInterfaceTab({ frontendPreference, imageGenerationFrontendPreference, isImageGenerationMode, isTextMode, }), [frontendPreference, imageGenerationFrontendPreference, isTextMode, isImageGenerationMode], ); useEffect(() => { const cleanup = window.electronAPI.kobold.onServerReady(() => { setTimeout(() => { setIsServerReady(true); setActiveInterfaceTab(defaultInterfaceTab); }, SERVER_READY_DELAY_MS); }); return cleanup; }, [defaultInterfaceTab]); useEffect(() => { setColorScheme(appColorScheme); }, [appColorScheme, setColorScheme]); useEffect(() => { const updateTray = async () => { const config = await window.electronAPI.kobold.getSelectedConfig(); const displayModel = model || sdmodel || null; const modelName = displayModel ? (displayModel.split('/').pop() ?? displayModel) : null; void window.electronAPI.app.updateTrayState({ config: config ?? null, model: modelName, monitoringEnabled: systemMonitoringEnabled, screen: currentScreen, }); }; void updateTray(); }, [currentScreen, model, sdmodel, systemMonitoringEnabled]); const performEject = useCallback(() => { window.electronAPI.kobold.stopKoboldCpp(); setIsServerReady(false); setActiveInterfaceTab('terminal'); setCurrentScreen('launch'); }, []); useEffect(() => { const ejectCleanup = window.electronAPI.app.onTrayEject(() => { performEject(); }); return () => { ejectCleanup(); }; }, [performEject]); useEffect(() => { const crashCleanup = window.electronAPI.kobold.onKoboldCrashed((crashData) => { setCrashInfo(crashData); }); return () => { crashCleanup(); }; }, []); const { updateInfo: binaryUpdateInfo, showUpdateModal, checkForUpdates, skipUpdate, closeModal, } = useUpdateChecker(); const { handleDownload, loadingRemote } = useKoboldBackendsStore(); const determineScreen = useCallback((currentVersion: unknown, hasSeenWelcome: boolean) => { if (!hasSeenWelcome) { setCurrentScreen('welcome'); } else if (currentVersion) { setCurrentScreen('launch'); } else { setCurrentScreen('download'); } }, []); useEffect(() => { const checkInstallation = async () => { const [currentBackend, hasSeenWelcome] = await Promise.all([ window.electronAPI.kobold.getCurrentBackend(), window.electronAPI.config.get('hasSeenWelcome') as Promise, ]); determineScreen(currentBackend, hasSeenWelcome); setHasInitialized(true); }; void checkInstallation(); }, [determineScreen]); useEffect(() => { if (loadingRemote || !hasInitialized) { return undefined; } const runUpdateCheck = async () => { const currentBackend = await window.electronAPI.kobold.getCurrentBackend(); if (!currentBackend) { return; } void checkForUpdates(); }; const initialTimeout = setTimeout(() => { void runUpdateCheck(); }, 5000); const interval = setInterval( () => { void runUpdateCheck(); }, 6 * 60 * 60 * 1000, ); return () => { clearTimeout(initialTimeout); clearInterval(interval); }; }, [loadingRemote, hasInitialized, checkForUpdates]); const handleBinaryUpdate = async (download: DownloadItem) => { const currentBackend = await window.electronAPI.kobold.getCurrentBackend(); await handleDownload({ isUpdate: true, item: download, oldBackendPath: currentBackend?.path, wasCurrentBinary: true, }); closeModal(); }; const handleEject = async () => { const skipEjectConfirmation = await window.electronAPI.config.get('skipEjectConfirmation'); if (skipEjectConfirmation) { performEject(); } else { setEjectConfirmModalOpen(true); } }; const handleEjectConfirm = (skipConfirmation: boolean) => { if (skipConfirmation) { window.electronAPI.config.set('skipEjectConfirmation', true); } performEject(); }; const handleWelcomeComplete = async () => { window.electronAPI.config.set('hasSeenWelcome', true); const currentBackend = await window.electronAPI.kobold.getCurrentBackend(); determineScreen(currentBackend, true); }; const handleDownloadComplete = () => setCurrentScreen('launch'); const handleLaunch = () => { setActiveInterfaceTab('terminal'); setCurrentScreen('interface'); }; return ( void handleEject()} onTabChange={setActiveInterfaceTab} /> {currentScreen === null ? (
Loading...
) : ( void handleWelcomeComplete()} onDownloadComplete={() => handleDownloadComplete()} onLaunch={handleLaunch} /> )}
setEjectConfirmModalOpen(false)} onConfirm={handleEjectConfirm} /> setCrashInfo(null)} crashInfo={crashInfo} />
); };