import { useState, useEffect } from 'react'; import { AppShell, Loader, Center, Stack, Text } from '@mantine/core'; import { DownloadScreen } from '@/components/screens/Download'; import { LaunchScreen } from '@/components/screens/Launch'; import { InterfaceScreen } from '@/components/screens/Interface'; import { WelcomeScreen } from '@/components/screens/Welcome'; import { UpdateAvailableModal } from '@/components/UpdateAvailableModal'; import { SettingsModal } from '@/components/settings/SettingsModal'; import { EjectConfirmModal } from '@/components/EjectConfirmModal'; import { ScreenTransition } from '@/components/ScreenTransition'; import { AppHeader } from '@/components/AppHeader'; import { useUpdateChecker } from '@/hooks/useUpdateChecker'; import { useKoboldVersions } from '@/hooks/useKoboldVersions'; import { UI } from '@/constants'; import type { DownloadItem } from '@/types/electron'; type Screen = 'welcome' | 'download' | 'launch' | 'interface'; export const App = () => { const [currentScreen, setCurrentScreen] = useState(null); const [settingsOpened, setSettingsOpened] = useState(false); const [hasInitialized, setHasInitialized] = useState(false); const [showEjectModal, setShowEjectModal] = useState(false); const [activeInterfaceTab, setActiveInterfaceTab] = useState( 'terminal' ); const [isImageGenerationMode, setIsImageGenerationMode] = useState(false); const { updateInfo: binaryUpdateInfo, showUpdateModal, checkForUpdates, dismissUpdate, } = useUpdateChecker(); const { handleDownload: sharedHandleDownload, downloading, downloadProgress, } = useKoboldVersions(); const setCurrentScreenWithTransition = (screen: Screen) => { setCurrentScreen(screen); }; useEffect(() => { const checkInstallation = async () => { try { const [versions, currentBinaryPath, hasSeenWelcome] = await Promise.all( [ window.electronAPI.kobold.getInstalledVersions(), window.electronAPI.config.get( 'currentKoboldBinary' ) as Promise, window.electronAPI.config.get('hasSeenWelcome') as Promise, ] ); if (!hasSeenWelcome) { setCurrentScreenWithTransition('welcome'); } else if (versions.length > 0) { let current = null; if (currentBinaryPath) { current = versions.find((v) => v.path === currentBinaryPath); } if (!current) { current = versions[0]; if (current) { await window.electronAPI.config.set( 'currentKoboldBinary', current.path ); } } setCurrentScreenWithTransition('launch'); } else { setCurrentScreenWithTransition('download'); } setHasInitialized(true); if (versions.length > 0) { setTimeout(() => { checkForUpdates(); }, 2000); } } catch (error) { window.electronAPI.logs.logError( 'Error checking installation:', error as Error ); setHasInitialized(true); } }; checkInstallation(); const cleanupInstallDirListener = window.electronAPI.kobold.onInstallDirChanged(() => { checkInstallation(); }); const cleanupVersionsListener = window.electronAPI.kobold.onVersionsUpdated( () => { checkInstallation(); } ); return () => { cleanupInstallDirListener(); cleanupVersionsListener(); }; }, [checkForUpdates]); const handleBinaryUpdate = async (download: DownloadItem) => { try { const success = await sharedHandleDownload('asset', download, true, true); if (success) { dismissUpdate(); } } catch (error) { window.electronAPI.logs.logError( 'Failed to update binary:', error as Error ); } }; const handleDownloadComplete = async () => { try { const [versions, currentBinaryPath] = await Promise.all([ window.electronAPI.kobold.getInstalledVersions(), window.electronAPI.config.get('currentKoboldBinary') as Promise, ]); if (versions.length > 0) { let current = null; if (currentBinaryPath) { current = versions.find((v) => v.path === currentBinaryPath); } if (!current) { current = versions[0]; if (current) { await window.electronAPI.config.set( 'currentKoboldBinary', current.path ); } } } } catch (error) { window.electronAPI.logs.logError( 'Error refreshing versions after download:', error as Error ); } setTimeout(() => { setCurrentScreenWithTransition('launch'); }, 100); }; const handleLaunch = () => { setActiveInterfaceTab('terminal'); setCurrentScreenWithTransition('interface'); }; const handleBackToLaunch = () => { setCurrentScreenWithTransition('launch'); }; const handleEject = async () => { const skipEjectConfirmation = await window.electronAPI.config.get( 'skipEjectConfirmation' ); if (skipEjectConfirmation) { performEject(); } else { setShowEjectModal(true); } }; const performEject = async () => { try { await window.electronAPI.kobold.stopKoboldCpp(); } catch (error) { window.electronAPI.logs.logError( 'Error stopping KoboldCpp:', error as Error ); } handleBackToLaunch(); }; const handleEjectConfirm = async (skipConfirmation: boolean) => { if (skipConfirmation) { await window.electronAPI.config.set('skipEjectConfirmation', true); } performEject(); }; const handleWelcomeComplete = async () => { await window.electronAPI.config.set('hasSeenWelcome', true); const versions = await window.electronAPI.kobold.getInstalledVersions(); if (versions.length > 0) { setCurrentScreenWithTransition('launch'); } else { setCurrentScreenWithTransition('download'); } }; return ( <> {currentScreen !== 'welcome' && ( setSettingsOpened(true)} /> )} {currentScreen === null ? (
Loading...
) : ( <> )} {showUpdateModal && binaryUpdateInfo && ( )}
setSettingsOpened(false)} currentScreen={currentScreen || undefined} /> setShowEjectModal(false)} onConfirm={handleEjectConfirm} />
); };