prettify binary names, minor changes and improvements

This commit is contained in:
Egor 2025-09-03 17:55:56 -07:00
parent 1499445762
commit d7e09f2ea5
23 changed files with 112 additions and 113 deletions

View file

@ -8,7 +8,7 @@ A desktop app to easily run Large Language Models locally.
## Core Features ## Core Features
- **Run LLMs locally** powered by [KoboldCpp](https://github.com/LostRuins/koboldcpp) which itself is a highly modified fork of [llama.cpp](https://github.com/ggml-org/llama.cpp) - **Run LLMs locally** powered by a highly modified fork of [llama.cpp](https://github.com/ggml-org/llama.cpp)
- **Cross-platform desktop app** - Native support for Windows, macOS, and Linux (including Wayland) - **Cross-platform desktop app** - Native support for Windows, macOS, and Linux (including Wayland)
- **Automatic updates** - Download and keep your KoboldCpp binary up-to-date effortlessly - **Automatic updates** - Download and keep your KoboldCpp binary up-to-date effortlessly
- **Smart process management** - Prevents runaway background processes and system resource waste - **Smart process management** - Prevents runaway background processes and system resource waste
@ -150,8 +150,12 @@ You can use the CLI mode on Windows in exactly the same way as in the Linux/macO
yarn dev yarn dev
``` ```
## Future Considerations
- migrate the project to Tauri from Electron?
- transition to using llama.cpp binaries directly instead of running them indirectly through koboldcpp?
- seamless support for more frontends?
## License ## License
AGPL v3 License - see LICENSE file for details AGPL v3 License - see LICENSE file for details
# test

View file

@ -1,7 +1,7 @@
{ {
"name": "gerbil", "name": "gerbil",
"productName": "Gerbil", "productName": "Gerbil",
"version": "0.9.9", "version": "1.0.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": "./",

View file

@ -1,6 +1,5 @@
import { Checkbox, Group } from '@mantine/core'; import { Checkbox, Group } from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip'; import { InfoTooltip } from '@/components/InfoTooltip';
import styles from '@/styles/layout.module.css';
interface CheckboxWithTooltipProps { interface CheckboxWithTooltipProps {
checked: boolean; checked: boolean;
@ -17,7 +16,7 @@ export const CheckboxWithTooltip = ({
tooltip, tooltip,
disabled = false, disabled = false,
}: CheckboxWithTooltipProps) => ( }: CheckboxWithTooltipProps) => (
<div className={styles.minWidth200}> <div style={{ minWidth: '12.5rem' }}>
<Group gap="xs" align="center"> <Group gap="xs" align="center">
<Checkbox <Checkbox
checked={checked} checked={checked}

View file

@ -233,7 +233,7 @@ const COMMAND_LINE_ARGUMENTS: ArgumentInfo[] = [
{ {
flag: '--cli', flag: '--cli',
description: description:
'Does not launch KoboldCpp HTTP server. Instead, enables KoboldCpp from the command line, accepting interactive console input and displaying responses to the terminal.', 'Does not launch the HTTP server. Instead, enables input from the command line, accepting interactive console input and displaying responses to the terminal.',
type: 'boolean', type: 'boolean',
category: 'Advanced', category: 'Advanced',
}, },

View file

@ -12,7 +12,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Download } from 'lucide-react'; import { Download } from 'lucide-react';
import { MouseEvent } from 'react'; import { MouseEvent } from 'react';
import styles from '@/styles/layout.module.css'; import { pretifyBinName } from '@/utils/assets';
interface DownloadCardProps { interface DownloadCardProps {
name: string; name: string;
@ -126,10 +126,10 @@ export const DownloadCard = ({
})} })}
> >
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<div className={styles.flex1}> <div style={{ flex: 1 }}>
<Group gap="xs" align="center" mb="xs"> <Group gap="xs" align="center" mb="xs">
<Text fw={500} size="sm"> <Text fw={500} size="sm">
{name} {pretifyBinName(name)}
</Text> </Text>
{isCurrent && ( {isCurrent && (
<Badge variant="light" color="blue" size="sm"> <Badge variant="light" color="blue" size="sm">

View file

@ -2,7 +2,6 @@ import { Group, TextInput, Button } from '@mantine/core';
import { File, Search } from 'lucide-react'; import { File, Search } from 'lucide-react';
import { LabelWithTooltip } from '@/components/LabelWithTooltip'; import { LabelWithTooltip } from '@/components/LabelWithTooltip';
import { getInputValidationState } from '@/utils/validation'; import { getInputValidationState } from '@/utils/validation';
import styles from '@/styles/layout.module.css';
interface ModelFileFieldProps { interface ModelFileFieldProps {
label: string; label: string;
@ -41,7 +40,7 @@ export const ModelFileField = ({
<div> <div>
<LabelWithTooltip label={label} tooltip={tooltip} /> <LabelWithTooltip label={label} tooltip={tooltip} />
<Group gap="xs" align="flex-start"> <Group gap="xs" align="flex-start">
<div className={styles.flex1}> <div style={{ flex: 1 }}>
<TextInput <TextInput
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}

View file

@ -34,7 +34,7 @@ export const ServerTab = ({
> >
<Stack align="center" gap="md" mt="xl"> <Stack align="center" gap="md" mt="xl">
<Text c="dimmed" size="lg"> <Text c="dimmed" size="lg">
Waiting for the KoboldCpp server to start... Waiting for the server to start...
</Text> </Text>
<Text c="dimmed" size="sm"> <Text c="dimmed" size="sm">
The {isImageGenerationMode ? 'image generation' : 'chat'} interface The {isImageGenerationMode ? 'image generation' : 'chat'} interface

View file

@ -7,7 +7,6 @@ import {
useComputedColorScheme, useComputedColorScheme,
} from '@mantine/core'; } from '@mantine/core';
import { ChevronDown } from 'lucide-react'; import { ChevronDown } from 'lucide-react';
import styles from '@/styles/layout.module.css';
import { SERVER_READY_SIGNALS, TITLEBAR_HEIGHT } from '@/constants'; import { SERVER_READY_SIGNALS, TITLEBAR_HEIGHT } from '@/constants';
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal'; import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
import { useLaunchConfigStore } from '@/stores/launchConfig'; import { useLaunchConfigStore } from '@/stores/launchConfig';
@ -120,14 +119,19 @@ export const TerminalTab = ({
ref={scrollAreaRef} ref={scrollAreaRef}
viewportRef={viewportRef} viewportRef={viewportRef}
onScrollPositionChange={handleScroll} onScrollPositionChange={handleScroll}
className={styles.terminalScrollArea} style={{
flex: 1,
fontFamily: 'Monaco, Menlo, Ubuntu Mono, monospace',
fontSize: '0.875em',
lineHeight: 1.4,
}}
scrollbarSize={8} scrollbarSize={8}
offsetScrollbars={false} offsetScrollbars={false}
> >
<Box p="md"> <Box p="md">
{terminalContent.length === 0 ? ( {terminalContent.length === 0 ? (
<Text c="dimmed" style={{ fontFamily: 'inherit' }}> <Text c="dimmed" style={{ fontFamily: 'inherit' }}>
Starting KoboldCpp... Starting...
</Text> </Text>
) : ( ) : (
<div <div

View file

@ -217,7 +217,7 @@ export const AdvancedTab = () => {
<Text size="sm" fw={500}> <Text size="sm" fw={500}>
Additional arguments Additional arguments
</Text> </Text>
<InfoTooltip label="Additional command line arguments to pass to the KoboldCPP binary. Leave this empty if you don't know what they are." /> <InfoTooltip label="Additional command line arguments to pass to the binary. Leave this empty if you don't know what they are." />
</Group> </Group>
<Button <Button
size="xs" size="xs"

View file

@ -2,7 +2,6 @@ import { Stack, Text, Group, Button, Select } from '@mantine/core';
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { Save, File, Plus, Check } from 'lucide-react'; import { Save, File, Plus, Check } from 'lucide-react';
import type { ConfigFile } from '@/types'; import type { ConfigFile } from '@/types';
import styles from '@/styles/layout.module.css';
import { CreateConfigModal } from './CreateConfigModal'; import { CreateConfigModal } from './CreateConfigModal';
interface ConfigFileManagerProps { interface ConfigFileManagerProps {
@ -67,7 +66,7 @@ export const ConfigFileManager = ({
Configuration Configuration
</Text> </Text>
<Group gap="xs" align="flex-end"> <Group gap="xs" align="flex-end">
<div className={styles.flex1}> <div style={{ flex: 1 }}>
<Select <Select
placeholder="Select a configuration file" placeholder="Select a configuration file"
value={selectedFile} value={selectedFile}

View file

@ -35,7 +35,7 @@ export const NetworkTab = () => {
<Text size="sm" fw={500}> <Text size="sm" fw={500}>
Host Host
</Text> </Text>
<InfoTooltip label="The hostname or IP address on which KoboldCpp will bind its webserver to." /> <InfoTooltip label="The hostname or IP address to bind the webserver to." />
</Group> </Group>
<TextInput <TextInput
placeholder="localhost" placeholder="localhost"
@ -49,7 +49,7 @@ export const NetworkTab = () => {
<Text size="sm" fw={500}> <Text size="sm" fw={500}>
Port Port
</Text> </Text>
<InfoTooltip label="The port number on which KoboldCpp will listen for connections. Leave empty to use default port 5001." /> <InfoTooltip label="The port number on which the server will listen for connections. Leave empty to use default port 5001." />
</Group> </Group>
<TextInput <TextInput
placeholder="5001" placeholder="5001"
@ -105,7 +105,7 @@ export const NetworkTab = () => {
checked={remotetunnel} checked={remotetunnel}
onChange={handleRemotetunnelChange} onChange={handleRemotetunnelChange}
label="Remote Tunnel" label="Remote Tunnel"
tooltip="Creates a trycloudflare tunnel. Allows you to access koboldcpp from other devices over an internet URL." tooltip="Creates a trycloudflare tunnel. Allows you to access your server from other devices over an internet URL."
/> />
<CheckboxWithTooltip <CheckboxWithTooltip

View file

@ -9,9 +9,10 @@ import {
Group, Group,
List, List,
ThemeIcon, ThemeIcon,
Anchor, Image,
} from '@mantine/core'; } from '@mantine/core';
import { Check, ExternalLink } from 'lucide-react'; import { Check } from 'lucide-react';
import iconUrl from '/icon.png';
interface WelcomeScreenProps { interface WelcomeScreenProps {
onGetStarted: () => void; onGetStarted: () => void;
@ -23,9 +24,12 @@ export const WelcomeScreen = ({ onGetStarted }: WelcomeScreenProps) => (
<Card withBorder radius="md" shadow="sm" p="xl"> <Card withBorder radius="md" shadow="sm" p="xl">
<Stack gap="lg" align="center"> <Stack gap="lg" align="center">
<Stack gap="md" align="center"> <Stack gap="md" align="center">
<Title order={1} ta="center"> <Group gap="md" mr="xl" align="center">
{PRODUCT_NAME} <Image src={iconUrl} alt={PRODUCT_NAME} w={36} h={36} />
</Title> <Title order={1} ta="center">
{PRODUCT_NAME}
</Title>
</Group>
<Text size="lg" c="dimmed" ta="center" maw={600}> <Text size="lg" c="dimmed" ta="center" maw={600}>
Run Large Language Models locally Run Large Language Models locally
</Text> </Text>
@ -84,44 +88,9 @@ export const WelcomeScreen = ({ onGetStarted }: WelcomeScreenProps) => (
</Text> </Text>
</Stack> </Stack>
<Button size="lg" onClick={onGetStarted}> <Button size="lg" mt="lg" onClick={onGetStarted}>
Get Started Get Started
</Button> </Button>
<Group gap="lg" mt="md">
<Anchor
href="#"
onClick={(e) => {
e.preventDefault();
window.electronAPI.app.openExternal(
'https://github.com/LostRuins/koboldcpp'
);
}}
size="sm"
c="dimmed"
>
<Group gap={4} align="center">
<span>About KoboldCpp</span>
<ExternalLink size={12} />
</Group>
</Anchor>
<Anchor
href="#"
onClick={(e) => {
e.preventDefault();
window.electronAPI.app.openExternal(
'https://github.com/lone-cloud/gerbil'
);
}}
size="sm"
c="dimmed"
>
<Group gap={4} align="center">
<span>About Gerbil</span>
<ExternalLink size={12} />
</Group>
</Anchor>
</Group>
</Stack> </Stack>
</Card> </Card>
</Stack> </Stack>

View file

@ -10,8 +10,10 @@ import {
Badge, Badge,
Button, Button,
rem, rem,
ActionIcon,
Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { Github, FolderOpen } from 'lucide-react'; import { Github, FolderOpen, Copy } from 'lucide-react';
import { safeExecute } from '@/utils/logger'; import { safeExecute } from '@/utils/logger';
import type { VersionInfo } from '@/types/electron'; import type { VersionInfo } from '@/types/electron';
import { PRODUCT_NAME } from '@/constants'; import { PRODUCT_NAME } from '@/constants';
@ -50,6 +52,22 @@ export const AboutTab = () => {
}, },
]; ];
const copyVersionInfo = async () => {
const info = [
`${PRODUCT_NAME} v${versionInfo.appVersion}`,
`Electron: ${versionInfo.electronVersion}`,
`Node.js: ${versionInfo.nodeVersion}`,
`Chromium: ${versionInfo.chromeVersion}`,
`V8: ${versionInfo.v8Version}`,
`OS: ${versionInfo.platform} ${versionInfo.arch} (${versionInfo.osVersion})`,
].join('\n');
await safeExecute(
() => navigator.clipboard.writeText(info),
'Failed to copy version info'
);
};
return ( return (
<Stack gap="md"> <Stack gap="md">
<Card withBorder radius="md" p="xs"> <Card withBorder radius="md" p="xs">
@ -118,7 +136,23 @@ export const AboutTab = () => {
</Group> </Group>
</Card> </Card>
<Card withBorder radius="md" p="xs"> <Card withBorder radius="md" p="xs" style={{ position: 'relative' }}>
<Tooltip label="Copy info">
<ActionIcon
variant="subtle"
size="sm"
onClick={copyVersionInfo}
aria-label="Copy Info"
style={{
position: 'absolute',
top: 8,
right: 8,
zIndex: 1,
}}
>
<Copy style={{ width: rem(14), height: rem(14) }} />
</ActionIcon>
</Tooltip>
<Stack gap="xs"> <Stack gap="xs">
{versionItems.map((item, index) => ( {versionItems.map((item, index) => (
<Group key={index} gap="md" align="center" wrap="nowrap"> <Group key={index} gap="md" align="center" wrap="nowrap">

View file

@ -12,7 +12,6 @@ import {
Anchor, Anchor,
} from '@mantine/core'; } from '@mantine/core';
import { Folder, FolderOpen, Monitor } from 'lucide-react'; import { Folder, FolderOpen, Monitor } from 'lucide-react';
import styles from '@/styles/layout.module.css';
import type { FrontendPreference } from '@/types'; import type { FrontendPreference } from '@/types';
import { FRONTENDS } from '@/constants'; import { FRONTENDS } from '@/constants';
@ -39,7 +38,7 @@ export const GeneralTab = () => {
const frontendConfigs: FrontendConfig[] = useMemo( const frontendConfigs: FrontendConfig[] = useMemo(
() => [ () => [
{ value: 'koboldcpp', label: 'KoboldCpp (Built-in)' }, { value: 'koboldcpp', label: 'Built-in Interface' },
{ {
value: 'sillytavern', value: 'sillytavern',
label: FRONTENDS.SILLYTAVERN, label: FRONTENDS.SILLYTAVERN,
@ -195,7 +194,7 @@ export const GeneralTab = () => {
value={installDir} value={installDir}
readOnly readOnly
placeholder="Default installation directory" placeholder="Default installation directory"
className={styles.flex1} style={{ flex: 1 }}
leftSection={<Folder style={{ width: rem(16), height: rem(16) }} />} leftSection={<Folder style={{ width: rem(16), height: rem(16) }} />}
/> />
<Button <Button

View file

@ -283,7 +283,7 @@ export const useLaunchLogic = ({
); );
} }
} catch (err) { } catch (err) {
error('Error launching KoboldCpp:', err as Error); error('Error launching:', err as Error);
} finally { } finally {
setIsLaunching(false); setIsLaunching(false);
} }

View file

@ -77,7 +77,7 @@ const checkGpuWarnings = async (
warnings.push({ warnings.push({
type: 'warning', type: 'warning',
message: message:
'Your KoboldCpp binary supports CUDA and you have an NVIDIA GPU, but CUDA runtime is not detected on your system.', 'Your binary supports CUDA and you have an NVIDIA GPU, but CUDA runtime is not detected on your system.',
}); });
} }
@ -89,7 +89,7 @@ const checkGpuWarnings = async (
warnings.push({ warnings.push({
type: 'warning', type: 'warning',
message: message:
'Your KoboldCpp binary supports ROCm and you have an AMD GPU, but ROCm runtime is not detected on your system.', 'Your binary supports ROCm and you have an AMD GPU, but ROCm runtime is not detected on your system.',
}); });
} }

View file

@ -27,14 +27,14 @@ export class LightweightCliHandler {
if (!currentBinary) { if (!currentBinary) {
console.error( console.error(
'Error: No KoboldCpp binary found. Please run the GUI first to download KoboldCpp.' 'Error: No binary found. Please run the GUI first to download the binary.'
); );
process.exit(1); process.exit(1);
} }
if (!(await pathExists(currentBinary))) { if (!(await pathExists(currentBinary))) {
console.error(`Error: KoboldCpp binary not found at: ${currentBinary}`); console.error(`Error: Binary not found at: ${currentBinary}`);
console.error('Please run the GUI to download or reconfigure KoboldCpp.'); console.error('Please run the GUI to download and configure the binary.');
process.exit(1); process.exit(1);
} }
@ -75,12 +75,12 @@ export class LightweightCliHandler {
}); });
child.on('error', (error) => { child.on('error', (error) => {
console.error(`Failed to start KoboldCpp: ${error.message}`); console.error(`Failed to start: ${error.message}`);
reject(error); reject(error);
}); });
const handleSignal = async () => { const handleSignal = async () => {
console.log('\nReceived termination signal, terminating KoboldCpp...'); console.log('\nReceived termination signal, terminating...');
if (!child.killed) { if (!child.killed) {
await terminateProcess(child, { await terminateProcess(child, {
timeoutMs: 5000, timeoutMs: 5000,

View file

@ -77,7 +77,7 @@ export class KoboldCppManager {
try { try {
if (this.koboldProcess && !this.koboldProcess.killed) { if (this.koboldProcess && !this.koboldProcess.killed) {
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
'Stopping KoboldCpp process before update...' 'Stopping process before update...'
); );
await this.cleanup(); await this.cleanup();
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
@ -91,7 +91,7 @@ export class KoboldCppManager {
); );
throw new Error( throw new Error(
`Cannot update: Failed to remove existing installation. ` + `Cannot update: Failed to remove existing installation. ` +
`Please ensure KoboldCpp is stopped and try again. ` + `Please ensure the server is stopped and try again. ` +
`Error: ${(error as Error).message}` `Error: ${(error as Error).message}`
); );
} }
@ -182,7 +182,7 @@ export class KoboldCppManager {
} }
if (!launcherPath || !(await pathExists(launcherPath))) { if (!launcherPath || !(await pathExists(launcherPath))) {
throw new Error('Failed to find or create koboldcpp launcher'); throw new Error('Failed to find or create launcher');
} }
return launcherPath; return launcherPath;
@ -633,8 +633,8 @@ export class KoboldCppManager {
if (!currentVersion || !(await pathExists(currentVersion.path))) { if (!currentVersion || !(await pathExists(currentVersion.path))) {
const rawPath = this.configManager.getCurrentKoboldBinary(); const rawPath = this.configManager.getCurrentKoboldBinary();
const error = currentVersion const error = currentVersion
? `KoboldCpp binary file does not exist at path: ${currentVersion.path}` ? `Binary file does not exist at path: ${currentVersion.path}`
: 'No KoboldCpp version configured'; : 'No version configured';
this.logManager.logError( this.logManager.logError(
`Launch failed: ${error}. Raw config path: "${rawPath}", Current version: ${JSON.stringify(currentVersion)}` `Launch failed: ${error}. Raw config path: "${rawPath}", Current version: ${JSON.stringify(currentVersion)}`
@ -717,10 +717,7 @@ export class KoboldCppManager {
}); });
child.on('error', (error) => { child.on('error', (error) => {
this.logManager.logError( this.logManager.logError(`Process error: ${error.message}`, error);
`KoboldCpp process error: ${error.message}`,
error
);
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`\n[ERROR] Process error: ${error.message}\n` `\n[ERROR] Process error: ${error.message}\n`
@ -736,7 +733,7 @@ export class KoboldCppManager {
} catch (error) { } catch (error) {
const errorMessage = (error as Error).message; const errorMessage = (error as Error).message;
this.logManager.logError( this.logManager.logError(
`Failed to launch KoboldCpp: ${errorMessage}`, `Failed to launch: ${errorMessage}`,
error as Error error as Error
); );
return { success: false, error: errorMessage }; return { success: false, error: errorMessage };

View file

@ -1,19 +1,19 @@
import { app } from 'electron'; import { app } from 'electron';
import { join } from 'path'; import { join } from 'path';
import winston from 'winston'; import { createLogger, format, type Logger } from 'winston';
import 'winston-daily-rotate-file'; import DailyRotateFile from 'winston-daily-rotate-file';
export class LogManager { export class LogManager {
private logger: winston.Logger; private logger: Logger;
constructor() { constructor() {
const logsDir = join(app.getPath('userData'), 'logs'); const logsDir = join(app.getPath('userData'), 'logs');
this.logger = winston.createLogger({ this.logger = createLogger({
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info', level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
format: winston.format.combine( format: format.combine(
winston.format.timestamp(), format.timestamp(),
winston.format.printf(({ timestamp, level, message, error }) => { format.printf(({ timestamp, level, message, error }) => {
const processInfo = `[${process.type || 'unknown'}:${process.pid}]`; const processInfo = `[${process.type || 'unknown'}:${process.pid}]`;
let logEntry = `${timestamp} ${processInfo} [${level.toUpperCase()}] ${message}`; let logEntry = `${timestamp} ${processInfo} [${level.toUpperCase()}] ${message}`;
@ -28,7 +28,7 @@ export class LogManager {
}) })
), ),
transports: [ transports: [
new winston.transports.DailyRotateFile({ new DailyRotateFile({
filename: join(logsDir, 'gerbil-%DATE%.log'), filename: join(logsDir, 'gerbil-%DATE%.log'),
datePattern: 'YYYY-MM-DD', datePattern: 'YYYY-MM-DD',
maxSize: '10m', maxSize: '10m',

View file

@ -176,7 +176,7 @@ export class OpenWebUIManager {
await this.stopFrontend(); await this.stopFrontend();
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`Preparing Open WebUI to connect to KoboldCpp at ${koboldHost}:${koboldPort}...` `Preparing Open WebUI to connect at ${koboldHost}:${koboldPort}...`
); );
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
@ -252,13 +252,13 @@ export class OpenWebUIManager {
await this.waitForOpenWebUIToStart(); await this.waitForOpenWebUIToStart();
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`Open WebUI is ready and auto-configured for KoboldCpp!` `Open WebUI is ready and auto-configured!`
); );
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`Access Open WebUI at: http://localhost:${config.port}` `Access Open WebUI at: http://localhost:${config.port}`
); );
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`KoboldCpp connection: ${koboldUrl}/v1 (auto-configured)` `Connection: ${koboldUrl}/v1 (auto-configured)`
); );
} catch (error) { } catch (error) {
this.logManager.logError( this.logManager.logError(

View file

@ -363,7 +363,7 @@ export class SillyTavernManager {
textgenSettings.type = 'koboldcpp'; textgenSettings.type = 'koboldcpp';
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`Configured SillyTavern for text generation with KoboldCpp at ${koboldUrl}` `Configured SillyTavern for text generation at ${koboldUrl}`
); );
await writeJsonFile(configPath, settings); await writeJsonFile(configPath, settings);
@ -461,7 +461,7 @@ export class SillyTavernManager {
await this.stopFrontend(); await this.stopFrontend();
this.windowManager.sendKoboldOutput( this.windowManager.sendKoboldOutput(
`Preparing SillyTavern to connect to KoboldCpp at ${koboldHost}:${koboldPort}...` `Preparing SillyTavern to connect at ${koboldHost}:${koboldPort}...`
); );
await this.ensureSillyTavernSettings(); await this.ensureSillyTavernSettings();

View file

@ -1,14 +0,0 @@
.minWidth200 {
min-width: 12.5rem;
}
.flex1 {
flex: 1;
}
.terminalScrollArea {
flex: 1;
font-family: Monaco, Menlo, 'Ubuntu Mono', monospace;
font-size: 0.875em;
line-height: 1.4;
}

View file

@ -53,3 +53,12 @@ export const sortDownloadsByType = <T extends { name: string }>(
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
export const pretifyBinName = (binName: string) =>
binName
.replace('koboldcpp-', '')
.replace('-x64', ' (x64) ')
.replace(' -', ' ')
.replace('rocm', '- ROCm')
.replace('nocuda', '- NoCUDA')
.replace('oldpc', '- OldPC');