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