code refactoring

This commit is contained in:
lone-cloud 2025-09-19 01:02:08 -07:00
parent 73ec41d61b
commit 54d59c8018
24 changed files with 247 additions and 219 deletions

View file

@ -0,0 +1,63 @@
import { ScreenTransition } from '@/components/App/ScreenTransition';
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 type { InterfaceTab, Screen } from '@/types';
interface AppRouterProps {
currentScreen: Screen | null;
hasInitialized: boolean;
activeInterfaceTab: InterfaceTab;
onWelcomeComplete: () => void;
onDownloadComplete: () => void;
onLaunch: () => void;
onTabChange: (tab: InterfaceTab) => void;
}
export const AppRouter = ({
currentScreen,
hasInitialized,
activeInterfaceTab,
onWelcomeComplete,
onDownloadComplete,
onLaunch,
onTabChange,
}: AppRouterProps) => {
const isInterfaceScreen = currentScreen === 'interface';
return (
<>
<ScreenTransition
isActive={currentScreen === 'welcome'}
shouldAnimate={hasInitialized}
>
<WelcomeScreen onGetStarted={onWelcomeComplete} />
</ScreenTransition>
<ScreenTransition
isActive={currentScreen === 'download'}
shouldAnimate={hasInitialized}
>
<DownloadScreen onDownloadComplete={onDownloadComplete} />
</ScreenTransition>
<ScreenTransition
isActive={currentScreen === 'launch'}
shouldAnimate={hasInitialized}
>
<LaunchScreen onLaunch={onLaunch} />
</ScreenTransition>
<ScreenTransition
isActive={isInterfaceScreen}
shouldAnimate={hasInitialized}
>
<InterfaceScreen
activeTab={activeInterfaceTab}
onTabChange={onTabChange}
/>
</ScreenTransition>
</>
);
};

View file

@ -9,10 +9,12 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Minus, Square, X, Copy, Settings } from 'lucide-react'; import { Minus, Square, X, Copy, Settings } from 'lucide-react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useInterfaceOptions } from '@/hooks/useInterfaceSelection'; import { useLaunchConfigStore } from '@/stores/launchConfig';
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import { getAvailableInterfaceOptions } from '@/utils/interface';
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds'; import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
import { SettingsModal } from '@/components/settings/SettingsModal'; import { SettingsModal } from '@/components/settings/SettingsModal';
import { UpdateButton } from '@/components/UpdateButton'; import { UpdateButton } from '@/components/App/UpdateButton';
import { useAppColorScheme } from '@/hooks/useAppColorScheme'; import { useAppColorScheme } from '@/hooks/useAppColorScheme';
import icon from '/icon.png'; import icon from '/icon.png';
import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants'; import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
@ -32,12 +34,44 @@ export const TitleBar = ({
onTabChange, onTabChange,
}: TitleBarProps) => { }: TitleBarProps) => {
const colorScheme = useAppColorScheme(); const colorScheme = useAppColorScheme();
const interfaceOptions = useInterfaceOptions();
const { handleLogoClick, getLogoStyles } = useLogoClickSounds(); const { handleLogoClick, getLogoStyles } = useLogoClickSounds();
const [isMaximized, setIsMaximized] = useState(false); const [isMaximized, setIsMaximized] = useState(false);
const [isSelectOpen, setIsSelectOpen] = useState(false); const [isSelectOpen, setIsSelectOpen] = useState(false);
const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [settingsModalOpen, setSettingsModalOpen] = useState(false);
const { isTextMode, isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference } = useFrontendPreferenceStore();
const interfaceOptions = getAvailableInterfaceOptions({
frontendPreference,
isTextMode,
isImageGenerationMode,
});
const handleTabChange = (value: string | null) => {
if (value === 'eject') {
onEject();
} else if (value) {
onTabChange(value as InterfaceTab);
}
};
const renderOption = ({
option,
}: {
option: { value: string; label: string };
}) => (
<Box
style={{
textAlign: 'center',
color:
option.value === 'eject' ? 'var(--mantine-color-red-6)' : undefined,
fontWeight: option.value === 'eject' ? 600 : undefined,
}}
>
{option.label}
</Box>
);
useEffect(() => { useEffect(() => {
const cleanup = window.electronAPI.app.onWindowStateToggle(() => const cleanup = window.electronAPI.app.onWindowStateToggle(() =>
setIsMaximized((prev) => !prev) setIsMaximized((prev) => !prev)
@ -93,37 +127,15 @@ export const TitleBar = ({
<Select <Select
placeholder="Interface" placeholder="Interface"
value={currentTab} value={currentTab}
onChange={(value) => { onChange={handleTabChange}
if (value === 'eject') {
onEject();
} else {
onTabChange(value as InterfaceTab);
}
}}
onDropdownOpen={() => setIsSelectOpen(true)} onDropdownOpen={() => setIsSelectOpen(true)}
onDropdownClose={() => setIsSelectOpen(false)} onDropdownClose={() => setIsSelectOpen(false)}
data={interfaceOptions} data={interfaceOptions}
renderOption={({ option }) => ( renderOption={renderOption}
<Box
style={{
textAlign: 'center',
color:
option.value === 'eject'
? 'var(--mantine-color-red-6)'
: undefined,
fontWeight: option.value === 'eject' ? 600 : undefined,
}}
>
{option.label}
</Box>
)}
allowDeselect={false} allowDeselect={false}
variant="unstyled" variant="unstyled"
size="sm" size="sm"
style={{ style={{ textAlign: 'center', minWidth: '7.5rem' }}
textAlign: 'center',
minWidth: '7.5rem',
}}
styles={{ styles={{
input: { input: {
textAlign: 'center', textAlign: 'center',

View file

@ -11,7 +11,9 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Download, X, ExternalLink } from 'lucide-react'; import { Download, X, ExternalLink } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import type { InstalledVersion, DownloadItem } from '@/types/electron'; import type { DownloadItem } from '@/types/electron';
import type { BinaryUpdateInfo } from '@/hooks/useUpdateChecker';
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { getDisplayNameFromPath } from '@/utils/version'; import { getDisplayNameFromPath } from '@/utils/version';
import { GITHUB_API, MODAL_STYLES_WITH_TITLEBAR } from '@/constants'; import { GITHUB_API, MODAL_STYLES_WITH_TITLEBAR } from '@/constants';
import { safeExecute } from '@/utils/logger'; import { safeExecute } from '@/utils/logger';
@ -19,24 +21,27 @@ import { safeExecute } from '@/utils/logger';
interface UpdateAvailableModalProps { interface UpdateAvailableModalProps {
opened: boolean; opened: boolean;
onClose: () => void; onClose: () => void;
currentVersion?: InstalledVersion; updateInfo?: BinaryUpdateInfo;
availableUpdate?: DownloadItem;
onUpdate: (download: DownloadItem) => Promise<void>; onUpdate: (download: DownloadItem) => Promise<void>;
isDownloading?: boolean;
downloadProgress?: number;
} }
export const UpdateAvailableModal = ({ export const UpdateAvailableModal = ({
opened, opened,
onClose, onClose,
currentVersion, updateInfo,
availableUpdate,
onUpdate, onUpdate,
isDownloading = false,
downloadProgress = 0,
}: UpdateAvailableModalProps) => { }: UpdateAvailableModalProps) => {
const { downloading, downloadProgress } = useKoboldVersions();
const currentVersion = updateInfo?.currentVersion;
const availableUpdate = updateInfo?.availableUpdate;
const [isUpdating, setIsUpdating] = useState(false); const [isUpdating, setIsUpdating] = useState(false);
const isDownloading =
!!availableUpdate && downloading === availableUpdate.name;
const currentProgress = availableUpdate
? downloadProgress[availableUpdate.name] || 0
: 0;
const handleUpdate = async () => { const handleUpdate = async () => {
if (availableUpdate) { if (availableUpdate) {
setIsUpdating(true); setIsUpdating(true);
@ -113,7 +118,7 @@ export const UpdateAvailableModal = ({
<Stack gap="xs" style={{ minHeight: '2.75rem' }}> <Stack gap="xs" style={{ minHeight: '2.75rem' }}>
<Progress <Progress
value={isDownloading ? Math.min(downloadProgress, 100) : 0} value={isDownloading ? Math.min(currentProgress, 100) : 0}
color="orange" color="orange"
radius="xl" radius="xl"
style={{ style={{
@ -131,7 +136,7 @@ export const UpdateAvailableModal = ({
}} }}
> >
{isDownloading {isDownloading
? `${Math.min(downloadProgress, 100).toFixed(1)}% complete` ? `${Math.min(currentProgress, 100).toFixed(1)}% complete`
: 'Preparing update...'} : 'Preparing update...'}
</Text> </Text>
</Stack> </Stack>

View file

@ -1,17 +1,21 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { AppShell, Loader, Center, Stack, Text } from '@mantine/core'; import {
import { DownloadScreen } from '@/components/screens/Download'; AppShell,
import { LaunchScreen } from '@/components/screens/Launch'; Loader,
import { InterfaceScreen } from '@/components/screens/Interface'; Center,
import { WelcomeScreen } from '@/components/screens/Welcome'; Stack,
import { UpdateAvailableModal } from '@/components/UpdateAvailableModal'; Text,
import { EjectConfirmModal } from '@/components/EjectConfirmModal'; useMantineColorScheme,
import { ScreenTransition } from '@/components/ScreenTransition'; } from '@mantine/core';
import { TitleBar } from '@/components/TitleBar'; import { UpdateAvailableModal } from '@/components/App/UpdateAvailableModal';
import { StatusBar } from '@/components/StatusBar'; import { EjectConfirmModal } from '@/components/App/EjectConfirmModal';
import { ErrorBoundary } from '@/components/ErrorBoundary'; import { TitleBar } from '@/components/App/TitleBar';
import { StatusBar } from '@/components/App/StatusBar';
import { ErrorBoundary } from '@/components/App/ErrorBoundary';
import { AppRouter } from '@/components/App/Router';
import { useUpdateChecker } from '@/hooks/useUpdateChecker'; import { useUpdateChecker } from '@/hooks/useUpdateChecker';
import { useKoboldVersions } from '@/hooks/useKoboldVersions'; import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { useAppColorScheme } from '@/hooks/useAppColorScheme';
import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants'; import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants';
import type { DownloadItem } from '@/types/electron'; import type { DownloadItem } from '@/types/electron';
import type { InterfaceTab, Screen } from '@/types'; import type { InterfaceTab, Screen } from '@/types';
@ -24,6 +28,13 @@ export const App = () => {
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false); const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
const isInterfaceScreen = currentScreen === 'interface'; const isInterfaceScreen = currentScreen === 'interface';
const appColorScheme = useAppColorScheme();
const { setColorScheme } = useMantineColorScheme();
useEffect(() => {
setColorScheme(appColorScheme);
}, [appColorScheme, setColorScheme]);
const { const {
updateInfo: binaryUpdateInfo, updateInfo: binaryUpdateInfo,
showUpdateModal, showUpdateModal,
@ -31,11 +42,7 @@ export const App = () => {
dismissUpdate, dismissUpdate,
} = useUpdateChecker(); } = useUpdateChecker();
const { const { handleDownload: sharedHandleDownload } = useKoboldVersions();
handleDownload: sharedHandleDownload,
downloading,
downloadProgress,
} = useKoboldVersions();
useEffect(() => { useEffect(() => {
const checkInstallation = async () => { const checkInstallation = async () => {
@ -77,23 +84,6 @@ export const App = () => {
} }
}; };
const handleDownloadComplete = async () => {
await window.electronAPI.kobold.getCurrentVersion();
setTimeout(() => {
setCurrentScreen('launch');
}, 100);
};
const handleLaunch = () => {
setActiveInterfaceTab('terminal');
setCurrentScreen('interface');
};
const handleBackToLaunch = () => {
setCurrentScreen('launch');
};
const handleEject = async () => { const handleEject = async () => {
const skipEjectConfirmation = await window.electronAPI.config.get( const skipEjectConfirmation = await window.electronAPI.config.get(
'skipEjectConfirmation' 'skipEjectConfirmation'
@ -108,7 +98,7 @@ export const App = () => {
const performEject = () => { const performEject = () => {
window.electronAPI.kobold.stopKoboldCpp(); window.electronAPI.kobold.stopKoboldCpp();
handleBackToLaunch(); setCurrentScreen('launch');
}; };
const handleEjectConfirm = (skipConfirmation: boolean) => { const handleEjectConfirm = (skipConfirmation: boolean) => {
@ -118,15 +108,17 @@ export const App = () => {
performEject(); performEject();
}; };
const handleWelcomeComplete = async () => { const handleWelcomeComplete = () => {
window.electronAPI.config.set('hasSeenWelcome', true); setCurrentScreen('download');
};
const versions = await window.electronAPI.kobold.getInstalledVersions(); const handleDownloadComplete = () => {
if (versions.length > 0) { setTimeout(() => setCurrentScreen('launch'), 500);
setCurrentScreen('launch'); };
} else {
setCurrentScreen('download'); const handleLaunch = () => {
} setActiveInterfaceTab('terminal');
setCurrentScreen('interface');
}; };
return ( return (
@ -165,53 +157,23 @@ export const App = () => {
</Stack> </Stack>
</Center> </Center>
) : ( ) : (
<> <AppRouter
<ScreenTransition currentScreen={currentScreen}
isActive={currentScreen === 'welcome'} hasInitialized={hasInitialized}
shouldAnimate={hasInitialized} activeInterfaceTab={activeInterfaceTab}
> onWelcomeComplete={handleWelcomeComplete}
<WelcomeScreen onGetStarted={handleWelcomeComplete} /> onDownloadComplete={handleDownloadComplete}
</ScreenTransition> onLaunch={handleLaunch}
onTabChange={setActiveInterfaceTab}
<ScreenTransition />
isActive={currentScreen === 'download'}
shouldAnimate={hasInitialized}
>
<DownloadScreen onDownloadComplete={handleDownloadComplete} />
</ScreenTransition>
<ScreenTransition
isActive={currentScreen === 'launch'}
shouldAnimate={hasInitialized}
>
<LaunchScreen onLaunch={handleLaunch} />
</ScreenTransition>
<ScreenTransition
isActive={isInterfaceScreen}
shouldAnimate={hasInitialized}
>
<InterfaceScreen
activeTab={activeInterfaceTab}
onTabChange={setActiveInterfaceTab}
/>
</ScreenTransition>
</>
)} )}
</ErrorBoundary> </ErrorBoundary>
<UpdateAvailableModal <UpdateAvailableModal
opened={showUpdateModal && !!binaryUpdateInfo} opened={showUpdateModal && !!binaryUpdateInfo}
onClose={dismissUpdate} onClose={dismissUpdate}
currentVersion={binaryUpdateInfo?.currentVersion} updateInfo={binaryUpdateInfo || undefined}
availableUpdate={binaryUpdateInfo?.availableUpdate}
onUpdate={handleBinaryUpdate} onUpdate={handleBinaryUpdate}
isDownloading={downloading === binaryUpdateInfo?.availableUpdate.name}
downloadProgress={
binaryUpdateInfo
? downloadProgress[binaryUpdateInfo.availableUpdate.name] || 0
: 0
}
/> />
</AppShell.Main> </AppShell.Main>

View file

@ -1,6 +1,9 @@
import { Box, Text, Stack } from '@mantine/core'; import { Box, Text, Stack } from '@mantine/core';
import { useMemo } from 'react';
import { useLaunchConfigStore } from '@/stores/launchConfig';
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import { getServerInterfaceInfo } from '@/utils/interface';
import { TITLEBAR_HEIGHT, STATUSBAR_HEIGHT } from '@/constants'; import { TITLEBAR_HEIGHT, STATUSBAR_HEIGHT } from '@/constants';
import { useServerInterfaceInfo } from '@/hooks/useInterfaceSelection';
interface ServerTabProps { interface ServerTabProps {
serverUrl?: string; serverUrl?: string;
@ -13,9 +16,24 @@ export const ServerTab = ({
isServerReady, isServerReady,
activeTab, activeTab,
}: ServerTabProps) => { }: ServerTabProps) => {
const { url: iframeUrl, title } = useServerInterfaceInfo( const { isImageGenerationMode } = useLaunchConfigStore();
serverUrl || '', const { frontendPreference } = useFrontendPreferenceStore();
activeTab
const effectiveImageMode =
activeTab === 'chat-image'
? true
: activeTab === 'chat-text'
? false
: isImageGenerationMode;
const { url: iframeUrl, title } = useMemo(
() =>
getServerInterfaceInfo({
frontendPreference,
isImageGenerationMode: effectiveImageMode,
serverUrl: serverUrl || '',
}),
[frontendPreference, effectiveImageMode, serverUrl]
); );
if (!isServerReady || !serverUrl) { if (!isServerReady || !serverUrl) {

View file

@ -14,7 +14,7 @@ import {
} from '@/constants'; } from '@/constants';
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal'; import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
import { useLaunchConfigStore } from '@/stores/launchConfig'; import { useLaunchConfigStore } from '@/stores/launchConfig';
import { useFrontendPreference } from '@/hooks/useInterfaceSelection'; import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import { useAppColorScheme } from '@/hooks/useAppColorScheme'; import { useAppColorScheme } from '@/hooks/useAppColorScheme';
interface TerminalTabProps { interface TerminalTabProps {
@ -28,7 +28,7 @@ export interface TerminalTabRef {
export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>( export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>(
({ onServerReady }, ref) => { ({ onServerReady }, ref) => {
const { host, port, isImageGenerationMode } = useLaunchConfigStore(); const { host, port, isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference } = useFrontendPreference(); const { frontendPreference } = useFrontendPreferenceStore();
const colorScheme = useAppColorScheme(); const colorScheme = useAppColorScheme();
const [terminalContent, setTerminalContent] = useState(''); const [terminalContent, setTerminalContent] = useState('');
const [isUserScrolling, setIsUserScrolling] = useState(false); const [isUserScrolling, setIsUserScrolling] = useState(false);

View file

@ -1,10 +1,12 @@
import { useState, useCallback, useRef, useEffect } from 'react'; import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { ServerTab } from '@/components/screens/Interface/ServerTab'; import { ServerTab } from '@/components/screens/Interface/ServerTab';
import { import {
TerminalTab, TerminalTab,
type TerminalTabRef, type TerminalTabRef,
} from '@/components/screens/Interface/TerminalTab'; } from '@/components/screens/Interface/TerminalTab';
import { useDefaultInterfaceTab } from '@/hooks/useInterfaceSelection'; import { useLaunchConfigStore } from '@/stores/launchConfig';
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import { getDefaultInterfaceTab } from '@/utils/interface';
import type { InterfaceTab } from '@/types'; import type { InterfaceTab } from '@/types';
interface InterfaceScreenProps { interface InterfaceScreenProps {
@ -19,7 +21,19 @@ export const InterfaceScreen = ({
const [serverUrl, setServerUrl] = useState(''); const [serverUrl, setServerUrl] = useState('');
const [isServerReady, setIsServerReady] = useState(false); const [isServerReady, setIsServerReady] = useState(false);
const terminalTabRef = useRef<TerminalTabRef>(null); const terminalTabRef = useRef<TerminalTabRef>(null);
const defaultInterfaceTab = useDefaultInterfaceTab();
const { isTextMode, isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference } = useFrontendPreferenceStore();
const defaultInterfaceTab = useMemo(
() =>
getDefaultInterfaceTab({
frontendPreference,
isTextMode,
isImageGenerationMode,
}),
[frontendPreference, isTextMode, isImageGenerationMode]
);
const handleServerReady = useCallback( const handleServerReady = useCallback(
(url: string) => { (url: string) => {

View file

@ -9,7 +9,7 @@ import {
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { InfoTooltip } from '@/components/InfoTooltip'; import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip'; import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { CommandLineArgumentsModal } from '@/components/CommandLineArgumentsModal'; import { CommandLineArgumentsModal } from '@/components/screens/Launch/CommandLineArgumentsModal';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
export const AdvancedTab = () => { export const AdvancedTab = () => {

View file

@ -1,7 +1,7 @@
import { Stack, Text, Group, TextInput, Slider } from '@mantine/core'; import { Stack, Text, Group, TextInput, Slider } from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip'; import { InfoTooltip } from '@/components/InfoTooltip';
import { BackendSelector } from '@/components/screens/Launch/GeneralTab/BackendSelector'; import { BackendSelector } from '@/components/screens/Launch/GeneralTab/BackendSelector';
import { ModelFileField } from '@/components/ModelFileField'; import { ModelFileField } from '@/components/screens/Launch/ModelFileField';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
export const GeneralTab = () => { export const GeneralTab = () => {

View file

@ -1,6 +1,6 @@
import { Stack } from '@mantine/core'; import { Stack } from '@mantine/core';
import { useState } from 'react'; import { useState } from 'react';
import { ModelFileField } from '@/components/ModelFileField'; import { ModelFileField } from '@/components/screens/Launch/ModelFileField';
import { SelectWithTooltip } from '@/components/SelectWithTooltip'; import { SelectWithTooltip } from '@/components/SelectWithTooltip';
import { IMAGE_MODEL_PRESETS } from '@/constants/imageModelPresets'; import { IMAGE_MODEL_PRESETS } from '@/constants/imageModelPresets';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';

View file

@ -12,6 +12,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Folder, FolderOpen, Monitor } from 'lucide-react'; import { Folder, FolderOpen, Monitor } from 'lucide-react';
import type { FrontendPreference } from '@/types'; import type { FrontendPreference } from '@/types';
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import { FRONTENDS } from '@/constants'; import { FRONTENDS } from '@/constants';
interface FrontendRequirement { interface FrontendRequirement {
@ -36,8 +37,8 @@ export const GeneralTab = ({
isOnInterfaceScreen = false, isOnInterfaceScreen = false,
}: GeneralTabProps) => { }: GeneralTabProps) => {
const [installDir, setInstallDir] = useState(''); const [installDir, setInstallDir] = useState('');
const [FrontendPreference, setFrontendPreference] = const { frontendPreference, setFrontendPreference, loadFrontendPreference } =
useState<FrontendPreference>('koboldcpp'); useFrontendPreferenceStore();
const [frontendRequirements, setFrontendRequirements] = useState< const [frontendRequirements, setFrontendRequirements] = useState<
Map<string, boolean> Map<string, boolean>
>(new Map()); >(new Map());
@ -108,13 +109,12 @@ export const GeneralTab = ({
setFrontendRequirements(requirementResults); setFrontendRequirements(requirementResults);
const currentFrontendConfig = frontendConfigs.find( const currentFrontendConfig = frontendConfigs.find(
(config) => config.value === FrontendPreference (config) => config.value === frontendPreference
); );
if (currentFrontendConfig && !requirementResults.get(FrontendPreference)) { if (currentFrontendConfig && !requirementResults.get(frontendPreference)) {
window.electronAPI.config.set('frontendPreference', 'koboldcpp');
setFrontendPreference('koboldcpp'); setFrontendPreference('koboldcpp');
} }
}, [frontendConfigs, FrontendPreference]); }, [frontendConfigs, frontendPreference, setFrontendPreference]);
useEffect(() => { useEffect(() => {
const initialize = async () => { const initialize = async () => {
@ -132,10 +132,10 @@ export const GeneralTab = ({
window.addEventListener('focus', handleFocus); window.addEventListener('focus', handleFocus);
return () => window.removeEventListener('focus', handleFocus); return () => window.removeEventListener('focus', handleFocus);
}, [checkAllFrontendRequirements]); }, [checkAllFrontendRequirements, loadFrontendPreference]);
const getSelectedFrontendConfig = () => const getSelectedFrontendConfig = () =>
frontendConfigs.find((config) => config.value === FrontendPreference); frontendConfigs.find((config) => config.value === frontendPreference);
const getUnmetRequirements = () => { const getUnmetRequirements = () => {
const selectedConfig = getSelectedFrontendConfig(); const selectedConfig = getSelectedFrontendConfig();
@ -157,10 +157,10 @@ export const GeneralTab = ({
frontendRequirements.get(frontendValue) ?? true; frontendRequirements.get(frontendValue) ?? true;
useEffect(() => { useEffect(() => {
if (FrontendPreference) { if (frontendPreference) {
checkAllFrontendRequirements(); checkAllFrontendRequirements();
} }
}, [FrontendPreference, checkAllFrontendRequirements]); }, [frontendPreference, checkAllFrontendRequirements]);
const loadCurrentInstallDir = async () => { const loadCurrentInstallDir = async () => {
const currentDir = await window.electronAPI.kobold.getCurrentInstallDir(); const currentDir = await window.electronAPI.kobold.getCurrentInstallDir();
@ -169,16 +169,6 @@ export const GeneralTab = ({
} }
}; };
const loadFrontendPreference = async () => {
const frontendPreference =
await window.electronAPI.config.get('frontendPreference');
if (frontendPreference) {
setFrontendPreference(
(frontendPreference as FrontendPreference) || 'koboldcpp'
);
}
};
const handleSelectInstallDir = async () => { const handleSelectInstallDir = async () => {
const selectedDir = const selectedDir =
await window.electronAPI.kobold.selectInstallDirectory(); await window.electronAPI.kobold.selectInstallDirectory();
@ -195,8 +185,6 @@ export const GeneralTab = ({
return; return;
await checkAllFrontendRequirements(); await checkAllFrontendRequirements();
window.electronAPI.config.set('frontendPreference', value);
setFrontendPreference(value as FrontendPreference); setFrontendPreference(value as FrontendPreference);
}; };
@ -263,7 +251,7 @@ export const GeneralTab = ({
)} )}
<Select <Select
value={FrontendPreference} value={frontendPreference}
onChange={handleFrontendPreferenceChange} onChange={handleFrontendPreferenceChange}
disabled={isOnInterfaceScreen} disabled={isOnInterfaceScreen}
onClick={() => checkAllFrontendRequirements()} onClick={() => checkAllFrontendRequirements()}

View file

@ -1,11 +1,9 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useMantineColorScheme } from '@mantine/core';
type ColorScheme = 'light' | 'dark'; type ColorScheme = 'light' | 'dark';
export const useAppColorScheme = () => { export const useAppColorScheme = () => {
const [colorScheme, setColorScheme] = useState<ColorScheme>('light'); const [colorScheme, setColorScheme] = useState<ColorScheme>('light');
const { setColorScheme: setMantineColorScheme } = useMantineColorScheme();
useEffect(() => { useEffect(() => {
const loadColorScheme = async () => { const loadColorScheme = async () => {
@ -24,12 +22,11 @@ export const useAppColorScheme = () => {
} }
setColorScheme(resolvedScheme); setColorScheme(resolvedScheme);
setMantineColorScheme(resolvedScheme);
} }
}; };
void loadColorScheme(); void loadColorScheme();
}, [setMantineColorScheme]); }, []);
return colorScheme; return colorScheme;
}; };

View file

@ -1,62 +1,32 @@
import { useEffect } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { useLaunchConfigStore } from '@/stores/launchConfig'; import type { FrontendPreference } from '@/types';
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import {
getAvailableInterfaceOptions,
getDefaultInterfaceTab,
getServerInterfaceInfo,
} from '@/utils/interface';
export function useFrontendPreference() { export function useFrontendPreference() {
const { frontendPreference, setFrontendPreference, loadFromConfig } = const [frontendPreference, setFrontendPreferenceState] =
useFrontendPreferenceStore(); useState<FrontendPreference>('koboldcpp');
useEffect(() => { useEffect(() => {
const loadFromConfig = async () => {
const preference = (await window.electronAPI.config.get(
'frontendPreference'
)) as FrontendPreference;
setFrontendPreferenceState(preference || 'koboldcpp');
};
loadFromConfig(); loadFromConfig();
}, [loadFromConfig]); }, []);
const setFrontendPreference = useCallback(
(preference: FrontendPreference) => {
setFrontendPreferenceState(preference);
window.electronAPI.config.set('frontendPreference', preference);
},
[]
);
return { return {
frontendPreference, frontendPreference,
setFrontendPreference, setFrontendPreference,
}; };
} }
export function useInterfaceOptions() {
const { isTextMode, isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference } = useFrontendPreference();
return getAvailableInterfaceOptions({
frontendPreference,
isTextMode,
isImageGenerationMode,
});
}
export function useDefaultInterfaceTab() {
const { isTextMode, isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference } = useFrontendPreference();
return getDefaultInterfaceTab({
frontendPreference,
isTextMode,
isImageGenerationMode,
});
}
export function useServerInterfaceInfo(serverUrl: string, activeTab?: string) {
const { isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference } = useFrontendPreference();
const effectiveImageMode =
activeTab === 'chat-image'
? true
: activeTab === 'chat-text'
? false
: isImageGenerationMode;
return getServerInterfaceInfo({
frontendPreference,
isImageGenerationMode: effectiveImageMode,
serverUrl,
});
}

View file

@ -8,13 +8,13 @@ import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { getROCmDownload } from '@/utils/rocm'; import { getROCmDownload } from '@/utils/rocm';
import type { InstalledVersion, DownloadItem } from '@/types/electron'; import type { InstalledVersion, DownloadItem } from '@/types/electron';
interface UpdateInfo { export interface BinaryUpdateInfo {
currentVersion: InstalledVersion; currentVersion: InstalledVersion;
availableUpdate: DownloadItem; availableUpdate: DownloadItem;
} }
export const useUpdateChecker = () => { export const useUpdateChecker = () => {
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null); const [updateInfo, setUpdateInfo] = useState<BinaryUpdateInfo | null>(null);
const [isChecking, setIsChecking] = useState(false); const [isChecking, setIsChecking] = useState(false);
const [showUpdateModal, setShowUpdateModal] = useState(false); const [showUpdateModal, setShowUpdateModal] = useState(false);
const [dismissedUpdates, setDismissedUpdates] = useState<Set<string>>( const [dismissedUpdates, setDismissedUpdates] = useState<Set<string>>(

View file

@ -1,7 +1,7 @@
import { StrictMode } from 'react'; import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { MantineProvider, createTheme } from '@mantine/core'; import { MantineProvider, createTheme } from '@mantine/core';
import { App } from '@/App.tsx'; import { App } from '@/components/App';
import '@/styles/index.css'; import '@/styles/index.css';
const theme = createTheme({ const theme = createTheme({

View file

@ -1,23 +1,22 @@
import { create } from 'zustand'; import { create } from 'zustand';
import type { FrontendPreference } from '@/types'; import type { FrontendPreference } from '@/types';
interface FrontendPreferenceState { interface FrontendPreferenceStore {
frontendPreference: FrontendPreference; frontendPreference: FrontendPreference;
setFrontendPreference: (preference: FrontendPreference) => void; setFrontendPreference: (preference: FrontendPreference) => void;
loadFromConfig: () => Promise<void>; loadFrontendPreference: () => Promise<void>;
} }
export const useFrontendPreferenceStore = create<FrontendPreferenceState>()( export const useFrontendPreferenceStore = create<FrontendPreferenceStore>(
(set) => ({ (set) => ({
frontendPreference: 'koboldcpp', frontendPreference: 'koboldcpp',
setFrontendPreference: (preference: FrontendPreference) => { setFrontendPreference: (preference: FrontendPreference) => {
set({ frontendPreference: preference }); set({ frontendPreference: preference });
window.electronAPI.config.set('frontendPreference', preference); window.electronAPI.config.set('frontendPreference', preference);
}, },
loadFromConfig: async () => { loadFrontendPreference: async () => {
const preference = (await window.electronAPI.config.get( const preference = (await window.electronAPI.config.get(
'frontendPreference' 'frontendPreference'
)) as FrontendPreference; )) as FrontendPreference;