remove the double try/catch cases on the frontend side for cleaner code

This commit is contained in:
Egor 2025-09-18 18:30:21 -07:00
parent 3eb97f5683
commit 0b905a7f67
22 changed files with 250 additions and 364 deletions

View file

@ -12,7 +12,6 @@ import { StatusBar } from '@/components/StatusBar';
import { ErrorBoundary } from '@/components/ErrorBoundary'; import { ErrorBoundary } from '@/components/ErrorBoundary';
import { useUpdateChecker } from '@/hooks/useUpdateChecker'; import { useUpdateChecker } from '@/hooks/useUpdateChecker';
import { useKoboldVersions } from '@/hooks/useKoboldVersions'; import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { safeExecute } from '@/utils/logger';
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';
@ -40,7 +39,6 @@ export const App = () => {
useEffect(() => { useEffect(() => {
const checkInstallation = async () => { const checkInstallation = async () => {
await safeExecute(async () => {
const [currentVersion, hasSeenWelcome] = await Promise.all([ const [currentVersion, hasSeenWelcome] = await Promise.all([
window.electronAPI.kobold.getCurrentVersion(), window.electronAPI.kobold.getCurrentVersion(),
window.electronAPI.config.get('hasSeenWelcome') as Promise<boolean>, window.electronAPI.config.get('hasSeenWelcome') as Promise<boolean>,
@ -59,7 +57,6 @@ export const App = () => {
checkForUpdates(); checkForUpdates();
}, 2000); }, 2000);
} }
}, 'Error checking installation:');
setHasInitialized(true); setHasInitialized(true);
}; };
@ -69,7 +66,6 @@ export const App = () => {
}, []); }, []);
const handleBinaryUpdate = async (download: DownloadItem) => { const handleBinaryUpdate = async (download: DownloadItem) => {
await safeExecute(async () => {
const success = await sharedHandleDownload({ const success = await sharedHandleDownload({
item: download, item: download,
isUpdate: true, isUpdate: true,
@ -79,13 +75,10 @@ export const App = () => {
if (success) { if (success) {
dismissUpdate(); dismissUpdate();
} }
}, 'Failed to update binary:');
}; };
const handleDownloadComplete = async () => { const handleDownloadComplete = async () => {
await safeExecute(async () => {
await window.electronAPI.kobold.getCurrentVersion(); await window.electronAPI.kobold.getCurrentVersion();
}, 'Error refreshing versions after download:');
setTimeout(() => { setTimeout(() => {
setCurrentScreen('launch'); setCurrentScreen('launch');

View file

@ -2,7 +2,6 @@ import { ReactNode } from 'react';
import { Center, Stack, Text, Button, Alert, rem } from '@mantine/core'; import { Center, Stack, Text, Button, Alert, rem } from '@mantine/core';
import { AlertTriangle, FolderOpen } from 'lucide-react'; import { AlertTriangle, FolderOpen } from 'lucide-react';
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
import { safeExecute } from '@/utils/logger';
interface ErrorBoundaryProps { interface ErrorBoundaryProps {
children: ReactNode; children: ReactNode;
@ -39,12 +38,7 @@ const ErrorFallback = ({
leftSection={ leftSection={
<FolderOpen style={{ width: rem(16), height: rem(16) }} /> <FolderOpen style={{ width: rem(16), height: rem(16) }} />
} }
onClick={async () => { onClick={() => window.electronAPI.app.showLogsFolder()}
await safeExecute(
() => window.electronAPI.app.showLogsFolder(),
'Failed to open logs folder'
);
}}
> >
Show Logs Folder Show Logs Folder
</Button> </Button>

View file

@ -1,6 +1,5 @@
import { Badge, Tooltip } from '@mantine/core'; import { Badge, Tooltip } from '@mantine/core';
import { useState } from 'react'; import { useState } from 'react';
import { safeExecute } from '@/utils/logger';
interface PerformanceBadgeProps { interface PerformanceBadgeProps {
label: string; label: string;
@ -16,12 +15,9 @@ export const PerformanceBadge = ({
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const handlePerformanceClick = async () => { const handlePerformanceClick = async () => {
const result = await safeExecute( const result = await window.electronAPI.app.openPerformanceManager();
() => window.electronAPI.app.openPerformanceManager(),
'Failed to open performance manager'
);
if (result && !result.success) { if (!result.success) {
window.electronAPI.logs.logError( window.electronAPI.logs.logError(
`Failed to open performance manager: ${result.error}` `Failed to open performance manager: ${result.error}`
); );

View file

@ -1,11 +1,10 @@
import { useState, useCallback, useEffect, useRef } from 'react'; import { useState, useCallback, useRef, useEffect } from 'react';
import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core'; import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
import { DownloadCard } from '@/components/DownloadCard'; import { DownloadCard } from '@/components/DownloadCard';
import { getPlatformDisplayName } from '@/utils/platform'; import { getPlatformDisplayName } from '@/utils/platform';
import { formatDownloadSize } from '@/utils/format'; import { formatDownloadSize } from '@/utils/format';
import { getAssetDescription } from '@/utils/assets'; import { getAssetDescription } from '@/utils/assets';
import { useKoboldVersions } from '@/hooks/useKoboldVersions'; import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import { safeExecute } from '@/utils/logger';
import type { DownloadItem } from '@/types/electron'; import type { DownloadItem } from '@/types/electron';
interface DownloadScreenProps { interface DownloadScreenProps {
@ -32,7 +31,6 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
async (download: DownloadItem) => { async (download: DownloadItem) => {
setDownloadingAsset(download.name); setDownloadingAsset(download.name);
await safeExecute(async () => {
const success = await sharedHandleDownload({ const success = await sharedHandleDownload({
item: download, item: download,
isUpdate: false, isUpdate: false,
@ -46,7 +44,6 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
setDownloadingAsset(null); setDownloadingAsset(null);
}, 200); }, 200);
} }
}, `Failed to download ${download.name}:`);
setDownloadingAsset(null); setDownloadingAsset(null);
}, },

View file

@ -11,7 +11,6 @@ import { InfoTooltip } from '@/components/InfoTooltip';
import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip'; import { CheckboxWithTooltip } from '@/components/CheckboxWithTooltip';
import { CommandLineArgumentsModal } from '@/components/CommandLineArgumentsModal'; import { CommandLineArgumentsModal } from '@/components/CommandLineArgumentsModal';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { safeExecute } from '@/utils/logger';
export const AdvancedTab = () => { export const AdvancedTab = () => {
const { const {
@ -56,10 +55,7 @@ export const AdvancedTab = () => {
useEffect(() => { useEffect(() => {
const detectBackendSupport = async () => { const detectBackendSupport = async () => {
const support = await safeExecute( const support = await window.electronAPI.kobold.detectBackendSupport();
() => window.electronAPI.kobold.detectBackendSupport(),
'Failed to detect backend support:'
);
if (support) { if (support) {
setBackendSupport({ setBackendSupport({

View file

@ -4,7 +4,6 @@ import { InfoTooltip } from '@/components/InfoTooltip';
import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem'; import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem';
import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector'; import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { safeExecute } from '@/utils/logger';
import type { BackendOption } from '@/types'; import type { BackendOption } from '@/types';
export const BackendSelector = () => { export const BackendSelector = () => {
@ -26,10 +25,8 @@ export const BackendSelector = () => {
useEffect(() => { useEffect(() => {
const loadBackends = async () => { const loadBackends = async () => {
setIsLoadingBackends(true); setIsLoadingBackends(true);
const backends = await safeExecute( const backends =
() => window.electronAPI.kobold.getAvailableBackends(true), await window.electronAPI.kobold.getAvailableBackends(true);
'Failed to detect available backends:'
);
setAvailableBackends(backends || []); setAvailableBackends(backends || []);
setIsLoadingBackends(false); setIsLoadingBackends(false);
hasInitialized.current = true; hasInitialized.current = true;

View file

@ -1,6 +1,6 @@
import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core'; import { Card, Container, Stack, Tabs, Group, Button } from '@mantine/core';
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { logError, safeExecute } from '@/utils/logger'; import { logError } from '@/utils/logger';
import { useLaunchConfig } from '@/hooks/useLaunchConfig'; import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { useLaunchLogic } from '@/hooks/useLaunchLogic'; import { useLaunchLogic } from '@/hooks/useLaunchLogic';
import { useWarnings } from '@/hooks/useWarnings'; import { useWarnings } from '@/hooks/useWarnings';
@ -81,10 +81,7 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
}); });
const setHappyDefaults = useCallback(async () => { const setHappyDefaults = useCallback(async () => {
const backends = await safeExecute( const backends = await window.electronAPI.kobold.getAvailableBackends();
() => window.electronAPI.kobold.getAvailableBackends(),
'Failed to set defaults:'
);
if (!backend && backends && backends.length > 0) { if (!backend && backends && backends.length > 0) {
handleBackendChange(backends[0].value); handleBackendChange(backends[0].value);
@ -183,7 +180,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
}); });
const handleCreateNewConfig = async (configName: string) => { const handleCreateNewConfig = async (configName: string) => {
await safeExecute(async () => {
const fullConfigName = `${configName}.json`; const fullConfigName = `${configName}.json`;
const saveSuccess = await window.electronAPI.kobold.saveConfigFile( const saveSuccess = await window.electronAPI.kobold.saveConfigFile(
fullConfigName, fullConfigName,
@ -200,7 +196,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
new Error('Save operation failed') new Error('Save operation failed')
); );
} }
}, 'Failed to create new configuration:');
}; };
const handleSaveConfig = async () => { const handleSaveConfig = async () => {
@ -212,7 +207,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
return false; return false;
} }
const success = await safeExecute(async () => {
const saveSuccess = await window.electronAPI.kobold.saveConfigFile( const saveSuccess = await window.electronAPI.kobold.saveConfigFile(
selectedFile, selectedFile,
buildConfigData() buildConfigData()
@ -227,9 +221,6 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
} }
return true; return true;
}, 'Failed to save configuration:');
return success ?? false;
}; };
useEffect(() => { useEffect(() => {

View file

@ -24,10 +24,7 @@ export const AboutTab = () => {
const { handleLogoClick, getLogoStyles } = useLogoClickSounds(); const { handleLogoClick, getLogoStyles } = useLogoClickSounds();
useEffect(() => { useEffect(() => {
const loadVersionInfo = async () => { const loadVersionInfo = async () => {
const info = await safeExecute( const info = await window.electronAPI.app.getVersionInfo();
() => window.electronAPI.app.getVersionInfo(),
'Failed to load version info'
);
if (info) { if (info) {
setVersionInfo(info); setVersionInfo(info);
} }
@ -48,9 +45,7 @@ export const AboutTab = () => {
{ label: 'Electron', value: versionInfo.electronVersion }, { label: 'Electron', value: versionInfo.electronVersion },
{ {
label: 'Node.js', label: 'Node.js',
value: value: versionInfo.nodeJsSystemVersion
versionInfo.nodeJsSystemVersion &&
versionInfo.nodeJsSystemVersion !== versionInfo.nodeVersion
? `${versionInfo.nodeVersion} (System: ${versionInfo.nodeJsSystemVersion})` ? `${versionInfo.nodeVersion} (System: ${versionInfo.nodeJsSystemVersion})`
: versionInfo.nodeVersion, : versionInfo.nodeVersion,
}, },
@ -66,21 +61,9 @@ export const AboutTab = () => {
]; ];
const copyVersionInfo = async () => { const copyVersionInfo = async () => {
const nodeJsInfo = const info = versionItems
versionInfo.nodeJsSystemVersion && .map((item) => `${item.label}: ${item.value}`)
versionInfo.nodeJsSystemVersion !== versionInfo.nodeVersion .join('\n');
? `${versionInfo.nodeVersion} (system: ${versionInfo.nodeJsSystemVersion})`
: versionInfo.nodeVersion;
const info = [
`${PRODUCT_NAME}: ${versionInfo.appVersion}`,
`Electron: ${versionInfo.electronVersion}`,
`Node.js: ${nodeJsInfo}`,
`Chromium: ${versionInfo.chromeVersion}`,
`V8: ${versionInfo.v8Version}`,
`OS: ${versionInfo.platform} ${versionInfo.arch} (${versionInfo.osVersion})`,
...(versionInfo.uvVersion ? [`uv: ${versionInfo.uvVersion}`] : []),
].join('\n');
await safeExecute( await safeExecute(
() => navigator.clipboard.writeText(info), () => navigator.clipboard.writeText(info),
@ -128,15 +111,11 @@ export const AboutTab = () => {
leftSection={ leftSection={
<Github style={{ width: rem(16), height: rem(16) }} /> <Github style={{ width: rem(16), height: rem(16) }} />
} }
onClick={async () => { onClick={() =>
await safeExecute(
() =>
window.electronAPI.app.openExternal( window.electronAPI.app.openExternal(
'https://github.com/lone-cloud/gerbil' 'https://github.com/lone-cloud/gerbil'
), )
'Failed to open GitHub link' }
);
}}
style={{ textDecoration: 'none' }} style={{ textDecoration: 'none' }}
> >
GitHub GitHub
@ -148,12 +127,7 @@ export const AboutTab = () => {
leftSection={ leftSection={
<FolderOpen style={{ width: rem(16), height: rem(16) }} /> <FolderOpen style={{ width: rem(16), height: rem(16) }} />
} }
onClick={async () => { onClick={() => window.electronAPI.app.showLogsFolder()}
await safeExecute(
() => window.electronAPI.app.showLogsFolder(),
'Failed to open logs folder'
);
}}
> >
Show Logs Show Logs
</Button> </Button>

View file

@ -10,7 +10,6 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Sun, Moon, Monitor } from 'lucide-react'; import { Sun, Moon, Monitor } from 'lucide-react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { safeExecute } from '@/utils/logger';
import { useAppColorScheme } from '@/hooks/useAppColorScheme'; import { useAppColorScheme } from '@/hooks/useAppColorScheme';
import { import {
zoomLevelToPercentage, zoomLevelToPercentage,
@ -33,14 +32,8 @@ export const AppearanceTab = () => {
useEffect(() => { useEffect(() => {
const loadSettings = async () => { const loadSettings = async () => {
const [currentZoom, savedColorScheme] = await Promise.all([ const [currentZoom, savedColorScheme] = await Promise.all([
safeExecute( window.electronAPI.app.getZoomLevel(),
() => window.electronAPI.app.getZoomLevel(), window.electronAPI.app.getColorScheme(),
'Failed to load zoom level:'
),
safeExecute(
() => window.electronAPI.app.getColorScheme(),
'Failed to load color scheme:'
),
]); ]);
if (typeof currentZoom === 'number') { if (typeof currentZoom === 'number') {
@ -56,38 +49,29 @@ export const AppearanceTab = () => {
void loadSettings(); void loadSettings();
}, []); }, []);
const handleColorSchemeChange = async (value: string) => { const handleColorSchemeChange = (value: string) => {
const newColorScheme = value as MantineColorScheme; const newColorScheme = value as MantineColorScheme;
setRawColorScheme(newColorScheme); setRawColorScheme(newColorScheme);
await safeExecute( void window.electronAPI.app.setColorScheme(newColorScheme);
() => window.electronAPI.app.setColorScheme(newColorScheme),
'Failed to save color scheme:'
);
}; };
const handleZoomChange = async (newZoomLevel: number) => { const handleZoomChange = (newZoomLevel: number) => {
setZoomLevel(newZoomLevel); setZoomLevel(newZoomLevel);
const percentage = zoomLevelToPercentage(newZoomLevel); const percentage = zoomLevelToPercentage(newZoomLevel);
setZoomPercentage(percentage.toString()); setZoomPercentage(percentage.toString());
await safeExecute( void window.electronAPI.app.setZoomLevel(newZoomLevel);
() => window.electronAPI.app.setZoomLevel(newZoomLevel),
'Failed to set zoom level:'
);
}; };
const handleZoomPercentageChange = async (value: string) => { const handleZoomPercentageChange = (value: string) => {
setZoomPercentage(value); setZoomPercentage(value);
const numValue = Number(value); const numValue = Number(value);
if (!isNaN(numValue) && isValidZoomPercentage(numValue)) { if (!isNaN(numValue) && isValidZoomPercentage(numValue)) {
const newZoomLevel = percentageToZoomLevel(numValue); const newZoomLevel = percentageToZoomLevel(numValue);
setZoomLevel(newZoomLevel); setZoomLevel(newZoomLevel);
await safeExecute( void window.electronAPI.app.setZoomLevel(newZoomLevel);
() => window.electronAPI.app.setZoomLevel(newZoomLevel),
'Failed to set zoom level:'
);
} }
}; };

View file

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useMemo } from 'react'; import { useState, useEffect, useCallback, useMemo } from 'react';
import { tryExecute, safeExecute } from '@/utils/logger'; import { tryExecute } from '@/utils/logger';
import { import {
Stack, Stack,
Text, Text,
@ -99,11 +99,8 @@ export const GeneralTab = ({
for (const config of frontendConfigs) { for (const config of frontendConfigs) {
if (config.requirementCheck) { if (config.requirementCheck) {
const isAvailable = await safeExecute( const isAvailable = await config.requirementCheck();
config.requirementCheck, requirementResults.set(config.value, isAvailable);
`Failed to check requirements for ${config.label}:`
);
requirementResults.set(config.value, isAvailable ?? false);
} else { } else {
requirementResults.set(config.value, true); requirementResults.set(config.value, true);
} }
@ -163,20 +160,15 @@ export const GeneralTab = ({
}, [FrontendPreference, checkAllFrontendRequirements]); }, [FrontendPreference, checkAllFrontendRequirements]);
const loadCurrentInstallDir = async () => { const loadCurrentInstallDir = async () => {
const currentDir = await safeExecute( const currentDir = await window.electronAPI.kobold.getCurrentInstallDir();
() => window.electronAPI.kobold.getCurrentInstallDir(),
'Failed to load install directory:'
);
if (currentDir) { if (currentDir) {
setInstallDir(currentDir); setInstallDir(currentDir);
} }
}; };
const loadFrontendPreference = async () => { const loadFrontendPreference = async () => {
const frontendPreference = await safeExecute( const frontendPreference =
() => window.electronAPI.config.get('frontendPreference'), await window.electronAPI.config.get('frontendPreference');
'Failed to load frontend preference:'
);
if (frontendPreference) { if (frontendPreference) {
setFrontendPreference( setFrontendPreference(
(frontendPreference as FrontendPreference) || 'koboldcpp' (frontendPreference as FrontendPreference) || 'koboldcpp'
@ -185,10 +177,8 @@ export const GeneralTab = ({
}; };
const handleSelectInstallDir = async () => { const handleSelectInstallDir = async () => {
const selectedDir = await safeExecute( const selectedDir =
() => window.electronAPI.kobold.selectInstallDirectory(), await window.electronAPI.kobold.selectInstallDirectory();
'Failed to select install directory:'
);
if (selectedDir) { if (selectedDir) {
setInstallDir(selectedDir); setInstallDir(selectedDir);
} }

View file

@ -16,7 +16,6 @@ import {
stripAssetExtensions, stripAssetExtensions,
compareVersions, compareVersions,
} from '@/utils/version'; } from '@/utils/version';
import { safeExecute } from '@/utils/logger';
import { formatDownloadSize } from '@/utils/format'; import { formatDownloadSize } from '@/utils/format';
import { useKoboldVersions } from '@/hooks/useKoboldVersions'; import { useKoboldVersions } from '@/hooks/useKoboldVersions';
@ -60,7 +59,6 @@ export const VersionsTab = () => {
const loadInstalledVersions = useCallback(async () => { const loadInstalledVersions = useCallback(async () => {
setLoadingInstalled(true); setLoadingInstalled(true);
await safeExecute(async () => {
const [versions, currentVersion] = await Promise.all([ const [versions, currentVersion] = await Promise.all([
window.electronAPI.kobold.getInstalledVersions(), window.electronAPI.kobold.getInstalledVersions(),
window.electronAPI.kobold.getCurrentVersion(), window.electronAPI.kobold.getCurrentVersion(),
@ -68,16 +66,12 @@ export const VersionsTab = () => {
setInstalledVersions(versions); setInstalledVersions(versions);
setCurrentVersion(currentVersion); setCurrentVersion(currentVersion);
}, 'Failed to load installed versions:');
setLoadingInstalled(false); setLoadingInstalled(false);
}, []); }, []);
const loadLatestRelease = useCallback(async () => { const loadLatestRelease = useCallback(async () => {
const release = await safeExecute( const release = await getLatestReleaseWithDownloadStatus();
() => getLatestReleaseWithDownloadStatus(),
'Failed to load latest release:'
);
if (release) { if (release) {
setLatestRelease(release); setLatestRelease(release);
} }
@ -174,11 +168,8 @@ export const VersionsTab = () => {
}, [downloading]); }, [downloading]);
const handleDownload = async (version: VersionInfo) => { const handleDownload = async (version: VersionInfo) => {
await safeExecute(async () => {
const download = availableDownloads.find((d) => d.name === version.name); const download = availableDownloads.find((d) => d.name === version.name);
if (!download) { if (!download) return;
throw new Error('Download not found');
}
const success = await sharedHandleDownload({ const success = await sharedHandleDownload({
item: download, item: download,
@ -189,15 +180,11 @@ export const VersionsTab = () => {
if (success) { if (success) {
await loadInstalledVersions(); await loadInstalledVersions();
} }
}, 'Failed to download:');
}; };
const handleUpdate = async (version: VersionInfo) => { const handleUpdate = async (version: VersionInfo) => {
await safeExecute(async () => {
const download = availableDownloads.find((d) => d.name === version.name); const download = availableDownloads.find((d) => d.name === version.name);
if (!download) { if (!download) return;
throw new Error('Download not found');
}
const success = await sharedHandleDownload({ const success = await sharedHandleDownload({
item: download, item: download,
@ -208,21 +195,18 @@ export const VersionsTab = () => {
if (success) { if (success) {
await loadInstalledVersions(); await loadInstalledVersions();
} }
}, 'Failed to update:');
}; };
const makeCurrent = async (version: VersionInfo) => { const makeCurrent = async (version: VersionInfo) => {
if (!version.installedPath) return; if (!version.installedPath) return;
await safeExecute(async () => {
const success = await window.electronAPI.kobold.setCurrentVersion( const success = await window.electronAPI.kobold.setCurrentVersion(
version.installedPath! version.installedPath
); );
if (success) { if (success) {
await loadInstalledVersions(); await loadInstalledVersions();
} }
}, 'Failed to set current version:');
}; };
if (loadingInstalled || loadingPlatform || loadingRemote) { if (loadingInstalled || loadingPlatform || loadingRemote) {

View file

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useMantineColorScheme } from '@mantine/core'; import { useMantineColorScheme } from '@mantine/core';
import { safeExecute } from '@/utils/logger';
type ColorScheme = 'light' | 'dark'; type ColorScheme = 'light' | 'dark';
@ -10,10 +9,7 @@ export const useAppColorScheme = () => {
useEffect(() => { useEffect(() => {
const loadColorScheme = async () => { const loadColorScheme = async () => {
const rawScheme = await safeExecute( const rawScheme = await window.electronAPI.app.getColorScheme();
() => window.electronAPI.app.getColorScheme(),
'Failed to get app color scheme'
);
if (rawScheme) { if (rawScheme) {
let resolvedScheme: ColorScheme; let resolvedScheme: ColorScheme;

View file

@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect } from 'react';
import { safeExecute, tryExecute } from '@/utils/logger'; import { tryExecute } from '@/utils/logger';
import { compareVersions } from '@/utils/version'; import { compareVersions } from '@/utils/version';
import { GITHUB_API } from '@/constants'; import { GITHUB_API } from '@/constants';
@ -19,18 +19,12 @@ export const useAppUpdateChecker = () => {
useEffect(() => { useEffect(() => {
const initializeUpdater = async () => { const initializeUpdater = async () => {
const [canUpdate, isDownloaded] = await Promise.all([ const [canUpdate, isDownloaded] = await Promise.all([
safeExecute( window.electronAPI.updater.canAutoUpdate(),
() => window.electronAPI.updater.canAutoUpdate(), window.electronAPI.updater.isUpdateDownloaded(),
'Failed to check auto-update capability'
),
safeExecute(
() => window.electronAPI.updater.isUpdateDownloaded(),
'Failed to check update download status'
),
]); ]);
if (canUpdate !== null) setCanAutoUpdate(canUpdate); setCanAutoUpdate(canUpdate);
if (isDownloaded !== null) setIsUpdateDownloaded(isDownloaded); setIsUpdateDownloaded(isDownloaded);
}; };
void initializeUpdater(); void initializeUpdater();

View file

@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect } from 'react';
import { tryExecute, safeExecute } from '@/utils/logger'; import { tryExecute } from '@/utils/logger';
import { import {
getDisplayNameFromPath, getDisplayNameFromPath,
compareVersions, compareVersions,
@ -27,13 +27,9 @@ export const useUpdateChecker = () => {
useEffect(() => { useEffect(() => {
const loadDismissedUpdates = async () => { const loadDismissedUpdates = async () => {
const dismissed = await safeExecute( const dismissed = (await window.electronAPI.config.get(
async () => 'dismissedUpdates'
(await window.electronAPI.config.get('dismissedUpdates')) as )) as string[] | undefined;
| string[]
| undefined,
'Failed to load dismissed updates'
);
if (dismissed) { if (dismissed) {
setDismissedUpdates(new Set(dismissed)); setDismissedUpdates(new Set(dismissed));

View file

@ -1,5 +1,4 @@
import { useEffect, useState, useCallback, useMemo } from 'react'; import { useEffect, useState, useCallback, useMemo } from 'react';
import { safeExecute } from '@/utils/logger';
import type { BackendOption, BackendSupport } from '@/types'; import type { BackendOption, BackendSupport } from '@/types';
export interface Warning { export interface Warning {
@ -105,10 +104,7 @@ const checkVramWarnings = async (backend: string): Promise<Warning[]> => {
const isGpuBackend = ['cuda', 'rocm', 'vulkan', 'clblast'].includes(backend); const isGpuBackend = ['cuda', 'rocm', 'vulkan', 'clblast'].includes(backend);
if (isGpuBackend) { if (isGpuBackend) {
const gpuMemoryInfo = await safeExecute( const gpuMemoryInfo = await window.electronAPI.kobold.detectGPUMemory();
() => window.electronAPI.kobold.detectGPUMemory(),
'Failed to detect GPU memory:'
);
if (gpuMemoryInfo) { if (gpuMemoryInfo) {
const lowVramThreshold = 8; const lowVramThreshold = 8;
@ -189,7 +185,6 @@ const checkBackendWarnings = async (params?: {
}): Promise<Warning[]> => { }): Promise<Warning[]> => {
const warnings: Warning[] = []; const warnings: Warning[] = [];
const result = await safeExecute(async () => {
const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([ const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([
window.electronAPI.kobold.detectBackendSupport(), window.electronAPI.kobold.detectBackendSupport(),
window.electronAPI.kobold.detectGPUCapabilities(), window.electronAPI.kobold.detectGPUCapabilities(),
@ -227,9 +222,6 @@ const checkBackendWarnings = async (params?: {
} }
return warnings; return warnings;
}, 'Failed to check backend warnings:');
return result || warnings;
}; };
export const useWarnings = ({ export const useWarnings = ({
@ -253,7 +245,6 @@ export const useWarnings = ({
return; return;
} }
const result = await safeExecute(async () => {
const [cpuCapabilitiesResult, availableBackends] = await Promise.all([ const [cpuCapabilitiesResult, availableBackends] = await Promise.all([
window.electronAPI.kobold.detectCPU(), window.electronAPI.kobold.detectCPU(),
window.electronAPI.kobold.getAvailableBackends(), window.electronAPI.kobold.getAvailableBackends(),
@ -264,16 +255,15 @@ export const useWarnings = ({
avx2: cpuCapabilitiesResult.avx2, avx2: cpuCapabilitiesResult.avx2,
}; };
return checkBackendWarnings({ const result = await checkBackendWarnings({
backend, backend,
cpuCapabilities, cpuCapabilities,
noavx2, noavx2,
failsafe, failsafe,
availableBackends, availableBackends,
}); });
}, 'Failed to check backend warnings:');
setBackendWarnings(result || []); setBackendWarnings(result);
}, [backend, noavx2, failsafe]); }, [backend, noavx2, failsafe]);
useEffect(() => { useEffect(() => {

View file

@ -37,7 +37,6 @@ setupAutoUpdater();
export const checkForUpdates = async () => { export const checkForUpdates = async () => {
if (isDevelopment) { if (isDevelopment) {
logError('Auto-updater: Cannot check for updates in development mode');
return false; return false;
} }
@ -51,7 +50,6 @@ export const checkForUpdates = async () => {
export const downloadUpdate = async () => { export const downloadUpdate = async () => {
if (isDevelopment) { if (isDevelopment) {
logError('Auto-updater: Cannot download updates in development mode');
return false; return false;
} }

View file

@ -1,21 +1,29 @@
import { app } from 'electron'; import { app } from 'electron';
import { join } from 'path'; import { join } from 'path';
import { createLogger, format, type Logger } from 'winston'; import { mkdir } from 'fs/promises';
import { createLogger, format } from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file'; import DailyRotateFile from 'winston-daily-rotate-file';
import { isDevelopment } from '@/utils/node/environment'; import { isDevelopment } from '@/utils/node/environment';
let logger: Logger | null = null; const ensureLogsDirectory = async (logsDir: string) => {
let isInitialized = false; try {
await mkdir(logsDir, { recursive: true });
export const initializeLogger = () => { } catch {
if (isInitialized) return; return;
}
};
const createAppLogger = () => {
const logsDir = join(app.getPath('userData'), 'logs'); const logsDir = join(app.getPath('userData'), 'logs');
logger = createLogger({ void ensureLogsDirectory(logsDir);
return createLogger({
level: isDevelopment ? 'debug' : 'info', level: isDevelopment ? 'debug' : 'info',
format: format.combine( format: format.combine(
format.timestamp(), format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss.SSS',
}),
format.printf(({ timestamp, level, message, error }) => { format.printf(({ timestamp, level, message, error }) => {
let logEntry = `${timestamp} [MAIN] [${level.toUpperCase()}] ${message}`; let logEntry = `${timestamp} [MAIN] [${level.toUpperCase()}] ${message}`;
@ -40,25 +48,17 @@ export const initializeLogger = () => {
}), }),
], ],
}); });
isInitialized = true;
}; };
const ensureInitialized = () => { const logger = createAppLogger();
if (!isInitialized) {
initializeLogger();
}
};
export const logError = (message: string, error?: Error) => { export const logError = (message: string, error?: Error) => {
ensureInitialized(); logger.error(message, { error });
logger!.error(message, { error });
flushLogs(); flushLogs();
}; };
export const flushLogs = () => { export const flushLogs = () => {
ensureInitialized(); const fileTransport = logger.transports.find(
const fileTransport = logger!.transports.find(
(t) => t.constructor.name === 'DailyRotateFile' (t) => t.constructor.name === 'DailyRotateFile'
); );
if ( if (

View file

@ -1,4 +1,4 @@
import { execa } from 'execa'; import { spawn } from 'child_process';
import { platform } from 'process'; import { platform } from 'process';
import { safeExecute } from '@/utils/node/logger'; import { safeExecute } from '@/utils/node/logger';
@ -12,16 +12,37 @@ const LINUX_PERFORMANCE_APPS = [
]; ];
async function tryLaunchCommand(command: string, args: string[] = []) { async function tryLaunchCommand(command: string, args: string[] = []) {
try { return new Promise((resolve) => {
await execa(command, args, { const child = spawn(command, args, {
detached: true, detached: true,
stdio: 'ignore', stdio: 'ignore',
timeout: 2000,
}); });
return true;
} catch { let hasResolved = false;
return false;
child.on('error', () => {
if (!hasResolved) {
hasResolved = true;
resolve(false);
} }
});
child.on('spawn', () => {
if (!hasResolved) {
hasResolved = true;
child.unref();
resolve(true);
}
});
setTimeout(() => {
if (!hasResolved) {
hasResolved = true;
child.kill();
resolve(false);
}
}, 2000);
});
} }
export const openPerformanceManager = async () => export const openPerformanceManager = async () =>

View file

@ -1,5 +1,4 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { safeExecute } from '@/utils/logger';
import type { FrontendPreference } from '@/types'; import type { FrontendPreference } from '@/types';
interface FrontendPreferenceState { interface FrontendPreferenceState {
@ -15,19 +14,15 @@ export const useFrontendPreferenceStore = create<FrontendPreferenceState>()(
setFrontendPreference: (preference: FrontendPreference) => { setFrontendPreference: (preference: FrontendPreference) => {
set({ frontendPreference: preference }); set({ frontendPreference: preference });
safeExecute(async () => { window.electronAPI.config.set('frontendPreference', preference);
await window.electronAPI.config.set('frontendPreference', preference);
}, 'Error saving frontend preference:');
}, },
loadFromConfig: async () => { loadFromConfig: async () => {
await safeExecute(async () => {
const preference = (await window.electronAPI.config.get( const preference = (await window.electronAPI.config.get(
'frontendPreference' 'frontendPreference'
)) as FrontendPreference; )) as FrontendPreference;
set({ frontendPreference: preference || 'koboldcpp' }); set({ frontendPreference: preference || 'koboldcpp' });
}, 'Error loading frontend preference:');
}, },
}) })
); );

View file

@ -3,7 +3,7 @@ import {
createTryExecute, createTryExecute,
createTryExecuteImmediate, createTryExecuteImmediate,
createSafeTryExecute, createSafeTryExecute,
} from '@/utils/shared/logger-core'; } from '@/utils/logger-core';
export const logError = (message: string, error: Error) => { export const logError = (message: string, error: Error) => {
window.electronAPI.logs.logError(message, error); window.electronAPI.logs.logError(message, error);

View file

@ -3,7 +3,7 @@ import {
createSafeExecute, createSafeExecute,
createTryExecute, createTryExecute,
createSafeTryExecute, createSafeTryExecute,
} from '@/utils/shared/logger-core'; } from '@/utils/logger-core';
export const safeExecute = createSafeExecute(logError); export const safeExecute = createSafeExecute(logError);
export const tryExecute = createTryExecute(logError); export const tryExecute = createTryExecute(logError);