remove the comfyui frontend, clean up koboldcpp's terminal spam to be more meaningful, start proxying kcpp APIs through 127.0.0.1:5002

This commit is contained in:
Egor 2025-11-16 14:54:35 -08:00
parent 0c869ea029
commit f2d15591a7
19 changed files with 364 additions and 899 deletions

View file

@ -161,7 +161,7 @@ jobs:
license=('AGPL-3.0-or-later')
depends=('gtk3' 'nss')
optdepends=('nodejs: Required for SillyTavern integration'
'uv: Required for OpenWebUI and ComfyUI integrations')
'uv: Required for OpenWebUI integration')
provides=('gerbil')
conflicts=('gerbil-git')
source=("gerbil-${pkgver}.AppImage::${{ steps.release_info.outputs.appimage_url }}"
@ -298,7 +298,7 @@ jobs:
depends = gtk3
depends = nss
optdepends = nodejs: Required for SillyTavern integration
optdepends = uv: Required for OpenWebUI and ComfyUI integrations
optdepends = uv: Required for OpenWebUI integration
provides = gerbil
conflicts = gerbil-git
source = gerbil-${{ steps.release_info.outputs.version }}.AppImage::${{ steps.release_info.outputs.appimage_url }}

View file

@ -1,7 +1,7 @@
{
"name": "gerbil",
"productName": "Gerbil",
"version": "1.8.5",
"version": "1.9.0",
"description": "Run Large Language Models locally",
"main": "out/main/index.js",
"homepage": "./",

View file

@ -6,16 +6,11 @@ import { getServerInterfaceInfo } from '@/utils/interface';
import { TITLEBAR_HEIGHT, STATUSBAR_HEIGHT } from '@/constants';
interface ServerTabProps {
serverUrl?: string;
isServerReady?: boolean;
activeTab?: string;
}
export const ServerTab = ({
serverUrl,
isServerReady,
activeTab,
}: ServerTabProps) => {
export const ServerTab = ({ isServerReady, activeTab }: ServerTabProps) => {
const { isImageGenerationMode } = useLaunchConfigStore();
const { frontendPreference, imageGenerationFrontendPreference } =
usePreferencesStore();
@ -33,17 +28,11 @@ export const ServerTab = ({
frontendPreference,
imageGenerationFrontendPreference,
isImageGenerationMode: effectiveImageMode,
serverUrl: serverUrl || '',
}),
[
frontendPreference,
imageGenerationFrontendPreference,
effectiveImageMode,
serverUrl,
]
[frontendPreference, imageGenerationFrontendPreference, effectiveImageMode]
);
if (!isServerReady || !serverUrl) {
if (!isServerReady) {
return (
<Box
style={{
@ -60,10 +49,7 @@ export const ServerTab = ({
</Text>
<Text c="dimmed" size="sm">
The{' '}
{title.toLowerCase().includes('ui') ||
title.toLowerCase().includes('comfy')
? 'image generation'
: 'chat'}{' '}
{title.toLowerCase().includes('ui') ? 'image generation' : 'chat'}{' '}
interface will load automatically when ready
</Text>
</Stack>

View file

@ -95,8 +95,6 @@ export const TerminalTab = forwardRef<TerminalTabRef, TerminalTabProps>(
signalToCheck = SERVER_READY_SIGNALS.SILLYTAVERN;
} else if (effectiveFrontend === 'openwebui') {
signalToCheck = SERVER_READY_SIGNALS.OPENWEBUI;
} else if (effectiveFrontend === 'comfyui') {
signalToCheck = SERVER_READY_SIGNALS.COMFYUI;
}
if (newData.includes(signalToCheck)) {

View file

@ -18,7 +18,6 @@ export const InterfaceScreen = ({
activeTab,
onTabChange,
}: InterfaceScreenProps) => {
const [serverUrl, setServerUrl] = useState('');
const [isServerReady, setIsServerReady] = useState(false);
const terminalTabRef = useRef<TerminalTabRef>(null);
@ -42,16 +41,13 @@ export const InterfaceScreen = ({
]
);
const handleServerReady = useCallback(
(url: string) => {
setServerUrl(url);
setIsServerReady(true);
if (onTabChange) {
onTabChange(defaultInterfaceTab);
}
},
[onTabChange, defaultInterfaceTab]
);
const handleServerReady = useCallback(() => {
setIsServerReady(true);
if (onTabChange) {
onTabChange(defaultInterfaceTab);
}
}, [onTabChange, defaultInterfaceTab]);
useEffect(() => {
if (activeTab === 'terminal' && terminalTabRef.current) {
@ -78,7 +74,6 @@ export const InterfaceScreen = ({
}}
>
<ServerTab
serverUrl={serverUrl}
isServerReady={isServerReady}
activeTab={activeTab || undefined}
/>

View file

@ -18,7 +18,6 @@ interface FrontendRequirement {
interface FrontendConfig {
value: string;
label: string;
badges: string[];
requirements?: FrontendRequirement[];
requirementCheck?: () => Promise<boolean>;
}
@ -46,12 +45,10 @@ export const FrontendInterfaceSelector = ({
{
value: 'koboldcpp',
label: 'Built-in',
badges: ['Text', 'Image'],
},
{
value: 'sillytavern',
label: FRONTENDS.SILLYTAVERN,
badges: ['Text', 'Image'],
requirements: [
{
id: 'nodejs',
@ -65,20 +62,6 @@ export const FrontendInterfaceSelector = ({
{
value: 'openwebui',
label: FRONTENDS.OPENWEBUI,
badges: ['Text', 'Image'],
requirements: [
{
id: 'uv',
name: 'uv',
url: 'https://docs.astral.sh/uv/getting-started/installation/',
},
],
requirementCheck: () => window.electronAPI.dependencies.isUvAvailable(),
},
{
value: 'comfyui',
label: FRONTENDS.COMFYUI,
badges: ['Image'],
requirements: [
{
id: 'uv',

View file

@ -10,7 +10,6 @@ export const SERVER_READY_SIGNALS = {
KOBOLDCPP: 'Please connect to custom endpoint at',
SILLYTAVERN: 'SillyTavern is listening on',
OPENWEBUI: 'Waiting for application startup.',
COMFYUI: 'Starting server',
} as const;
export const DEFAULT_CONTEXT_SIZE = 4096;
@ -24,24 +23,17 @@ export const SILLYTAVERN = {
PORT: 3000,
PROXY_PORT: 3001,
get URL() {
return `http://localhost:${this.PORT}`;
return `http://127.0.0.1:${this.PORT}`;
},
get PROXY_URL() {
return `http://localhost:${this.PROXY_PORT}`;
return `http://127.0.0.1:${this.PROXY_PORT}`;
},
} as const;
export const OPENWEBUI = {
PORT: 8080,
get URL() {
return `http://localhost:${this.PORT}`;
},
} as const;
export const COMFYUI = {
PORT: 8188,
get URL() {
return `http://localhost:${this.PORT}`;
return `http://127.0.0.1:${this.PORT}`;
},
} as const;
@ -51,7 +43,6 @@ export const GITHUB_API = {
KOBOLDCPP_REPO: 'LostRuins/koboldcpp',
KOBOLDCPP_ROCM_REPO: 'YellowRoseCx/koboldcpp-rocm',
GERBIL_REPO: 'lone-cloud/gerbil',
COMFYUI_REPO: 'comfyanonymous/ComfyUI',
get LATEST_RELEASE_URL() {
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest` as const;
},
@ -61,12 +52,6 @@ export const GITHUB_API = {
get ROCM_LATEST_RELEASE_URL() {
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_ROCM_REPO}/releases/latest` as const;
},
get COMFYUI_DOWNLOAD_URL() {
return `${this.GITHUB_BASE_URL}/${this.COMFYUI_REPO}/archive/refs/heads/master.zip` as const;
},
get COMFYUI_LATEST_COMMIT_URL() {
return `${this.BASE_URL}/repos/${this.COMFYUI_REPO}/commits/master` as const;
},
get GERBIL_GITHUB_URL() {
return `${this.GITHUB_BASE_URL}/${this.GERBIL_REPO}` as const;
},
@ -94,7 +79,6 @@ export const FRONTENDS = {
STABLE_UI: 'Stable UI',
SILLYTAVERN: 'SillyTavern',
OPENWEBUI: 'Open WebUI',
COMFYUI: 'ComfyUI',
} as const;
export const ZOOM = {

View file

@ -1,50 +0,0 @@
export const KLITE_CSS_OVERRIDE = `
<style id="gerbil-css-override">
* {
transition: 100ms ease all;
}
.maincontainer {
padding-right: 0 !important;
padding-left: 0 !important;
}
.adaptivecontainer {
width: 100% !important;
}
#lastreq1 {
margin: 0 10px;
}
#inputrow {
padding: 0 10px;
}
#navbarNavDropdown {
padding: 0;
}
#actionmenuitems {
margin-left: 10px;
}
#inputrow > :nth-child(1) {
padding-right: 0 !important;
}
#inputrow.show_mode > :nth-child(1) {
flex: 0 0 70px;
margin-right: 4px;
}
#inputrow > :nth-child(3) {
flex: 0 0 70px;
padding-right: 0 !important;
}
#inputrow.show_mode > :nth-child(3) button {
background-color: #129c00;
font-size: 14px;
}
#inputrow.show_mode > :nth-child(3) button:hover {
background-color: #058105;
}
</style>`;

7
src/constants/proxy.ts Normal file
View file

@ -0,0 +1,7 @@
export const PROXY = {
HOST: '127.0.0.1',
PORT: 5002,
get URL() {
return `http://${this.HOST}:${this.PORT}`;
},
} as const;

View file

@ -16,7 +16,6 @@ import { safeExecute } from '@/utils/node/logging';
import { stopKoboldCpp } from '@/main/modules/koboldcpp/launcher';
import { stopFrontend as stopSillyTavern } from '@/main/modules/sillytavern';
import { stopFrontend as stopOpenWebUI } from '@/main/modules/openwebui';
import { stopFrontend as stopComfyUI } from '@/main/modules/comfyui';
import { setupIPCHandlers } from '@/main/ipc';
import { ensureDir } from '@/utils/node/fs';
import { PRODUCT_NAME } from '@/constants';
@ -75,7 +74,6 @@ export async function initializeApp(options?: { startMinimized?: boolean }) {
stopKoboldCpp(),
stopSillyTavern(),
stopOpenWebUI(),
stopComfyUI(),
];
const timeoutPromise = new Promise<void>((resolve) => {

View file

@ -35,7 +35,6 @@ import { getConfigDir, openPathHandler, openUrl } from '@/utils/node/path';
import { logError } from '@/utils/node/logging';
import { stopFrontend as stopSillyTavernFrontend } from '@/main/modules/sillytavern';
import { stopFrontend as stopOpenWebUIFrontend } from '@/main/modules/openwebui';
import { stopFrontend as stopComfyUIFrontend } from '@/main/modules/comfyui';
import {
isUvAvailable,
isNpxAvailable,
@ -147,7 +146,6 @@ export function setupIPCHandlers() {
stopKoboldCpp();
stopSillyTavernFrontend();
stopOpenWebUIFrontend();
stopComfyUIFrontend();
});
ipcMain.handle('kobold:parseConfigFile', (_, filePath) =>

View file

@ -1,599 +0,0 @@
import { spawn } from 'child_process';
import { dirname, join } from 'path';
import {
access,
stat,
mkdir,
unlink,
copyFile,
readFile,
writeFile,
rm,
} from 'fs/promises';
import { platform, on } from 'process';
import type { ChildProcess } from 'child_process';
import yauzl from 'yauzl';
import { createWriteStream } from 'fs';
import { app } from 'electron';
import { logError, safeExecute, tryExecute } from '@/utils/node/logging';
import { sendKoboldOutput } from './window';
import { getInstallDir } from './config';
import { COMFYUI, SERVER_READY_SIGNALS, GITHUB_API } from '@/constants';
import { terminateProcess } from '@/utils/node/process';
import { parseKoboldConfig } from '@/utils/node/kobold';
import { ensureDir } from '@/utils/node/fs';
import { detectGPU } from './hardware';
import { getUvEnvironment } from './dependencies';
interface ComfyUIVersionInfo {
sha: string;
date: string;
}
async function getLatestComfyUIVersion() {
return safeExecute(async () => {
const response = await fetch(GITHUB_API.COMFYUI_LATEST_COMMIT_URL);
if (!response.ok) {
throw new Error(
`Failed to fetch ComfyUI version: ${response.statusText}`
);
}
const data = await response.json();
return {
sha: data.sha,
date: data.commit.committer.date,
};
}, 'Failed to fetch latest ComfyUI version');
}
async function getCurrentComfyUIVersion(workspaceDir: string) {
try {
const versionFile = join(workspaceDir, '.version.json');
const content = await readFile(versionFile, 'utf-8');
return JSON.parse(content);
} catch {
return null;
}
}
async function saveComfyUIVersion(
workspaceDir: string,
version: ComfyUIVersionInfo
) {
await tryExecute(async () => {
const versionFile = join(workspaceDir, '.version.json');
await writeFile(versionFile, JSON.stringify(version, null, 2));
}, 'Failed to save ComfyUI version info');
}
async function shouldUpdateComfyUI(workspaceDir: string) {
const current = await getCurrentComfyUIVersion(workspaceDir);
const latest = await getLatestComfyUIVersion();
if (!current || !latest) {
return false;
}
return current.sha !== latest.sha;
}
let comfyUIProcess: ChildProcess | null = null;
function getPythonPath(workspaceDir: string) {
const isWindows = platform === 'win32';
const pythonExecutable = isWindows ? 'python.exe' : 'python';
const scriptsDir = isWindows ? 'Scripts' : 'bin';
return join(workspaceDir, '.venv', scriptsDir, pythonExecutable);
}
on('SIGINT', () => {
void stopFrontend();
});
on('SIGTERM', () => {
void stopFrontend();
});
async function shouldForceCPUMode() {
try {
const gpuInfo = await detectGPU();
const { hasAMD, hasNVIDIA } = gpuInfo;
const isWindows = platform === 'win32';
return (hasAMD && isWindows) || (!hasAMD && !hasNVIDIA);
} catch {
return true;
}
}
async function getPyTorchInstallArgs(pythonPath: string) {
const args = [
'pip',
'install',
'--python',
pythonPath,
'torch',
'torchvision',
'torchaudio',
];
try {
const gpuInfo = await detectGPU();
const { hasAMD, hasNVIDIA } = gpuInfo;
const isWindows = platform === 'win32';
if (hasAMD && !isWindows) {
sendKoboldOutput(
'AMD GPU detected, installing PyTorch with ROCm support...'
);
args.push('--index-url', 'https://download.pytorch.org/whl/rocm6.4');
} else if (hasNVIDIA) {
sendKoboldOutput(
'NVIDIA GPU detected, installing PyTorch with CUDA support...'
);
args.push('--extra-index-url', 'https://download.pytorch.org/whl/cu121');
} else {
if (hasAMD && isWindows) {
sendKoboldOutput(
'AMD GPU detected on Windows, installing CPU PyTorch (ROCm not available on Windows)...'
);
} else {
sendKoboldOutput(
'No dedicated GPU detected, installing CPU-only PyTorch...'
);
}
args.push('--index-url', 'https://download.pytorch.org/whl/cpu');
}
} catch {
sendKoboldOutput('Could not detect GPU, installing default PyTorch...');
}
return args;
}
async function downloadAndExtractComfyUI(workspaceDir: string) {
sendKoboldOutput('Downloading ComfyUI...');
const response = await fetch(GITHUB_API.COMFYUI_DOWNLOAD_URL);
if (!response.ok) {
throw new Error(`Failed to download ComfyUI: ${response.statusText}`);
}
const zipPath = join(workspaceDir, 'comfyui.zip');
const stream = createWriteStream(zipPath);
if (response.body) {
const reader = response.body.getReader();
const pump = async (): Promise<void> => {
const { done, value } = await reader.read();
if (done) {
stream.end();
return;
}
stream.write(Buffer.from(value));
return pump();
};
await pump();
await new Promise<void>((resolve, reject) => {
stream.on('finish', resolve);
stream.on('error', reject);
});
}
const zipStats = await stat(zipPath);
sendKoboldOutput(
`ComfyUI downloaded (${Math.round(zipStats.size / 1024 / 1024)}MB), extracting...`
);
await new Promise<void>((resolve, reject) => {
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
if (err) {
reject(err);
return;
}
zipfile!.readEntry();
zipfile!.on('entry', (entry) => {
const entryPath = entry.fileName;
const destPath = join(workspaceDir, entryPath.replace(/^[^/]+\//, ''));
if (/\/$/.test(entryPath)) {
mkdir(destPath, { recursive: true })
.then(() => zipfile!.readEntry())
.catch(reject);
} else {
zipfile!.openReadStream(entry, (err, readStream) => {
if (err) {
reject(err);
return;
}
mkdir(dirname(destPath), { recursive: true })
.then(() => {
const writeStream = createWriteStream(destPath);
readStream!.pipe(writeStream);
writeStream.on('close', () => zipfile!.readEntry());
writeStream.on('error', reject);
})
.catch(reject);
});
}
});
zipfile!.on('end', () => {
unlink(zipPath)
.then(() => resolve())
.catch(reject);
});
});
});
sendKoboldOutput('ComfyUI extracted successfully');
const latestVersion = await getLatestComfyUIVersion();
if (latestVersion) {
await saveComfyUIVersion(workspaceDir, latestVersion);
}
}
async function installDependencies(
workspaceDir: string,
env: Record<string, string | undefined>
) {
sendKoboldOutput('Installing ComfyUI dependencies...');
const requirementsPath = join(workspaceDir, 'requirements.txt');
const requirementsExists = await access(requirementsPath)
.then(() => true)
.catch(() => false);
if (!requirementsExists) {
throw new Error('requirements.txt not found in ComfyUI directory');
}
const pythonPath = getPythonPath(workspaceDir);
const torchInstallArgs = await getPyTorchInstallArgs(pythonPath);
const torchInstallProcess = spawn('uv', torchInstallArgs, {
stdio: 'pipe',
env,
});
return new Promise<void>((resolve, reject) => {
torchInstallProcess.on('exit', (torchCode: number | null) => {
if (torchCode === 0) {
sendKoboldOutput(
'PyTorch installed successfully, installing remaining dependencies...'
);
const pipInstallProcess = spawn(
'uv',
[
'pip',
'install',
'--python',
getPythonPath(workspaceDir),
'-r',
requirementsPath,
],
{
stdio: 'pipe',
env,
}
);
pipInstallProcess.on('exit', (pipCode: number | null) => {
if (pipCode === 0) {
sendKoboldOutput('ComfyUI dependencies installed successfully');
const verifyProcess = spawn(
getPythonPath(workspaceDir),
[
'-c',
'import torch; print("PyTorch version:", torch.__version__)',
],
{
stdio: 'pipe',
env,
}
);
verifyProcess.on('exit', (verifyCode: number | null) => {
if (verifyCode === 0) {
sendKoboldOutput('ComfyUI installation verified successfully');
resolve();
} else {
reject(
new Error(
`ComfyUI verification failed with code ${verifyCode}`
)
);
}
});
verifyProcess.on('error', (error) => {
reject(error);
});
if (verifyProcess.stdout) {
verifyProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (verifyProcess.stderr) {
verifyProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(
`Verify Error: ${data.toString('utf8')}`,
true
);
});
}
} else {
reject(
new Error(`Dependency installation failed with code ${pipCode}`)
);
}
});
pipInstallProcess.on('error', (error) => {
reject(error);
});
if (pipInstallProcess.stdout) {
pipInstallProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (pipInstallProcess.stderr) {
pipInstallProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
} else {
reject(new Error(`PyTorch installation failed with code ${torchCode}`));
}
});
torchInstallProcess.on('error', (error) => {
reject(error);
});
if (torchInstallProcess.stdout) {
torchInstallProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (torchInstallProcess.stderr) {
torchInstallProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
});
}
async function installComfyUI(workspaceDir: string) {
const env = await getUvEnvironment();
sendKoboldOutput('Creating virtual environment...');
const installProcess = spawn(
'uv',
['venv', '--python', '3.11', join(workspaceDir, '.venv')],
{
stdio: 'pipe',
env,
}
);
return new Promise<void>((resolve, reject) => {
installProcess.on('exit', async (code) => {
if (code === 0) {
try {
await downloadAndExtractComfyUI(workspaceDir);
await installDependencies(workspaceDir, env);
resolve();
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
} else {
reject(
new Error(`Virtual environment creation failed with code ${code}`)
);
}
});
installProcess.on('error', (error) => {
reject(error);
});
if (installProcess.stdout) {
installProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (installProcess.stderr) {
installProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
});
}
async function updateComfyUI(workspaceDir: string) {
const backupDir = join(workspaceDir, 'backup');
await ensureDir(backupDir);
const filesToBackup = ['main.py', 'requirements.txt'];
const backupPromises = filesToBackup.map(async (file) => {
const srcPath = join(workspaceDir, file);
const destPath = join(backupDir, file);
try {
await copyFile(srcPath, destPath);
} catch (error) {
void error;
}
});
await Promise.all(backupPromises);
try {
await downloadAndExtractComfyUI(workspaceDir);
sendKoboldOutput('ComfyUI updated successfully');
await rm(backupDir, { recursive: true, force: true });
} catch (error) {
sendKoboldOutput('Update failed, restoring backup...');
const restorePromises = filesToBackup.map(async (file) => {
const srcPath = join(backupDir, file);
const destPath = join(workspaceDir, file);
try {
await copyFile(srcPath, destPath);
} catch (error) {
void error;
}
});
await Promise.all(restorePromises);
await rm(backupDir, { recursive: true, force: true });
throw error;
}
}
async function ensureComfyUIInstalled() {
const installDir = getInstallDir();
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
await ensureDir(comfyUIWorkspace);
const comfyUIMainPath = join(comfyUIWorkspace, 'main.py');
const isComfyUIInstalled = await access(comfyUIMainPath)
.then(() => true)
.catch(() => false);
const needsUpdate =
isComfyUIInstalled && (await shouldUpdateComfyUI(comfyUIWorkspace));
if (!isComfyUIInstalled) {
sendKoboldOutput('ComfyUI not found, installing...');
await installComfyUI(comfyUIWorkspace);
} else if (needsUpdate) {
sendKoboldOutput('ComfyUI update available, updating...');
await updateComfyUI(comfyUIWorkspace);
} else {
sendKoboldOutput('ComfyUI found in workspace and up to date');
}
}
async function waitForComfyUIToStart() {
return new Promise<void>((resolve, reject) => {
const checkForOutput = (data: Buffer) => {
const output = data.toString();
if (output.includes(SERVER_READY_SIGNALS.COMFYUI)) {
sendKoboldOutput('ComfyUI is now running!');
resolve();
if (comfyUIProcess?.stdout) {
comfyUIProcess.stdout.removeListener('data', checkForOutput);
}
}
};
if (comfyUIProcess?.stdout) {
comfyUIProcess.stdout.on('data', checkForOutput);
} else {
reject(new Error('ComfyUI process stdout not available'));
}
});
}
export async function startFrontend(args: string[]) {
try {
const config = {
name: 'comfyui',
port: COMFYUI.PORT,
};
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
sendKoboldOutput(`Starting ComfyUI frontend on port ${config.port}...`);
await ensureComfyUIInstalled();
const installDir = getInstallDir();
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
const pythonPath = getPythonPath(comfyUIWorkspace);
const comfyUIArgs = [
join(comfyUIWorkspace, 'main.py'),
'--port',
config.port.toString(),
'--enable-cors-header',
];
const forceCPU = await shouldForceCPUMode();
if (forceCPU) {
comfyUIArgs.push('--cpu');
sendKoboldOutput(
'Forcing ComfyUI to use CPU mode (no compatible GPU acceleration available)'
);
}
if (koboldHost && koboldPort) {
sendKoboldOutput(
`Connecting to KoboldCpp at ${koboldHost}:${koboldPort}`
);
}
sendKoboldOutput(`Running ComfyUI with Python: ${pythonPath}`);
sendKoboldOutput(`ComfyUI args: ${comfyUIArgs.join(' ')}`);
comfyUIProcess = spawn(pythonPath, comfyUIArgs, {
cwd: comfyUIWorkspace,
stdio: 'pipe',
});
const version = await app.getVersion();
sendKoboldOutput(`ComfyUI started via Gerbil v${version}`);
if (comfyUIProcess.stdout) {
comfyUIProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (comfyUIProcess.stderr) {
comfyUIProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
comfyUIProcess.on('exit', (code: number | null, signal: string | null) => {
if (code === null && signal) {
sendKoboldOutput(`ComfyUI process terminated by signal: ${signal}`);
} else {
sendKoboldOutput(`ComfyUI process exited with code: ${code}`);
}
comfyUIProcess = null;
});
comfyUIProcess.on('error', (error: Error) => {
logError('ComfyUI process error', error);
comfyUIProcess = null;
});
await waitForComfyUIToStart();
} catch (error) {
logError('Failed to start ComfyUI', error as Error);
throw error;
}
}
export const stopFrontend = () => terminateProcess(comfyUIProcess);

View file

@ -1,20 +1,20 @@
import { spawn, ChildProcess } from 'child_process';
import { readFile, writeFile, copyFile } from 'fs/promises';
import { join } from 'path';
import { terminateProcess } from '@/utils/node/process';
import { logError, tryExecute, safeExecute } from '@/utils/node/logging';
import { sendKoboldOutput } from '../window';
import { logError, safeExecute } from '@/utils/node/logging';
import { sendKoboldOutput } from '@/main/modules/window';
import { SERVER_READY_SIGNALS } from '@/constants';
import { KLITE_CSS_OVERRIDE } from '@/constants/patches';
import { pathExists } from '@/utils/node/fs';
import { parseKoboldConfig } from '@/utils/node/kobold';
import { getAssetPath } from '@/utils/node/path';
import { getCurrentVersion } from './version';
import { getCurrentKoboldBinary, get as getConfig } from '../config';
import { getCurrentVersion } from '../version';
import {
getCurrentKoboldBinary,
get as getConfig,
} from '@/main/modules/config';
import { startFrontend as startSillyTavernFrontend } from '@/main/modules/sillytavern';
import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui';
import { startFrontend as startComfyUIFrontend } from '@/main/modules/comfyui';
import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches';
import { startProxy, stopProxy } from '../proxy';
import type {
FrontendPreference,
ImageGenerationFrontendPreference,
@ -22,70 +22,6 @@ import type {
let koboldProcess: ChildProcess | null = null;
const patchKliteEmbd = (unpackedDir: string) =>
tryExecute(async () => {
const possiblePaths = [
join(unpackedDir, '_internal', 'embd_res', 'klite.embd'),
join(unpackedDir, 'klite.embd'),
];
let kliteEmbdPath: string | null = null;
for (const path of possiblePaths) {
if (await pathExists(path)) {
kliteEmbdPath = path;
break;
}
}
if (!kliteEmbdPath) {
return;
}
const content = await readFile(kliteEmbdPath, 'utf8');
if (content.includes('</head>')) {
let patchedContent = content;
if (content.includes('gerbil-css-override')) {
patchedContent = patchedContent.replace(
/<style id="gerbil-css-override">[\s\S]*?<\/style>\s*/g,
''
);
}
if (content.includes('gerbil-autoscroll-patches')) {
patchedContent = patchedContent.replace(
/<script id="gerbil-autoscroll-patches">[\s\S]*?<\/script>\s*/g,
''
);
}
patchedContent = patchedContent.replace(
'</head>',
`${KLITE_CSS_OVERRIDE}\n</head>`
);
await writeFile(kliteEmbdPath, patchedContent, 'utf8');
}
}, 'Failed to patch klite.embd');
const patchKcppSduiEmbd = (unpackedDir: string) =>
tryExecute(async () => {
const possiblePaths = [
join(unpackedDir, '_internal', 'embd_res', 'kcpp_sdui.embd'),
join(unpackedDir, 'kcpp_sdui.embd'),
];
const sourceAssetPath = getAssetPath('kcpp_sdui.embd');
for (const targetPath of possiblePaths) {
if (await pathExists(targetPath)) {
await copyFile(sourceAssetPath, targetPath);
break;
}
}
}, 'Failed to patch kcpp_sdui.embd');
export async function launchKoboldCpp(
args: string[] = [],
frontendPreference: FrontendPreference = 'koboldcpp',
@ -129,6 +65,9 @@ export async function launchKoboldCpp(
}
const finalArgs = [...args];
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
await startProxy(koboldHost, koboldPort);
const child = spawn(currentVersion.path, finalArgs, {
stdio: ['pipe', 'pipe', 'pipe'],
@ -144,7 +83,7 @@ export async function launchKoboldCpp(
let readyResolve:
| ((value: { success: boolean; pid?: number; error?: string }) => void)
| null = null;
let _readyReject: ((error: Error) => void) | null = null;
let readyReject: ((error: Error) => void) | null = null;
let isReady = false;
const readyPromise = new Promise<{
@ -153,12 +92,15 @@ export async function launchKoboldCpp(
error?: string;
}>((resolve, reject) => {
readyResolve = resolve;
_readyReject = reject;
readyReject = reject;
});
child.stdout?.on('data', (data) => {
const output = data.toString();
sendKoboldOutput(output, true);
const filtered = filterSpam(output);
if (filtered.trim()) {
sendKoboldOutput(filtered, true);
}
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
isReady = true;
@ -168,7 +110,10 @@ export async function launchKoboldCpp(
child.stderr?.on('data', (data) => {
const output = data.toString();
sendKoboldOutput(output, true);
const filtered = filterSpam(output);
if (filtered.trim()) {
sendKoboldOutput(filtered, true);
}
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
isReady = true;
@ -188,7 +133,7 @@ export async function launchKoboldCpp(
koboldProcess = null;
if (!isReady) {
_readyReject?.(
readyReject?.(
new Error(
`Process exited before ready signal (code: ${code}, signal: ${signal})`
)
@ -203,7 +148,7 @@ export async function launchKoboldCpp(
koboldProcess = null;
if (!isReady) {
_readyReject?.(error);
readyReject?.(error);
}
});
@ -215,17 +160,23 @@ export async function launchKoboldCpp(
}
}
export const stopKoboldCpp = () => terminateProcess(koboldProcess);
export async function stopKoboldCpp() {
await stopProxy();
return terminateProcess(koboldProcess);
}
export const launchKoboldCppWithCustomFrontends = async (args: string[] = []) =>
safeExecute(async () => {
const frontendPreference = (await getConfig(
'frontendPreference'
)) as FrontendPreference;
const [frontendPreference, imageGenerationFrontendPreference] =
(await Promise.all([
getConfig('frontendPreference'),
getConfig('imageGenerationFrontendPreference'),
])) as [
FrontendPreference,
ImageGenerationFrontendPreference | undefined,
];
const imageGenerationFrontendPreference = (await getConfig(
'imageGenerationFrontendPreference'
)) as ImageGenerationFrontendPreference | undefined;
const { isTextMode } = parseKoboldConfig(args);
const result = await launchKoboldCpp(
args,
@ -233,8 +184,6 @@ export const launchKoboldCppWithCustomFrontends = async (args: string[] = []) =>
imageGenerationFrontendPreference
);
const { isImageMode, isTextMode } = parseKoboldConfig(args);
if (
frontendPreference === 'koboldcpp' ||
(!isTextMode && imageGenerationFrontendPreference === 'builtin')
@ -246,8 +195,6 @@ export const launchKoboldCppWithCustomFrontends = async (args: string[] = []) =>
startSillyTavernFrontend(args);
} else if (frontendPreference === 'openwebui') {
startOpenWebUIFrontend(args);
} else if (frontendPreference === 'comfyui' && isImageMode) {
startComfyUIFrontend(args);
}
return result;

View file

@ -0,0 +1,149 @@
import { readFile, writeFile, copyFile } from 'fs/promises';
import { join } from 'path';
import { tryExecute } from '@/utils/node/logging';
import { pathExists } from '@/utils/node/fs';
import { getAssetPath } from '@/utils/node/path';
const KLITE_CSS_OVERRIDE = `
<style id="gerbil-css-override">
* {
transition: 100ms ease all;
}
.maincontainer {
padding-right: 0 !important;
padding-left: 0 !important;
}
.adaptivecontainer {
width: 100% !important;
}
#lastreq1 {
margin: 0 10px;
}
#inputrow {
padding: 0 10px;
}
#navbarNavDropdown {
padding: 0;
}
#actionmenuitems {
margin-left: 10px;
}
#inputrow > :nth-child(1) {
padding-right: 0 !important;
}
#inputrow.show_mode > :nth-child(1) {
flex: 0 0 70px;
margin-right: 4px;
}
#inputrow > :nth-child(3) {
flex: 0 0 70px;
padding-right: 0 !important;
}
#inputrow.show_mode > :nth-child(3) button {
background-color: #129c00;
font-size: 14px;
}
#inputrow.show_mode > :nth-child(3) button:hover {
background-color: #058105;
}
</style>`;
export const patchKliteEmbd = (unpackedDir: string) =>
tryExecute(async () => {
const possiblePaths = [
join(unpackedDir, '_internal', 'embd_res', 'klite.embd'),
join(unpackedDir, 'klite.embd'),
];
let kliteEmbdPath: string | null = null;
for (const path of possiblePaths) {
if (await pathExists(path)) {
kliteEmbdPath = path;
break;
}
}
if (!kliteEmbdPath) {
return;
}
const content = await readFile(kliteEmbdPath, 'utf8');
if (content.includes('</head>')) {
let patchedContent = content;
if (content.includes('gerbil-css-override')) {
patchedContent = patchedContent.replace(
/<style id="gerbil-css-override">[\s\S]*?<\/style>\s*/g,
''
);
}
if (content.includes('gerbil-autoscroll-patches')) {
patchedContent = patchedContent.replace(
/<script id="gerbil-autoscroll-patches">[\s\S]*?<\/script>\s*/g,
''
);
}
patchedContent = patchedContent.replace(
'</head>',
`${KLITE_CSS_OVERRIDE}\n</head>`
);
await writeFile(kliteEmbdPath, patchedContent, 'utf8');
}
}, 'Failed to patch klite.embd');
export const patchKcppSduiEmbd = (unpackedDir: string) =>
tryExecute(async () => {
const possiblePaths = [
join(unpackedDir, '_internal', 'embd_res', 'kcpp_sdui.embd'),
join(unpackedDir, 'kcpp_sdui.embd'),
];
const sourceAssetPath = getAssetPath('kcpp_sdui.embd');
for (const targetPath of possiblePaths) {
if (await pathExists(targetPath)) {
await copyFile(sourceAssetPath, targetPath);
break;
}
}
}, 'Failed to patch kcpp_sdui.embd');
export function filterSpam(output: string) {
const spamPatterns = [
/^print_info:/,
/^llama_model_load_from_file_impl:/,
/^llama_model_loader:/,
/^init_tokenizer:/,
/^load:/,
/^load_tensors:/,
/^llama_context:/,
/^llama_kv_cache/,
/^set_abort_callback:/,
/^attach_threadpool:/,
/^ggml_vulkan:/,
/^Namespace\(/,
/^==========$/,
/^Loading Chat Completions Adapter:/,
/^Chat Completions Adapter Loaded$/,
/^Chat completion heuristic:/,
/^Embedded .* loaded\.$/,
];
return output
.split('\n')
.filter((line) => !spamPatterns.some((pattern) => pattern.test(line)))
.map((line) => line.replace(/^Welcome to KoboldCpp - Version/, 'Version'))
.join('\n');
}

View file

@ -0,0 +1,107 @@
import http from 'http';
import type { IncomingMessage, ServerResponse } from 'http';
import { logError } from '@/utils/node/logging';
import { sendKoboldOutput } from '../window';
import { PROXY } from '@/constants/proxy';
let proxyServer: http.Server | null = null;
let koboldCppHost = 'localhost';
let koboldCppPort = 5001;
const replaceKoboldWithGerbil = (data: string) => {
try {
return data.replace(/"koboldcpp\//g, '"gerbil/');
} catch {
return data;
}
};
const proxyRequest = (
clientReq: IncomingMessage,
clientRes: ServerResponse
) => {
const options = {
hostname: koboldCppHost,
port: koboldCppPort,
path: clientReq.url,
method: clientReq.method,
headers: clientReq.headers,
};
const proxyReq = http.request(options, (proxyRes) => {
const isJson =
proxyRes.headers['content-type']?.includes('application/json');
if (isJson) {
let body = '';
proxyRes.on('data', (chunk) => {
body += chunk.toString();
});
proxyRes.on('end', () => {
const modifiedBody = replaceKoboldWithGerbil(body);
clientRes.writeHead(proxyRes.statusCode || 200, {
...proxyRes.headers,
'content-length': Buffer.byteLength(modifiedBody),
});
clientRes.end(modifiedBody);
});
} else {
clientRes.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
proxyRes.pipe(clientRes);
}
});
proxyReq.on('error', (error) => {
logError(`Proxy request error: ${error.message}`, error);
if (!clientRes.headersSent) {
clientRes.writeHead(502);
clientRes.end('Bad Gateway');
}
});
clientReq.pipe(proxyReq);
};
export const startProxy = (targetHost: string, targetPort: number) =>
new Promise<void>((resolve, reject) => {
if (proxyServer) {
resolve();
return;
}
koboldCppHost = targetHost;
koboldCppPort = targetPort;
proxyServer = http.createServer((req, res) => {
proxyRequest(req, res);
});
proxyServer.on('error', (error) => {
logError(`Proxy server error: ${error.message}`, error);
reject(error);
});
proxyServer.listen(PROXY.PORT, PROXY.HOST, () => {
sendKoboldOutput(`Proxy server started on port ${PROXY.PORT}`);
resolve();
});
});
export const stopProxy = () =>
new Promise<void>((resolve) => {
if (!proxyServer) {
resolve();
return;
}
proxyServer.close(() => {
proxyServer = null;
resolve();
});
});
export const isProxyRunning = () => proxyServer !== null;

View file

@ -11,6 +11,7 @@ import { OPENWEBUI, SERVER_READY_SIGNALS } from '@/constants';
import { terminateProcess } from '@/utils/node/process';
import { parseKoboldConfig } from '@/utils/node/kobold';
import { getUvEnvironment } from './dependencies';
import { PROXY } from '@/constants/proxy';
let openWebUIProcess: ChildProcess | null = null;
@ -79,6 +80,7 @@ export async function startFrontend(args: string[]) {
);
const koboldUrl = `http://${koboldHost}:${koboldPort}`;
const proxyUrl = PROXY.URL;
const openWebUIArgs = [
...OPENWEBUI_BASE_ARGS,
@ -94,7 +96,7 @@ export async function startFrontend(args: string[]) {
const openWebUIDataDir = join(installDir, 'openwebui-data');
const envConfig: Record<string, string> = {
OPENAI_API_BASE_URL: `${koboldUrl}/v1`,
OPENAI_API_BASE_URL: `${proxyUrl}/v1`,
DATA_DIR: openWebUIDataDir,
WEBUI_AUTH: 'false',
WEBUI_SECRET_KEY: 'gerbil',
@ -150,7 +152,7 @@ export async function startFrontend(args: string[]) {
sendKoboldOutput(`Open WebUI is ready and auto-configured!`);
sendKoboldOutput(`Access Open WebUI at: http://localhost:${config.port}`);
sendKoboldOutput(`Text Generation: ${koboldUrl}/v1 (auto-configured)`);
sendKoboldOutput(`Text Generation: ${proxyUrl}/v1 (auto-configured)`);
if (isImageMode) {
sendKoboldOutput(`Image Generation: ${koboldUrl} (auto-configured)`);
}

View file

@ -8,6 +8,7 @@ import type { ChildProcess } from 'child_process';
import { logError, tryExecute } from '@/utils/node/logging';
import { sendKoboldOutput } from './window';
import { SILLYTAVERN, SERVER_READY_SIGNALS } from '@/constants';
import { PROXY } from '@/constants/proxy';
import { terminateProcess } from '@/utils/node/process';
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/node/fs';
import { parseKoboldConfig } from '@/utils/node/kobold';
@ -97,26 +98,17 @@ async function ensureSillyTavernSettings() {
let hasResolved = false;
initProcess.on('exit', (code: number | null, signal: string | null) => {
sendKoboldOutput(
signal
? `SillyTavern init process terminated with signal ${signal}`
: `SillyTavern init process exited with code ${code}`
);
if (!hasResolved) {
hasResolved = true;
if (code !== 0) {
const errorMsg =
code === 4294963214
? 'SillyTavern failed to install due to EBUSY error (resource busy or locked). This is a critical error.'
: `SillyTavern initialization failed with exit code ${code}`;
const errorMsg = signal
? `SillyTavern init terminated with signal ${signal}`
: `SillyTavern initialization failed with exit code ${code}`;
logError('SillyTavern initialization failed:', new Error(errorMsg));
sendKoboldOutput(`CRITICAL ERROR: ${errorMsg}`);
logError(errorMsg, new Error(errorMsg));
reject(new Error(errorMsg));
} else {
sendKoboldOutput('SillyTavern settings should now be generated');
resolve();
}
}
@ -125,8 +117,7 @@ async function ensureSillyTavernSettings() {
initProcess.on('error', (error) => {
if (!hasResolved) {
hasResolved = true;
logError('Failed to initialize SillyTavern settings:', error);
sendKoboldOutput(`SillyTavern initialization error: ${error.message}`);
logError('SillyTavern initialization error', error);
reject(error);
}
});
@ -159,11 +150,7 @@ async function ensureSillyTavernSettings() {
});
}
async function setupSillyTavernConfig(
koboldHost: string,
koboldPort: number,
isImageMode: boolean
) {
async function setupSillyTavernConfig(isImageMode: boolean) {
const success = await tryExecute(async () => {
const configPath = getSillyTavernSettingsPath();
let settings: Record<string, unknown> = {};
@ -181,23 +168,12 @@ async function setupSillyTavernConfig(
}
}
const koboldUrl = `http://${koboldHost}:${koboldPort}`;
const proxyUrl = PROXY.URL;
if (!settings.power_user) settings.power_user = {};
const powerUser = settings.power_user as Record<string, unknown>;
powerUser.auto_connect = true;
if (isImageMode) {
sendKoboldOutput(
`Image generation mode detected. Please configure SillyTavern manually:\n` +
`1. Open SillyTavern and navigate to Settings (top-right gear icon)\n` +
`2. Go to 'Extensions' tab and enable 'Image Generation'\n` +
`3. In Image Generation settings, set Source to 'Stable Diffusion WebUI (AUTOMATIC1111)'\n` +
`4. Set API URL to: ${koboldUrl}\n` +
`5. Click 'Connect' to test the connection`
);
}
if (!settings.textgenerationwebui_settings)
settings.textgenerationwebui_settings = {};
const textgenSettings = settings.textgenerationwebui_settings as Record<
@ -207,15 +183,26 @@ async function setupSillyTavernConfig(
if (!textgenSettings.server_urls) textgenSettings.server_urls = {};
const serverUrls = textgenSettings.server_urls as Record<string, unknown>;
serverUrls.koboldcpp = koboldUrl;
serverUrls.koboldcpp = proxyUrl;
settings.main_api = 'textgenerationwebui';
textgenSettings.type = 'koboldcpp';
sendKoboldOutput(
`Configured SillyTavern for text generation at ${koboldUrl}`
`Configured SillyTavern for text generation at ${proxyUrl}`
);
if (isImageMode) {
sendKoboldOutput(
`Image generation mode detected. Configure SillyTavern manually:\n` +
`1. Open SillyTavern Settings (top-right gear icon)\n` +
`2. Go to 'Extensions' tab and enable 'Image Generation'\n` +
`3. Set Source to 'Stable Diffusion WebUI (AUTOMATIC1111)'\n` +
`4. Set API URL to: ${proxyUrl}/sdui\n` +
`5. Click 'Connect' to test the connection`
);
}
await writeJsonFile(configPath, settings);
sendKoboldOutput(`SillyTavern configuration updated successfully!`);
@ -251,7 +238,7 @@ async function waitForSillyTavernToStart(_port: number) {
});
}
function createProxyServer(targetPort: number, proxyPort: number) {
const createProxyServer = (targetPort: number, proxyPort: number) => {
proxyServer = createServer((req, res) => {
const options = {
hostname: 'localhost',
@ -278,15 +265,13 @@ function createProxyServer(targetPort: number, proxyPort: number) {
});
proxyServer.listen(proxyPort, () => {
sendKoboldOutput(
`Proxy server started on port ${proxyPort}, forwarding to SillyTavern on port ${targetPort}`
);
sendKoboldOutput(`SillyTavern CORS proxy started on port ${proxyPort}`);
});
proxyServer.on('error', (err) => {
logError('Proxy server error:', err);
logError('SillyTavern proxy error:', err);
});
}
};
export async function startFrontend(args: string[]) {
try {
@ -295,20 +280,14 @@ export async function startFrontend(args: string[]) {
port: SILLYTAVERN.PORT,
proxyPort: SILLYTAVERN.PROXY_PORT,
};
const {
host: koboldHost,
port: koboldPort,
isImageMode,
} = parseKoboldConfig(args);
const { isImageMode } = parseKoboldConfig(args);
await stopFrontend();
sendKoboldOutput(
`Preparing SillyTavern to connect at ${koboldHost}:${koboldPort}...`
);
sendKoboldOutput(`Preparing SillyTavern to connect via proxy...`);
await ensureSillyTavernSettings();
await setupSillyTavernConfig(koboldHost, koboldPort, isImageMode);
await setupSillyTavernConfig(isImageMode);
sendKoboldOutput(
`Starting ${config.name} frontend on port ${config.port}...`
@ -320,8 +299,6 @@ export async function startFrontend(args: string[]) {
config.port.toString(),
];
sendKoboldOutput('Final port check before starting SillyTavern...');
sillyTavernProcess = await createNpxProcess(sillyTavernArgs);
if (sillyTavernProcess.stdout) {

View file

@ -12,11 +12,7 @@ export type ChatMode = 'text' | 'image';
export type SdConvDirectMode = 'off' | 'vaeonly' | 'full';
export type FrontendPreference =
| 'koboldcpp'
| 'sillytavern'
| 'openwebui'
| 'comfyui';
export type FrontendPreference = 'koboldcpp' | 'sillytavern' | 'openwebui';
export type ImageGenerationFrontendPreference = 'match' | 'builtin';

View file

@ -3,7 +3,8 @@ import type {
InterfaceTab,
ImageGenerationFrontendPreference,
} from '@/types';
import { FRONTENDS, SILLYTAVERN, OPENWEBUI, COMFYUI } from '@/constants';
import { FRONTENDS, SILLYTAVERN, OPENWEBUI } from '@/constants';
import { PROXY } from '@/constants/proxy';
export interface InterfaceOption {
value: InterfaceTab | 'eject';
@ -58,12 +59,7 @@ export function getAvailableInterfaceOptions({
}
if (isImageGenerationMode) {
if (effectiveImageFrontend === 'comfyui') {
chatItems.push({
value: 'chat-image',
label: FRONTENDS.COMFYUI,
});
} else if (effectiveImageFrontend === 'koboldcpp') {
if (effectiveImageFrontend === 'koboldcpp') {
chatItems.push({
value: 'chat-image',
label: FRONTENDS.STABLE_UI,
@ -102,10 +98,6 @@ export function getDefaultInterfaceTab({
return 'chat-text';
}
if (effectiveImageFrontend === 'comfyui' && isImageGenerationMode) {
return 'chat-image';
}
if (isTextMode) {
return 'chat-text';
}
@ -122,23 +114,25 @@ export interface ServerInterfaceInfo {
title: string;
}
export interface ServerInterfaceParams {
frontendPreference: FrontendPreference;
imageGenerationFrontendPreference?: ImageGenerationFrontendPreference;
isImageGenerationMode: boolean;
}
export function getServerInterfaceInfo({
frontendPreference,
imageGenerationFrontendPreference = 'match',
isImageGenerationMode,
serverUrl,
}: {
frontendPreference: FrontendPreference;
imageGenerationFrontendPreference?: ImageGenerationFrontendPreference;
isImageGenerationMode: boolean;
serverUrl: string;
}) {
}: ServerInterfaceParams) {
const proxyUrl = PROXY.URL;
if (
isImageGenerationMode &&
imageGenerationFrontendPreference === 'builtin'
) {
return {
url: `${serverUrl}/sdui`,
url: `${proxyUrl}/sdui`,
title: FRONTENDS.STABLE_UI,
};
}
@ -157,15 +151,8 @@ export function getServerInterfaceInfo({
};
}
if (frontendPreference === 'comfyui' && isImageGenerationMode) {
return {
url: COMFYUI.URL,
title: FRONTENDS.COMFYUI,
};
}
return {
url: isImageGenerationMode ? `${serverUrl}/sdui` : serverUrl,
url: isImageGenerationMode ? `${proxyUrl}/sdui` : proxyUrl,
title: isImageGenerationMode
? FRONTENDS.STABLE_UI
: FRONTENDS.KOBOLDAI_LITE,