mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
new BackendCrashModal component to notify the user if kcpp crashes at runtime, replace the kcpp tunnel implementation with our own custom one and present its copyable URL in the statusbar
This commit is contained in:
parent
f825cd0d2f
commit
c111dfbddc
13 changed files with 447 additions and 86 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -12,7 +12,7 @@
|
|||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "prettier.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@
|
|||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^38.7.2",
|
||||
|
|
@ -74,6 +74,7 @@
|
|||
"@mantine/core": "^8.3.9",
|
||||
"@mantine/hooks": "^8.3.9",
|
||||
"@uiw/react-codemirror": "^4.25.3",
|
||||
"cloudflared": "^0.7.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"execa": "^9.6.1",
|
||||
"lucide-react": "^0.555.0",
|
||||
|
|
|
|||
66
src/components/App/BackendCrashModal.tsx
Normal file
66
src/components/App/BackendCrashModal.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { Text, Group, Button, Stack } from '@mantine/core';
|
||||
import { Modal } from '@/components/Modal';
|
||||
import type { KoboldCrashInfo } from '@/types/ipc';
|
||||
|
||||
interface BackendCrashModalProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
crashInfo: KoboldCrashInfo | null;
|
||||
}
|
||||
|
||||
const getCrashDescription = (crashInfo: KoboldCrashInfo) => {
|
||||
if (crashInfo.errorMessage) {
|
||||
return crashInfo.errorMessage;
|
||||
}
|
||||
|
||||
if (crashInfo.signal) {
|
||||
const signalDescriptions: Record<string, string> = {
|
||||
SIGKILL: 'The process was forcefully terminated',
|
||||
SIGSEGV: 'Memory access violation (segmentation fault)',
|
||||
SIGABRT: 'The process aborted unexpectedly',
|
||||
SIGBUS: 'Bus error (invalid memory access)',
|
||||
SIGFPE: 'Floating-point exception',
|
||||
SIGILL: 'Illegal instruction',
|
||||
SIGTERM: 'The process was terminated',
|
||||
SIGSTOP: 'The process was stopped',
|
||||
SIGHUP: 'The process lost its controlling terminal',
|
||||
};
|
||||
|
||||
return (
|
||||
signalDescriptions[crashInfo.signal] ||
|
||||
`Terminated by signal ${crashInfo.signal}`
|
||||
);
|
||||
}
|
||||
|
||||
if (crashInfo.exitCode !== null) {
|
||||
return `Process exited with error code ${crashInfo.exitCode}`;
|
||||
}
|
||||
|
||||
return 'The process terminated unexpectedly';
|
||||
};
|
||||
|
||||
export const BackendCrashModal = ({
|
||||
opened,
|
||||
onClose,
|
||||
crashInfo,
|
||||
}: BackendCrashModalProps) => {
|
||||
if (!crashInfo) return null;
|
||||
|
||||
const description = getCrashDescription(crashInfo);
|
||||
|
||||
return (
|
||||
<Modal opened={opened} onClose={onClose} title="Backend Crashed">
|
||||
<Stack gap="md">
|
||||
<Text size="sm">{description}</Text>
|
||||
|
||||
<Text size="sm" c="dimmed">
|
||||
Check the terminal output for more details about the crash.
|
||||
</Text>
|
||||
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button onClick={onClose}>Dismiss</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,8 +1,16 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { Group, AppShell, ActionIcon, Tooltip } from '@mantine/core';
|
||||
import { NotepadText } from 'lucide-react';
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import {
|
||||
Group,
|
||||
AppShell,
|
||||
ActionIcon,
|
||||
Tooltip,
|
||||
CopyButton,
|
||||
} from '@mantine/core';
|
||||
import { NotepadText, Globe, Check } from 'lucide-react';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { useNotepadStore } from '@/stores/notepad';
|
||||
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||
import { getTunnelInterfaceUrl } from '@/utils/interface';
|
||||
import type {
|
||||
CpuMetrics,
|
||||
MemoryMetrics,
|
||||
|
|
@ -20,9 +28,27 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
|||
null
|
||||
);
|
||||
const [gpuMetrics, setGpuMetrics] = useState<GpuMetrics | null>(null);
|
||||
const { resolvedColorScheme: colorScheme, systemMonitoringEnabled } =
|
||||
usePreferencesStore();
|
||||
const [tunnelBaseUrl, setTunnelBaseUrl] = useState<string | null>(null);
|
||||
const {
|
||||
resolvedColorScheme: colorScheme,
|
||||
systemMonitoringEnabled,
|
||||
frontendPreference,
|
||||
imageGenerationFrontendPreference,
|
||||
} = usePreferencesStore();
|
||||
const { isVisible, setVisible } = useNotepadStore();
|
||||
const { isImageGenerationMode } = useLaunchConfigStore();
|
||||
|
||||
const tunnelUrl = useMemo(() => {
|
||||
if (!tunnelBaseUrl) return null;
|
||||
if (frontendPreference === 'sillytavern' || frontendPreference === 'openwebui') {
|
||||
return tunnelBaseUrl;
|
||||
}
|
||||
return getTunnelInterfaceUrl(tunnelBaseUrl, {
|
||||
frontendPreference,
|
||||
imageGenerationFrontendPreference,
|
||||
isImageGenerationMode,
|
||||
});
|
||||
}, [tunnelBaseUrl, frontendPreference, imageGenerationFrontendPreference, isImageGenerationMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemMonitoringEnabled) {
|
||||
|
|
@ -63,6 +89,11 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
|||
};
|
||||
}, [maxDataPoints, systemMonitoringEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
const cleanup = window.electronAPI.kobold.onTunnelUrlChanged(setTunnelBaseUrl);
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
const displayCpuMetrics = systemMonitoringEnabled ? cpuMetrics : null;
|
||||
const displayMemoryMetrics = systemMonitoringEnabled ? memoryMetrics : null;
|
||||
const displayGpuMetrics = systemMonitoringEnabled ? gpuMetrics : null;
|
||||
|
|
@ -90,6 +121,25 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
|
|||
<NotepadText size="1.25rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
{tunnelUrl && (
|
||||
<CopyButton value={tunnelUrl}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip
|
||||
label={copied ? 'Copied!' : 'Copy Tunnel URL'}
|
||||
position="top"
|
||||
>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
color={copied ? 'teal' : undefined}
|
||||
onClick={copy}
|
||||
>
|
||||
{copied ? <Check size="1.25rem" /> : <Globe size="1.25rem" />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from '@mantine/core';
|
||||
import { UpdateAvailableModal } from '@/components/App/UpdateAvailableModal';
|
||||
import { EjectConfirmModal } from '@/components/App/EjectConfirmModal';
|
||||
import { BackendCrashModal } from '@/components/App/BackendCrashModal';
|
||||
import { TitleBar } from '@/components/App/TitleBar';
|
||||
import { StatusBar } from '@/components/App/StatusBar';
|
||||
import { ErrorBoundary } from '@/components/App/ErrorBoundary';
|
||||
|
|
@ -21,6 +22,7 @@ import { useLaunchConfigStore } from '@/stores/launchConfig';
|
|||
import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants';
|
||||
import type { DownloadItem } from '@/types/electron';
|
||||
import type { InterfaceTab, Screen } from '@/types';
|
||||
import type { KoboldCrashInfo } from '@/types/ipc';
|
||||
|
||||
export const App = () => {
|
||||
const [currentScreen, setCurrentScreen] = useState<Screen | null>(null);
|
||||
|
|
@ -28,6 +30,7 @@ export const App = () => {
|
|||
const [activeInterfaceTab, setActiveInterfaceTab] =
|
||||
useState<InterfaceTab>('terminal');
|
||||
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
|
||||
const [crashInfo, setCrashInfo] = useState<KoboldCrashInfo | null>(null);
|
||||
const isInterfaceScreen = currentScreen === 'interface';
|
||||
|
||||
const { resolvedColorScheme: appColorScheme, systemMonitoringEnabled } =
|
||||
|
|
@ -73,6 +76,18 @@ export const App = () => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const crashCleanup = window.electronAPI.kobold.onKoboldCrashed(
|
||||
(crashData) => {
|
||||
setCrashInfo(crashData);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
crashCleanup();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const {
|
||||
updateInfo: binaryUpdateInfo,
|
||||
showUpdateModal,
|
||||
|
|
@ -245,6 +260,12 @@ export const App = () => {
|
|||
onConfirm={handleEjectConfirm}
|
||||
/>
|
||||
|
||||
<BackendCrashModal
|
||||
opened={crashInfo !== null}
|
||||
onClose={() => setCrashInfo(null)}
|
||||
crashInfo={crashInfo}
|
||||
/>
|
||||
|
||||
<NotepadContainer />
|
||||
</AppShell>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import { platform } from 'process';
|
|||
|
||||
import { terminateProcess } from '@/utils/node/process';
|
||||
import { logError, safeExecute } from '@/utils/node/logging';
|
||||
import { sendKoboldOutput } from '@/main/modules/window';
|
||||
import { sendKoboldOutput, sendToRenderer } from '@/main/modules/window';
|
||||
import { SERVER_READY_SIGNALS } from '@/constants';
|
||||
import type { KoboldCrashInfo } from '@/types/ipc';
|
||||
import { pathExists } from '@/utils/node/fs';
|
||||
import { parseKoboldConfig } from '@/utils/node/kobold';
|
||||
import { getCurrentBackend } from '../backend';
|
||||
|
|
@ -17,6 +18,7 @@ import { startFrontend as startSillyTavernFrontend } from '@/main/modules/sillyt
|
|||
import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui';
|
||||
import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches';
|
||||
import { startProxy, stopProxy } from '../proxy';
|
||||
import { startTunnel, stopTunnel } from '../tunnel';
|
||||
import { resolveModelPath, abortActiveDownloads } from '../model-download';
|
||||
import type {
|
||||
FrontendPreference,
|
||||
|
|
@ -25,6 +27,8 @@ import type {
|
|||
} from '@/types';
|
||||
|
||||
let koboldProcess: ChildProcess | null = null;
|
||||
let isIntentionalStop = false;
|
||||
let hasProcessStartedSuccessfully = false;
|
||||
const preLaunchProcesses = new Set<ChildProcess>();
|
||||
|
||||
function spawnPreLaunchCommands(commands: string[]) {
|
||||
|
|
@ -149,6 +153,9 @@ export async function launchKoboldCpp(
|
|||
await stopKoboldCpp();
|
||||
}
|
||||
|
||||
isIntentionalStop = false;
|
||||
hasProcessStartedSuccessfully = false;
|
||||
|
||||
if (preLaunchCommands.length > 0) {
|
||||
spawnPreLaunchCommands(preLaunchCommands);
|
||||
}
|
||||
|
|
@ -172,7 +179,14 @@ export async function launchKoboldCpp(
|
|||
|
||||
const binaryDir = currentBackend.path.split(/[/\\]/).slice(0, -1).join('/');
|
||||
|
||||
const { isImageMode, isTextMode, debugmode } = parseKoboldConfig(args);
|
||||
const {
|
||||
isImageMode,
|
||||
isTextMode,
|
||||
debugmode,
|
||||
remotetunnel,
|
||||
host: koboldHost,
|
||||
port: koboldPort,
|
||||
} = parseKoboldConfig(args);
|
||||
|
||||
if (frontendPreference === 'koboldcpp') {
|
||||
if (isImageMode) {
|
||||
|
|
@ -186,12 +200,12 @@ export async function launchKoboldCpp(
|
|||
}
|
||||
|
||||
const resolvedArgs = await resolveModelPaths(args);
|
||||
const finalArgs = [...resolvedArgs];
|
||||
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
|
||||
const finalArgs = resolvedArgs.filter((arg) => arg !== '--remotetunnel');
|
||||
|
||||
await startProxy(koboldHost, koboldPort);
|
||||
|
||||
const child = spawn(currentBackend.path, finalArgs, {
|
||||
cwd: binaryDir,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
});
|
||||
|
|
@ -202,6 +216,10 @@ export async function launchKoboldCpp(
|
|||
|
||||
sendKoboldOutput(commandLine);
|
||||
|
||||
if (remotetunnel) {
|
||||
startTunnel();
|
||||
}
|
||||
|
||||
let readyResolve:
|
||||
| ((value: { success: boolean; pid?: number; error?: string }) => void)
|
||||
| null = null;
|
||||
|
|
@ -217,6 +235,10 @@ export async function launchKoboldCpp(
|
|||
readyReject = reject;
|
||||
});
|
||||
|
||||
const handleServerReady = () => {
|
||||
readyResolve?.({ success: true, pid: child.pid });
|
||||
};
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
const filtered = debugmode ? output : filterSpam(output);
|
||||
|
|
@ -226,7 +248,8 @@ export async function launchKoboldCpp(
|
|||
|
||||
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
|
||||
isReady = true;
|
||||
readyResolve?.({ success: true, pid: child.pid });
|
||||
hasProcessStartedSuccessfully = true;
|
||||
handleServerReady();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -239,20 +262,35 @@ export async function launchKoboldCpp(
|
|||
|
||||
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
|
||||
isReady = true;
|
||||
readyResolve?.({ success: true, pid: child.pid });
|
||||
hasProcessStartedSuccessfully = true;
|
||||
handleServerReady();
|
||||
}
|
||||
});
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
const isCrash = signal !== null || (code !== null && code !== 0);
|
||||
const displayMessage = signal
|
||||
? `\n[INFO] Process terminated with signal ${signal}`
|
||||
? `\nProcess terminated with signal ${signal}`
|
||||
: code === 0
|
||||
? `\n[INFO] Process exited successfully`
|
||||
? `\nProcess exited successfully`
|
||||
: code && (code > 1 || code < 0)
|
||||
? `\n[ERROR] Process exited with code ${code}`
|
||||
: `\n[INFO] Process exited with code ${code}`;
|
||||
? `\nProcess exited with code ${code}`
|
||||
: `\nProcess exited with code ${code}`;
|
||||
sendKoboldOutput(displayMessage);
|
||||
|
||||
const wasIntentionalStop = isIntentionalStop;
|
||||
const hadStartedSuccessfully = hasProcessStartedSuccessfully;
|
||||
koboldProcess = null;
|
||||
isIntentionalStop = false;
|
||||
hasProcessStartedSuccessfully = false;
|
||||
|
||||
if (isCrash && hadStartedSuccessfully && !wasIntentionalStop) {
|
||||
const crashInfo: KoboldCrashInfo = {
|
||||
exitCode: code,
|
||||
signal,
|
||||
};
|
||||
sendToRenderer('kobold-crashed', crashInfo);
|
||||
}
|
||||
|
||||
if (!isReady) {
|
||||
readyReject?.(
|
||||
|
|
@ -266,9 +304,18 @@ export async function launchKoboldCpp(
|
|||
child.on('error', (error) => {
|
||||
logError(`Process error: ${error.message}`, error);
|
||||
|
||||
sendKoboldOutput(`\n[ERROR] Process error: ${error.message}\n`);
|
||||
sendKoboldOutput(`\nProcess error: ${error.message}\n`);
|
||||
koboldProcess = null;
|
||||
|
||||
if (isReady) {
|
||||
const crashInfo: KoboldCrashInfo = {
|
||||
exitCode: null,
|
||||
signal: null,
|
||||
errorMessage: error.message,
|
||||
};
|
||||
sendToRenderer('kobold-crashed', crashInfo);
|
||||
}
|
||||
|
||||
if (!isReady) {
|
||||
readyReject?.(error);
|
||||
}
|
||||
|
|
@ -285,7 +332,9 @@ export async function launchKoboldCpp(
|
|||
export async function stopKoboldCpp() {
|
||||
abortActiveDownloads();
|
||||
stopProxy();
|
||||
stopTunnel();
|
||||
stopPreLaunchProcesses();
|
||||
isIntentionalStop = true;
|
||||
return terminateProcess(koboldProcess);
|
||||
}
|
||||
|
||||
|
|
|
|||
97
src/main/modules/koboldcpp/tunnel.ts
Normal file
97
src/main/modules/koboldcpp/tunnel.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import fs from 'fs';
|
||||
import { Tunnel, bin, install } from 'cloudflared';
|
||||
|
||||
import { logError } from '@/utils/node/logging';
|
||||
import { sendKoboldOutput, sendToRenderer } from '../window';
|
||||
import { PROXY } from '@/constants/proxy';
|
||||
|
||||
let activeTunnel: Tunnel | null = null;
|
||||
let tunnelUrl: string | null = null;
|
||||
|
||||
export const startTunnel = async () => {
|
||||
if (activeTunnel) {
|
||||
return tunnelUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
sendKoboldOutput('Starting Cloudflare tunnel...');
|
||||
|
||||
if (!fs.existsSync(bin)) {
|
||||
sendKoboldOutput('Installing cloudflared binary...');
|
||||
await install(bin);
|
||||
sendKoboldOutput('cloudflared binary installed');
|
||||
}
|
||||
|
||||
const tunnel = Tunnel.quick(`http://${PROXY.HOST}:${PROXY.PORT}`, {
|
||||
'--no-autoupdate': true,
|
||||
});
|
||||
|
||||
activeTunnel = tunnel;
|
||||
|
||||
const url = await new Promise<string>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Tunnel connection timed out'));
|
||||
}, 30000);
|
||||
|
||||
tunnel.once('url', (url) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(url);
|
||||
});
|
||||
|
||||
tunnel.once('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
tunnelUrl = url;
|
||||
sendKoboldOutput(`Tunnel ready at ${tunnelUrl}`);
|
||||
sendToRenderer('tunnel-url-changed', tunnelUrl);
|
||||
|
||||
tunnel.on('error', (error) => {
|
||||
logError(`Tunnel error: ${error.message}`, error);
|
||||
sendKoboldOutput(`[TUNNEL ERROR] ${error.message}`);
|
||||
});
|
||||
|
||||
tunnel.on('exit', (code, signal) => {
|
||||
sendKoboldOutput(
|
||||
`Tunnel process exited (code: ${code}, signal: ${signal})`
|
||||
);
|
||||
activeTunnel = null;
|
||||
tunnelUrl = null;
|
||||
sendToRenderer('tunnel-url-changed', null);
|
||||
});
|
||||
|
||||
return tunnelUrl;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logError(`Failed to start tunnel: ${errorMessage}`, error as Error);
|
||||
sendKoboldOutput(`[TUNNEL ERROR] ${errorMessage}`);
|
||||
activeTunnel = null;
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const stopTunnel = () => {
|
||||
if (!activeTunnel) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sendKoboldOutput('Stopping Cloudflare tunnel...');
|
||||
activeTunnel.stop();
|
||||
activeTunnel = null;
|
||||
tunnelUrl = null;
|
||||
sendToRenderer('tunnel-url-changed', null);
|
||||
sendKoboldOutput('Tunnel stopped');
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
logError(`Failed to stop tunnel: ${errorMessage}`, error as Error);
|
||||
activeTunnel = null;
|
||||
tunnelUrl = null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getTunnelUrl = () => tunnelUrl;
|
||||
|
||||
export const isTunnelActive = () => activeTunnel !== null;
|
||||
|
|
@ -14,6 +14,7 @@ import type {
|
|||
MemoryMetrics,
|
||||
GpuMetrics,
|
||||
} from '@/main/modules/monitoring';
|
||||
import type { KoboldCrashInfo } from '@/types/ipc';
|
||||
|
||||
const koboldAPI: KoboldAPI = {
|
||||
getInstalledBackends: () => ipcRenderer.invoke('kobold:getInstalledBackends'),
|
||||
|
|
@ -105,6 +106,23 @@ const koboldAPI: KoboldAPI = {
|
|||
ipcRenderer.removeListener('kobold-output', handler);
|
||||
};
|
||||
},
|
||||
onKoboldCrashed: (callback) => {
|
||||
const handler = (_: IpcRendererEvent, crashInfo: KoboldCrashInfo) =>
|
||||
callback(crashInfo);
|
||||
ipcRenderer.on('kobold-crashed', handler);
|
||||
|
||||
return () => {
|
||||
ipcRenderer.removeListener('kobold-crashed', handler);
|
||||
};
|
||||
},
|
||||
onTunnelUrlChanged: (callback) => {
|
||||
const handler = (_: IpcRendererEvent, url: string | null) => callback(url);
|
||||
ipcRenderer.on('tunnel-url-changed', handler);
|
||||
|
||||
return () => {
|
||||
ipcRenderer.removeListener('tunnel-url-changed', handler);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const appAPI: AppAPI = {
|
||||
|
|
|
|||
5
src/types/electron.d.ts
vendored
5
src/types/electron.d.ts
vendored
|
|
@ -18,6 +18,7 @@ import type {
|
|||
MemoryMetrics,
|
||||
GpuMetrics,
|
||||
} from '@/main/modules/monitoring';
|
||||
import type { KoboldCrashInfo } from '@/types/ipc';
|
||||
|
||||
export interface GitHubAsset {
|
||||
name: string;
|
||||
|
|
@ -176,6 +177,10 @@ export interface KoboldAPI {
|
|||
onInstallDirChanged: (callback: (newPath: string) => void) => () => void;
|
||||
onVersionsUpdated: (callback: () => void) => () => void;
|
||||
onKoboldOutput: (callback: (data: string) => void) => () => void;
|
||||
onKoboldCrashed: (
|
||||
callback: (crashInfo: KoboldCrashInfo) => void
|
||||
) => () => void;
|
||||
onTunnelUrlChanged: (callback: (url: string | null) => void) => () => void;
|
||||
}
|
||||
|
||||
export interface SystemVersionInfo {
|
||||
|
|
|
|||
10
src/types/ipc.d.ts
vendored
10
src/types/ipc.d.ts
vendored
|
|
@ -3,15 +3,25 @@ export type IPCChannel =
|
|||
| 'install-dir-changed'
|
||||
| 'versions-updated'
|
||||
| 'kobold-output'
|
||||
| 'kobold-crashed'
|
||||
| 'tunnel-url-changed'
|
||||
| 'window-maximized'
|
||||
| 'window-unmaximized'
|
||||
| 'line-numbers-changed';
|
||||
|
||||
export interface KoboldCrashInfo {
|
||||
exitCode: number | null;
|
||||
signal: string | null;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface IPCChannelPayloads {
|
||||
'download-progress': [progress: number];
|
||||
'install-dir-changed': [newPath: string];
|
||||
'versions-updated': [];
|
||||
'kobold-output': [message: string];
|
||||
'kobold-crashed': [crashInfo: KoboldCrashInfo];
|
||||
'tunnel-url-changed': [url: string | null];
|
||||
'window-maximized': [];
|
||||
'window-unmaximized': [];
|
||||
'line-numbers-changed': [showLineNumbers: boolean];
|
||||
|
|
|
|||
|
|
@ -175,3 +175,34 @@ export function getServerInterfaceInfo({
|
|||
: FRONTENDS.KOBOLDAI_LITE,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTunnelInterfaceUrl(
|
||||
tunnelBaseUrl: string,
|
||||
params: Omit<ServerInterfaceParams, 'frontendPreference'> & {
|
||||
frontendPreference: Exclude<
|
||||
FrontendPreference,
|
||||
'sillytavern' | 'openwebui'
|
||||
>;
|
||||
}
|
||||
) {
|
||||
const {
|
||||
frontendPreference,
|
||||
imageGenerationFrontendPreference,
|
||||
isImageGenerationMode,
|
||||
} = params;
|
||||
|
||||
if (
|
||||
isImageGenerationMode &&
|
||||
imageGenerationFrontendPreference === 'builtin'
|
||||
) {
|
||||
return `${tunnelBaseUrl}/sdui`;
|
||||
}
|
||||
|
||||
if (frontendPreference === 'llamacpp') {
|
||||
return isImageGenerationMode
|
||||
? `${tunnelBaseUrl}/sdui`
|
||||
: `${tunnelBaseUrl}/lcpp`;
|
||||
}
|
||||
|
||||
return isImageGenerationMode ? `${tunnelBaseUrl}/sdui` : tunnelBaseUrl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export function parseKoboldConfig(args: string[]) {
|
|||
let hasSdModel = false;
|
||||
let hasTextModel = false;
|
||||
let debugmode = false;
|
||||
let remotetunnel = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (
|
||||
|
|
@ -22,11 +23,13 @@ export function parseKoboldConfig(args: string[]) {
|
|||
hasTextModel = true;
|
||||
} else if (args[i] === '--debugmode') {
|
||||
debugmode = true;
|
||||
} else if (args[i] === '--remotetunnel') {
|
||||
remotetunnel = true;
|
||||
}
|
||||
}
|
||||
|
||||
const isImageMode = hasSdModel;
|
||||
const isTextMode = hasTextModel;
|
||||
|
||||
return { host, port, isImageMode, isTextMode, debugmode };
|
||||
return { host, port, isImageMode, isTextMode, debugmode, remotetunnel };
|
||||
}
|
||||
|
|
|
|||
142
yarn.lock
142
yarn.lock
|
|
@ -1452,106 +1452,106 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:^8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.48.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:^8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.48.1"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.48.0"
|
||||
"@typescript-eslint/utils": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.1"
|
||||
"@typescript-eslint/type-utils": "npm:8.48.1"
|
||||
"@typescript-eslint/utils": "npm:8.48.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.1"
|
||||
graphemer: "npm:^1.4.0"
|
||||
ignore: "npm:^7.0.0"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.48.0
|
||||
"@typescript-eslint/parser": ^8.48.1
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/5f4f9ac3ace3f615bac428859026b70fb7fa236666cfe8856fed3add7e4ba73c7113264c2df7a9d68247b679dfcc21b0414488bda7b9b3de1c209b1807ed7842
|
||||
checksum: 10c0/aeb4692ac27ded73dce5ddba08d46f15d617651f629cdfc5e874dd4ac767eac0523807f1f4e51f6f80675efff78e5937690f1c58740b8cb92b44b87d757a6a1a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:^8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.48.0"
|
||||
"@typescript-eslint/parser@npm:^8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/parser@npm:8.48.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.1"
|
||||
"@typescript-eslint/types": "npm:8.48.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.1"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/180753e1dc55cd5174a236b738d3b0dd6dd6c131797cd417b3b3b8fac344168f3d21bd49eae6c0a075be29ed69b7bc74d97cadd917f1f4d4c113c29e76c1f9cd
|
||||
checksum: 10c0/54ec22c82cc631f56131bfed9747f8cadf52ab123463a406c5221f258f9533431c4a33ebe21ef178840d50235e69bb370d36aa2fd6a066e7223b38bfa41a1788
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.48.0"
|
||||
"@typescript-eslint/project-service@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.48.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.48.0"
|
||||
"@typescript-eslint/types": "npm:^8.48.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.48.1"
|
||||
"@typescript-eslint/types": "npm:^8.48.1"
|
||||
debug: "npm:^4.3.4"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/6e1d08312fe55a91ba37eb19131af91ad7834bafd15d1cddb83a1e35e5134382e10dc0b14531036ba1c075ce4cba627123625ed6f2e209fb3355f3dda25da0a1
|
||||
checksum: 10c0/0aeeea5e65d0f837bd9a47265f144f14ca72969d259ee929e63e06526b21f4e990e70c7bafdb2ceb3783373df7d9f5bae32c328a4c6403606f01339bc984b3f5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.48.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.48.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
checksum: 10c0/0766e365901a8af9d9e41fa70464254aacf8b4d167734d88b6cdaa0235e86bfdffc57a3e39a20e105929b8df499d252090f64f81f86770f74626ca809afe54b6
|
||||
"@typescript-eslint/types": "npm:8.48.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.1"
|
||||
checksum: 10c0/16514823784cb598817b87d3d2b4fb618ab8b2378b3401a4c1160a5c914e51e7a925c3c1e7be73e0250e38390f0be70fecb3e0e0bdde7b243d74444933b95d3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.48.0, @typescript-eslint/tsconfig-utils@npm:^8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/52e9ce8ffbaf32f3c6f4b8fa8af6e3901c430411e137a0baf650fcefdd8edf3dcc4569eba726a28424471d4d1d96b815aa4cf7b63aa7b67380efd6a8dd354222
|
||||
checksum: 10c0/0d540f7ab3018ed1bab8f008c0d30229e0ea12806fdbf1c756572b5cf536a1f2a6c59ca2544c09bcd5b89dcfcf79e5f6be3d765e725492b9c7e4cd64fcecffc6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.48.0"
|
||||
"@typescript-eslint/type-utils@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.48.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
"@typescript-eslint/utils": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.1"
|
||||
"@typescript-eslint/utils": "npm:8.48.1"
|
||||
debug: "npm:^4.3.4"
|
||||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/72ab5c7d183b844e4870bfa5dfeb68e2e7ce5f3e1b33c06d5a8e70f0d0a012c9152ad15071d41ba3788266109804a9f4cdb85d664b11df8948bc930e29e0c244
|
||||
checksum: 10c0/c98a71f7d374be249ecc7c9f20b0a867a73ad4f64e646a6bf9f2c1a5d74f0dc7bd59e9c94a0842068caa366af39ae0c550ede6d653b5c9418a0a587510bbb6d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.48.0, @typescript-eslint/types@npm:^8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/types@npm:8.48.0"
|
||||
checksum: 10c0/865a8f4ae4a50aa8976f3d7e0f874f1a1c80227ec53ded68644d41011c729a489bb59f70683b29237ab945716ea0258e1d47387163379eab3edaaf5e5cc3b757
|
||||
"@typescript-eslint/types@npm:8.48.1, @typescript-eslint/types@npm:^8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/types@npm:8.48.1"
|
||||
checksum: 10c0/366b8140f4c69319f1796b66b33c0c6e16eb6cbe543b9517003104e12ed143b620c1433ccf60d781a629d9433bd509a363c0c9d21fd438c17bb8840733af6caa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.48.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.48.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.0"
|
||||
"@typescript-eslint/project-service": "npm:8.48.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.48.1"
|
||||
"@typescript-eslint/types": "npm:8.48.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.48.1"
|
||||
debug: "npm:^4.3.4"
|
||||
minimatch: "npm:^9.0.4"
|
||||
semver: "npm:^7.6.0"
|
||||
|
|
@ -1559,32 +1559,32 @@ __metadata:
|
|||
ts-api-utils: "npm:^2.1.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/f17dd35f7b82654fae9fe83c2eb650572464dbce0170d55b3ef94b99e9aae010f2cbadd436089c8e59eef97d41719ace3a2deb4ac3cdfac26d43b36f34df5590
|
||||
checksum: 10c0/72c0802f74222160f6a13ebbd32b0d504142a2427678c87ea78fc32672c65fd522377d43b31a97c944cbd0aefc36b320bf02f04e47c44f2797d6ccd0a8aa30ec
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.48.0"
|
||||
"@typescript-eslint/utils@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.48.1"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.48.1"
|
||||
"@typescript-eslint/types": "npm:8.48.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.48.1"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/56334312d1dc114a5c8b05dac4da191c40a416a5705fa76797ebdc9f6a96d35727fd0993cf8776f5c4411837e5fc2151bfa61d3eecc98b24f5a821a63a4d56f3
|
||||
checksum: 10c0/1775ac217b578f52d6c1e85258098f8ef764d04830c6ce11043b434860da80f1a5f7cc1b9f2e0a63de161e83b8d876f7ae8362d7644d5d8e636e60ad5eeff4e2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.48.0":
|
||||
version: 8.48.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.48.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.48.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.48.0"
|
||||
"@typescript-eslint/types": "npm:8.48.1"
|
||||
eslint-visitor-keys: "npm:^4.2.1"
|
||||
checksum: 10c0/20ae9ec255a786de40cdba281b63f634a642dcc34d2a79c5ffc160109f7f6227c28ae2c64be32cbc53dc68dc398c3da715bfcce90422b5024f15f7124a3c1704
|
||||
checksum: 10c0/ecf4078ce63c296dd340672b516f42bf452534c75af7e7d6c1a3f32b143ff184cb3a4071d7429a9f870371ff9091a790acce28b85ce3c450bfc60554c79d43ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -2304,6 +2304,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cloudflared@npm:^0.7.1":
|
||||
version: 0.7.1
|
||||
resolution: "cloudflared@npm:0.7.1"
|
||||
bin:
|
||||
cloudflared: lib/cloudflared.js
|
||||
checksum: 10c0/e5cb2a44f514c19f80078eed019cf772d545ff58e528359b5d31a96de0885e959085fee37b21d3c99053d93b5a4abeb8c23b5cb66127257ad4dc92ef01912626
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"clsx@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "clsx@npm:2.1.1"
|
||||
|
|
@ -3715,10 +3724,11 @@ __metadata:
|
|||
"@types/react": "npm:^19.2.7"
|
||||
"@types/react-dom": "npm:^19.2.3"
|
||||
"@types/yauzl": "npm:^2.10.3"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.48.0"
|
||||
"@typescript-eslint/parser": "npm:^8.48.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.48.1"
|
||||
"@typescript-eslint/parser": "npm:^8.48.1"
|
||||
"@uiw/react-codemirror": "npm:^4.25.3"
|
||||
"@vitejs/plugin-react": "npm:^5.1.1"
|
||||
cloudflared: "npm:^0.7.1"
|
||||
cross-env: "npm:^10.1.0"
|
||||
electron: "npm:^38.7.2"
|
||||
electron-builder: "npm:^26.0.12"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue