From 38c504be8b4811d6f0dad415da9b4838b0e10399 Mon Sep 17 00:00:00 2001 From: Egor Date: Thu, 18 Sep 2025 13:03:40 -0700 Subject: [PATCH] refactor to use a util to try/catch->logError, hover states on badges --- .github/copilot-instructions.md | 1 + package.json | 2 +- src/components/PerformanceBadge.tsx | 27 ++++++----- src/hooks/useAppUpdateChecker.ts | 19 ++++---- src/hooks/useKoboldVersions.ts | 8 ++-- src/hooks/useLaunchLogic.ts | 12 +++-- src/hooks/useUpdateChecker.ts | 40 ++++++++--------- src/main/gui.ts | 8 ++-- src/main/ipc.ts | 63 +++++++++++++------------- src/main/modules/autoUpdater.ts | 29 +++++------- src/main/modules/binary.ts | 33 +++++--------- src/main/modules/comfyui.ts | 11 ++--- src/main/modules/config.ts | 24 +++++----- src/main/modules/hardware.ts | 69 ++++++++++++++--------------- src/main/modules/koboldcpp.ts | 17 +++---- src/main/modules/monitoring.ts | 20 +++------ src/main/modules/openwebui.ts | 21 ++++----- src/main/modules/performance.ts | 16 +++---- src/main/modules/sillytavern.ts | 16 +++---- src/utils/logger.ts | 37 +++++----------- src/utils/node/logger.ts | 10 +++++ src/utils/shared/logger-core.ts | 46 +++++++++++++++++++ src/utils/terminal.ts | 11 +++-- 23 files changed, 264 insertions(+), 276 deletions(-) create mode 100644 src/utils/node/logger.ts create mode 100644 src/utils/shared/logger-core.ts diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a63f79f..c6880bc 100755 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -10,6 +10,7 @@ - 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 - 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 diff --git a/package.json b/package.json index 7d54342..9e6bb0b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gerbil", "productName": "Gerbil", - "version": "1.4.0-beta.2", + "version": "1.4.0-beta.3", "description": "Run Large Language Models locally", "main": "out/main/index.js", "homepage": "./", diff --git a/src/components/PerformanceBadge.tsx b/src/components/PerformanceBadge.tsx index 9d4f4c7..0ba2169 100644 --- a/src/components/PerformanceBadge.tsx +++ b/src/components/PerformanceBadge.tsx @@ -1,4 +1,6 @@ import { Badge, Tooltip } from '@mantine/core'; +import { useState } from 'react'; +import { safeExecute } from '@/utils/logger'; interface PerformanceBadgeProps { label: string; @@ -11,18 +13,17 @@ export const PerformanceBadge = ({ value, tooltipLabel, }: PerformanceBadgeProps) => { + const [isHovered, setIsHovered] = useState(false); + const handlePerformanceClick = async () => { - try { - const result = await window.electronAPI.app.openPerformanceManager(); - if (!result.success) { - window.electronAPI.logs.logError( - `Failed to open performance manager: ${result.error}` - ); - } - } catch (error) { + const result = await safeExecute( + () => window.electronAPI.app.openPerformanceManager(), + 'Failed to open performance manager' + ); + + if (result && !result.success) { window.electronAPI.logs.logError( - 'Error opening performance manager', - error as Error + `Failed to open performance manager: ${result.error}` ); } }; @@ -36,7 +37,13 @@ export const PerformanceBadge = ({ minWidth: '5rem', textAlign: 'center', cursor: 'pointer', + transition: 'background-color 0.2s ease', + backgroundColor: isHovered + ? 'var(--mantine-color-blue-1)' + : undefined, }} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} onClick={handlePerformanceClick} > {label}: {value} diff --git a/src/hooks/useAppUpdateChecker.ts b/src/hooks/useAppUpdateChecker.ts index 2d0be4c..7ca88eb 100644 --- a/src/hooks/useAppUpdateChecker.ts +++ b/src/hooks/useAppUpdateChecker.ts @@ -1,5 +1,5 @@ import { useState, useCallback, useEffect } from 'react'; -import { logError, safeExecute } from '@/utils/logger'; +import { safeExecute, tryExecute } from '@/utils/logger'; import { compareVersions } from '@/utils/version'; import { GITHUB_API } from '@/constants'; @@ -40,16 +40,15 @@ export const useAppUpdateChecker = () => { if (!canAutoUpdate) return; setIsDownloading(true); - try { + + await tryExecute(async () => { const success = await window.electronAPI.updater.downloadUpdate(); if (success) { setIsUpdateDownloaded(true); } - } catch (err) { - logError('Failed to download update:', err as Error); - } finally { - setIsDownloading(false); - } + }, 'Failed to download update'); + + setIsDownloading(false); }, [canAutoUpdate]); const installUpdate = useCallback(() => { @@ -59,7 +58,7 @@ export const useAppUpdateChecker = () => { }, [isUpdateDownloaded]); const checkForAppUpdates = useCallback(async () => { - try { + await tryExecute(async () => { const currentVersion = await window.electronAPI.app.getVersion(); const response = await fetch( @@ -87,9 +86,7 @@ export const useAppUpdateChecker = () => { }; setUpdateInfo(updateInfo); - } catch (err) { - logError('Failed to check for app updates:', err as Error); - } + }, 'Failed to check for app updates'); }, []); useEffect(() => { diff --git a/src/hooks/useKoboldVersions.ts b/src/hooks/useKoboldVersions.ts index 1fba4ed..7154758 100644 --- a/src/hooks/useKoboldVersions.ts +++ b/src/hooks/useKoboldVersions.ts @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { logError } from '@/utils/logger'; +import { logError, tryExecuteImmediate } from '@/utils/logger'; import { getROCmDownload } from '@/utils/rocm'; import { GITHUB_API } from '@/constants'; import { filterAssetsByPlatform } from '@/utils/platform'; @@ -41,15 +41,13 @@ const loadFromCache = (): CachedReleaseData | null => { }; const saveToCache = (releases: DownloadItem[]) => { - try { + tryExecuteImmediate(() => { const data: CachedReleaseData = { releases, timestamp: Date.now(), }; localStorage.setItem(CACHE_KEY, JSON.stringify(data)); - } catch (err) { - logError('Failed to save releases to cache', err as Error); - } + }, 'Failed to save releases to cache'); }; const transformReleaseToDownloadItems = ( diff --git a/src/hooks/useLaunchLogic.ts b/src/hooks/useLaunchLogic.ts index 9dc73c9..b776334 100644 --- a/src/hooks/useLaunchLogic.ts +++ b/src/hooks/useLaunchLogic.ts @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react'; -import { logError } from '@/utils/logger'; +import { tryExecute } from '@/utils/logger'; import type { SdConvDirectMode } from '@/types'; interface UseLaunchLogicProps { @@ -258,7 +258,7 @@ export const useLaunchLogic = ({ onLaunch(); - try { + await tryExecute(async () => { const args: string[] = [ ...buildModelArgs(model, sdmodel, launchArgs), ...buildConfigArgs(hasImageModel, launchArgs), @@ -283,11 +283,9 @@ export const useLaunchLogic = ({ new Error(errorMessage) ); } - } catch (err) { - logError('Error launching:', err as Error); - } finally { - setIsLaunching(false); - } + }, 'Error launching'); + + setIsLaunching(false); }, [model, sdmodel, isLaunching, onLaunch] ); diff --git a/src/hooks/useUpdateChecker.ts b/src/hooks/useUpdateChecker.ts index 5e51cc5..765d4c8 100644 --- a/src/hooks/useUpdateChecker.ts +++ b/src/hooks/useUpdateChecker.ts @@ -1,5 +1,5 @@ import { useState, useCallback, useEffect } from 'react'; -import { logError } from '@/utils/logger'; +import { tryExecute, safeExecute } from '@/utils/logger'; import { getDisplayNameFromPath, compareVersions, @@ -27,32 +27,30 @@ export const useUpdateChecker = () => { useEffect(() => { const loadDismissedUpdates = async () => { - try { - const dismissed = (await window.electronAPI.config.get( - 'dismissedUpdates' - )) as string[] | undefined; - if (dismissed) { - setDismissedUpdates(new Set(dismissed)); - } - setDismissedUpdatesLoaded(true); - } catch (err) { - logError('Failed to load dismissed updates:', err as Error); - setDismissedUpdatesLoaded(true); + const dismissed = await safeExecute( + async () => + (await window.electronAPI.config.get('dismissedUpdates')) as + | string[] + | undefined, + 'Failed to load dismissed updates' + ); + + if (dismissed) { + setDismissedUpdates(new Set(dismissed)); } + setDismissedUpdatesLoaded(true); }; loadDismissedUpdates(); }, []); const saveDismissedUpdates = useCallback(async (updates: Set) => { - try { + await tryExecute(async () => { await window.electronAPI.config.set( 'dismissedUpdates', Array.from(updates) ); - } catch (err) { - logError('Failed to save dismissed updates:', err as Error); - } + }, 'Failed to save dismissed updates'); }, []); const checkForUpdates = useCallback(async () => { @@ -62,7 +60,7 @@ export const useUpdateChecker = () => { setIsChecking(true); - try { + await tryExecute(async () => { const [currentVersion, rocmDownload] = await Promise.all([ window.electronAPI.kobold.getCurrentVersion(), getROCmDownload(), @@ -105,11 +103,9 @@ export const useUpdateChecker = () => { } } } - } catch (err) { - logError('Failed to check for updates:', err as Error); - } finally { - setIsChecking(false); - } + }, 'Failed to check for updates'); + + setIsChecking(false); }, [dismissedUpdates, dismissedUpdatesLoaded, releases]); const dismissUpdate = useCallback(async () => { diff --git a/src/main/gui.ts b/src/main/gui.ts index aba42f4..cded5ce 100644 --- a/src/main/gui.ts +++ b/src/main/gui.ts @@ -8,7 +8,7 @@ import { initialize as initializeConfig, getInstallDir, } from '@/main/modules/config'; -import { logError } from '@/main/modules/logging'; +import { safeExecute } from '@/utils/node/logger'; import { cleanup } from '@/main/modules/koboldcpp'; import { getSillyTavernManager } from '@/main/modules/sillytavern'; import { cleanup as cleanupOpenWebUI } from '@/main/modules/openwebui'; @@ -38,7 +38,7 @@ export async function initializeApp() { app.on('before-quit', async (event) => { event.preventDefault(); - try { + await safeExecute(async () => { const cleanupPromises = [ cleanup(), getSillyTavernManager().cleanup(), @@ -53,9 +53,7 @@ export async function initializeApp() { }); await Promise.race([Promise.all(cleanupPromises), timeoutPromise]); - } catch (error) { - logError('Error during cleanup:', error as Error); - } + }, 'Error during cleanup'); cleanupWindow(); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 24c4718..3ddfb86 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -25,6 +25,7 @@ import { setColorScheme, } from '@/main/modules/config'; import { logError } from '@/main/modules/logging'; +import { safeExecute } from '@/utils/node/logger'; import { getSillyTavernManager } from '@/main/modules/sillytavern'; import { startFrontend as startOpenWebUIFrontend, @@ -64,8 +65,8 @@ import { import type { FrontendPreference } from '@/types'; import { getAppVersion } from '@/utils/node/fs'; -async function launchKoboldCppWithCustomFrontends(args: string[] = []) { - try { +const launchKoboldCppWithCustomFrontends = async (args: string[] = []) => + (await safeExecute(async () => { const frontendPreference = (await getConfig( 'frontendPreference' )) as FrontendPreference; @@ -83,14 +84,10 @@ async function launchKoboldCppWithCustomFrontends(args: string[] = []) { } return result; - } catch (error) { - logError('Error in enhanced launch:', error as Error); - return { - success: false, - error: (error as Error).message, - }; - } -} + }, 'Error in enhanced launch')) || { + success: false, + error: 'Launch failed', + }; export function setupIPCHandlers() { ipcMain.handle('kobold:downloadRelease', async (_, asset) => ({ @@ -188,18 +185,18 @@ export function setupIPCHandlers() { }; }); - ipcMain.handle('app:showLogsFolder', async () => { - try { - const logsDir = join(app.getPath('userData'), 'logs'); - await shell.openPath(logsDir); - return { success: true }; - } catch (error) { - logError('Failed to open logs folder:', error as Error); - throw new Error( - `Failed to open logs folder: ${(error as Error).message}` - ); - } - }); + ipcMain.handle( + 'app:showLogsFolder', + async () => + (await safeExecute(async () => { + const logsDir = join(app.getPath('userData'), 'logs'); + await shell.openPath(logsDir); + return { success: true }; + }, 'Failed to open logs folder')) || { + success: false, + error: 'Failed to open logs folder', + } + ); ipcMain.handle('app:minimizeWindow', () => getMainWindow()?.minimize()); @@ -239,17 +236,17 @@ export function setupIPCHandlers() { } ); - ipcMain.handle('app:openExternal', async (_, url: string) => { - try { - await shell.openExternal(url); - return { success: true }; - } catch (error) { - logError('Failed to open external URL:', error as Error); - throw new Error( - `Failed to open external URL: ${(error as Error).message}` - ); - } - }); + ipcMain.handle( + 'app:openExternal', + async (_, url: string) => + (await safeExecute(async () => { + await shell.openExternal(url); + return { success: true }; + }, 'Failed to open external URL')) || { + success: false, + error: 'Failed to open external URL', + } + ); const mainWindow = getMainWindow(); if (mainWindow) { diff --git a/src/main/modules/autoUpdater.ts b/src/main/modules/autoUpdater.ts index b2f6d75..76171b0 100644 --- a/src/main/modules/autoUpdater.ts +++ b/src/main/modules/autoUpdater.ts @@ -1,6 +1,7 @@ import { autoUpdater } from 'electron-updater'; import { app } from 'electron'; import { logError } from '@/main/modules/logging'; +import { safeExecute } from '@/utils/node/logger'; export interface UpdateInfo { version: string; @@ -33,25 +34,17 @@ function setupAutoUpdater() { setupAutoUpdater(); -export async function checkForUpdates() { - try { - const result = await autoUpdater.checkForUpdates(); - return result !== null; - } catch (error) { - logError('Failed to check for updates:', error as Error); - return false; - } -} +export const checkForUpdates = async () => + (await safeExecute( + () => autoUpdater.checkForUpdates(), + 'Failed to check for updates' + )) !== null; -export async function downloadUpdate() { - try { - await autoUpdater.downloadUpdate(); - return true; - } catch (error) { - logError('Failed to download update:', error as Error); - return false; - } -} +export const downloadUpdate = async () => + (await safeExecute( + () => autoUpdater.downloadUpdate(), + 'Failed to download update' + )) !== null; export function quitAndInstall() { if (updateDownloaded) { diff --git a/src/main/modules/binary.ts b/src/main/modules/binary.ts index c64c37e..0da3db1 100644 --- a/src/main/modules/binary.ts +++ b/src/main/modules/binary.ts @@ -1,8 +1,8 @@ import { join, dirname } from 'path'; import { pathExists } from '@/utils/node/fs'; -import { logError } from './logging'; import { getCurrentBinaryInfo } from './koboldcpp'; import { detectGPUCapabilities, detectCPU } from './hardware'; +import { tryExecute, safeExecute } from '@/utils/node/logger'; import type { BackendOption, BackendSupport } from '@/types'; const backendSupportCache = new Map(); @@ -22,7 +22,7 @@ async function detectBackendSupportFromPath(koboldBinaryPath: string) { cuda: false, }; - try { + await tryExecute(async () => { const binaryDir = dirname(koboldBinaryPath); const internalDir = join(binaryDir, '_internal'); @@ -60,16 +60,14 @@ async function detectBackendSupportFromPath(koboldBinaryPath: string) { support.noavx2 = noavx2; support.failsafe = failsafe; support.cuda = cuda; - } catch (error) { - logError('Error detecting backend support:', error as Error); - } + }, 'Error detecting backend support'); backendSupportCache.set(koboldBinaryPath, support); return support; } -export async function detectBackendSupport() { - try { +export const detectBackendSupport = async () => + (await safeExecute(async () => { const currentBinaryInfo = await getCurrentBinaryInfo(); if (!currentBinaryInfo?.path) { @@ -77,17 +75,11 @@ export async function detectBackendSupport() { } return detectBackendSupportFromPath(currentBinaryInfo.path); - } catch (error) { - logError('Error detecting current binary backend support:', error as Error); - return null; - } -} + }, 'Error detecting current binary backend support')) || null; -// eslint-disable-next-line sonarjs/cognitive-complexity -export async function getAvailableBackends( - includeDisabled = false -): Promise { - try { +export async function getAvailableBackends(includeDisabled = false) { + // eslint-disable-next-line sonarjs/cognitive-complexity + const result = await safeExecute(async () => { const [currentBinaryInfo, hardwareCapabilities, cpuCapabilities] = await Promise.all([ getCurrentBinaryInfo(), @@ -177,8 +169,7 @@ export async function getAvailableBackends( availableBackendsCache.set(cacheKey, backends); return backends; - } catch (error) { - logError('Failed to get available backends:', error as Error); - return [{ value: 'cpu', label: 'CPU' }]; - } + }, 'Failed to get available backends'); + + return result || [{ value: 'cpu', label: 'CPU' }]; } diff --git a/src/main/modules/comfyui.ts b/src/main/modules/comfyui.ts index 9ea843d..0ceeae0 100644 --- a/src/main/modules/comfyui.ts +++ b/src/main/modules/comfyui.ts @@ -5,6 +5,7 @@ import type { ChildProcess } from 'child_process'; import yauzl from 'yauzl'; import { logError } from './logging'; +import { safeExecute } from '@/utils/node/logger'; import { sendKoboldOutput } from './window'; import { getInstallDir } from './config'; import { COMFYUI, SERVER_READY_SIGNALS, GITHUB_API } from '@/constants'; @@ -20,7 +21,7 @@ interface ComfyUIVersionInfo { } async function getLatestComfyUIVersion(): Promise { - try { + return safeExecute(async () => { const response = await fetch(GITHUB_API.COMFYUI_LATEST_COMMIT_URL); if (!response.ok) { throw new Error( @@ -33,13 +34,7 @@ async function getLatestComfyUIVersion(): Promise { sha: data.sha, date: data.commit.committer.date, }; - } catch (error) { - logError( - 'Failed to fetch latest ComfyUI version', - error instanceof Error ? error : undefined - ); - return null; - } + }, 'Failed to fetch latest ComfyUI version'); } async function getCurrentComfyUIVersion( diff --git a/src/main/modules/config.ts b/src/main/modules/config.ts index f2c1d79..4b1a06d 100644 --- a/src/main/modules/config.ts +++ b/src/main/modules/config.ts @@ -1,5 +1,5 @@ -import { logError } from '@/main/modules/logging'; import { readJsonFile, writeJsonFile } from '@/utils/node/fs'; +import { safeExecute } from '@/utils/node/logger'; import { getConfigDir } from '@/utils/node/path'; import { homedir } from 'os'; import { join } from 'path'; @@ -23,21 +23,19 @@ let config: AppConfig = {}; let configPath: string; async function loadConfig() { - try { - const loadedConfig = await readJsonFile(configPath); - return loadedConfig || {}; - } catch (error) { - logError('Error loading config:', error as Error); - return {}; - } + const config = await safeExecute( + () => readJsonFile(configPath), + 'Error loading config' + ); + return config || {}; } async function saveConfig() { - try { - await writeJsonFile(configPath, config); - } catch (error) { - logError('Error saving config:', error as Error); - } + const success = await safeExecute( + () => writeJsonFile(configPath, config), + 'Error saving config' + ); + return success !== null; } export async function initialize() { diff --git a/src/main/modules/hardware.ts b/src/main/modules/hardware.ts index a3f2d68..4462841 100644 --- a/src/main/modules/hardware.ts +++ b/src/main/modules/hardware.ts @@ -1,6 +1,6 @@ /* eslint-disable no-comments/disallowComments */ import si from 'systeminformation'; -import { logError } from '@/main/modules/logging'; +import { safeExecute } from '@/utils/node/logger'; import { terminateProcess } from '@/utils/node/process'; import { getGPUData } from '@/utils/node/gpu'; import type { @@ -23,7 +23,7 @@ export async function detectCPU() { return cpuCapabilitiesCache; } - try { + const result = await safeExecute(async () => { const [cpu, flags] = await Promise.all([si.cpu(), si.cpuFlags()]); const devices: string[] = []; @@ -34,23 +34,24 @@ export async function detectCPU() { const avx = flags.includes('avx') || flags.includes('AVX'); const avx2 = flags.includes('avx2') || flags.includes('AVX2'); - cpuCapabilitiesCache = { + const capabilities = { avx, avx2, devices, }; - return cpuCapabilitiesCache; - } catch (error) { - logError('CPU detection failed:', error as Error); - const fallbackCapabilities = { - avx: false, - avx2: false, - devices: [], - }; - cpuCapabilitiesCache = fallbackCapabilities; - return fallbackCapabilities; - } + cpuCapabilitiesCache = capabilities; + return capabilities; + }, 'CPU detection failed'); + + const fallbackCapabilities = { + avx: false, + avx2: false, + devices: [], + }; + + cpuCapabilitiesCache = result || fallbackCapabilities; + return cpuCapabilitiesCache; } export async function detectGPU() { @@ -58,7 +59,7 @@ export async function detectGPU() { return basicGPUInfoCache; } - try { + const result = await safeExecute(async () => { const gpuData = await getGPUData(); let hasAMD = false; @@ -90,23 +91,24 @@ export async function detectGPU() { } } - basicGPUInfoCache = { + const basicInfo = { hasAMD, hasNVIDIA, gpuInfo: gpuInfo.length > 0 ? gpuInfo : ['No GPU information available'], }; - return basicGPUInfoCache; - } catch (error) { - logError('GPU detection failed:', error as Error); - const fallbackGPUInfo = { - hasAMD: false, - hasNVIDIA: false, - gpuInfo: ['GPU detection failed'], - }; - basicGPUInfoCache = fallbackGPUInfo; - return fallbackGPUInfo; - } + basicGPUInfoCache = basicInfo; + return basicInfo; + }, 'GPU detection failed'); + + const fallbackGPUInfo = { + hasAMD: false, + hasNVIDIA: false, + gpuInfo: ['GPU detection failed'], + }; + + basicGPUInfoCache = result || fallbackGPUInfo; + return basicGPUInfoCache; } export async function detectGPUCapabilities() { @@ -428,10 +430,9 @@ export async function detectGPUMemory() { return gpuMemoryInfoCache; } - const memoryInfo: GPUMemoryInfo[] = []; - - try { + const result = await safeExecute(async () => { const gpuData = await getGPUData(); + const memoryInfo: GPUMemoryInfo[] = []; for (const gpu of gpuData) { if (gpu.deviceName) { @@ -448,11 +449,9 @@ export async function detectGPUMemory() { } } - gpuMemoryInfoCache = memoryInfo; - } catch (error) { - logError('GPU memory detection failed:', error as Error); - gpuMemoryInfoCache = []; - } + return memoryInfo; + }, 'GPU memory detection failed'); + gpuMemoryInfoCache = result || []; return gpuMemoryInfoCache; } diff --git a/src/main/modules/koboldcpp.ts b/src/main/modules/koboldcpp.ts index 898a349..feb5805 100644 --- a/src/main/modules/koboldcpp.ts +++ b/src/main/modules/koboldcpp.ts @@ -25,6 +25,7 @@ import { setCurrentKoboldBinary, } from './config'; import { logError } from './logging'; +import { tryExecute } from '@/utils/node/logger'; import { sendKoboldOutput, getMainWindow, sendToRenderer } from './window'; import { PRODUCT_NAME, SERVER_READY_SIGNALS } from '@/constants'; import { @@ -207,9 +208,7 @@ export async function downloadRelease(asset: GitHubAsset) { return launcherPath; } catch (error) { logError('Failed to download or unpack binary:', error as Error); - throw new Error( - `Failed to download or unpack binary: ${(error as Error).message}` - ); + throw new Error('Failed to download or unpack binary'); } } @@ -232,7 +231,7 @@ async function unpackKoboldCpp(packedPath: string, unpackDir: string) { } async function patchKliteEmbd(unpackedDir: string) { - try { + await tryExecute(async () => { const possiblePaths = [ join(unpackedDir, '_internal', 'klite.embd'), join(unpackedDir, 'klite.embd'), @@ -276,13 +275,11 @@ async function patchKliteEmbd(unpackedDir: string) { await writeFile(kliteEmbdPath, patchedContent, 'utf8'); } - } catch (error) { - logError('Failed to patch klite.embd:', error as Error); - } + }, 'Failed to patch klite.embd'); } async function patchKcppSduiEmbd(unpackedDir: string) { - try { + await tryExecute(async () => { const possiblePaths = [ join(unpackedDir, '_internal', 'kcpp_sdui.embd'), join(unpackedDir, 'kcpp_sdui.embd'), @@ -296,9 +293,7 @@ async function patchKcppSduiEmbd(unpackedDir: string) { break; } } - } catch (error) { - logError('Failed to patch kcpp_sdui.embd:', error as Error); - } + }, 'Failed to patch kcpp_sdui.embd'); } async function getLauncherPath(unpackedDir: string) { diff --git a/src/main/modules/monitoring.ts b/src/main/modules/monitoring.ts index 1281f4b..13292fb 100644 --- a/src/main/modules/monitoring.ts +++ b/src/main/modules/monitoring.ts @@ -1,8 +1,8 @@ import si from 'systeminformation'; import { BrowserWindow } from 'electron'; import { platform } from 'process'; -import { logError } from '@/main/modules/logging'; import { getGPUData } from '@/utils/node/gpu'; +import { tryExecute } from '@/utils/node/logger'; export interface CpuMetrics { usage: number; @@ -90,7 +90,7 @@ export function stopMonitoring() { } async function collectAndSendCpuMetrics() { - try { + await tryExecute(async () => { const cpuData = await si.currentLoad(); const metrics: CpuMetrics = { usage: Math.round(cpuData.currentLoad), @@ -99,13 +99,11 @@ async function collectAndSendCpuMetrics() { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('cpu-metrics', metrics); } - } catch (error) { - logError('Failed to collect CPU metrics:', error as Error); - } + }, 'Failed to collect CPU metrics'); } async function collectAndSendMemoryMetrics() { - try { + await tryExecute(async () => { const memData = await si.mem(); const usedBytes = memData.active || memData.used; const totalBytes = memData.total; @@ -118,13 +116,11 @@ async function collectAndSendMemoryMetrics() { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('memory-metrics', metrics); } - } catch (error) { - logError('Failed to collect memory metrics:', error as Error); - } + }, 'Failed to collect memory metrics'); } async function collectAndSendGpuMetrics() { - try { + await tryExecute(async () => { const gpuData = await getGPUData(); const metrics: GpuMetrics = { gpus: gpuData.map((gpuInfo) => ({ @@ -142,7 +138,5 @@ async function collectAndSendGpuMetrics() { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('gpu-metrics', metrics); } - } catch (error) { - logError('Failed to collect GPU metrics:', error as Error); - } + }, 'Failed to collect GPU metrics'); } diff --git a/src/main/modules/openwebui.ts b/src/main/modules/openwebui.ts index 3ad9a46..9e3754f 100644 --- a/src/main/modules/openwebui.ts +++ b/src/main/modules/openwebui.ts @@ -3,6 +3,7 @@ import type { ChildProcess } from 'child_process'; import { join } from 'path'; import { logError } from './logging'; +import { safeTryExecute, tryExecute } from '@/utils/node/logger'; import { sendKoboldOutput } from './window'; import { getInstallDir } from './config'; import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants'; @@ -113,23 +114,19 @@ export async function startFrontend(args: string[]) { if (openWebUIProcess.stdout) { openWebUIProcess.stdout.on('data', (data: Buffer) => { - try { + safeTryExecute(() => { const output = data.toString('utf8'); sendKoboldOutput(output, true); - } catch (error) { - logError('Error processing stdout data:', error as Error); - } + }, 'Error processing stdout data'); }); } if (openWebUIProcess.stderr) { openWebUIProcess.stderr.on('data', (data: Buffer) => { - try { + safeTryExecute(() => { const output = data.toString('utf8'); sendKoboldOutput(output, true); - } catch (error) { - logError('Error processing stderr data:', error as Error); - } + }, 'Error processing stderr data'); }); } @@ -170,14 +167,12 @@ export async function stopFrontend() { if (openWebUIProcess) { sendKoboldOutput('Stopping Open WebUI...'); - try { - await terminateProcess(openWebUIProcess, { + await tryExecute(async () => { + await terminateProcess(openWebUIProcess!, { logError: (message, error) => logError(message, error), }); sendKoboldOutput('Open WebUI stopped'); - } catch (error) { - logError('Error stopping Open WebUI:', error as Error); - } + }, 'Error stopping Open WebUI'); openWebUIProcess = null; } diff --git a/src/main/modules/performance.ts b/src/main/modules/performance.ts index 680728a..6fba554 100644 --- a/src/main/modules/performance.ts +++ b/src/main/modules/performance.ts @@ -1,6 +1,6 @@ import { spawn } from 'child_process'; import { platform } from 'process'; -import { logError } from './logging'; +import { safeExecute } from '@/utils/node/logger'; const LINUX_PERFORMANCE_APPS = [ 'resources', @@ -45,8 +45,8 @@ async function tryLaunchCommand(command: string, args: string[] = []) { }); } -export async function openPerformanceManager() { - try { +export const openPerformanceManager = async () => + (await safeExecute(async () => { switch (platform) { case 'darwin': { const success = await tryLaunchCommand('open', [ @@ -87,9 +87,7 @@ export async function openPerformanceManager() { }; } } - } catch (error) { - const errorMessage = `Failed to open performance manager: ${(error as Error).message}`; - logError(errorMessage, error as Error); - return { success: false, error: errorMessage }; - } -} + }, 'Failed to open performance manager')) || { + success: false, + error: 'Failed to open performance manager', + }; diff --git a/src/main/modules/sillytavern.ts b/src/main/modules/sillytavern.ts index 6a3e17e..7c0fa56 100644 --- a/src/main/modules/sillytavern.ts +++ b/src/main/modules/sillytavern.ts @@ -5,6 +5,7 @@ import { join } from 'path'; import type { ChildProcess } from 'child_process'; import { logError } from './logging'; +import { tryExecute } from '@/utils/node/logger'; import { sendKoboldOutput } from './window'; import { SILLYTAVERN, SERVER_READY_SIGNALS } from '@/constants'; import { terminateProcess } from '@/utils/node/process'; @@ -166,7 +167,7 @@ async function setupSillyTavernConfig( koboldPort: number, isImageMode: boolean ) { - try { + const success = await tryExecute(async () => { const configPath = getSillyTavernSettingsPath(); let settings: Record = {}; @@ -221,10 +222,11 @@ async function setupSillyTavernConfig( await writeJsonFile(configPath, settings); sendKoboldOutput(`SillyTavern configuration updated successfully!`); - } catch (error) { - logError('Failed to setup SillyTavern config:', error as Error); + }, 'Failed to setup SillyTavern config'); + + if (!success) { 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() { if (sillyTavernProcess) { - try { + await tryExecute(async () => { await stopFrontend(); - } catch (error) { - logError('Error during SillyTavernManager cleanup:', error as Error); - } + }, 'Error during SillyTavernManager cleanup'); } } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 60312eb..61aafcf 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,30 +1,13 @@ +import { + createSafeExecute, + createTryExecute, + createTryExecuteImmediate, +} from '@/utils/shared/logger-core'; + 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 ( - operation: () => Promise, - errorMessage: string -): Promise => { - try { - return operation(); - } catch (error) { - logError(errorMessage, error as Error); - return null; - } -}; - -export const tryExecute = async ( - operation: () => Promise, - errorMessage: string -) => { - try { - await operation(); - return true; - } catch (error) { - logError(errorMessage, error as Error); - return false; - } -}; +export const safeExecute = createSafeExecute(logError); +export const tryExecute = createTryExecute(logError); +export const tryExecuteImmediate = createTryExecuteImmediate(logError); diff --git a/src/utils/node/logger.ts b/src/utils/node/logger.ts new file mode 100644 index 0000000..2d813aa --- /dev/null +++ b/src/utils/node/logger.ts @@ -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); diff --git a/src/utils/shared/logger-core.ts b/src/utils/shared/logger-core.ts new file mode 100644 index 0000000..a6ff39f --- /dev/null +++ b/src/utils/shared/logger-core.ts @@ -0,0 +1,46 @@ +type LoggerFunction = (message: string, error: Error) => void; + +export const createSafeExecute = + (logger: LoggerFunction) => + async (operation: () => Promise, errorMessage: string) => { + try { + return operation(); + } catch (error) { + logger(errorMessage, error as Error); + return null; + } + }; + +export const createTryExecute = + (logger: LoggerFunction) => + async (operation: () => Promise, errorMessage: string) => { + try { + await operation(); + return true; + } catch (error) { + logger(errorMessage, error as Error); + return false; + } + }; + +export const createSafeTryExecute = + (logger: LoggerFunction) => + (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; + } + }; diff --git a/src/utils/terminal.ts b/src/utils/terminal.ts index 3e41bf2..d36e591 100644 --- a/src/utils/terminal.ts +++ b/src/utils/terminal.ts @@ -1,7 +1,7 @@ -import { logError } from '@/utils/logger'; +import { tryExecuteImmediate } from '@/utils/logger'; export const handleTerminalOutput = (prevContent: string, newData: string) => { - try { + const result = tryExecuteImmediate(() => { if (newData.includes('\r')) { const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData); @@ -28,10 +28,9 @@ export const handleTerminalOutput = (prevContent: string, newData: string) => { } return prevContent + newData; - } catch (err) { - logError('Terminal Basic Error', err as Error); - return prevContent + newData; - } + }, 'Terminal Basic Error'); + + return result ?? prevContent + newData; }; const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi;