diff --git a/electron.vite.config.ts b/electron.vite.config.ts
index d9fd8ae..4d81656 100644
--- a/electron.vite.config.ts
+++ b/electron.vite.config.ts
@@ -27,6 +27,7 @@ export default defineConfig({
},
renderer: {
root: '.',
+ publicDir: 'assets',
build: {
outDir: 'dist',
rollupOptions: {
diff --git a/eslint.config.ts b/eslint.config.ts
index 7b0844a..efe731e 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -139,6 +139,13 @@ const config = [
'import/no-default-export': 'off',
},
},
+ {
+ files: ['**/*.d.ts'],
+ rules: {
+ 'no-comments/disallowComments': 'off',
+ 'import/no-default-export': 'off',
+ },
+ },
];
export default config;
diff --git a/index.html b/index.html
index bc322fb..4b66916 100644
--- a/index.html
+++ b/index.html
@@ -6,7 +6,7 @@
Friendly Kobold
diff --git a/src/App.tsx b/src/App.tsx
index 25aa785..170e675 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -122,6 +122,7 @@ export const App = () => {
};
const handleLaunch = () => {
+ setActiveInterfaceTab('terminal');
setCurrentScreen('interface');
};
diff --git a/src/components/AppHeader.css b/src/components/AppHeader/AppHeader.css
similarity index 100%
rename from src/components/AppHeader.css
rename to src/components/AppHeader/AppHeader.css
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader/index.tsx
similarity index 90%
rename from src/components/AppHeader.tsx
rename to src/components/AppHeader/index.tsx
index f47db6d..1d7190c 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader/index.tsx
@@ -11,7 +11,8 @@ import {
useMantineColorScheme,
} from '@mantine/core';
import { Settings, ArrowLeft } from 'lucide-react';
-import { StyledTooltip } from './StyledTooltip';
+import { StyledTooltip } from '../StyledTooltip';
+import { soundAssets, playSound } from '../../utils/sounds';
import './AppHeader.css';
type Screen = 'download' | 'launch' | 'interface';
@@ -44,21 +45,15 @@ export const AppHeader = ({
try {
if (logoClickCount >= 10 && Math.random() < 0.1) {
setIsElephantMode(true);
- const elephantAudio = new Audio('/assets/sounds/elephant-trunk.mp3');
- elephantAudio.volume = 0.6;
- elephantAudio.play().catch(() => {});
+ playSound(soundAssets.elephant, 0.6);
setTimeout(() => {
setIsElephantMode(false);
}, 1500);
} else {
setIsMouseSqueaking(true);
- const squeakNumber = Math.floor(Math.random() * 5) + 1;
- const squeakAudio = new Audio(
- `/assets/sounds/mouse-squeak${squeakNumber}.mp3`
- );
- squeakAudio.volume = 0.4;
- squeakAudio.play().catch(() => {});
+ const squeakNumber = Math.floor(Math.random() * 5);
+ playSound(soundAssets.mouseSqueaks[squeakNumber], 0.4);
setTimeout(() => {
setIsMouseSqueaking(false);
@@ -94,7 +89,7 @@ export const AppHeader = ({
) : (
{
const { themeMode, setThemeMode } = useTheme();
- const [isInstalling, setIsInstalling] = useState(false);
- const [installResult, setInstallResult] = useState<{
- success: boolean;
- error?: string;
- } | null>(null);
- const [platform] = useState(() => navigator.platform.toLowerCase());
-
- const handleInstallIcon = async () => {
- setIsInstalling(true);
- setInstallResult(null);
-
- try {
- const result = await window.electronAPI.system.installIcon();
- setInstallResult(result);
- } catch (error) {
- setInstallResult({
- success: false,
- error:
- error instanceof Error ? error.message : 'Unknown error occurred',
- });
- } finally {
- setIsInstalling(false);
- }
- };
-
- const isLinux = platform.includes('linux');
return (
@@ -91,45 +49,6 @@ export const AppearanceTab = () => {
]}
/>
-
- {isLinux && (
-
-
- System Integration
-
-
- Install application icon and desktop entry system-wide for better
- KDE Plasma/Wayland support
-
-
- {installResult && (
-
- ) : (
-
- )
- }
- color={installResult.success ? 'green' : 'red'}
- mb="md"
- >
- {installResult.success
- ? 'Icon installed successfully! You may need to log out and back in to see the changes.'
- : `Installation failed: ${installResult.error}`}
-
- )}
-
-
}
- onClick={handleInstallIcon}
- loading={isInstalling}
- variant="light"
- >
- {isInstalling ? 'Installing...' : 'Install System Icon'}
-
-
- )}
);
};
diff --git a/src/components/settings/GeneralTab.tsx b/src/components/settings/GeneralTab.tsx
index ac47a2d..a1f98d1 100644
--- a/src/components/settings/GeneralTab.tsx
+++ b/src/components/settings/GeneralTab.tsx
@@ -1,12 +1,23 @@
import { useState, useEffect } from 'react';
-import { Stack, Text, Group, TextInput, Button, rem } from '@mantine/core';
+import {
+ Stack,
+ Text,
+ Group,
+ TextInput,
+ Button,
+ rem,
+ Switch,
+} from '@mantine/core';
import { Folder, FolderOpen } from 'lucide-react';
export const GeneralTab = () => {
const [installDir, setInstallDir] = useState('');
+ const [minimizeToTray, setMinimizeToTray] = useState(false);
+ const [platform] = useState(() => navigator.platform.toLowerCase());
useEffect(() => {
loadCurrentInstallDir();
+ loadMinimizeToTray();
}, []);
const loadCurrentInstallDir = async () => {
@@ -21,6 +32,30 @@ export const GeneralTab = () => {
}
};
+ const loadMinimizeToTray = async () => {
+ try {
+ const setting = await window.electronAPI.config.get('minimizeToTray');
+ setMinimizeToTray(setting === true);
+ } catch (error) {
+ window.electronAPI.logs.logError(
+ 'Failed to load minimize to tray setting:',
+ error as Error
+ );
+ }
+ };
+
+ const handleMinimizeToTrayChange = async (checked: boolean) => {
+ try {
+ await window.electronAPI.config.set('minimizeToTray', checked);
+ setMinimizeToTray(checked);
+ } catch (error) {
+ window.electronAPI.logs.logError(
+ 'Failed to save minimize to tray setting:',
+ error as Error
+ );
+ }
+ };
+
const handleSelectInstallDir = async () => {
try {
const selectedDir =
@@ -65,6 +100,22 @@ export const GeneralTab = () => {
+
+ {!platform.includes('mac') && (
+
+
+ Window Behavior
+
+
+ handleMinimizeToTrayChange(event.currentTarget.checked)
+ }
+ label="Minimize to system tray"
+ description="When enabled, minimizing the window will hide it to the system tray instead of the taskbar"
+ />
+
+ )}
);
};
diff --git a/src/main/index.ts b/src/main/index.ts
index ac3e918..f34f3cb 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -32,7 +32,7 @@ class FriendlyKoboldApp {
this.logManager
);
this.ensureInstallDirectory();
- this.windowManager = new WindowManager();
+ this.windowManager = new WindowManager(this.configManager);
this.githubService = new GitHubService(this.logManager);
this.hardwareService = new HardwareService();
this.binaryService = new BinaryService(this.logManager);
diff --git a/src/main/managers/KoboldCppManager.ts b/src/main/managers/KoboldCppManager.ts
index 8aa5829..6166d69 100644
--- a/src/main/managers/KoboldCppManager.ts
+++ b/src/main/managers/KoboldCppManager.ts
@@ -926,11 +926,6 @@ export class KoboldCppManager {
const child = spawn(currentVersion.path, finalArgs, {
stdio: ['pipe', 'pipe', 'pipe'],
detached: false,
- env: {
- ...process.env,
- PYTHONDONTWRITEBYTECODE: '1',
- PYTHONUNBUFFERED: '1',
- },
});
this.koboldProcess = child;
@@ -962,8 +957,12 @@ export class KoboldCppManager {
child.on('exit', (code, signal) => {
if (mainWindow && !mainWindow.isDestroyed()) {
const displayMessage = signal
- ? `\nProcess terminated with signal ${signal}\n`
- : `\nProcess exited with code ${code}\n`;
+ ? `\n[INFO] Process terminated with signal ${signal}\n`
+ : code === 0
+ ? `\n[INFO] Process exited successfully\n`
+ : code && (code > 1 || code < 0)
+ ? `\n[ERROR] Process exited with code ${code}\n`
+ : `\n[INFO] Process exited with code ${code}\n`;
mainWindow.webContents.send('kobold-output', displayMessage);
}
this.koboldProcess = null;
@@ -978,7 +977,7 @@ export class KoboldCppManager {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(
'kobold-output',
- `\nProcess error: ${error.message}\n`
+ `\n[ERROR] Process error: ${error.message}\n`
);
}
this.koboldProcess = null;
diff --git a/src/main/managers/WindowManager.ts b/src/main/managers/WindowManager.ts
index 206c1cd..0f9e470 100644
--- a/src/main/managers/WindowManager.ts
+++ b/src/main/managers/WindowManager.ts
@@ -1,11 +1,24 @@
-import { BrowserWindow, app, Menu, shell, ipcMain } from 'electron';
+import {
+ BrowserWindow,
+ app,
+ Menu,
+ shell,
+ ipcMain,
+ Tray,
+ nativeImage,
+} from 'electron';
import * as os from 'os';
import { join } from 'path';
+import { ConfigManager } from './ConfigManager';
export class WindowManager {
private mainWindow: BrowserWindow | null = null;
+ private tray: Tray | null = null;
+ private configManager: ConfigManager;
- constructor() {}
+ constructor(configManager: ConfigManager) {
+ this.configManager = configManager;
+ }
private getIconPath(): string {
if (process.env.NODE_ENV === 'development') {
@@ -100,6 +113,16 @@ export class WindowManager {
app.quit();
});
+ this.mainWindow.on('minimize', () => {
+ if (process.platform !== 'darwin') {
+ const minimizeToTray = this.configManager.get('minimizeToTray');
+ if (minimizeToTray) {
+ this.mainWindow?.hide();
+ this.createTray();
+ }
+ }
+ });
+
this.setupContextMenu();
return this.mainWindow;
}
@@ -112,8 +135,87 @@ export class WindowManager {
if (this.mainWindow) {
this.mainWindow.removeAllListeners();
}
+ if (this.tray) {
+ this.tray.destroy();
+ this.tray = null;
+ }
}
+ private createTray() {
+ if (this.tray) {
+ return;
+ }
+
+ try {
+ const iconPath = this.getIconPath();
+ let trayIcon;
+
+ if (process.platform === 'darwin') {
+ trayIcon = nativeImage
+ .createFromPath(iconPath)
+ .resize({ width: 16, height: 16 });
+ trayIcon.setTemplateImage(true);
+ } else {
+ trayIcon = nativeImage
+ .createFromPath(iconPath)
+ .resize({ width: 22, height: 22 });
+ }
+
+ if (trayIcon.isEmpty()) {
+ trayIcon = nativeImage.createFromDataURL(
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAFYSURBVDiNpZM9SwNBEIafgwQSCxsLwcJCG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLG1sLg=='
+ );
+ }
+
+ this.tray = new Tray(trayIcon);
+
+ const isLinuxWayland =
+ process.platform === 'linux' &&
+ (process.env.WAYLAND_DISPLAY ||
+ process.env.XDG_SESSION_TYPE === 'wayland');
+
+ this.tray.setToolTip('Friendly Kobold');
+
+ const contextMenu = Menu.buildFromTemplate([
+ ...(isLinuxWayland
+ ? []
+ : [
+ {
+ label: 'Show',
+ click: () => {
+ this.mainWindow?.show();
+ this.mainWindow?.focus();
+ },
+ },
+ ]),
+ {
+ label: 'Quit',
+ click: () => {
+ app.quit();
+ },
+ },
+ ]);
+
+ this.tray.setContextMenu(contextMenu);
+
+ this.tray.on('click', () => {
+ if (this.mainWindow?.isVisible()) {
+ this.mainWindow.hide();
+ } else {
+ this.mainWindow?.show();
+ this.mainWindow?.focus();
+ }
+ });
+
+ this.tray.on('double-click', () => {
+ this.mainWindow?.show();
+ this.mainWindow?.focus();
+ });
+ } catch {
+ this.configManager.get('minimizeToTray') &&
+ this.mainWindow?.webContents.send('tray-creation-failed');
+ }
+ }
private setupContextMenu() {
if (!this.mainWindow) return;
diff --git a/src/main/templates/about-dialog.html b/src/main/templates/about-dialog.html
index 0d6552d..d857067 100644
--- a/src/main/templates/about-dialog.html
+++ b/src/main/templates/about-dialog.html
@@ -13,6 +13,7 @@
color: #333;
line-height: 1.6;
}
+
.container {
max-width: 400px;
margin: 0 auto;
@@ -63,12 +64,53 @@
background: #f0f0f0;
}
.primary {
- background: #0366d6;
+ background: #228be6;
color: white;
- border-color: #0366d6;
+ border-color: #228be6;
}
.primary:hover {
- background: #0256c7;
+ background: #1c7ed6;
+ }
+
+ /* Dark mode styles - moved to end to ensure proper override */
+ @media (prefers-color-scheme: dark) {
+ body {
+ background: #1a1a1a !important;
+ color: #e0e0e0 !important;
+ }
+ .container {
+ background: #2d2d2d !important;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
+ }
+ h1 {
+ color: #ffffff !important;
+ }
+ .version-info {
+ background: #3a3a3a !important;
+ color: #e0e0e0 !important;
+ }
+ .github-link {
+ color: #58a6ff !important;
+ }
+ .github-link:hover {
+ color: #79c0ff !important;
+ }
+ button {
+ background: #3a3a3a !important;
+ border-color: #555 !important;
+ color: #e0e0e0 !important;
+ }
+ button:hover {
+ background: #4a4a4a !important;
+ }
+ .primary {
+ background: #228be6 !important;
+ border-color: #228be6 !important;
+ color: white !important;
+ }
+ .primary:hover {
+ background: #1c7ed6 !important;
+ }
}
diff --git a/src/main/utils/IPCHandlers.ts b/src/main/utils/IPCHandlers.ts
index b4d6835..28c565a 100644
--- a/src/main/utils/IPCHandlers.ts
+++ b/src/main/utils/IPCHandlers.ts
@@ -1,8 +1,5 @@
import { ipcMain, dialog } from 'electron';
import { shell, app } from 'electron';
-import { spawn } from 'child_process';
-import { join } from 'path';
-import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
import { ConfigManager } from '@/main/managers/ConfigManager';
import { LogManager } from '@/main/managers/LogManager';
@@ -255,115 +252,5 @@ export class IPCHandlers {
this.logManager.logError(message, error);
}
);
-
- ipcMain.handle('system:installIcon', async () => {
- try {
- if (process.platform !== 'linux') {
- return {
- success: false,
- error: 'Icon installation is only available on Linux',
- };
- }
-
- const iconSource = join(process.cwd(), 'assets/icon.png');
- const desktopSource = join(
- process.cwd(),
- 'assets/friendly-kobold.desktop'
- );
-
- const iconDest = '/usr/share/pixmaps/friendly-kobold.png';
- const desktopDest = '/usr/share/applications/friendly-kobold.desktop';
-
- let execPath: string;
- if (process.env.APPIMAGE) {
- execPath = process.env.APPIMAGE;
- } else {
- const appImagePath = join(
- process.cwd(),
- 'release',
- 'Friendly Kobold-0.1.0.AppImage'
- );
- if (existsSync(appImagePath)) {
- execPath = appImagePath;
- } else {
- return {
- success: false,
- error:
- 'Could not find AppImage executable. Please build the application first.',
- };
- }
- }
-
- const tempDesktopPath = join(
- require('os').tmpdir(),
- 'friendly-kobold.desktop'
- );
- const desktopContent = readFileSync(desktopSource, 'utf8');
- const updatedDesktopContent = desktopContent.replace(
- /^Exec=.*$/m,
- `Exec="${execPath}" %U`
- );
- writeFileSync(tempDesktopPath, updatedDesktopContent);
-
- const commands = [
- ['pkexec', 'cp', iconSource, iconDest],
- ['pkexec', 'cp', tempDesktopPath, desktopDest],
- ['update-desktop-database', '/usr/share/applications/'],
- ];
-
- for (const command of commands) {
- try {
- await new Promise((resolve, reject) => {
- const childProcess = spawn(command[0], command.slice(1), {
- stdio: ['ignore', 'pipe', 'pipe'],
- });
-
- childProcess.on('close', (code) => {
- if (code === 0) {
- resolve();
- } else {
- reject(new Error(`Command failed with exit code ${code}`));
- }
- });
-
- childProcess.on('error', (err) => {
- reject(err);
- });
- });
- } catch (error) {
- this.logManager.logError(
- `Icon installation command failed: ${command.join(' ')}`,
- error as Error
- );
- if (command[0] === 'pkexec') {
- try {
- unlinkSync(tempDesktopPath);
- } catch {
- void 0;
- }
- return {
- success: false,
- error:
- 'Permission denied. Please ensure you have admin privileges.',
- };
- }
- }
- }
-
- try {
- unlinkSync(tempDesktopPath);
- } catch {
- void 0;
- }
-
- return { success: true };
- } catch (error) {
- this.logManager.logError(
- 'Failed to install system icon:',
- error as Error
- );
- return { success: false, error: (error as Error).message };
- }
- });
}
}
diff --git a/src/preload/index.ts b/src/preload/index.ts
index 87043de..75f3ea7 100644
--- a/src/preload/index.ts
+++ b/src/preload/index.ts
@@ -4,7 +4,6 @@ import type {
AppAPI,
ConfigAPI,
LogsAPI,
- SystemAPI,
UpdateInfo,
} from '@/types/electron';
import type { GPUCapabilities } from '@/types/hardware';
@@ -149,14 +148,9 @@ const logsAPI: LogsAPI = {
ipcRenderer.invoke('logs:logError', message, error),
};
-const systemAPI: SystemAPI = {
- installIcon: () => ipcRenderer.invoke('system:installIcon'),
-};
-
contextBridge.exposeInMainWorld('electronAPI', {
kobold: koboldAPI,
app: appAPI,
config: configAPI,
logs: logsAPI,
- system: systemAPI,
});
diff --git a/src/screens/Interface/index.tsx b/src/screens/Interface/index.tsx
index 7f413b4..8a70706 100644
--- a/src/screens/Interface/index.tsx
+++ b/src/screens/Interface/index.tsx
@@ -21,7 +21,6 @@ export const InterfaceScreen = ({
(url: string) => {
setServerUrl(url);
setIsServerReady(true);
-
if (onTabChange) {
onTabChange(isImageGenerationMode ? 'image' : 'chat');
}
diff --git a/src/screens/Launch/BackendSelector.tsx b/src/screens/Launch/BackendSelector.tsx
index aeb4a86..b86d124 100644
--- a/src/screens/Launch/BackendSelector.tsx
+++ b/src/screens/Launch/BackendSelector.tsx
@@ -1,15 +1,6 @@
-import {
- Text,
- Group,
- Select,
- Badge,
- Card,
- useMantineColorScheme,
- ActionIcon,
- Tooltip,
-} from '@mantine/core';
+import { Text, Group, Select, Badge, ActionIcon, Tooltip } from '@mantine/core';
import { useState, useEffect } from 'react';
-import { AlertTriangle } from 'lucide-react';
+import { AlertTriangle, Info } from 'lucide-react';
import { InfoTooltip } from '@/components/InfoTooltip';
interface BackendSelectorProps {
@@ -29,9 +20,6 @@ export const BackendSelector = ({
noavx2 = false,
failsafe = false,
}: BackendSelectorProps) => {
- const { colorScheme } = useMantineColorScheme();
- const isDark = colorScheme === 'dark';
-
const [availableBackends, setAvailableBackends] = useState<
Array<{ value: string; label: string; devices?: string[] }>
>([]);
@@ -120,6 +108,17 @@ export const BackendSelector = ({
});
}
+ if (
+ availableBackends.length > 0 &&
+ availableBackends.some((b) => b.value === 'cpu')
+ ) {
+ warnings.push({
+ type: 'info',
+ message:
+ "Performance Note: LLMs run significantly faster on GPU-accelerated systems. Consider using NVIDIA's CUDA or AMD's ROCm backends for optimal performance.",
+ });
+ }
+
return warnings;
};
@@ -131,6 +130,7 @@ export const BackendSelector = ({
{!isLoadingBackends &&
+ availableBackends.length > 0 &&
getWarnings().map((warning, index) => (
-
-
+
+ {warning.type === 'warning' ? (
+
+ ) : (
+
+ )}
))}
@@ -207,20 +215,6 @@ export const BackendSelector = ({
))}
)}
- {!isLoadingBackends &&
- backend === 'cpu' &&
- availableBackends.length > 0 && (
-
-
-
- Performance Note:
- {' '}
- LLMs run significantly faster on GPU-accelerated systems. Consider
- using NVIDIA's CUDA or AMD's ROCm backends for optimal
- performance.
-
-
- )}
);
};
diff --git a/src/screens/Launch/index.tsx b/src/screens/Launch/index.tsx
index 6fc0ea0..4bfd629 100644
--- a/src/screens/Launch/index.tsx
+++ b/src/screens/Launch/index.tsx
@@ -392,7 +392,7 @@ export const LaunchScreen = ({
args.push('--port', actualPort.toString());
}
- if (host !== 'localhost') {
+ if (host !== 'localhost' && host !== '') {
args.push('--host', host);
}
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts
index 3ce21c4..04b78af 100644
--- a/src/types/electron.d.ts
+++ b/src/types/electron.d.ts
@@ -4,7 +4,6 @@ import type {
BasicGPUInfo,
HardwareInfo,
PlatformInfo,
- SystemCapabilities,
} from '@/types/hardware';
interface GitHubAsset {
@@ -14,7 +13,7 @@ interface GitHubAsset {
created_at: string;
}
-interface GitHubRelease {
+export interface GitHubRelease {
tag_name: string;
name: string;
published_at: string;
@@ -22,7 +21,7 @@ interface GitHubRelease {
assets: GitHubAsset[];
}
-interface UpdateInfo {
+export interface UpdateInfo {
currentVersion: string;
latestVersion: string;
releaseInfo: GitHubRelease;
@@ -38,7 +37,7 @@ interface ReleaseWithStatus {
}>;
}
-interface InstalledVersion {
+export interface InstalledVersion {
version: string;
path: string;
type: 'github' | 'rocm';
@@ -168,10 +167,6 @@ export interface LogsAPI {
logError: (message: string, error?: Error) => Promise;
}
-interface SystemAPI {
- installIcon: () => Promise<{ success: boolean; error?: string }>;
-}
-
declare global {
interface Window {
electronAPI: {
@@ -179,7 +174,6 @@ declare global {
app: AppAPI;
config: ConfigAPI;
logs: LogsAPI;
- system: SystemAPI;
};
}
}
diff --git a/src/types/electron.ts b/src/types/electron.ts
new file mode 100644
index 0000000..04b78af
--- /dev/null
+++ b/src/types/electron.ts
@@ -0,0 +1,179 @@
+import type {
+ CPUCapabilities,
+ GPUCapabilities,
+ BasicGPUInfo,
+ HardwareInfo,
+ PlatformInfo,
+} from '@/types/hardware';
+
+interface GitHubAsset {
+ name: string;
+ browser_download_url: string;
+ size: number;
+ created_at: string;
+}
+
+export interface GitHubRelease {
+ tag_name: string;
+ name: string;
+ published_at: string;
+ body: string;
+ assets: GitHubAsset[];
+}
+
+export interface UpdateInfo {
+ currentVersion: string;
+ latestVersion: string;
+ releaseInfo: GitHubRelease;
+ hasUpdate: boolean;
+}
+
+interface ReleaseWithStatus {
+ release: GitHubRelease;
+ availableAssets: Array<{
+ asset: GitHubAsset;
+ isDownloaded: boolean;
+ installedVersion?: string;
+ }>;
+}
+
+export interface InstalledVersion {
+ version: string;
+ path: string;
+ type: 'github' | 'rocm';
+ filename: string;
+ size?: number;
+}
+
+interface ROCmDownload {
+ name: string;
+ url: string;
+ size: number;
+ type: 'rocm';
+}
+
+export interface KoboldAPI {
+ getInstalledVersion: () => Promise;
+ getInstalledVersions: () => Promise;
+ getCurrentVersion: () => Promise;
+ getCurrentBinaryInfo: () => Promise<{
+ path: string;
+ filename: string;
+ } | null>;
+ setCurrentVersion: (version: string) => Promise;
+ getVersionFromBinary: (binaryPath: string) => Promise;
+ getLatestRelease: () => Promise;
+ getAllReleases: () => Promise;
+ getPlatform: () => Promise;
+ detectGPU: () => Promise;
+ detectCPU: () => Promise;
+ detectGPUCapabilities: () => Promise;
+ detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
+ detectHardware: () => Promise;
+ detectAllCapabilities: () => Promise;
+ detectBackendSupport: (binaryPath: string) => Promise<{
+ rocm: boolean;
+ vulkan: boolean;
+ clblast: boolean;
+ noavx2: boolean;
+ failsafe: boolean;
+ cuda: boolean;
+ }>;
+ getAvailableBackends: (
+ binaryPath: string,
+ hardwareCapabilities: GPUCapabilities
+ ) => Promise>;
+ clearBinaryCache: () => Promise;
+ getCurrentInstallDir: () => Promise;
+ selectInstallDirectory: () => Promise;
+ downloadRelease: (
+ asset: GitHubAsset
+ ) => Promise<{ success: boolean; path?: string; error?: string }>;
+ downloadROCm: () => Promise<{
+ success: boolean;
+ path?: string;
+ error?: string;
+ }>;
+ getROCmDownload: () => Promise;
+ checkForUpdates: () => Promise;
+ getLatestReleaseWithStatus: () => Promise;
+ launchKoboldCpp: (
+ args?: string[],
+ configFilePath?: string
+ ) => Promise<{ success: boolean; pid?: number; error?: string }>;
+ openInstallDialog: () => Promise<{ success: boolean; path?: string }>;
+ getConfigFiles: () => Promise<
+ Array<{ name: string; path: string; size: number }>
+ >;
+ saveConfigFile: (
+ configName: string,
+ configData: {
+ gpulayers?: number;
+ contextsize?: number;
+ model_param?: string;
+ port?: number;
+ host?: string;
+ multiuser?: number;
+ multiplayer?: boolean;
+ remotetunnel?: boolean;
+ nocertify?: boolean;
+ websearch?: boolean;
+ noshift?: boolean;
+ flashattention?: boolean;
+ noavx2?: boolean;
+ failsafe?: boolean;
+ usecuda?: boolean;
+ usevulkan?: boolean;
+ useclblast?: boolean;
+ sdmodel?: string;
+ sdt5xxl?: string;
+ sdclipl?: string;
+ sdclipg?: string;
+ sdphotomaker?: string;
+ sdvae?: string;
+ [key: string]: unknown;
+ }
+ ) => Promise;
+ getSelectedConfig: () => Promise;
+ setSelectedConfig: (configName: string) => Promise;
+ parseConfigFile: (filePath: string) => Promise<{
+ gpulayers?: number;
+ contextsize?: number;
+ model_param?: string;
+ [key: string]: unknown;
+ } | null>;
+ selectModelFile: () => Promise;
+ stopKoboldCpp: () => void;
+ confirmEject: () => Promise;
+ onDownloadProgress: (callback: (progress: number) => void) => void;
+ onUpdateAvailable: (callback: (updateInfo: UpdateInfo) => void) => void;
+ onInstallDirChanged: (callback: (newPath: string) => void) => () => void;
+ onVersionsUpdated: (callback: () => void) => () => void;
+ onKoboldOutput: (callback: (data: string) => void) => () => void;
+ removeAllListeners: (channel: string) => void;
+}
+
+export interface AppAPI {
+ getVersion: () => Promise;
+ openExternal: (url: string) => Promise;
+}
+
+export interface ConfigAPI {
+ get: (key: string) => Promise;
+ set: (key: string, value: unknown) => Promise;
+}
+
+export interface LogsAPI {
+ logError: (message: string, error?: Error) => Promise;
+}
+
+declare global {
+ interface Window {
+ electronAPI: {
+ kobold: KoboldAPI;
+ app: AppAPI;
+ config: ConfigAPI;
+ logs: LogsAPI;
+ };
+ }
+}
diff --git a/src/types/hardware.ts b/src/types/hardware.d.ts
similarity index 100%
rename from src/types/hardware.ts
rename to src/types/hardware.d.ts
diff --git a/src/types/index.ts b/src/types/index.d.ts
similarity index 100%
rename from src/types/index.ts
rename to src/types/index.d.ts
diff --git a/src/types/vite-env.d.ts b/src/types/vite-env.d.ts
new file mode 100644
index 0000000..9e31444
--- /dev/null
+++ b/src/types/vite-env.d.ts
@@ -0,0 +1,31 @@
+///
+
+declare module '*.png' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpeg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.gif' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.svg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.webp' {
+ const src: string;
+ export default src;
+}
diff --git a/src/utils/sounds.ts b/src/utils/sounds.ts
new file mode 100644
index 0000000..07d5bfa
--- /dev/null
+++ b/src/utils/sounds.ts
@@ -0,0 +1,20 @@
+export const soundAssets = {
+ elephant: '/sounds/elephant-trunk.mp3',
+ mouseSqueaks: [
+ '/sounds/mouse-squeak1.mp3',
+ '/sounds/mouse-squeak2.mp3',
+ '/sounds/mouse-squeak3.mp3',
+ '/sounds/mouse-squeak4.mp3',
+ '/sounds/mouse-squeak5.mp3',
+ ],
+};
+
+export const playSound = (soundUrl: string, volume = 0.5): void => {
+ try {
+ const audio = new Audio(soundUrl);
+ audio.volume = volume;
+ audio.play().catch(() => {});
+ } catch {
+ void 0;
+ }
+};
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..5d46fa0
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,46 @@
+///
+
+declare module '*.png' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpeg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.gif' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.svg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.webp' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.mp3' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.wav' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.ogg' {
+ const src: string;
+ export default src;
+}