From ef9d814dd25ab4c4f65079fbf64b83c53b9f4047 Mon Sep 17 00:00:00 2001 From: Egor Date: Tue, 23 Dec 2025 20:30:56 -0800 Subject: [PATCH] re-implement sillytavern to be manually and locally installed instead of relying on npx, update systeminformation, less main layout padding --- package.json | 6 +- src/components/App/index.tsx | 4 +- src/main/modules/sillytavern.ts | 139 ++++++++++++++++++++++++-------- yarn.lock | 20 ++--- 4 files changed, 123 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index bf86fcf..98d559f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gerbil", "productName": "Gerbil", - "version": "1.17.1", + "version": "1.18.0", "description": "Run Large Language Models locally", "main": "out/main/index.js", "homepage": "./", @@ -41,7 +41,7 @@ "dependencies": { "@codemirror/search": "^6.5.11", "@codemirror/theme-one-dark": "^6.1.3", - "@codemirror/view": "^6.39.5", + "@codemirror/view": "^6.39.6", "@fontsource/inter": "^5.2.8", "@huggingface/gguf": "^0.3.2", "@mantine/core": "^8.3.10", @@ -58,7 +58,7 @@ "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", - "systeminformation": "^5.27.15", + "systeminformation": "^5.27.16", "winston": "^3.19.0", "winston-daily-rotate-file": "^5.0.0", "yauzl": "^3.2.0", diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 040a28a..a721009 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -252,8 +252,10 @@ export const App = () => { position: 'relative', overflow: 'auto', paddingTop: 0, + paddingBottom: 0, + paddingLeft: isInterfaceScreen ? 0 : '.5rem', + paddingRight: isInterfaceScreen ? 0 : '.5rem', top: TITLEBAR_HEIGHT, - paddingBottom: isInterfaceScreen ? 0 : '1rem', }} > diff --git a/src/main/modules/sillytavern.ts b/src/main/modules/sillytavern.ts index 29a34b8..b0c64ff 100644 --- a/src/main/modules/sillytavern.ts +++ b/src/main/modules/sillytavern.ts @@ -1,6 +1,5 @@ import { spawn } from 'child_process'; import { createServer, request, type Server } from 'http'; -import { homedir } from 'os'; import { join } from 'path'; import { platform, on } from 'process'; import type { ChildProcess } from 'child_process'; @@ -13,14 +12,12 @@ import { terminateProcess } from '@/utils/node/process'; import { pathExists, readJsonFile, writeJsonFile } from '@/utils/node/fs'; import { parseKoboldConfig } from '@/utils/node/kobold'; import { getNodeEnvironment } from './dependencies'; +import { getInstallDir } from './config'; let sillyTavernProcess: ChildProcess | null = null; let proxyServer: Server | null = null; -let detectedDataRoot: string | null = null; const SILLYTAVERN_BASE_ARGS = [ - 'sillytavern', - '--global', '--listen', '--browserLaunchEnabled', 'false', @@ -35,47 +32,120 @@ on('SIGTERM', () => { void stopFrontend(); }); -function getFallbackDataRoot() { - const home = homedir(); - - switch (platform) { - case 'win32': - return join(home, 'AppData', 'Local', 'SillyTavern', 'Data', 'data'); - case 'darwin': - return join( - home, - 'Library', - 'Application Support', - 'SillyTavern', - 'data' - ); - case 'linux': - default: - return join(home, '.local', 'share', 'SillyTavern', 'data'); - } +function getSillyTavernDataDir() { + return join(getInstallDir(), 'sillytavern-data'); } -function getSillyTavernDataRoot() { - if (detectedDataRoot) { - return detectedDataRoot; - } +function getSillyTavernInstallDir() { + return join(getInstallDir(), 'sillytavern-server'); +} - const fallback = getFallbackDataRoot(); - detectedDataRoot = fallback; - return fallback; +function getSillyTavernServerPath() { + return join( + getSillyTavernInstallDir(), + 'node_modules', + 'sillytavern', + 'server.js' + ); } function getSillyTavernSettingsPath() { - const dataRoot = getSillyTavernDataRoot(); - return join(dataRoot, 'default-user', 'settings.json'); + return join(getSillyTavernDataDir(), 'default-user', 'settings.json'); +} + +async function ensureSillyTavernInstalled() { + const serverPath = getSillyTavernServerPath(); + const installDir = getSillyTavernInstallDir(); + const env = await getNodeEnvironment(); + + const nodeModulesPath = join(installDir, 'node_modules'); + const jsquashFlatPath = join(nodeModulesPath, '@jsquash'); + + if (await pathExists(jsquashFlatPath)) { + sendKoboldOutput('Detected old flat installation, cleaning up...'); + await tryExecute(async () => { + await new Promise((resolve, reject) => { + const rmCmd = platform === 'win32' ? 'rmdir' : 'rm'; + const rmArgs = + platform === 'win32' + ? ['/s', '/q', nodeModulesPath] + : ['-rf', nodeModulesPath]; + + spawn(rmCmd, rmArgs, { + stdio: 'inherit', + shell: true, + }) + .on('exit', (code) => + code === 0 + ? resolve() + : reject(new Error(`Failed with code ${code}`)) + ) + .on('error', reject); + }); + }, 'Failed to clean old installation'); + } + + if (await pathExists(serverPath)) { + sendKoboldOutput('Checking for SillyTavern updates...'); + } else { + sendKoboldOutput('Installing SillyTavern via npm...'); + } + + return new Promise((resolve, reject) => { + const npmProcess = spawn( + 'npm', + [ + 'install', + 'sillytavern@latest', + '--prefix', + installDir, + '--no-save', + '--install-strategy=nested', + ], + { + stdio: ['pipe', 'pipe', 'pipe'], + env, + shell: platform === 'win32', + } + ); + + if (npmProcess.stdout) { + npmProcess.stdout.on('data', (data: Buffer) => { + sendKoboldOutput(data.toString().trim()); + }); + } + + if (npmProcess.stderr) { + npmProcess.stderr.on('data', (data: Buffer) => { + sendKoboldOutput(data.toString().trim()); + }); + } + + npmProcess.on('exit', (code) => { + if (code === 0) { + sendKoboldOutput('SillyTavern is ready'); + resolve(); + } else { + reject(new Error(`npm install failed with code ${code}`)); + } + }); + + npmProcess.on('error', (error) => { + reject(error); + }); + }); } async function createNpxProcess(args: string[]) { const env = await getNodeEnvironment(); - return spawn('npx', args, { + const serverJsPath = getSillyTavernServerPath(); + const installDir = getSillyTavernInstallDir(); + + return spawn('node', [serverJsPath, ...args], { stdio: ['pipe', 'pipe', 'pipe'], detached: false, env, + cwd: installDir, shell: platform === 'win32', }); } @@ -288,6 +358,7 @@ export async function startFrontend(args: string[]) { sendKoboldOutput(`Preparing SillyTavern to connect via proxy...`); + await ensureSillyTavernInstalled(); await ensureSillyTavernSettings(); await setupSillyTavernConfig(isImageMode); @@ -295,10 +366,14 @@ export async function startFrontend(args: string[]) { `Starting ${config.name} frontend on port ${config.port}...` ); + const sillyTavernDataDir = getSillyTavernDataDir(); + const sillyTavernArgs = [ ...SILLYTAVERN_BASE_ARGS, '--port', config.port.toString(), + '--dataRoot', + sillyTavernDataDir, ]; sillyTavernProcess = await createNpxProcess(sillyTavernArgs); diff --git a/yarn.lock b/yarn.lock index c48dd6e..d60e57d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -315,15 +315,15 @@ __metadata: languageName: node linkType: hard -"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0, @codemirror/view@npm:^6.39.5": - version: 6.39.5 - resolution: "@codemirror/view@npm:6.39.5" +"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0, @codemirror/view@npm:^6.39.6": + version: 6.39.6 + resolution: "@codemirror/view@npm:6.39.6" dependencies: "@codemirror/state": "npm:^6.5.0" crelt: "npm:^1.0.6" style-mod: "npm:^4.1.0" w3c-keyname: "npm:^2.2.4" - checksum: 10c0/54fc600b54c336283e8c73b6a0a99156880682121d73c2d4b74fc5a93f956e33b54ba16cd7debbf5fc28a48b18aeb643217e5200e3af0588909ca90cb275e957 + checksum: 10c0/2c858a22c871f1865e09a4eb50b2398fc2ac945d217645c79692904bd0b48688ca2ba186fdfd5ed07fce671e22ae4424935efabcbc4d947c9559f801a4e94053 languageName: node linkType: hard @@ -4124,7 +4124,7 @@ __metadata: dependencies: "@codemirror/search": "npm:^6.5.11" "@codemirror/theme-one-dark": "npm:^6.1.3" - "@codemirror/view": "npm:^6.39.5" + "@codemirror/view": "npm:^6.39.6" "@eslint/js": "npm:^9.39.2" "@fontsource/inter": "npm:^5.2.8" "@huggingface/gguf": "npm:^0.3.2" @@ -4165,7 +4165,7 @@ __metadata: rehype-sanitize: "npm:^6.0.0" remark-gfm: "npm:^4.0.1" rollup-plugin-visualizer: "npm:^6.0.5" - systeminformation: "npm:^5.27.15" + systeminformation: "npm:^5.27.16" typescript: "npm:^5.9.3" vite: "npm:^7.3.0" winston: "npm:^3.19.0" @@ -7840,12 +7840,12 @@ __metadata: languageName: node linkType: hard -"systeminformation@npm:^5.27.15": - version: 5.27.15 - resolution: "systeminformation@npm:5.27.15" +"systeminformation@npm:^5.27.16": + version: 5.27.16 + resolution: "systeminformation@npm:5.27.16" bin: systeminformation: lib/cli.js - checksum: 10c0/839d629eb9200ed9bc726a73d8b3a8cce0d77bd7796a983944099bd7a3ee48b93b27397a94eab3a5cc04a5fe61e46dcf3732691d33d70bdd0c638afca984990a + checksum: 10c0/2fc5fe3d081aeafff02ab89fa516df47559f15d017790f018208f75926e402df7f6e383c1a1716429093558545d4c8d57dd8fc2cf9aa568c4edc2f8e2dd0423f conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android) languageName: node linkType: hard