re-implement sillytavern to be manually and locally installed instead of relying on npx, update systeminformation, less main layout padding

This commit is contained in:
Egor 2025-12-23 20:30:56 -08:00
parent f9feb71926
commit ef9d814dd2
4 changed files with 123 additions and 46 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "gerbil", "name": "gerbil",
"productName": "Gerbil", "productName": "Gerbil",
"version": "1.17.1", "version": "1.18.0",
"description": "Run Large Language Models locally", "description": "Run Large Language Models locally",
"main": "out/main/index.js", "main": "out/main/index.js",
"homepage": "./", "homepage": "./",
@ -41,7 +41,7 @@
"dependencies": { "dependencies": {
"@codemirror/search": "^6.5.11", "@codemirror/search": "^6.5.11",
"@codemirror/theme-one-dark": "^6.1.3", "@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.39.5", "@codemirror/view": "^6.39.6",
"@fontsource/inter": "^5.2.8", "@fontsource/inter": "^5.2.8",
"@huggingface/gguf": "^0.3.2", "@huggingface/gguf": "^0.3.2",
"@mantine/core": "^8.3.10", "@mantine/core": "^8.3.10",
@ -58,7 +58,7 @@
"rehype-raw": "^7.0.0", "rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0", "rehype-sanitize": "^6.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"systeminformation": "^5.27.15", "systeminformation": "^5.27.16",
"winston": "^3.19.0", "winston": "^3.19.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"yauzl": "^3.2.0", "yauzl": "^3.2.0",

View file

@ -252,8 +252,10 @@ export const App = () => {
position: 'relative', position: 'relative',
overflow: 'auto', overflow: 'auto',
paddingTop: 0, paddingTop: 0,
paddingBottom: 0,
paddingLeft: isInterfaceScreen ? 0 : '.5rem',
paddingRight: isInterfaceScreen ? 0 : '.5rem',
top: TITLEBAR_HEIGHT, top: TITLEBAR_HEIGHT,
paddingBottom: isInterfaceScreen ? 0 : '1rem',
}} }}
> >
<ErrorBoundary> <ErrorBoundary>

View file

@ -1,6 +1,5 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { createServer, request, type Server } from 'http'; import { createServer, request, type Server } from 'http';
import { homedir } from 'os';
import { join } from 'path'; import { join } from 'path';
import { platform, on } from 'process'; import { platform, on } from 'process';
import type { ChildProcess } from 'child_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 { pathExists, readJsonFile, writeJsonFile } from '@/utils/node/fs';
import { parseKoboldConfig } from '@/utils/node/kobold'; import { parseKoboldConfig } from '@/utils/node/kobold';
import { getNodeEnvironment } from './dependencies'; import { getNodeEnvironment } from './dependencies';
import { getInstallDir } from './config';
let sillyTavernProcess: ChildProcess | null = null; let sillyTavernProcess: ChildProcess | null = null;
let proxyServer: Server | null = null; let proxyServer: Server | null = null;
let detectedDataRoot: string | null = null;
const SILLYTAVERN_BASE_ARGS = [ const SILLYTAVERN_BASE_ARGS = [
'sillytavern',
'--global',
'--listen', '--listen',
'--browserLaunchEnabled', '--browserLaunchEnabled',
'false', 'false',
@ -35,47 +32,120 @@ on('SIGTERM', () => {
void stopFrontend(); void stopFrontend();
}); });
function getFallbackDataRoot() { function getSillyTavernDataDir() {
const home = homedir(); return join(getInstallDir(), 'sillytavern-data');
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 getSillyTavernDataRoot() { function getSillyTavernInstallDir() {
if (detectedDataRoot) { return join(getInstallDir(), 'sillytavern-server');
return detectedDataRoot; }
}
const fallback = getFallbackDataRoot(); function getSillyTavernServerPath() {
detectedDataRoot = fallback; return join(
return fallback; getSillyTavernInstallDir(),
'node_modules',
'sillytavern',
'server.js'
);
} }
function getSillyTavernSettingsPath() { function getSillyTavernSettingsPath() {
const dataRoot = getSillyTavernDataRoot(); return join(getSillyTavernDataDir(), 'default-user', 'settings.json');
return join(dataRoot, '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<void>((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<void>((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[]) { async function createNpxProcess(args: string[]) {
const env = await getNodeEnvironment(); const env = await getNodeEnvironment();
return spawn('npx', args, { const serverJsPath = getSillyTavernServerPath();
const installDir = getSillyTavernInstallDir();
return spawn('node', [serverJsPath, ...args], {
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
detached: false, detached: false,
env, env,
cwd: installDir,
shell: platform === 'win32', shell: platform === 'win32',
}); });
} }
@ -288,6 +358,7 @@ export async function startFrontend(args: string[]) {
sendKoboldOutput(`Preparing SillyTavern to connect via proxy...`); sendKoboldOutput(`Preparing SillyTavern to connect via proxy...`);
await ensureSillyTavernInstalled();
await ensureSillyTavernSettings(); await ensureSillyTavernSettings();
await setupSillyTavernConfig(isImageMode); await setupSillyTavernConfig(isImageMode);
@ -295,10 +366,14 @@ export async function startFrontend(args: string[]) {
`Starting ${config.name} frontend on port ${config.port}...` `Starting ${config.name} frontend on port ${config.port}...`
); );
const sillyTavernDataDir = getSillyTavernDataDir();
const sillyTavernArgs = [ const sillyTavernArgs = [
...SILLYTAVERN_BASE_ARGS, ...SILLYTAVERN_BASE_ARGS,
'--port', '--port',
config.port.toString(), config.port.toString(),
'--dataRoot',
sillyTavernDataDir,
]; ];
sillyTavernProcess = await createNpxProcess(sillyTavernArgs); sillyTavernProcess = await createNpxProcess(sillyTavernArgs);

View file

@ -315,15 +315,15 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@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.5 version: 6.39.6
resolution: "@codemirror/view@npm:6.39.5" resolution: "@codemirror/view@npm:6.39.6"
dependencies: dependencies:
"@codemirror/state": "npm:^6.5.0" "@codemirror/state": "npm:^6.5.0"
crelt: "npm:^1.0.6" crelt: "npm:^1.0.6"
style-mod: "npm:^4.1.0" style-mod: "npm:^4.1.0"
w3c-keyname: "npm:^2.2.4" w3c-keyname: "npm:^2.2.4"
checksum: 10c0/54fc600b54c336283e8c73b6a0a99156880682121d73c2d4b74fc5a93f956e33b54ba16cd7debbf5fc28a48b18aeb643217e5200e3af0588909ca90cb275e957 checksum: 10c0/2c858a22c871f1865e09a4eb50b2398fc2ac945d217645c79692904bd0b48688ca2ba186fdfd5ed07fce671e22ae4424935efabcbc4d947c9559f801a4e94053
languageName: node languageName: node
linkType: hard linkType: hard
@ -4124,7 +4124,7 @@ __metadata:
dependencies: dependencies:
"@codemirror/search": "npm:^6.5.11" "@codemirror/search": "npm:^6.5.11"
"@codemirror/theme-one-dark": "npm:^6.1.3" "@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" "@eslint/js": "npm:^9.39.2"
"@fontsource/inter": "npm:^5.2.8" "@fontsource/inter": "npm:^5.2.8"
"@huggingface/gguf": "npm:^0.3.2" "@huggingface/gguf": "npm:^0.3.2"
@ -4165,7 +4165,7 @@ __metadata:
rehype-sanitize: "npm:^6.0.0" rehype-sanitize: "npm:^6.0.0"
remark-gfm: "npm:^4.0.1" remark-gfm: "npm:^4.0.1"
rollup-plugin-visualizer: "npm:^6.0.5" rollup-plugin-visualizer: "npm:^6.0.5"
systeminformation: "npm:^5.27.15" systeminformation: "npm:^5.27.16"
typescript: "npm:^5.9.3" typescript: "npm:^5.9.3"
vite: "npm:^7.3.0" vite: "npm:^7.3.0"
winston: "npm:^3.19.0" winston: "npm:^3.19.0"
@ -7840,12 +7840,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"systeminformation@npm:^5.27.15": "systeminformation@npm:^5.27.16":
version: 5.27.15 version: 5.27.16
resolution: "systeminformation@npm:5.27.15" resolution: "systeminformation@npm:5.27.16"
bin: bin:
systeminformation: lib/cli.js 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) conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android)
languageName: node languageName: node
linkType: hard linkType: hard