mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-04 12:13:28 -07:00
more excessive try/catch clean up, never wait for config set and logError, better titlebar color in dark mode
This commit is contained in:
parent
75f44eb7e0
commit
ccb230a31d
20 changed files with 170 additions and 249 deletions
|
|
@ -8,7 +8,7 @@ A desktop app to easily run Large Language Models locally.
|
|||
|
||||
## Core Features
|
||||
|
||||
- **Run LLMs locally** powered by a highly modified fork of [llama.cpp](https://github.com/ggml-org/llama.cpp)
|
||||
- **Run LLMs locally** powered by [KoboldCpp](https://github.com/LostRuins/koboldcpp) which itself is a highly modified fork of [llama.cpp](https://github.com/ggml-org/llama.cpp)
|
||||
- **Cross-platform desktop app** - Native support for Windows, macOS, and Linux (including Wayland)
|
||||
- **Automatic updates** - Download and keep your KoboldCpp binary up-to-date effortlessly
|
||||
- **Smart process management** - Prevents runaway background processes and system resource waste
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/png" href="/icon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.4.0-beta.4",
|
||||
"version": "1.4.0-beta.5",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
|
|||
|
|
@ -111,15 +111,15 @@ export const App = () => {
|
|||
handleBackToLaunch();
|
||||
};
|
||||
|
||||
const handleEjectConfirm = async (skipConfirmation: boolean) => {
|
||||
const handleEjectConfirm = (skipConfirmation: boolean) => {
|
||||
if (skipConfirmation) {
|
||||
await window.electronAPI.config.set('skipEjectConfirmation', true);
|
||||
window.electronAPI.config.set('skipEjectConfirmation', true);
|
||||
}
|
||||
performEject();
|
||||
};
|
||||
|
||||
const handleWelcomeComplete = async () => {
|
||||
await window.electronAPI.config.set('hasSeenWelcome', true);
|
||||
window.electronAPI.config.set('hasSeenWelcome', true);
|
||||
|
||||
const versions = await window.electronAPI.kobold.getInstalledVersions();
|
||||
if (versions.length > 0) {
|
||||
|
|
|
|||
|
|
@ -38,19 +38,6 @@ export const TitleBar = ({
|
|||
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||
|
||||
const handleMinimize = () => {
|
||||
window.electronAPI.app.minimizeWindow();
|
||||
};
|
||||
|
||||
const handleMaximize = async () => {
|
||||
await window.electronAPI.app.maximizeWindow();
|
||||
setIsMaximized(!isMaximized);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
window.electronAPI.app.closeWindow();
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell.Header
|
||||
style={{ display: 'flex', flexDirection: 'column', border: 'none' }}
|
||||
|
|
@ -64,7 +51,7 @@ export const TitleBar = ({
|
|||
justifyContent: 'space-between',
|
||||
backgroundColor:
|
||||
colorScheme === 'dark'
|
||||
? 'var(--mantine-color-dark-8)'
|
||||
? 'var(--mantine-color-dark-6)'
|
||||
: 'var(--mantine-color-gray-1)',
|
||||
border: '1px solid var(--mantine-color-default-border)',
|
||||
WebkitAppRegion: isSelectOpen ? 'no-drag' : 'drag',
|
||||
|
|
@ -154,8 +141,6 @@ export const TitleBar = ({
|
|||
aria-label="Open settings"
|
||||
tabIndex={-1}
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
margin: '2px 1px 1px',
|
||||
outline: 'none',
|
||||
}}
|
||||
>
|
||||
|
|
@ -175,19 +160,22 @@ export const TitleBar = ({
|
|||
{[
|
||||
{
|
||||
icon: <Minus size="1rem" />,
|
||||
onClick: handleMinimize,
|
||||
onClick: () => window.electronAPI.app.minimizeWindow(),
|
||||
color: undefined,
|
||||
label: 'Minimize window',
|
||||
},
|
||||
{
|
||||
icon: isMaximized ? <Copy size="1rem" /> : <Square size="1rem" />,
|
||||
onClick: handleMaximize,
|
||||
onClick: () => {
|
||||
window.electronAPI.app.maximizeWindow();
|
||||
setIsMaximized(!isMaximized);
|
||||
},
|
||||
color: undefined,
|
||||
label: isMaximized ? 'Restore window' : 'Maximize window',
|
||||
},
|
||||
{
|
||||
icon: <X size="1.25rem" />,
|
||||
onClick: handleClose,
|
||||
onClick: () => window.electronAPI.app.closeWindow(),
|
||||
color: 'red' as const,
|
||||
label: 'Close window',
|
||||
},
|
||||
|
|
@ -200,10 +188,6 @@ export const TitleBar = ({
|
|||
color={button.color}
|
||||
aria-label={button.label}
|
||||
tabIndex={-1}
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
margin: '2px 1px 1px',
|
||||
}}
|
||||
>
|
||||
{button.icon}
|
||||
</ActionIcon>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const AboutTab = () => {
|
|||
{
|
||||
label: PRODUCT_NAME,
|
||||
value: versionInfo.isAUR
|
||||
? `${versionInfo.appVersion} (AUR Package)`
|
||||
? `${versionInfo.appVersion} (AUR)`
|
||||
: versionInfo.appVersion,
|
||||
},
|
||||
{ label: 'Electron', value: versionInfo.electronVersion },
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { tryExecute } from '@/utils/logger';
|
||||
import {
|
||||
Stack,
|
||||
Text,
|
||||
|
|
@ -112,10 +111,7 @@ export const GeneralTab = ({
|
|||
(config) => config.value === FrontendPreference
|
||||
);
|
||||
if (currentFrontendConfig && !requirementResults.get(FrontendPreference)) {
|
||||
await tryExecute(
|
||||
() => window.electronAPI.config.set('frontendPreference', 'koboldcpp'),
|
||||
'Failed to reset frontend preference:'
|
||||
);
|
||||
window.electronAPI.config.set('frontendPreference', 'koboldcpp');
|
||||
setFrontendPreference('koboldcpp');
|
||||
}
|
||||
}, [frontendConfigs, FrontendPreference]);
|
||||
|
|
@ -129,6 +125,13 @@ export const GeneralTab = ({
|
|||
]);
|
||||
};
|
||||
initialize();
|
||||
|
||||
const handleFocus = () => {
|
||||
checkAllFrontendRequirements();
|
||||
};
|
||||
|
||||
window.addEventListener('focus', handleFocus);
|
||||
return () => window.removeEventListener('focus', handleFocus);
|
||||
}, [checkAllFrontendRequirements]);
|
||||
|
||||
const getSelectedFrontendConfig = () =>
|
||||
|
|
@ -191,13 +194,10 @@ export const GeneralTab = ({
|
|||
)
|
||||
return;
|
||||
|
||||
const success = await tryExecute(
|
||||
() => window.electronAPI.config.set('frontendPreference', value),
|
||||
'Failed to save frontend preference:'
|
||||
);
|
||||
if (success) {
|
||||
setFrontendPreference(value as FrontendPreference);
|
||||
}
|
||||
await checkAllFrontendRequirements();
|
||||
|
||||
window.electronAPI.config.set('frontendPreference', value);
|
||||
setFrontendPreference(value as FrontendPreference);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -266,6 +266,7 @@ export const GeneralTab = ({
|
|||
value={FrontendPreference}
|
||||
onChange={handleFrontendPreferenceChange}
|
||||
disabled={isOnInterfaceScreen}
|
||||
onClick={() => checkAllFrontendRequirements()}
|
||||
data={frontendConfigs.map((config) => ({
|
||||
value: config.value,
|
||||
label: config.label,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { tryExecute } from '@/utils/logger';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { compareVersions } from '@/utils/version';
|
||||
import { GITHUB_API } from '@/constants';
|
||||
|
||||
|
|
@ -35,18 +34,16 @@ export const useAppUpdateChecker = () => {
|
|||
|
||||
setIsDownloading(true);
|
||||
|
||||
await tryExecute(async () => {
|
||||
const checkResult = await window.electronAPI.updater.checkForUpdates();
|
||||
if (!checkResult) {
|
||||
setIsDownloading(false);
|
||||
return;
|
||||
}
|
||||
const checkResult = await window.electronAPI.updater.checkForUpdates();
|
||||
if (!checkResult) {
|
||||
setIsDownloading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await window.electronAPI.updater.downloadUpdate();
|
||||
if (success) {
|
||||
setIsUpdateDownloaded(true);
|
||||
}
|
||||
}, 'Failed to download update');
|
||||
const success = await window.electronAPI.updater.downloadUpdate();
|
||||
if (success) {
|
||||
setIsUpdateDownloaded(true);
|
||||
}
|
||||
|
||||
setIsDownloading(false);
|
||||
}, [canAutoUpdate]);
|
||||
|
|
@ -58,7 +55,7 @@ export const useAppUpdateChecker = () => {
|
|||
}, [isUpdateDownloaded]);
|
||||
|
||||
const checkForAppUpdates = useCallback(async () => {
|
||||
await tryExecute(async () => {
|
||||
try {
|
||||
const currentVersion = await window.electronAPI.app.getVersion();
|
||||
|
||||
const response = await fetch(
|
||||
|
|
@ -86,7 +83,12 @@ export const useAppUpdateChecker = () => {
|
|||
};
|
||||
|
||||
setUpdateInfo(updateInfo);
|
||||
}, 'Failed to check for app updates');
|
||||
} catch (error) {
|
||||
window.electronAPI.logs.logError(
|
||||
'Failed to check for app updates',
|
||||
error instanceof Error ? error : undefined
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { logError, tryExecuteImmediate } from '@/utils/logger';
|
||||
import { logError } from '@/utils/logger';
|
||||
import { getROCmDownload } from '@/utils/rocm';
|
||||
import { GITHUB_API } from '@/constants';
|
||||
import { filterAssetsByPlatform } from '@/utils/platform';
|
||||
|
|
@ -41,13 +41,15 @@ const loadFromCache = (): CachedReleaseData | null => {
|
|||
};
|
||||
|
||||
const saveToCache = (releases: DownloadItem[]) => {
|
||||
tryExecuteImmediate(() => {
|
||||
try {
|
||||
const data: CachedReleaseData = {
|
||||
releases,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
||||
}, 'Failed to save releases to cache');
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
};
|
||||
|
||||
const transformReleaseToDownloadItems = (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState, useCallback } from 'react';
|
||||
import { tryExecute } from '@/utils/logger';
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { SdConvDirectMode } from '@/types';
|
||||
|
||||
interface UseLaunchLogicProps {
|
||||
|
|
@ -258,32 +257,30 @@ export const useLaunchLogic = ({
|
|||
|
||||
onLaunch();
|
||||
|
||||
await tryExecute(async () => {
|
||||
const args: string[] = [
|
||||
...buildModelArgs(model, sdmodel, launchArgs),
|
||||
...buildConfigArgs(hasImageModel, launchArgs),
|
||||
...buildBackendArgs(launchArgs),
|
||||
];
|
||||
const args: string[] = [
|
||||
...buildModelArgs(model, sdmodel, launchArgs),
|
||||
...buildConfigArgs(hasImageModel, launchArgs),
|
||||
...buildBackendArgs(launchArgs),
|
||||
];
|
||||
|
||||
if (launchArgs.additionalArguments.trim()) {
|
||||
const additionalArgs = launchArgs.additionalArguments
|
||||
.trim()
|
||||
.split(/\s+/);
|
||||
args.push(...additionalArgs);
|
||||
}
|
||||
if (launchArgs.additionalArguments.trim()) {
|
||||
const additionalArgs = launchArgs.additionalArguments
|
||||
.trim()
|
||||
.split(/\s+/);
|
||||
args.push(...additionalArgs);
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.kobold.launchKoboldCpp(args);
|
||||
const result = await window.electronAPI.kobold.launchKoboldCpp(args);
|
||||
|
||||
if (result.success) {
|
||||
onLaunch();
|
||||
} else {
|
||||
const errorMessage = result.error || 'Unknown launch error';
|
||||
window.electronAPI.logs.logError(
|
||||
'Launch failed:',
|
||||
new Error(errorMessage)
|
||||
);
|
||||
}
|
||||
}, 'Error launching');
|
||||
if (result.success) {
|
||||
onLaunch();
|
||||
} else {
|
||||
const errorMessage = result.error || 'Unknown launch error';
|
||||
window.electronAPI.logs.logError(
|
||||
'Launch failed:',
|
||||
new Error(errorMessage)
|
||||
);
|
||||
}
|
||||
|
||||
setIsLaunching(false);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { tryExecute } from '@/utils/logger';
|
||||
import {
|
||||
getDisplayNameFromPath,
|
||||
compareVersions,
|
||||
|
|
@ -40,13 +39,8 @@ export const useUpdateChecker = () => {
|
|||
loadDismissedUpdates();
|
||||
}, []);
|
||||
|
||||
const saveDismissedUpdates = useCallback(async (updates: Set<string>) => {
|
||||
await tryExecute(async () => {
|
||||
await window.electronAPI.config.set(
|
||||
'dismissedUpdates',
|
||||
Array.from(updates)
|
||||
);
|
||||
}, 'Failed to save dismissed updates');
|
||||
const saveDismissedUpdates = useCallback((updates: Set<string>) => {
|
||||
window.electronAPI.config.set('dismissedUpdates', Array.from(updates));
|
||||
}, []);
|
||||
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
|
|
@ -56,50 +50,46 @@ export const useUpdateChecker = () => {
|
|||
|
||||
setIsChecking(true);
|
||||
|
||||
await tryExecute(async () => {
|
||||
const [currentVersion, rocmDownload] = await Promise.all([
|
||||
window.electronAPI.kobold.getCurrentVersion(),
|
||||
getROCmDownload(),
|
||||
]);
|
||||
const [currentVersion, rocmDownload] = await Promise.all([
|
||||
window.electronAPI.kobold.getCurrentVersion(),
|
||||
getROCmDownload(),
|
||||
]);
|
||||
|
||||
if (!currentVersion) {
|
||||
return;
|
||||
}
|
||||
if (!currentVersion) {
|
||||
return;
|
||||
if (!currentVersion) {
|
||||
setIsChecking(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const availableDownloads: DownloadItem[] = [...releases];
|
||||
if (rocmDownload) {
|
||||
availableDownloads.push(rocmDownload);
|
||||
}
|
||||
|
||||
const currentDisplayName = getDisplayNameFromPath(currentVersion);
|
||||
|
||||
const matchingDownload = availableDownloads.find(
|
||||
(download: DownloadItem) => {
|
||||
const downloadBaseName = stripAssetExtensions(download.name);
|
||||
return downloadBaseName === currentDisplayName;
|
||||
}
|
||||
);
|
||||
|
||||
const availableDownloads: DownloadItem[] = [...releases];
|
||||
if (rocmDownload) {
|
||||
availableDownloads.push(rocmDownload);
|
||||
}
|
||||
if (matchingDownload && matchingDownload.version) {
|
||||
const hasUpdate =
|
||||
compareVersions(matchingDownload.version, currentVersion.version) > 0;
|
||||
|
||||
const currentDisplayName = getDisplayNameFromPath(currentVersion);
|
||||
if (hasUpdate) {
|
||||
const updateKey = `${currentVersion.path}-${matchingDownload.version}`;
|
||||
|
||||
const matchingDownload = availableDownloads.find(
|
||||
(download: DownloadItem) => {
|
||||
const downloadBaseName = stripAssetExtensions(download.name);
|
||||
return downloadBaseName === currentDisplayName;
|
||||
}
|
||||
);
|
||||
|
||||
if (matchingDownload && matchingDownload.version) {
|
||||
const hasUpdate =
|
||||
compareVersions(matchingDownload.version, currentVersion.version) > 0;
|
||||
|
||||
if (hasUpdate) {
|
||||
const updateKey = `${currentVersion.path}-${matchingDownload.version}`;
|
||||
|
||||
if (!dismissedUpdates.has(updateKey)) {
|
||||
setUpdateInfo({
|
||||
currentVersion,
|
||||
availableUpdate: matchingDownload,
|
||||
});
|
||||
setShowUpdateModal(true);
|
||||
}
|
||||
if (!dismissedUpdates.has(updateKey)) {
|
||||
setUpdateInfo({
|
||||
currentVersion,
|
||||
availableUpdate: matchingDownload,
|
||||
});
|
||||
setShowUpdateModal(true);
|
||||
}
|
||||
}
|
||||
}, 'Failed to check for updates');
|
||||
}
|
||||
|
||||
setIsChecking(false);
|
||||
}, [dismissedUpdates, dismissedUpdatesLoaded, releases]);
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export function setupIPCHandlers() {
|
|||
|
||||
ipcMain.handle('config:get', (_, key) => getConfig(key));
|
||||
|
||||
ipcMain.handle('config:set', (_, key, value) => setConfig(key, value));
|
||||
ipcMain.on('config:set', (_, key, value) => setConfig(key, value));
|
||||
|
||||
ipcMain.handle('app:getVersion', () => getAppVersion());
|
||||
|
||||
|
|
@ -265,7 +265,7 @@ export function setupIPCHandlers() {
|
|||
|
||||
ipcMain.handle('app:openPerformanceManager', () => openPerformanceManager());
|
||||
|
||||
ipcMain.handle('logs:logError', (_, message: string, error?: Error) =>
|
||||
ipcMain.on('logs:logError', (_, message: string, error?: Error) =>
|
||||
logError(message, error)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { join } from 'path';
|
|||
import { on } from 'process';
|
||||
|
||||
import { logError } from './logging';
|
||||
import { safeTryExecute, tryExecute } from '@/utils/node/logger';
|
||||
import { tryExecute } from '@/utils/node/logger';
|
||||
import { sendKoboldOutput } from './window';
|
||||
import { getInstallDir } from './config';
|
||||
import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants';
|
||||
|
|
@ -115,19 +115,15 @@ export async function startFrontend(args: string[]) {
|
|||
|
||||
if (openWebUIProcess.stdout) {
|
||||
openWebUIProcess.stdout.on('data', (data: Buffer) => {
|
||||
safeTryExecute(() => {
|
||||
const output = data.toString('utf8');
|
||||
sendKoboldOutput(output, true);
|
||||
}, 'Error processing stdout data');
|
||||
const output = data.toString('utf8');
|
||||
sendKoboldOutput(output, true);
|
||||
});
|
||||
}
|
||||
|
||||
if (openWebUIProcess.stderr) {
|
||||
openWebUIProcess.stderr.on('data', (data: Buffer) => {
|
||||
safeTryExecute(() => {
|
||||
const output = data.toString('utf8');
|
||||
sendKoboldOutput(output, true);
|
||||
}, 'Error processing stderr data');
|
||||
const output = data.toString('utf8');
|
||||
sendKoboldOutput(output, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,12 +99,12 @@ const appAPI: AppAPI = {
|
|||
|
||||
const configAPI: ConfigAPI = {
|
||||
get: (key) => ipcRenderer.invoke('config:get', key),
|
||||
set: (key, value) => ipcRenderer.invoke('config:set', key, value),
|
||||
set: (key, value) => ipcRenderer.send('config:set', key, value),
|
||||
};
|
||||
|
||||
const logsAPI: LogsAPI = {
|
||||
logError: (message, error) =>
|
||||
ipcRenderer.invoke('logs:logError', message, error),
|
||||
ipcRenderer.send('logs:logError', message, error),
|
||||
};
|
||||
|
||||
const dependenciesAPI: DependenciesAPI = {
|
||||
|
|
|
|||
4
src/types/electron.d.ts
vendored
4
src/types/electron.d.ts
vendored
|
|
@ -175,11 +175,11 @@ export interface AppAPI {
|
|||
|
||||
export interface ConfigAPI {
|
||||
get: (key: string) => Promise<unknown>;
|
||||
set: (key: string, value: unknown) => Promise<void>;
|
||||
set: (key: string, value: unknown) => void;
|
||||
}
|
||||
|
||||
export interface LogsAPI {
|
||||
logError: (message: string, error?: Error) => Promise<void>;
|
||||
logError: (message: string, error?: Error) => void;
|
||||
}
|
||||
|
||||
export interface DependenciesAPI {
|
||||
|
|
|
|||
15
src/types/vite-env.d.ts
vendored
15
src/types/vite-env.d.ts
vendored
|
|
@ -15,11 +15,6 @@ declare module '*.jpeg' {
|
|||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
|
|
@ -34,13 +29,3 @@ declare module '*.mp3' {
|
|||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.wav' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.ogg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
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;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import {
|
||||
createSafeExecute,
|
||||
createTryExecute,
|
||||
createTryExecuteImmediate,
|
||||
createSafeTryExecute,
|
||||
} from '@/utils/logger-core';
|
||||
|
||||
export const logError = (message: string, error: Error) => {
|
||||
window.electronAPI.logs.logError(message, error);
|
||||
};
|
||||
|
||||
export const safeExecute = createSafeExecute(logError);
|
||||
export const tryExecute = createTryExecute(logError);
|
||||
export const tryExecuteImmediate = createTryExecuteImmediate(logError);
|
||||
export const safeTryExecute = createSafeTryExecute(logError);
|
||||
export const safeExecute = async <T>(
|
||||
operation: () => Promise<T>,
|
||||
errorMessage: string
|
||||
) => {
|
||||
try {
|
||||
return operation();
|
||||
} catch (error) {
|
||||
logError(errorMessage, error as Error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,26 @@
|
|||
import { logError } from '@/main/modules/logging';
|
||||
import {
|
||||
createSafeExecute,
|
||||
createTryExecute,
|
||||
createSafeTryExecute,
|
||||
} from '@/utils/logger-core';
|
||||
|
||||
export const safeExecute = createSafeExecute(logError);
|
||||
export const tryExecute = createTryExecute(logError);
|
||||
export const safeTryExecute = createSafeTryExecute(logError);
|
||||
export const safeExecute = async <T>(
|
||||
operation: () => Promise<T>,
|
||||
errorMessage: string
|
||||
) => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,36 +1,30 @@
|
|||
import { safeTryExecute } from '@/utils/logger';
|
||||
|
||||
export const handleTerminalOutput = (prevContent: string, newData: string) => {
|
||||
const result = safeTryExecute(() => {
|
||||
if (newData.includes('\r')) {
|
||||
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
|
||||
if (newData.includes('\r')) {
|
||||
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
|
||||
|
||||
if (hasStandaloneCarriageReturns) {
|
||||
const combined = prevContent + newData;
|
||||
if (hasStandaloneCarriageReturns) {
|
||||
const combined = prevContent + newData;
|
||||
|
||||
const lines = combined.split(/(\r?\n)/);
|
||||
const processedLines: string[] = [];
|
||||
const lines = combined.split(/(\r?\n)/);
|
||||
const processedLines: string[] = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i += 2) {
|
||||
const line = lines[i] || '';
|
||||
const lineBreak = lines[i + 1] || '';
|
||||
for (let i = 0; i < lines.length; i += 2) {
|
||||
const line = lines[i] || '';
|
||||
const lineBreak = lines[i + 1] || '';
|
||||
|
||||
if (line.includes('\r')) {
|
||||
const parts = line.split('\r');
|
||||
processedLines.push(parts[parts.length - 1] + lineBreak);
|
||||
} else {
|
||||
processedLines.push(line + lineBreak);
|
||||
}
|
||||
if (line.includes('\r')) {
|
||||
const parts = line.split('\r');
|
||||
processedLines.push(parts[parts.length - 1] + lineBreak);
|
||||
} else {
|
||||
processedLines.push(line + lineBreak);
|
||||
}
|
||||
|
||||
return processedLines.join('').replace(/\r?\n$/, '');
|
||||
}
|
||||
|
||||
return processedLines.join('').replace(/\r?\n$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
return prevContent + newData;
|
||||
}, 'Terminal Basic Error');
|
||||
|
||||
return result ?? prevContent + newData;
|
||||
return prevContent + newData;
|
||||
};
|
||||
|
||||
const URL_REGEX = /(https?:\/\/[^\s<>"{}|\\^`[\]]+)/gi;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue