From 967fc85842970fd74e64f66f7f7ea37df4d67aa9 Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 17 Aug 2025 03:47:12 -0700 Subject: [PATCH] bug fixes and improvements --- electron.vite.config.ts | 1 + eslint.config.ts | 7 + index.html | 2 +- src/App.tsx | 1 + src/components/{ => AppHeader}/AppHeader.css | 0 .../{AppHeader.tsx => AppHeader/index.tsx} | 17 +- src/components/settings/AppearanceTab.tsx | 85 +-------- src/components/settings/GeneralTab.tsx | 53 +++++- src/main/index.ts | 2 +- src/main/managers/KoboldCppManager.ts | 15 +- src/main/managers/WindowManager.ts | 106 ++++++++++- src/main/templates/about-dialog.html | 48 ++++- src/main/utils/IPCHandlers.ts | 113 ----------- src/preload/index.ts | 6 - src/screens/Interface/index.tsx | 1 - src/screens/Launch/BackendSelector.tsx | 54 +++--- src/screens/Launch/index.tsx | 2 +- src/types/electron.d.ts | 12 +- src/types/electron.ts | 179 ++++++++++++++++++ src/types/{hardware.ts => hardware.d.ts} | 0 src/types/{index.ts => index.d.ts} | 0 src/types/vite-env.d.ts | 31 +++ src/utils/sounds.ts | 20 ++ src/vite-env.d.ts | 46 +++++ 24 files changed, 531 insertions(+), 270 deletions(-) rename src/components/{ => AppHeader}/AppHeader.css (100%) rename src/components/{AppHeader.tsx => AppHeader/index.tsx} (90%) create mode 100644 src/types/electron.ts rename src/types/{hardware.ts => hardware.d.ts} (100%) rename src/types/{index.ts => index.d.ts} (100%) create mode 100644 src/types/vite-env.d.ts create mode 100644 src/utils/sounds.ts create mode 100644 src/vite-env.d.ts 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 = ({ ) : ( Friendly Kobold { 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}`} - - )} - - -
- )}
); }; 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; +}