refactor to use a util to try/catch->logError, hover states on badges

This commit is contained in:
Egor 2025-09-18 13:03:40 -07:00
parent 2723b79c6e
commit 38c504be8b
23 changed files with 264 additions and 276 deletions

View file

@ -10,6 +10,7 @@
- Stop asking me to run the "dev" script to test changes - Stop asking me to run the "dev" script to test changes
- Try to move helper functions from component code to their own separate files to help minimize clutter - Try to move helper functions from component code to their own separate files to help minimize clutter
- Always use absolute imports (e.g. `import { MyComponent } from '@/components/MyComponent'`) - Always use absolute imports (e.g. `import { MyComponent } from '@/components/MyComponent'`)
- Never add explicit return types to functions. We want to rely on implicit types as much as possible
### Logging and Error Handling ### Logging and Error Handling

View file

@ -1,7 +1,7 @@
{ {
"name": "gerbil", "name": "gerbil",
"productName": "Gerbil", "productName": "Gerbil",
"version": "1.4.0-beta.2", "version": "1.4.0-beta.3",
"description": "Run Large Language Models locally", "description": "Run Large Language Models locally",
"main": "out/main/index.js", "main": "out/main/index.js",
"homepage": "./", "homepage": "./",

View file

@ -1,4 +1,6 @@
import { Badge, Tooltip } from '@mantine/core'; import { Badge, Tooltip } from '@mantine/core';
import { useState } from 'react';
import { safeExecute } from '@/utils/logger';
interface PerformanceBadgeProps { interface PerformanceBadgeProps {
label: string; label: string;
@ -11,20 +13,19 @@ export const PerformanceBadge = ({
value, value,
tooltipLabel, tooltipLabel,
}: PerformanceBadgeProps) => { }: PerformanceBadgeProps) => {
const [isHovered, setIsHovered] = useState(false);
const handlePerformanceClick = async () => { const handlePerformanceClick = async () => {
try { const result = await safeExecute(
const result = await window.electronAPI.app.openPerformanceManager(); () => window.electronAPI.app.openPerformanceManager(),
if (!result.success) { 'Failed to open performance manager'
);
if (result && !result.success) {
window.electronAPI.logs.logError( window.electronAPI.logs.logError(
`Failed to open performance manager: ${result.error}` `Failed to open performance manager: ${result.error}`
); );
} }
} catch (error) {
window.electronAPI.logs.logError(
'Error opening performance manager',
error as Error
);
}
}; };
return ( return (
@ -36,7 +37,13 @@ export const PerformanceBadge = ({
minWidth: '5rem', minWidth: '5rem',
textAlign: 'center', textAlign: 'center',
cursor: 'pointer', cursor: 'pointer',
transition: 'background-color 0.2s ease',
backgroundColor: isHovered
? 'var(--mantine-color-blue-1)'
: undefined,
}} }}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onClick={handlePerformanceClick} onClick={handlePerformanceClick}
> >
{label}: {value} {label}: {value}

View file

@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect } from 'react';
import { logError, safeExecute } from '@/utils/logger'; import { safeExecute, tryExecute } from '@/utils/logger';
import { compareVersions } from '@/utils/version'; import { compareVersions } from '@/utils/version';
import { GITHUB_API } from '@/constants'; import { GITHUB_API } from '@/constants';
@ -40,16 +40,15 @@ export const useAppUpdateChecker = () => {
if (!canAutoUpdate) return; if (!canAutoUpdate) return;
setIsDownloading(true); setIsDownloading(true);
try {
await tryExecute(async () => {
const success = await window.electronAPI.updater.downloadUpdate(); const success = await window.electronAPI.updater.downloadUpdate();
if (success) { if (success) {
setIsUpdateDownloaded(true); setIsUpdateDownloaded(true);
} }
} catch (err) { }, 'Failed to download update');
logError('Failed to download update:', err as Error);
} finally {
setIsDownloading(false); setIsDownloading(false);
}
}, [canAutoUpdate]); }, [canAutoUpdate]);
const installUpdate = useCallback(() => { const installUpdate = useCallback(() => {
@ -59,7 +58,7 @@ export const useAppUpdateChecker = () => {
}, [isUpdateDownloaded]); }, [isUpdateDownloaded]);
const checkForAppUpdates = useCallback(async () => { const checkForAppUpdates = useCallback(async () => {
try { await tryExecute(async () => {
const currentVersion = await window.electronAPI.app.getVersion(); const currentVersion = await window.electronAPI.app.getVersion();
const response = await fetch( const response = await fetch(
@ -87,9 +86,7 @@ export const useAppUpdateChecker = () => {
}; };
setUpdateInfo(updateInfo); setUpdateInfo(updateInfo);
} catch (err) { }, 'Failed to check for app updates');
logError('Failed to check for app updates:', err as Error);
}
}, []); }, []);
useEffect(() => { useEffect(() => {

View file

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { logError } from '@/utils/logger'; import { logError, tryExecuteImmediate } from '@/utils/logger';
import { getROCmDownload } from '@/utils/rocm'; import { getROCmDownload } from '@/utils/rocm';
import { GITHUB_API } from '@/constants'; import { GITHUB_API } from '@/constants';
import { filterAssetsByPlatform } from '@/utils/platform'; import { filterAssetsByPlatform } from '@/utils/platform';
@ -41,15 +41,13 @@ const loadFromCache = (): CachedReleaseData | null => {
}; };
const saveToCache = (releases: DownloadItem[]) => { const saveToCache = (releases: DownloadItem[]) => {
try { tryExecuteImmediate(() => {
const data: CachedReleaseData = { const data: CachedReleaseData = {
releases, releases,
timestamp: Date.now(), timestamp: Date.now(),
}; };
localStorage.setItem(CACHE_KEY, JSON.stringify(data)); localStorage.setItem(CACHE_KEY, JSON.stringify(data));
} catch (err) { }, 'Failed to save releases to cache');
logError('Failed to save releases to cache', err as Error);
}
}; };
const transformReleaseToDownloadItems = ( const transformReleaseToDownloadItems = (

View file

@ -1,5 +1,5 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { logError } from '@/utils/logger'; import { tryExecute } from '@/utils/logger';
import type { SdConvDirectMode } from '@/types'; import type { SdConvDirectMode } from '@/types';
interface UseLaunchLogicProps { interface UseLaunchLogicProps {
@ -258,7 +258,7 @@ export const useLaunchLogic = ({
onLaunch(); onLaunch();
try { await tryExecute(async () => {
const args: string[] = [ const args: string[] = [
...buildModelArgs(model, sdmodel, launchArgs), ...buildModelArgs(model, sdmodel, launchArgs),
...buildConfigArgs(hasImageModel, launchArgs), ...buildConfigArgs(hasImageModel, launchArgs),
@ -283,11 +283,9 @@ export const useLaunchLogic = ({
new Error(errorMessage) new Error(errorMessage)
); );
} }
} catch (err) { }, 'Error launching');
logError('Error launching:', err as Error);
} finally {
setIsLaunching(false); setIsLaunching(false);
}
}, },
[model, sdmodel, isLaunching, onLaunch] [model, sdmodel, isLaunching, onLaunch]
); );

View file

@ -1,5 +1,5 @@
import { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect } from 'react';
import { logError } from '@/utils/logger'; import { tryExecute, safeExecute } from '@/utils/logger';
import { import {
getDisplayNameFromPath, getDisplayNameFromPath,
compareVersions, compareVersions,
@ -27,32 +27,30 @@ export const useUpdateChecker = () => {
useEffect(() => { useEffect(() => {
const loadDismissedUpdates = async () => { const loadDismissedUpdates = async () => {
try { 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));
} }
setDismissedUpdatesLoaded(true); setDismissedUpdatesLoaded(true);
} catch (err) {
logError('Failed to load dismissed updates:', err as Error);
setDismissedUpdatesLoaded(true);
}
}; };
loadDismissedUpdates(); loadDismissedUpdates();
}, []); }, []);
const saveDismissedUpdates = useCallback(async (updates: Set<string>) => { const saveDismissedUpdates = useCallback(async (updates: Set<string>) => {
try { await tryExecute(async () => {
await window.electronAPI.config.set( await window.electronAPI.config.set(
'dismissedUpdates', 'dismissedUpdates',
Array.from(updates) Array.from(updates)
); );
} catch (err) { }, 'Failed to save dismissed updates');
logError('Failed to save dismissed updates:', err as Error);
}
}, []); }, []);
const checkForUpdates = useCallback(async () => { const checkForUpdates = useCallback(async () => {
@ -62,7 +60,7 @@ export const useUpdateChecker = () => {
setIsChecking(true); setIsChecking(true);
try { await tryExecute(async () => {
const [currentVersion, rocmDownload] = await Promise.all([ const [currentVersion, rocmDownload] = await Promise.all([
window.electronAPI.kobold.getCurrentVersion(), window.electronAPI.kobold.getCurrentVersion(),
getROCmDownload(), getROCmDownload(),
@ -105,11 +103,9 @@ export const useUpdateChecker = () => {
} }
} }
} }
} catch (err) { }, 'Failed to check for updates');
logError('Failed to check for updates:', err as Error);
} finally {
setIsChecking(false); setIsChecking(false);
}
}, [dismissedUpdates, dismissedUpdatesLoaded, releases]); }, [dismissedUpdates, dismissedUpdatesLoaded, releases]);
const dismissUpdate = useCallback(async () => { const dismissUpdate = useCallback(async () => {

View file

@ -8,7 +8,7 @@ import {
initialize as initializeConfig, initialize as initializeConfig,
getInstallDir, getInstallDir,
} from '@/main/modules/config'; } from '@/main/modules/config';
import { logError } from '@/main/modules/logging'; import { safeExecute } from '@/utils/node/logger';
import { cleanup } from '@/main/modules/koboldcpp'; import { cleanup } from '@/main/modules/koboldcpp';
import { getSillyTavernManager } from '@/main/modules/sillytavern'; import { getSillyTavernManager } from '@/main/modules/sillytavern';
import { cleanup as cleanupOpenWebUI } from '@/main/modules/openwebui'; import { cleanup as cleanupOpenWebUI } from '@/main/modules/openwebui';
@ -38,7 +38,7 @@ export async function initializeApp() {
app.on('before-quit', async (event) => { app.on('before-quit', async (event) => {
event.preventDefault(); event.preventDefault();
try { await safeExecute(async () => {
const cleanupPromises = [ const cleanupPromises = [
cleanup(), cleanup(),
getSillyTavernManager().cleanup(), getSillyTavernManager().cleanup(),
@ -53,9 +53,7 @@ export async function initializeApp() {
}); });
await Promise.race([Promise.all(cleanupPromises), timeoutPromise]); await Promise.race([Promise.all(cleanupPromises), timeoutPromise]);
} catch (error) { }, 'Error during cleanup');
logError('Error during cleanup:', error as Error);
}
cleanupWindow(); cleanupWindow();

View file

@ -25,6 +25,7 @@ import {
setColorScheme, setColorScheme,
} from '@/main/modules/config'; } from '@/main/modules/config';
import { logError } from '@/main/modules/logging'; import { logError } from '@/main/modules/logging';
import { safeExecute } from '@/utils/node/logger';
import { getSillyTavernManager } from '@/main/modules/sillytavern'; import { getSillyTavernManager } from '@/main/modules/sillytavern';
import { import {
startFrontend as startOpenWebUIFrontend, startFrontend as startOpenWebUIFrontend,
@ -64,8 +65,8 @@ import {
import type { FrontendPreference } from '@/types'; import type { FrontendPreference } from '@/types';
import { getAppVersion } from '@/utils/node/fs'; import { getAppVersion } from '@/utils/node/fs';
async function launchKoboldCppWithCustomFrontends(args: string[] = []) { const launchKoboldCppWithCustomFrontends = async (args: string[] = []) =>
try { (await safeExecute(async () => {
const frontendPreference = (await getConfig( const frontendPreference = (await getConfig(
'frontendPreference' 'frontendPreference'
)) as FrontendPreference; )) as FrontendPreference;
@ -83,14 +84,10 @@ async function launchKoboldCppWithCustomFrontends(args: string[] = []) {
} }
return result; return result;
} catch (error) { }, 'Error in enhanced launch')) || {
logError('Error in enhanced launch:', error as Error);
return {
success: false, success: false,
error: (error as Error).message, error: 'Launch failed',
}; };
}
}
export function setupIPCHandlers() { export function setupIPCHandlers() {
ipcMain.handle('kobold:downloadRelease', async (_, asset) => ({ ipcMain.handle('kobold:downloadRelease', async (_, asset) => ({
@ -188,18 +185,18 @@ export function setupIPCHandlers() {
}; };
}); });
ipcMain.handle('app:showLogsFolder', async () => { ipcMain.handle(
try { 'app:showLogsFolder',
async () =>
(await safeExecute(async () => {
const logsDir = join(app.getPath('userData'), 'logs'); const logsDir = join(app.getPath('userData'), 'logs');
await shell.openPath(logsDir); await shell.openPath(logsDir);
return { success: true }; return { success: true };
} catch (error) { }, 'Failed to open logs folder')) || {
logError('Failed to open logs folder:', error as Error); success: false,
throw new Error( error: 'Failed to open logs folder',
`Failed to open logs folder: ${(error as Error).message}`
);
} }
}); );
ipcMain.handle('app:minimizeWindow', () => getMainWindow()?.minimize()); ipcMain.handle('app:minimizeWindow', () => getMainWindow()?.minimize());
@ -239,17 +236,17 @@ export function setupIPCHandlers() {
} }
); );
ipcMain.handle('app:openExternal', async (_, url: string) => { ipcMain.handle(
try { 'app:openExternal',
async (_, url: string) =>
(await safeExecute(async () => {
await shell.openExternal(url); await shell.openExternal(url);
return { success: true }; return { success: true };
} catch (error) { }, 'Failed to open external URL')) || {
logError('Failed to open external URL:', error as Error); success: false,
throw new Error( error: 'Failed to open external URL',
`Failed to open external URL: ${(error as Error).message}`
);
} }
}); );
const mainWindow = getMainWindow(); const mainWindow = getMainWindow();
if (mainWindow) { if (mainWindow) {

View file

@ -1,6 +1,7 @@
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import { app } from 'electron'; import { app } from 'electron';
import { logError } from '@/main/modules/logging'; import { logError } from '@/main/modules/logging';
import { safeExecute } from '@/utils/node/logger';
export interface UpdateInfo { export interface UpdateInfo {
version: string; version: string;
@ -33,25 +34,17 @@ function setupAutoUpdater() {
setupAutoUpdater(); setupAutoUpdater();
export async function checkForUpdates() { export const checkForUpdates = async () =>
try { (await safeExecute(
const result = await autoUpdater.checkForUpdates(); () => autoUpdater.checkForUpdates(),
return result !== null; 'Failed to check for updates'
} catch (error) { )) !== null;
logError('Failed to check for updates:', error as Error);
return false;
}
}
export async function downloadUpdate() { export const downloadUpdate = async () =>
try { (await safeExecute(
await autoUpdater.downloadUpdate(); () => autoUpdater.downloadUpdate(),
return true; 'Failed to download update'
} catch (error) { )) !== null;
logError('Failed to download update:', error as Error);
return false;
}
}
export function quitAndInstall() { export function quitAndInstall() {
if (updateDownloaded) { if (updateDownloaded) {

View file

@ -1,8 +1,8 @@
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { pathExists } from '@/utils/node/fs'; import { pathExists } from '@/utils/node/fs';
import { logError } from './logging';
import { getCurrentBinaryInfo } from './koboldcpp'; import { getCurrentBinaryInfo } from './koboldcpp';
import { detectGPUCapabilities, detectCPU } from './hardware'; import { detectGPUCapabilities, detectCPU } from './hardware';
import { tryExecute, safeExecute } from '@/utils/node/logger';
import type { BackendOption, BackendSupport } from '@/types'; import type { BackendOption, BackendSupport } from '@/types';
const backendSupportCache = new Map<string, BackendSupport>(); const backendSupportCache = new Map<string, BackendSupport>();
@ -22,7 +22,7 @@ async function detectBackendSupportFromPath(koboldBinaryPath: string) {
cuda: false, cuda: false,
}; };
try { await tryExecute(async () => {
const binaryDir = dirname(koboldBinaryPath); const binaryDir = dirname(koboldBinaryPath);
const internalDir = join(binaryDir, '_internal'); const internalDir = join(binaryDir, '_internal');
@ -60,16 +60,14 @@ async function detectBackendSupportFromPath(koboldBinaryPath: string) {
support.noavx2 = noavx2; support.noavx2 = noavx2;
support.failsafe = failsafe; support.failsafe = failsafe;
support.cuda = cuda; support.cuda = cuda;
} catch (error) { }, 'Error detecting backend support');
logError('Error detecting backend support:', error as Error);
}
backendSupportCache.set(koboldBinaryPath, support); backendSupportCache.set(koboldBinaryPath, support);
return support; return support;
} }
export async function detectBackendSupport() { export const detectBackendSupport = async () =>
try { (await safeExecute(async () => {
const currentBinaryInfo = await getCurrentBinaryInfo(); const currentBinaryInfo = await getCurrentBinaryInfo();
if (!currentBinaryInfo?.path) { if (!currentBinaryInfo?.path) {
@ -77,17 +75,11 @@ export async function detectBackendSupport() {
} }
return detectBackendSupportFromPath(currentBinaryInfo.path); return detectBackendSupportFromPath(currentBinaryInfo.path);
} catch (error) { }, 'Error detecting current binary backend support')) || null;
logError('Error detecting current binary backend support:', error as Error);
return null;
}
}
// eslint-disable-next-line sonarjs/cognitive-complexity export async function getAvailableBackends(includeDisabled = false) {
export async function getAvailableBackends( // eslint-disable-next-line sonarjs/cognitive-complexity
includeDisabled = false const result = await safeExecute(async () => {
): Promise<BackendOption[]> {
try {
const [currentBinaryInfo, hardwareCapabilities, cpuCapabilities] = const [currentBinaryInfo, hardwareCapabilities, cpuCapabilities] =
await Promise.all([ await Promise.all([
getCurrentBinaryInfo(), getCurrentBinaryInfo(),
@ -177,8 +169,7 @@ export async function getAvailableBackends(
availableBackendsCache.set(cacheKey, backends); availableBackendsCache.set(cacheKey, backends);
return backends; return backends;
} catch (error) { }, 'Failed to get available backends');
logError('Failed to get available backends:', error as Error);
return [{ value: 'cpu', label: 'CPU' }]; return result || [{ value: 'cpu', label: 'CPU' }];
}
} }

View file

@ -5,6 +5,7 @@ import type { ChildProcess } from 'child_process';
import yauzl from 'yauzl'; import yauzl from 'yauzl';
import { logError } from './logging'; import { logError } from './logging';
import { safeExecute } from '@/utils/node/logger';
import { sendKoboldOutput } from './window'; import { sendKoboldOutput } from './window';
import { getInstallDir } from './config'; import { getInstallDir } from './config';
import { COMFYUI, SERVER_READY_SIGNALS, GITHUB_API } from '@/constants'; import { COMFYUI, SERVER_READY_SIGNALS, GITHUB_API } from '@/constants';
@ -20,7 +21,7 @@ interface ComfyUIVersionInfo {
} }
async function getLatestComfyUIVersion(): Promise<ComfyUIVersionInfo | null> { async function getLatestComfyUIVersion(): Promise<ComfyUIVersionInfo | null> {
try { return safeExecute(async () => {
const response = await fetch(GITHUB_API.COMFYUI_LATEST_COMMIT_URL); const response = await fetch(GITHUB_API.COMFYUI_LATEST_COMMIT_URL);
if (!response.ok) { if (!response.ok) {
throw new Error( throw new Error(
@ -33,13 +34,7 @@ async function getLatestComfyUIVersion(): Promise<ComfyUIVersionInfo | null> {
sha: data.sha, sha: data.sha,
date: data.commit.committer.date, date: data.commit.committer.date,
}; };
} catch (error) { }, 'Failed to fetch latest ComfyUI version');
logError(
'Failed to fetch latest ComfyUI version',
error instanceof Error ? error : undefined
);
return null;
}
} }
async function getCurrentComfyUIVersion( async function getCurrentComfyUIVersion(

View file

@ -1,5 +1,5 @@
import { logError } from '@/main/modules/logging';
import { readJsonFile, writeJsonFile } from '@/utils/node/fs'; import { readJsonFile, writeJsonFile } from '@/utils/node/fs';
import { safeExecute } from '@/utils/node/logger';
import { getConfigDir } from '@/utils/node/path'; import { getConfigDir } from '@/utils/node/path';
import { homedir } from 'os'; import { homedir } from 'os';
import { join } from 'path'; import { join } from 'path';
@ -23,21 +23,19 @@ let config: AppConfig = {};
let configPath: string; let configPath: string;
async function loadConfig() { async function loadConfig() {
try { const config = await safeExecute(
const loadedConfig = await readJsonFile<AppConfig>(configPath); () => readJsonFile<AppConfig>(configPath),
return loadedConfig || {}; 'Error loading config'
} catch (error) { );
logError('Error loading config:', error as Error); return config || {};
return {};
}
} }
async function saveConfig() { async function saveConfig() {
try { const success = await safeExecute(
await writeJsonFile(configPath, config); () => writeJsonFile(configPath, config),
} catch (error) { 'Error saving config'
logError('Error saving config:', error as Error); );
} return success !== null;
} }
export async function initialize() { export async function initialize() {

View file

@ -1,6 +1,6 @@
/* eslint-disable no-comments/disallowComments */ /* eslint-disable no-comments/disallowComments */
import si from 'systeminformation'; import si from 'systeminformation';
import { logError } from '@/main/modules/logging'; import { safeExecute } from '@/utils/node/logger';
import { terminateProcess } from '@/utils/node/process'; import { terminateProcess } from '@/utils/node/process';
import { getGPUData } from '@/utils/node/gpu'; import { getGPUData } from '@/utils/node/gpu';
import type { import type {
@ -23,7 +23,7 @@ export async function detectCPU() {
return cpuCapabilitiesCache; return cpuCapabilitiesCache;
} }
try { const result = await safeExecute(async () => {
const [cpu, flags] = await Promise.all([si.cpu(), si.cpuFlags()]); const [cpu, flags] = await Promise.all([si.cpu(), si.cpuFlags()]);
const devices: string[] = []; const devices: string[] = [];
@ -34,23 +34,24 @@ export async function detectCPU() {
const avx = flags.includes('avx') || flags.includes('AVX'); const avx = flags.includes('avx') || flags.includes('AVX');
const avx2 = flags.includes('avx2') || flags.includes('AVX2'); const avx2 = flags.includes('avx2') || flags.includes('AVX2');
cpuCapabilitiesCache = { const capabilities = {
avx, avx,
avx2, avx2,
devices, devices,
}; };
return cpuCapabilitiesCache; cpuCapabilitiesCache = capabilities;
} catch (error) { return capabilities;
logError('CPU detection failed:', error as Error); }, 'CPU detection failed');
const fallbackCapabilities = { const fallbackCapabilities = {
avx: false, avx: false,
avx2: false, avx2: false,
devices: [], devices: [],
}; };
cpuCapabilitiesCache = fallbackCapabilities;
return fallbackCapabilities; cpuCapabilitiesCache = result || fallbackCapabilities;
} return cpuCapabilitiesCache;
} }
export async function detectGPU() { export async function detectGPU() {
@ -58,7 +59,7 @@ export async function detectGPU() {
return basicGPUInfoCache; return basicGPUInfoCache;
} }
try { const result = await safeExecute(async () => {
const gpuData = await getGPUData(); const gpuData = await getGPUData();
let hasAMD = false; let hasAMD = false;
@ -90,23 +91,24 @@ export async function detectGPU() {
} }
} }
basicGPUInfoCache = { const basicInfo = {
hasAMD, hasAMD,
hasNVIDIA, hasNVIDIA,
gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'], gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'],
}; };
return basicGPUInfoCache; basicGPUInfoCache = basicInfo;
} catch (error) { return basicInfo;
logError('GPU detection failed:', error as Error); }, 'GPU detection failed');
const fallbackGPUInfo = { const fallbackGPUInfo = {
hasAMD: false, hasAMD: false,
hasNVIDIA: false, hasNVIDIA: false,
gpuInfo: ['GPU detection failed'], gpuInfo: ['GPU detection failed'],
}; };
basicGPUInfoCache = fallbackGPUInfo;
return fallbackGPUInfo; basicGPUInfoCache = result || fallbackGPUInfo;
} return basicGPUInfoCache;
} }
export async function detectGPUCapabilities() { export async function detectGPUCapabilities() {
@ -428,10 +430,9 @@ export async function detectGPUMemory() {
return gpuMemoryInfoCache; return gpuMemoryInfoCache;
} }
const memoryInfo: GPUMemoryInfo[] = []; const result = await safeExecute(async () => {
try {
const gpuData = await getGPUData(); const gpuData = await getGPUData();
const memoryInfo: GPUMemoryInfo[] = [];
for (const gpu of gpuData) { for (const gpu of gpuData) {
if (gpu.deviceName) { if (gpu.deviceName) {
@ -448,11 +449,9 @@ export async function detectGPUMemory() {
} }
} }
gpuMemoryInfoCache = memoryInfo; return memoryInfo;
} catch (error) { }, 'GPU memory detection failed');
logError('GPU memory detection failed:', error as Error);
gpuMemoryInfoCache = [];
}
gpuMemoryInfoCache = result || [];
return gpuMemoryInfoCache; return gpuMemoryInfoCache;
} }

View file

@ -25,6 +25,7 @@ import {
setCurrentKoboldBinary, setCurrentKoboldBinary,
} from './config'; } from './config';
import { logError } from './logging'; import { logError } from './logging';
import { tryExecute } from '@/utils/node/logger';
import { sendKoboldOutput, getMainWindow, sendToRenderer } from './window'; import { sendKoboldOutput, getMainWindow, sendToRenderer } from './window';
import { PRODUCT_NAME, SERVER_READY_SIGNALS } from '@/constants'; import { PRODUCT_NAME, SERVER_READY_SIGNALS } from '@/constants';
import { import {
@ -207,9 +208,7 @@ export async function downloadRelease(asset: GitHubAsset) {
return launcherPath; return launcherPath;
} catch (error) { } catch (error) {
logError('Failed to download or unpack binary:', error as Error); logError('Failed to download or unpack binary:', error as Error);
throw new Error( throw new Error('Failed to download or unpack binary');
`Failed to download or unpack binary: ${(error as Error).message}`
);
} }
} }
@ -232,7 +231,7 @@ async function unpackKoboldCpp(packedPath: string, unpackDir: string) {
} }
async function patchKliteEmbd(unpackedDir: string) { async function patchKliteEmbd(unpackedDir: string) {
try { await tryExecute(async () => {
const possiblePaths = [ const possiblePaths = [
join(unpackedDir, '_internal', 'klite.embd'), join(unpackedDir, '_internal', 'klite.embd'),
join(unpackedDir, 'klite.embd'), join(unpackedDir, 'klite.embd'),
@ -276,13 +275,11 @@ async function patchKliteEmbd(unpackedDir: string) {
await writeFile(kliteEmbdPath, patchedContent, 'utf8'); await writeFile(kliteEmbdPath, patchedContent, 'utf8');
} }
} catch (error) { }, 'Failed to patch klite.embd');
logError('Failed to patch klite.embd:', error as Error);
}
} }
async function patchKcppSduiEmbd(unpackedDir: string) { async function patchKcppSduiEmbd(unpackedDir: string) {
try { await tryExecute(async () => {
const possiblePaths = [ const possiblePaths = [
join(unpackedDir, '_internal', 'kcpp_sdui.embd'), join(unpackedDir, '_internal', 'kcpp_sdui.embd'),
join(unpackedDir, 'kcpp_sdui.embd'), join(unpackedDir, 'kcpp_sdui.embd'),
@ -296,9 +293,7 @@ async function patchKcppSduiEmbd(unpackedDir: string) {
break; break;
} }
} }
} catch (error) { }, 'Failed to patch kcpp_sdui.embd');
logError('Failed to patch kcpp_sdui.embd:', error as Error);
}
} }
async function getLauncherPath(unpackedDir: string) { async function getLauncherPath(unpackedDir: string) {

View file

@ -1,8 +1,8 @@
import si from 'systeminformation'; import si from 'systeminformation';
import { BrowserWindow } from 'electron'; import { BrowserWindow } from 'electron';
import { platform } from 'process'; import { platform } from 'process';
import { logError } from '@/main/modules/logging';
import { getGPUData } from '@/utils/node/gpu'; import { getGPUData } from '@/utils/node/gpu';
import { tryExecute } from '@/utils/node/logger';
export interface CpuMetrics { export interface CpuMetrics {
usage: number; usage: number;
@ -90,7 +90,7 @@ export function stopMonitoring() {
} }
async function collectAndSendCpuMetrics() { async function collectAndSendCpuMetrics() {
try { await tryExecute(async () => {
const cpuData = await si.currentLoad(); const cpuData = await si.currentLoad();
const metrics: CpuMetrics = { const metrics: CpuMetrics = {
usage: Math.round(cpuData.currentLoad), usage: Math.round(cpuData.currentLoad),
@ -99,13 +99,11 @@ async function collectAndSendCpuMetrics() {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('cpu-metrics', metrics); mainWindow.webContents.send('cpu-metrics', metrics);
} }
} catch (error) { }, 'Failed to collect CPU metrics');
logError('Failed to collect CPU metrics:', error as Error);
}
} }
async function collectAndSendMemoryMetrics() { async function collectAndSendMemoryMetrics() {
try { await tryExecute(async () => {
const memData = await si.mem(); const memData = await si.mem();
const usedBytes = memData.active || memData.used; const usedBytes = memData.active || memData.used;
const totalBytes = memData.total; const totalBytes = memData.total;
@ -118,13 +116,11 @@ async function collectAndSendMemoryMetrics() {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('memory-metrics', metrics); mainWindow.webContents.send('memory-metrics', metrics);
} }
} catch (error) { }, 'Failed to collect memory metrics');
logError('Failed to collect memory metrics:', error as Error);
}
} }
async function collectAndSendGpuMetrics() { async function collectAndSendGpuMetrics() {
try { await tryExecute(async () => {
const gpuData = await getGPUData(); const gpuData = await getGPUData();
const metrics: GpuMetrics = { const metrics: GpuMetrics = {
gpus: gpuData.map((gpuInfo) => ({ gpus: gpuData.map((gpuInfo) => ({
@ -142,7 +138,5 @@ async function collectAndSendGpuMetrics() {
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('gpu-metrics', metrics); mainWindow.webContents.send('gpu-metrics', metrics);
} }
} catch (error) { }, 'Failed to collect GPU metrics');
logError('Failed to collect GPU metrics:', error as Error);
}
} }

View file

@ -3,6 +3,7 @@ import type { ChildProcess } from 'child_process';
import { join } from 'path'; import { join } from 'path';
import { logError } from './logging'; import { logError } from './logging';
import { safeTryExecute, tryExecute } from '@/utils/node/logger';
import { sendKoboldOutput } from './window'; import { sendKoboldOutput } from './window';
import { getInstallDir } from './config'; import { getInstallDir } from './config';
import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants'; import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants';
@ -113,23 +114,19 @@ export async function startFrontend(args: string[]) {
if (openWebUIProcess.stdout) { if (openWebUIProcess.stdout) {
openWebUIProcess.stdout.on('data', (data: Buffer) => { openWebUIProcess.stdout.on('data', (data: Buffer) => {
try { safeTryExecute(() => {
const output = data.toString('utf8'); const output = data.toString('utf8');
sendKoboldOutput(output, true); sendKoboldOutput(output, true);
} catch (error) { }, 'Error processing stdout data');
logError('Error processing stdout data:', error as Error);
}
}); });
} }
if (openWebUIProcess.stderr) { if (openWebUIProcess.stderr) {
openWebUIProcess.stderr.on('data', (data: Buffer) => { openWebUIProcess.stderr.on('data', (data: Buffer) => {
try { safeTryExecute(() => {
const output = data.toString('utf8'); const output = data.toString('utf8');
sendKoboldOutput(output, true); sendKoboldOutput(output, true);
} catch (error) { }, 'Error processing stderr data');
logError('Error processing stderr data:', error as Error);
}
}); });
} }
@ -170,14 +167,12 @@ export async function stopFrontend() {
if (openWebUIProcess) { if (openWebUIProcess) {
sendKoboldOutput('Stopping Open WebUI...'); sendKoboldOutput('Stopping Open WebUI...');
try { await tryExecute(async () => {
await terminateProcess(openWebUIProcess, { await terminateProcess(openWebUIProcess!, {
logError: (message, error) => logError(message, error), logError: (message, error) => logError(message, error),
}); });
sendKoboldOutput('Open WebUI stopped'); sendKoboldOutput('Open WebUI stopped');
} catch (error) { }, 'Error stopping Open WebUI');
logError('Error stopping Open WebUI:', error as Error);
}
openWebUIProcess = null; openWebUIProcess = null;
} }

View file

@ -1,6 +1,6 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { platform } from 'process'; import { platform } from 'process';
import { logError } from './logging'; import { safeExecute } from '@/utils/node/logger';
const LINUX_PERFORMANCE_APPS = [ const LINUX_PERFORMANCE_APPS = [
'resources', 'resources',
@ -45,8 +45,8 @@ async function tryLaunchCommand(command: string, args: string[] = []) {
}); });
} }
export async function openPerformanceManager() { export const openPerformanceManager = async () =>
try { (await safeExecute(async () => {
switch (platform) { switch (platform) {
case 'darwin': { case 'darwin': {
const success = await tryLaunchCommand('open', [ const success = await tryLaunchCommand('open', [
@ -87,9 +87,7 @@ export async function openPerformanceManager() {
}; };
} }
} }
} catch (error) { }, 'Failed to open performance manager')) || {
const errorMessage = `Failed to open performance manager: ${(error as Error).message}`; success: false,
logError(errorMessage, error as Error); error: 'Failed to open performance manager',
return { success: false, error: errorMessage }; };
}
}

View file

@ -5,6 +5,7 @@ import { join } from 'path';
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';
import { logError } from './logging'; import { logError } from './logging';
import { tryExecute } from '@/utils/node/logger';
import { sendKoboldOutput } from './window'; import { sendKoboldOutput } from './window';
import { SILLYTAVERN, SERVER_READY_SIGNALS } from '@/constants'; import { SILLYTAVERN, SERVER_READY_SIGNALS } from '@/constants';
import { terminateProcess } from '@/utils/node/process'; import { terminateProcess } from '@/utils/node/process';
@ -166,7 +167,7 @@ async function setupSillyTavernConfig(
koboldPort: number, koboldPort: number,
isImageMode: boolean isImageMode: boolean
) { ) {
try { const success = await tryExecute(async () => {
const configPath = getSillyTavernSettingsPath(); const configPath = getSillyTavernSettingsPath();
let settings: Record<string, unknown> = {}; let settings: Record<string, unknown> = {};
@ -221,10 +222,11 @@ async function setupSillyTavernConfig(
await writeJsonFile(configPath, settings); await writeJsonFile(configPath, settings);
sendKoboldOutput(`SillyTavern configuration updated successfully!`); sendKoboldOutput(`SillyTavern configuration updated successfully!`);
} catch (error) { }, 'Failed to setup SillyTavern config');
logError('Failed to setup SillyTavern config:', error as Error);
if (!success) {
sendKoboldOutput( sendKoboldOutput(
`Failed to configure SillyTavern: ${error instanceof Error ? error.message : String(error)}` `Failed to configure SillyTavern. Check logs for details.`
); );
} }
} }
@ -396,11 +398,9 @@ export async function stopFrontend() {
export async function cleanup() { export async function cleanup() {
if (sillyTavernProcess) { if (sillyTavernProcess) {
try { await tryExecute(async () => {
await stopFrontend(); await stopFrontend();
} catch (error) { }, 'Error during SillyTavernManager cleanup');
logError('Error during SillyTavernManager cleanup:', error as Error);
}
} }
} }

View file

@ -1,30 +1,13 @@
import {
createSafeExecute,
createTryExecute,
createTryExecuteImmediate,
} from '@/utils/shared/logger-core';
export const logError = (message: string, error: Error) => { export const logError = (message: string, error: Error) => {
if (window.electronAPI?.logs?.logError) {
window.electronAPI.logs.logError(message, error); window.electronAPI.logs.logError(message, error);
}
}; };
export const safeExecute = async <T>( export const safeExecute = createSafeExecute(logError);
operation: () => Promise<T>, export const tryExecute = createTryExecute(logError);
errorMessage: string export const tryExecuteImmediate = createTryExecuteImmediate(logError);
): Promise<T | null> => {
try {
return operation();
} catch (error) {
logError(errorMessage, error as Error);
return null;
}
};
export const tryExecute = async (
operation: () => Promise<void>,
errorMessage: string
) => {
try {
await operation();
return true;
} catch (error) {
logError(errorMessage, error as Error);
return false;
}
};

10
src/utils/node/logger.ts Normal file
View file

@ -0,0 +1,10 @@
import { logError } from '@/main/modules/logging';
import {
createSafeExecute,
createTryExecute,
createSafeTryExecute,
} from '@/utils/shared/logger-core';
export const safeExecute = createSafeExecute(logError);
export const tryExecute = createTryExecute(logError);
export const safeTryExecute = createSafeTryExecute(logError);

View file

@ -0,0 +1,46 @@
type LoggerFunction = (message: string, error: Error) => void;
export const createSafeExecute =
(logger: LoggerFunction) =>
async <T>(operation: () => Promise<T>, errorMessage: string) => {
try {
return operation();
} catch (error) {
logger(errorMessage, error as Error);
return null;
}
};
export const createTryExecute =
(logger: LoggerFunction) =>
async (operation: () => Promise<void>, errorMessage: string) => {
try {
await operation();
return true;
} catch (error) {
logger(errorMessage, error as Error);
return false;
}
};
export const createSafeTryExecute =
(logger: LoggerFunction) =>
<T>(operation: () => T, errorMessage: string) => {
try {
return operation();
} catch (error) {
logger(errorMessage, error as Error);
return null;
}
};
export const createTryExecuteImmediate =
(logger: LoggerFunction) => (operation: () => void, errorMessage: string) => {
try {
operation();
return true;
} catch (error) {
logger(errorMessage, error as Error);
return false;
}
};

View file

@ -1,7 +1,7 @@
import { logError } from '@/utils/logger'; import { tryExecuteImmediate } from '@/utils/logger';
export const handleTerminalOutput = (prevContent: string, newData: string) => { export const handleTerminalOutput = (prevContent: string, newData: string) => {
try { const result = tryExecuteImmediate(() => {
if (newData.includes('\r')) { if (newData.includes('\r')) {
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData); const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
@ -28,10 +28,9 @@ export const handleTerminalOutput = (prevContent: string, newData: string) => {
} }
return prevContent + newData; return prevContent + newData;
} catch (err) { }, 'Terminal Basic Error');
logError('Terminal Basic Error', err as Error);
return prevContent + newData; return result ?? prevContent + newData;
}
}; };
const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi; const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi;