mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
fix linux + windows app icons, don't allow default app menu shortcuts (ex page rrefresh) outside of dev, less timeouts for kcpp in case it needs to download
This commit is contained in:
parent
ce5dc0fce8
commit
9a20f0d1b0
7 changed files with 98 additions and 155 deletions
BIN
assets/icon.ico
Normal file
BIN
assets/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "0.9.8",
|
||||
"version": "0.9.9",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
@ -125,6 +125,7 @@
|
|||
},
|
||||
"win": {
|
||||
"compression": "normal",
|
||||
"icon": "assets/icon.ico",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
|
|
@ -154,6 +155,7 @@
|
|||
"linux": {
|
||||
"compression": "store",
|
||||
"category": "Utility",
|
||||
"icon": "src/assets/icon.png",
|
||||
"desktop": {
|
||||
"entry": {
|
||||
"Name": "Gerbil",
|
||||
|
|
|
|||
|
|
@ -132,12 +132,10 @@ export const AppearanceTab = () => {
|
|||
<Text fw={500} mb="sm">
|
||||
Zoom Level
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" mb="md">
|
||||
<Group justify="space-between" align="center" mb="md">
|
||||
<Text size="sm" c="dimmed">
|
||||
Adjust the zoom level of the application interface
|
||||
</Text>
|
||||
<Group gap="md" align="flex-end">
|
||||
<div style={{ flex: 1 }}>
|
||||
<Group justify="space-between" align="center" mb="xs">
|
||||
<TextInput
|
||||
value={zoomPercentage}
|
||||
onChange={(event) =>
|
||||
|
|
@ -147,9 +145,7 @@ export const AppearanceTab = () => {
|
|||
const value = event.currentTarget.value;
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue) || !isValidZoomPercentage(numValue)) {
|
||||
setZoomPercentage(
|
||||
zoomLevelToPercentage(zoomLevel).toString()
|
||||
);
|
||||
setZoomPercentage(zoomLevelToPercentage(zoomLevel).toString());
|
||||
}
|
||||
}}
|
||||
rightSection={
|
||||
|
|
@ -166,6 +162,8 @@ export const AppearanceTab = () => {
|
|||
}}
|
||||
/>
|
||||
</Group>
|
||||
<Group gap="md" align="flex-end">
|
||||
<div style={{ flex: 1 }}>
|
||||
<Slider
|
||||
value={zoomLevel}
|
||||
onChange={handleZoomChange}
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ export class KoboldCppManager {
|
|||
): Promise<void> {
|
||||
try {
|
||||
await execa(packedPath, ['--unpack', unpackDir], {
|
||||
timeout: 30000,
|
||||
timeout: 60000,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -501,7 +501,7 @@ export class KoboldCppManager {
|
|||
}
|
||||
|
||||
const result = await execa(launcherPath, ['--version'], {
|
||||
timeout: 10000,
|
||||
timeout: 30000,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
|
|
@ -674,14 +674,6 @@ export class KoboldCppManager {
|
|||
}>((resolve, reject) => {
|
||||
readyResolve = resolve;
|
||||
_readyReject = reject;
|
||||
|
||||
setTimeout(() => {
|
||||
if (!isReady) {
|
||||
reject(
|
||||
new Error('Timeout waiting for KoboldCpp ready signal (30s)')
|
||||
);
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
child.stdout?.on('data', (data) => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { spawn } from 'child_process';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { access } from 'fs/promises';
|
||||
|
||||
import { LogManager } from './LogManager';
|
||||
import { WindowManager } from './WindowManager';
|
||||
|
|
@ -55,16 +53,11 @@ export class OpenWebUIManager {
|
|||
> {
|
||||
const env = { ...process.env };
|
||||
|
||||
const cargoPath = join(homedir(), '.cargo', 'bin');
|
||||
|
||||
try {
|
||||
await access(cargoPath);
|
||||
env.PATH =
|
||||
process.platform === 'win32'
|
||||
? `${cargoPath};${env.PATH}`
|
||||
: `${cargoPath}:${env.PATH}`;
|
||||
} catch {
|
||||
return env;
|
||||
if (process.platform === 'win32') {
|
||||
env.PYTHONIOENCODING = 'utf-8';
|
||||
env.PYTHONLEGACYWINDOWSSTDIO = '1';
|
||||
env.PYTHONUTF8 = '1';
|
||||
env.CHCP = '65001';
|
||||
}
|
||||
|
||||
return env;
|
||||
|
|
@ -73,13 +66,17 @@ export class OpenWebUIManager {
|
|||
async isUvAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const env = await this.getUvEnvironment();
|
||||
const testProcess = spawn('uv', ['--version'], { stdio: 'pipe', env });
|
||||
const testProcess = spawn('uv', ['--version'], {
|
||||
stdio: 'pipe',
|
||||
env,
|
||||
shell: true,
|
||||
});
|
||||
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
testProcess.kill();
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
}, 10000);
|
||||
|
||||
testProcess.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
|
|
@ -101,10 +98,18 @@ export class OpenWebUIManager {
|
|||
env?: Record<string, string>
|
||||
): Promise<ChildProcess> {
|
||||
const uvEnv = await this.getUvEnvironment();
|
||||
const mergedEnv = { ...uvEnv, ...env };
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
mergedEnv.PYTHONIOENCODING = 'utf-8';
|
||||
mergedEnv.PYTHONUTF8 = '1';
|
||||
}
|
||||
|
||||
return spawn('uvx', args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
env: { ...uvEnv, ...env },
|
||||
env: mergedEnv,
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -113,15 +118,25 @@ export class OpenWebUIManager {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
const checkForOutput = (data: Buffer) => {
|
||||
const output = data.toString();
|
||||
try {
|
||||
const output = data.toString('utf8');
|
||||
if (output.includes(SERVER_READY_SIGNALS.OPENWEBUI)) {
|
||||
this.windowManager.sendKoboldOutput('Open WebUI is now running!');
|
||||
resolve();
|
||||
|
||||
if (this.openWebUIProcess?.stdout) {
|
||||
this.openWebUIProcess.stdout.removeListener('data', checkForOutput);
|
||||
this.openWebUIProcess.stdout.removeListener(
|
||||
'data',
|
||||
checkForOutput
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Error checking OpenWebUI output:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.openWebUIProcess?.stdout) {
|
||||
|
|
@ -182,13 +197,29 @@ export class OpenWebUIManager {
|
|||
|
||||
if (this.openWebUIProcess.stdout) {
|
||||
this.openWebUIProcess.stdout.on('data', (data: Buffer) => {
|
||||
this.windowManager.sendKoboldOutput(data.toString(), true);
|
||||
try {
|
||||
const output = data.toString('utf8');
|
||||
this.windowManager.sendKoboldOutput(output, true);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Error processing stdout data:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.openWebUIProcess.stderr) {
|
||||
this.openWebUIProcess.stderr.on('data', (data: Buffer) => {
|
||||
this.windowManager.sendKoboldOutput(data.toString(), true);
|
||||
try {
|
||||
const output = data.toString('utf8');
|
||||
this.windowManager.sendKoboldOutput(output, true);
|
||||
} catch (error) {
|
||||
this.logManager.logError(
|
||||
'Error processing stderr data:',
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { spawn } from 'child_process';
|
|||
import { createServer, request, type Server } from 'http';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { access, readdir } from 'fs/promises';
|
||||
import type { ChildProcess } from 'child_process';
|
||||
|
||||
import { LogManager } from './LogManager';
|
||||
|
|
@ -87,94 +86,12 @@ export class SillyTavernManager {
|
|||
return join(dataRoot, 'default-user', 'settings.json');
|
||||
}
|
||||
|
||||
private async tryAddPathToEnv(
|
||||
env: Record<string, string | undefined>,
|
||||
path: string
|
||||
): Promise<boolean> {
|
||||
const pathSeparator = process.platform === 'win32' ? ';' : ':';
|
||||
if (!env.PATH?.includes(path)) {
|
||||
env.PATH = `${path}${pathSeparator}${env.PATH}`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async tryVersionManagerPath(
|
||||
basePath: string,
|
||||
env: Record<string, string | undefined>
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await access(basePath);
|
||||
const versions = await readdir(basePath);
|
||||
if (versions.length > 0) {
|
||||
const latestVersion = versions.sort().pop();
|
||||
if (latestVersion) {
|
||||
const binSubPath = basePath.includes('fnm')
|
||||
? join('installation', 'bin')
|
||||
: 'bin';
|
||||
const nodeBinPath = join(basePath, latestVersion, binSubPath);
|
||||
|
||||
try {
|
||||
await access(nodeBinPath);
|
||||
return this.tryAddPathToEnv(env, nodeBinPath);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async getNodeEnvironment(): Promise<
|
||||
Record<string, string | undefined>
|
||||
> {
|
||||
const env = { ...process.env };
|
||||
|
||||
const versionManagerPaths = [
|
||||
join(homedir(), '.local', 'share', 'fnm', 'node-versions'),
|
||||
join(homedir(), '.nvm', 'versions', 'node'),
|
||||
join(homedir(), '.volta', 'tools', 'image', 'node'),
|
||||
join(homedir(), '.asdf', 'installs', 'nodejs'),
|
||||
];
|
||||
|
||||
const systemPaths: string[] = [];
|
||||
if (process.platform === 'darwin') {
|
||||
systemPaths.push('/opt/homebrew/bin', '/usr/local/bin');
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
versionManagerPaths.push(
|
||||
join(homedir(), 'AppData', 'Local', 'fnm', 'node-versions'),
|
||||
join(homedir(), 'AppData', 'Roaming', 'nvm')
|
||||
);
|
||||
}
|
||||
|
||||
for (const systemPath of systemPaths) {
|
||||
try {
|
||||
await access(systemPath);
|
||||
if (await this.tryAddPathToEnv(env, systemPath)) {
|
||||
return env;
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (const versionPath of versionManagerPaths) {
|
||||
if (await this.tryVersionManagerPath(versionPath, env)) {
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
async isNpxAvailable(): Promise<boolean> {
|
||||
try {
|
||||
const env = await this.getNodeEnvironment();
|
||||
const testProcess = spawn('npx', ['--version'], { stdio: 'pipe', env });
|
||||
const testProcess = spawn('npx', ['--version'], {
|
||||
stdio: 'pipe',
|
||||
shell: true,
|
||||
});
|
||||
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
|
|
@ -198,11 +115,10 @@ export class SillyTavernManager {
|
|||
}
|
||||
|
||||
private async createNpxProcess(args: string[]): Promise<ChildProcess> {
|
||||
const env = await this.getNodeEnvironment();
|
||||
return spawn('npx', args, {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
detached: false,
|
||||
env,
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ export class WindowManager {
|
|||
this.mainWindow = null;
|
||||
});
|
||||
|
||||
if (!this.isDevelopment()) {
|
||||
Menu.setApplicationMenu(null);
|
||||
}
|
||||
|
||||
this.mainWindow.webContents.on('will-navigate', (event, navigationUrl) => {
|
||||
const url = new URL(navigationUrl);
|
||||
if (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue