diff --git a/README.md b/README.md
index 872469d..f7bc960 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ A desktop app to easily run Large Language Models locally.
## 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)
- **Automatic updates** - Download and keep your KoboldCpp binary up-to-date effortlessly
- **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
```
+## 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
AGPL v3 License - see LICENSE file for details
-
-# test
diff --git a/package.json b/package.json
index f48fe3b..049fef0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "gerbil",
"productName": "Gerbil",
- "version": "0.9.9",
+ "version": "1.0.0",
"description": "Run Large Language Models locally",
"main": "out/main/index.js",
"homepage": "./",
diff --git a/src/components/CheckboxWithTooltip.tsx b/src/components/CheckboxWithTooltip.tsx
index 606b8af..ca5836d 100644
--- a/src/components/CheckboxWithTooltip.tsx
+++ b/src/components/CheckboxWithTooltip.tsx
@@ -1,6 +1,5 @@
import { Checkbox, Group } from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip';
-import styles from '@/styles/layout.module.css';
interface CheckboxWithTooltipProps {
checked: boolean;
@@ -17,7 +16,7 @@ export const CheckboxWithTooltip = ({
tooltip,
disabled = false,
}: CheckboxWithTooltipProps) => (
-
+
-
+
- {name}
+ {pretifyBinName(name)}
{isCurrent && (
diff --git a/src/components/ModelFileField.tsx b/src/components/ModelFileField.tsx
index 90fbb33..4a148b7 100644
--- a/src/components/ModelFileField.tsx
+++ b/src/components/ModelFileField.tsx
@@ -2,7 +2,6 @@ import { Group, TextInput, Button } from '@mantine/core';
import { File, Search } from 'lucide-react';
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
import { getInputValidationState } from '@/utils/validation';
-import styles from '@/styles/layout.module.css';
interface ModelFileFieldProps {
label: string;
@@ -41,7 +40,7 @@ export const ModelFileField = ({
-
+
- Waiting for the KoboldCpp server to start...
+ Waiting for the server to start...
The {isImageGenerationMode ? 'image generation' : 'chat'} interface
diff --git a/src/components/screens/Interface/TerminalTab.tsx b/src/components/screens/Interface/TerminalTab.tsx
index c2df986..5bbbf44 100644
--- a/src/components/screens/Interface/TerminalTab.tsx
+++ b/src/components/screens/Interface/TerminalTab.tsx
@@ -7,7 +7,6 @@ import {
useComputedColorScheme,
} from '@mantine/core';
import { ChevronDown } from 'lucide-react';
-import styles from '@/styles/layout.module.css';
import { SERVER_READY_SIGNALS, TITLEBAR_HEIGHT } from '@/constants';
import { handleTerminalOutput, processTerminalContent } from '@/utils/terminal';
import { useLaunchConfigStore } from '@/stores/launchConfig';
@@ -120,14 +119,19 @@ export const TerminalTab = ({
ref={scrollAreaRef}
viewportRef={viewportRef}
onScrollPositionChange={handleScroll}
- className={styles.terminalScrollArea}
+ style={{
+ flex: 1,
+ fontFamily: 'Monaco, Menlo, Ubuntu Mono, monospace',
+ fontSize: '0.875em',
+ lineHeight: 1.4,
+ }}
scrollbarSize={8}
offsetScrollbars={false}
>
{terminalContent.length === 0 ? (
- Starting KoboldCpp...
+ Starting...
) : (
{
Additional arguments
-
+
-
+
{
Host
-
+
{
Port
-
+
{
checked={remotetunnel}
onChange={handleRemotetunnelChange}
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."
/>
void;
@@ -23,9 +24,12 @@ export const WelcomeScreen = ({ onGetStarted }: WelcomeScreenProps) => (
-
- {PRODUCT_NAME}
-
+
+
+
+ {PRODUCT_NAME}
+
+
Run Large Language Models locally
@@ -84,44 +88,9 @@ export const WelcomeScreen = ({ onGetStarted }: WelcomeScreenProps) => (
-
+
Get Started
-
-
- {
- e.preventDefault();
- window.electronAPI.app.openExternal(
- 'https://github.com/LostRuins/koboldcpp'
- );
- }}
- size="sm"
- c="dimmed"
- >
-
- About KoboldCpp
-
-
-
- {
- e.preventDefault();
- window.electronAPI.app.openExternal(
- 'https://github.com/lone-cloud/gerbil'
- );
- }}
- size="sm"
- c="dimmed"
- >
-
- About Gerbil
-
-
-
-
diff --git a/src/components/settings/AboutTab.tsx b/src/components/settings/AboutTab.tsx
index e32d253..89da0b0 100644
--- a/src/components/settings/AboutTab.tsx
+++ b/src/components/settings/AboutTab.tsx
@@ -10,8 +10,10 @@ import {
Badge,
Button,
rem,
+ ActionIcon,
+ Tooltip,
} from '@mantine/core';
-import { Github, FolderOpen } from 'lucide-react';
+import { Github, FolderOpen, Copy } from 'lucide-react';
import { safeExecute } from '@/utils/logger';
import type { VersionInfo } from '@/types/electron';
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 (
@@ -118,7 +136,23 @@ export const AboutTab = () => {
-
+
+
+
+
+
+
{versionItems.map((item, index) => (
diff --git a/src/components/settings/GeneralTab.tsx b/src/components/settings/GeneralTab.tsx
index fa00b07..d72865f 100644
--- a/src/components/settings/GeneralTab.tsx
+++ b/src/components/settings/GeneralTab.tsx
@@ -12,7 +12,6 @@ import {
Anchor,
} from '@mantine/core';
import { Folder, FolderOpen, Monitor } from 'lucide-react';
-import styles from '@/styles/layout.module.css';
import type { FrontendPreference } from '@/types';
import { FRONTENDS } from '@/constants';
@@ -39,7 +38,7 @@ export const GeneralTab = () => {
const frontendConfigs: FrontendConfig[] = useMemo(
() => [
- { value: 'koboldcpp', label: 'KoboldCpp (Built-in)' },
+ { value: 'koboldcpp', label: 'Built-in Interface' },
{
value: 'sillytavern',
label: FRONTENDS.SILLYTAVERN,
@@ -195,7 +194,7 @@ export const GeneralTab = () => {
value={installDir}
readOnly
placeholder="Default installation directory"
- className={styles.flex1}
+ style={{ flex: 1 }}
leftSection={ }
/>
{
- console.error(`Failed to start KoboldCpp: ${error.message}`);
+ console.error(`Failed to start: ${error.message}`);
reject(error);
});
const handleSignal = async () => {
- console.log('\nReceived termination signal, terminating KoboldCpp...');
+ console.log('\nReceived termination signal, terminating...');
if (!child.killed) {
await terminateProcess(child, {
timeoutMs: 5000,
diff --git a/src/main/managers/KoboldCppManager.ts b/src/main/managers/KoboldCppManager.ts
index b600de6..2a62b16 100644
--- a/src/main/managers/KoboldCppManager.ts
+++ b/src/main/managers/KoboldCppManager.ts
@@ -77,7 +77,7 @@ export class KoboldCppManager {
try {
if (this.koboldProcess && !this.koboldProcess.killed) {
this.windowManager.sendKoboldOutput(
- 'Stopping KoboldCpp process before update...'
+ 'Stopping process before update...'
);
await this.cleanup();
await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -91,7 +91,7 @@ export class KoboldCppManager {
);
throw new Error(
`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}`
);
}
@@ -182,7 +182,7 @@ export class KoboldCppManager {
}
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;
@@ -633,8 +633,8 @@ export class KoboldCppManager {
if (!currentVersion || !(await pathExists(currentVersion.path))) {
const rawPath = this.configManager.getCurrentKoboldBinary();
const error = currentVersion
- ? `KoboldCpp binary file does not exist at path: ${currentVersion.path}`
- : 'No KoboldCpp version configured';
+ ? `Binary file does not exist at path: ${currentVersion.path}`
+ : 'No version configured';
this.logManager.logError(
`Launch failed: ${error}. Raw config path: "${rawPath}", Current version: ${JSON.stringify(currentVersion)}`
@@ -717,10 +717,7 @@ export class KoboldCppManager {
});
child.on('error', (error) => {
- this.logManager.logError(
- `KoboldCpp process error: ${error.message}`,
- error
- );
+ this.logManager.logError(`Process error: ${error.message}`, error);
this.windowManager.sendKoboldOutput(
`\n[ERROR] Process error: ${error.message}\n`
@@ -736,7 +733,7 @@ export class KoboldCppManager {
} catch (error) {
const errorMessage = (error as Error).message;
this.logManager.logError(
- `Failed to launch KoboldCpp: ${errorMessage}`,
+ `Failed to launch: ${errorMessage}`,
error as Error
);
return { success: false, error: errorMessage };
diff --git a/src/main/managers/LogManager.ts b/src/main/managers/LogManager.ts
index 6cf6db9..a4190c5 100644
--- a/src/main/managers/LogManager.ts
+++ b/src/main/managers/LogManager.ts
@@ -1,19 +1,19 @@
import { app } from 'electron';
import { join } from 'path';
-import winston from 'winston';
-import 'winston-daily-rotate-file';
+import { createLogger, format, type Logger } from 'winston';
+import DailyRotateFile from 'winston-daily-rotate-file';
export class LogManager {
- private logger: winston.Logger;
+ private logger: Logger;
constructor() {
const logsDir = join(app.getPath('userData'), 'logs');
- this.logger = winston.createLogger({
+ this.logger = createLogger({
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
- format: winston.format.combine(
- winston.format.timestamp(),
- winston.format.printf(({ timestamp, level, message, error }) => {
+ format: format.combine(
+ format.timestamp(),
+ format.printf(({ timestamp, level, message, error }) => {
const processInfo = `[${process.type || 'unknown'}:${process.pid}]`;
let logEntry = `${timestamp} ${processInfo} [${level.toUpperCase()}] ${message}`;
@@ -28,7 +28,7 @@ export class LogManager {
})
),
transports: [
- new winston.transports.DailyRotateFile({
+ new DailyRotateFile({
filename: join(logsDir, 'gerbil-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
maxSize: '10m',
diff --git a/src/main/managers/OpenWebUIManager.ts b/src/main/managers/OpenWebUIManager.ts
index 2a5138e..77914bb 100644
--- a/src/main/managers/OpenWebUIManager.ts
+++ b/src/main/managers/OpenWebUIManager.ts
@@ -176,7 +176,7 @@ export class OpenWebUIManager {
await this.stopFrontend();
this.windowManager.sendKoboldOutput(
- `Preparing Open WebUI to connect to KoboldCpp at ${koboldHost}:${koboldPort}...`
+ `Preparing Open WebUI to connect at ${koboldHost}:${koboldPort}...`
);
this.windowManager.sendKoboldOutput(
@@ -252,13 +252,13 @@ export class OpenWebUIManager {
await this.waitForOpenWebUIToStart();
this.windowManager.sendKoboldOutput(
- `Open WebUI is ready and auto-configured for KoboldCpp!`
+ `Open WebUI is ready and auto-configured!`
);
this.windowManager.sendKoboldOutput(
`Access Open WebUI at: http://localhost:${config.port}`
);
this.windowManager.sendKoboldOutput(
- `KoboldCpp connection: ${koboldUrl}/v1 (auto-configured)`
+ `Connection: ${koboldUrl}/v1 (auto-configured)`
);
} catch (error) {
this.logManager.logError(
diff --git a/src/main/managers/SillyTavernManager.ts b/src/main/managers/SillyTavernManager.ts
index 39b2bf8..178728c 100644
--- a/src/main/managers/SillyTavernManager.ts
+++ b/src/main/managers/SillyTavernManager.ts
@@ -363,7 +363,7 @@ export class SillyTavernManager {
textgenSettings.type = 'koboldcpp';
this.windowManager.sendKoboldOutput(
- `Configured SillyTavern for text generation with KoboldCpp at ${koboldUrl}`
+ `Configured SillyTavern for text generation at ${koboldUrl}`
);
await writeJsonFile(configPath, settings);
@@ -461,7 +461,7 @@ export class SillyTavernManager {
await this.stopFrontend();
this.windowManager.sendKoboldOutput(
- `Preparing SillyTavern to connect to KoboldCpp at ${koboldHost}:${koboldPort}...`
+ `Preparing SillyTavern to connect at ${koboldHost}:${koboldPort}...`
);
await this.ensureSillyTavernSettings();
diff --git a/src/styles/layout.module.css b/src/styles/layout.module.css
deleted file mode 100644
index 26196b0..0000000
--- a/src/styles/layout.module.css
+++ /dev/null
@@ -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;
-}
diff --git a/src/utils/assets.ts b/src/utils/assets.ts
index 338ed70..1d6c095 100644
--- a/src/utils/assets.ts
+++ b/src/utils/assets.ts
@@ -53,3 +53,12 @@ export const sortDownloadsByType = (
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');