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
- 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

View file

@ -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": "./",

View file

@ -1,4 +1,6 @@
import { Badge, Tooltip } from '@mantine/core';
import { useState } from 'react';
import { safeExecute } from '@/utils/logger';
interface PerformanceBadgeProps {
label: string;
@ -11,20 +13,19 @@ 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) {
const result = await safeExecute(
() => window.electronAPI.app.openPerformanceManager(),
'Failed to open performance manager'
);
if (result && !result.success) {
window.electronAPI.logs.logError(
`Failed to open performance manager: ${result.error}`
);
}
} catch (error) {
window.electronAPI.logs.logError(
'Error opening performance manager',
error as Error
);
}
};
return (
@ -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}

View file

@ -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 {
}, '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(() => {

View file

@ -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 = (

View file

@ -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 {
}, 'Error launching');
setIsLaunching(false);
}
},
[model, sdmodel, isLaunching, onLaunch]
);

View file

@ -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;
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);
} catch (err) {
logError('Failed to load dismissed updates:', err as Error);
setDismissedUpdatesLoaded(true);
}
};
loadDismissedUpdates();
}, []);
const saveDismissedUpdates = useCallback(async (updates: Set<string>) => {
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 {
}, 'Failed to check for updates');
setIsChecking(false);
}
}, [dismissedUpdates, dismissedUpdatesLoaded, releases]);
const dismissUpdate = useCallback(async () => {

View file

@ -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();

View file

@ -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 {
}, 'Error in enhanced launch')) || {
success: false,
error: (error as Error).message,
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 {
ipcMain.handle(
'app:showLogsFolder',
async () =>
(await safeExecute(async () => {
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}`
);
}, '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 {
ipcMain.handle(
'app:openExternal',
async (_, url: string) =>
(await safeExecute(async () => {
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}`
);
}, 'Failed to open external URL')) || {
success: false,
error: 'Failed to open external URL',
}
});
);
const mainWindow = getMainWindow();
if (mainWindow) {

View file

@ -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) {

View file

@ -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<string, BackendSupport>();
@ -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<BackendOption[]> {
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' }];
}

View file

@ -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<ComfyUIVersionInfo | null> {
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<ComfyUIVersionInfo | null> {
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(

View file

@ -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<AppConfig>(configPath);
return loadedConfig || {};
} catch (error) {
logError('Error loading config:', error as Error);
return {};
}
const config = await safeExecute(
() => readJsonFile<AppConfig>(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() {

View file

@ -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);
cpuCapabilitiesCache = capabilities;
return capabilities;
}, 'CPU detection failed');
const fallbackCapabilities = {
avx: false,
avx2: false,
devices: [],
};
cpuCapabilitiesCache = fallbackCapabilities;
return fallbackCapabilities;
}
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);
basicGPUInfoCache = basicInfo;
return basicInfo;
}, 'GPU detection failed');
const fallbackGPUInfo = {
hasAMD: false,
hasNVIDIA: false,
gpuInfo: ['GPU detection failed'],
};
basicGPUInfoCache = fallbackGPUInfo;
return fallbackGPUInfo;
}
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;
}

View file

@ -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) {

View file

@ -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');
}

View file

@ -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;
}

View file

@ -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',
};

View file

@ -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<string, unknown> = {};
@ -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');
}
}

View file

@ -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);
}
};
export const safeExecute = async <T>(
operation: () => Promise<T>,
errorMessage: string
): 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;
}
};
export const safeExecute = createSafeExecute(logError);
export const tryExecute = createTryExecute(logError);
export const tryExecuteImmediate = createTryExecuteImmediate(logError);

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) => {
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;