import { useState, useEffect } from 'react'; import { AppShell, Group, ActionIcon, rem, Loader, Center, Stack, Text, Button, Select, Badge, useMantineColorScheme, } from '@mantine/core'; import { Settings, ArrowLeft } from 'lucide-react'; import { DownloadScreen } from '@/screens/Download'; import { LaunchScreen } from '@/screens/Launch'; import { InterfaceScreen } from '@/screens/Interface'; import { UpdateDialog } from '@/components/UpdateDialog'; import { SettingsModal } from '@/components/settings/SettingsModal'; import { ScreenTransition } from '@/components/ScreenTransition'; import { StyledTooltip } from '@/components/StyledTooltip'; import { getDisplayNameFromPath } from '@/utils/versionUtils'; import type { UpdateInfo, InstalledVersion } from '@/types'; type Screen = 'download' | 'launch' | 'interface'; export const App = () => { const [currentScreen, setCurrentScreen] = useState(null); const [updateInfo, setUpdateInfo] = useState(null); const [showUpdateDialog, setShowUpdateDialog] = useState(false); const [settingsOpened, setSettingsOpened] = useState(false); const [hasInitialized, setHasInitialized] = useState(false); const [activeInterfaceTab, setActiveInterfaceTab] = useState( 'terminal' ); const [isImageGenerationMode, setIsImageGenerationMode] = useState(false); const [currentVersion, setCurrentVersion] = useState( null ); const [installedVersions, setInstalledVersions] = useState< InstalledVersion[] >([]); const { colorScheme } = useMantineColorScheme(); useEffect(() => { const checkInstallation = async () => { try { const [versions, currentBinaryPath] = await Promise.all([ window.electronAPI.kobold.getInstalledVersions(), window.electronAPI.config.get( 'currentKoboldBinary' ) as Promise, ]); setInstalledVersions(versions); 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 ); } } setCurrentVersion(current); setCurrentScreen('launch'); } else { setCurrentScreen('download'); } setHasInitialized(true); } catch (error) { console.error('Error checking installation:', error); setHasInitialized(true); } }; checkInstallation(); window.electronAPI.kobold.onUpdateAvailable((info) => { setUpdateInfo(info); setShowUpdateDialog(true); }); const cleanupInstallDirListener = window.electronAPI.kobold.onInstallDirChanged(() => { checkInstallation(); }); const cleanupVersionsListener = window.electronAPI.kobold.onVersionsUpdated( () => { checkInstallation(); } ); return () => { window.electronAPI.kobold.removeAllListeners('update-available'); cleanupInstallDirListener(); cleanupVersionsListener(); }; }, []); const handleDownloadComplete = async () => { try { const [versions, currentBinaryPath] = await Promise.all([ window.electronAPI.kobold.getInstalledVersions(), window.electronAPI.config.get('currentKoboldBinary') as Promise, ]); setInstalledVersions(versions); 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 ); } } setCurrentVersion(current); } } catch (error) { console.error('Error refreshing versions after download:', error); } setTimeout(() => { setCurrentScreen('launch'); }, 100); }; const handleVersionChange = async (versionPath: string | null) => { if (!versionPath) return; try { const success = await window.electronAPI.kobold.setCurrentVersion(versionPath); if (success) { await window.electronAPI.config.set('currentKoboldBinary', versionPath); const newCurrent = installedVersions.find( (v) => v.path === versionPath ); if (newCurrent) { setCurrentVersion(newCurrent); } } } catch (error) { console.error('Failed to change version:', error); } }; const handleLaunch = () => { setCurrentScreen('interface'); }; const handleBackToLaunch = () => { setCurrentScreen('launch'); }; const handleEject = async () => { try { const confirmed = await window.electronAPI.kobold.confirmEject(); if (!confirmed) { return; } } catch (error) { console.error('Error showing confirmation dialog:', error); return; } try { await window.electronAPI.kobold.stopKoboldCpp(); } catch (error) { console.error('Error stopping KoboldCpp:', error); } handleBackToLaunch(); }; const handleUpdateIgnore = () => { setShowUpdateDialog(false); }; const handleUpdateAccept = () => { setShowUpdateDialog(false); setCurrentScreen('download'); }; return ( <>
{currentScreen === 'interface' && ( )}
{currentScreen === 'interface' && ( ({ value: version.path, label: getDisplayNameFromPath(version), }))} placeholder="Select version" variant="unstyled" styles={{ input: { minWidth: '225px', textAlign: 'center', border: `1px solid var(--mantine-color-${colorScheme === 'dark' ? 'dark-4' : 'gray-4'})`, borderRadius: 'var(--mantine-radius-sm)', backgroundColor: colorScheme === 'dark' ? 'var(--mantine-color-dark-6)' : 'var(--mantine-color-gray-0)', fontWeight: 500, fontSize: '14px', padding: '6px 12px', transition: 'all 200ms ease', '&:hover': { backgroundColor: colorScheme === 'dark' ? 'var(--mantine-color-dark-5)' : 'var(--mantine-color-gray-1)', }, }, dropdown: { minWidth: '200px', }, }} /> v{currentVersion.version}
)}
setSettingsOpened(true)} aria-label="Open settings" style={{ transition: 'all 200ms ease', }} >
{currentScreen === null ? (
Loading...
) : ( <> )} {showUpdateDialog && updateInfo && ( )}
setSettingsOpened(false)} currentScreen={currentScreen || undefined} />
); };