mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
bug fixes and improvements
This commit is contained in:
parent
92bbcd1fda
commit
91b25622d4
24 changed files with 531 additions and 270 deletions
|
|
@ -27,6 +27,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
root: '.',
|
root: '.',
|
||||||
|
publicDir: 'assets',
|
||||||
build: {
|
build: {
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,13 @@ const config = [
|
||||||
'import/no-default-export': 'off',
|
'import/no-default-export': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.d.ts'],
|
||||||
|
rules: {
|
||||||
|
'no-comments/disallowComments': 'off',
|
||||||
|
'import/no-default-export': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; connect-src 'self' http: https:; script-src 'self'; style-src 'self' 'unsafe-inline' data:; frame-src 'self' http: https:;"
|
content="default-src 'self'; connect-src 'self' http: https:; script-src 'self' blob:; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' data:; frame-src 'self' http: https:;"
|
||||||
/>
|
/>
|
||||||
<title>Friendly Kobold</title>
|
<title>Friendly Kobold</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ export const App = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLaunch = () => {
|
const handleLaunch = () => {
|
||||||
|
setActiveInterfaceTab('terminal');
|
||||||
setCurrentScreen('interface');
|
setCurrentScreen('interface');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import {
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Settings, ArrowLeft } from 'lucide-react';
|
import { Settings, ArrowLeft } from 'lucide-react';
|
||||||
import { StyledTooltip } from './StyledTooltip';
|
import { StyledTooltip } from '../StyledTooltip';
|
||||||
|
import { soundAssets, playSound } from '../../utils/sounds';
|
||||||
import './AppHeader.css';
|
import './AppHeader.css';
|
||||||
|
|
||||||
type Screen = 'download' | 'launch' | 'interface';
|
type Screen = 'download' | 'launch' | 'interface';
|
||||||
|
|
@ -44,21 +45,15 @@ export const AppHeader = ({
|
||||||
try {
|
try {
|
||||||
if (logoClickCount >= 10 && Math.random() < 0.1) {
|
if (logoClickCount >= 10 && Math.random() < 0.1) {
|
||||||
setIsElephantMode(true);
|
setIsElephantMode(true);
|
||||||
const elephantAudio = new Audio('/assets/sounds/elephant-trunk.mp3');
|
playSound(soundAssets.elephant, 0.6);
|
||||||
elephantAudio.volume = 0.6;
|
|
||||||
elephantAudio.play().catch(() => {});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsElephantMode(false);
|
setIsElephantMode(false);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
setIsMouseSqueaking(true);
|
setIsMouseSqueaking(true);
|
||||||
const squeakNumber = Math.floor(Math.random() * 5) + 1;
|
const squeakNumber = Math.floor(Math.random() * 5);
|
||||||
const squeakAudio = new Audio(
|
playSound(soundAssets.mouseSqueaks[squeakNumber], 0.4);
|
||||||
`/assets/sounds/mouse-squeak${squeakNumber}.mp3`
|
|
||||||
);
|
|
||||||
squeakAudio.volume = 0.4;
|
|
||||||
squeakAudio.play().catch(() => {});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsMouseSqueaking(false);
|
setIsMouseSqueaking(false);
|
||||||
|
|
@ -94,7 +89,7 @@ export const AppHeader = ({
|
||||||
) : (
|
) : (
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icon.png"
|
src="/icon.png"
|
||||||
alt="Friendly Kobold"
|
alt="Friendly Kobold"
|
||||||
w={24}
|
w={24}
|
||||||
h={24}
|
h={24}
|
||||||
|
|
@ -1,51 +1,9 @@
|
||||||
import {
|
import { Stack, Text, Group, SegmentedControl, rem } from '@mantine/core';
|
||||||
Stack,
|
import { Sun, Moon, Monitor } from 'lucide-react';
|
||||||
Text,
|
|
||||||
Group,
|
|
||||||
SegmentedControl,
|
|
||||||
rem,
|
|
||||||
Button,
|
|
||||||
Alert,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import {
|
|
||||||
Sun,
|
|
||||||
Moon,
|
|
||||||
Monitor,
|
|
||||||
Download,
|
|
||||||
AlertCircle,
|
|
||||||
CheckCircle,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useTheme, type ThemeMode } from '@/contexts/ThemeContext';
|
import { useTheme, type ThemeMode } from '@/contexts/ThemeContext';
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
export const AppearanceTab = () => {
|
export const AppearanceTab = () => {
|
||||||
const { themeMode, setThemeMode } = useTheme();
|
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 (
|
return (
|
||||||
<Stack gap="lg" h="100%">
|
<Stack gap="lg" h="100%">
|
||||||
|
|
@ -91,45 +49,6 @@ export const AppearanceTab = () => {
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLinux && (
|
|
||||||
<div>
|
|
||||||
<Text fw={500} mb="sm">
|
|
||||||
System Integration
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" c="dimmed" mb="md">
|
|
||||||
Install application icon and desktop entry system-wide for better
|
|
||||||
KDE Plasma/Wayland support
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{installResult && (
|
|
||||||
<Alert
|
|
||||||
icon={
|
|
||||||
installResult.success ? (
|
|
||||||
<CheckCircle size={16} />
|
|
||||||
) : (
|
|
||||||
<AlertCircle size={16} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
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}`}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
leftSection={<Download size={16} />}
|
|
||||||
onClick={handleInstallIcon}
|
|
||||||
loading={isInstalling}
|
|
||||||
variant="light"
|
|
||||||
>
|
|
||||||
{isInstalling ? 'Installing...' : 'Install System Icon'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,23 @@
|
||||||
import { useState, useEffect } from 'react';
|
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';
|
import { Folder, FolderOpen } from 'lucide-react';
|
||||||
|
|
||||||
export const GeneralTab = () => {
|
export const GeneralTab = () => {
|
||||||
const [installDir, setInstallDir] = useState<string>('');
|
const [installDir, setInstallDir] = useState<string>('');
|
||||||
|
const [minimizeToTray, setMinimizeToTray] = useState<boolean>(false);
|
||||||
|
const [platform] = useState(() => navigator.platform.toLowerCase());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCurrentInstallDir();
|
loadCurrentInstallDir();
|
||||||
|
loadMinimizeToTray();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadCurrentInstallDir = async () => {
|
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 () => {
|
const handleSelectInstallDir = async () => {
|
||||||
try {
|
try {
|
||||||
const selectedDir =
|
const selectedDir =
|
||||||
|
|
@ -65,6 +100,22 @@ export const GeneralTab = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!platform.includes('mac') && (
|
||||||
|
<div>
|
||||||
|
<Text fw={500} mb="sm">
|
||||||
|
Window Behavior
|
||||||
|
</Text>
|
||||||
|
<Switch
|
||||||
|
checked={minimizeToTray}
|
||||||
|
onChange={(event) =>
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class FriendlyKoboldApp {
|
||||||
this.logManager
|
this.logManager
|
||||||
);
|
);
|
||||||
this.ensureInstallDirectory();
|
this.ensureInstallDirectory();
|
||||||
this.windowManager = new WindowManager();
|
this.windowManager = new WindowManager(this.configManager);
|
||||||
this.githubService = new GitHubService(this.logManager);
|
this.githubService = new GitHubService(this.logManager);
|
||||||
this.hardwareService = new HardwareService();
|
this.hardwareService = new HardwareService();
|
||||||
this.binaryService = new BinaryService(this.logManager);
|
this.binaryService = new BinaryService(this.logManager);
|
||||||
|
|
|
||||||
|
|
@ -926,11 +926,6 @@ export class KoboldCppManager {
|
||||||
const child = spawn(currentVersion.path, finalArgs, {
|
const child = spawn(currentVersion.path, finalArgs, {
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
detached: false,
|
detached: false,
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
PYTHONDONTWRITEBYTECODE: '1',
|
|
||||||
PYTHONUNBUFFERED: '1',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.koboldProcess = child;
|
this.koboldProcess = child;
|
||||||
|
|
@ -962,8 +957,12 @@ export class KoboldCppManager {
|
||||||
child.on('exit', (code, signal) => {
|
child.on('exit', (code, signal) => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
const displayMessage = signal
|
const displayMessage = signal
|
||||||
? `\nProcess terminated with signal ${signal}\n`
|
? `\n[INFO] Process terminated with signal ${signal}\n`
|
||||||
: `\nProcess exited with code ${code}\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);
|
mainWindow.webContents.send('kobold-output', displayMessage);
|
||||||
}
|
}
|
||||||
this.koboldProcess = null;
|
this.koboldProcess = null;
|
||||||
|
|
@ -978,7 +977,7 @@ export class KoboldCppManager {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send(
|
mainWindow.webContents.send(
|
||||||
'kobold-output',
|
'kobold-output',
|
||||||
`\nProcess error: ${error.message}\n`
|
`\n[ERROR] Process error: ${error.message}\n`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.koboldProcess = null;
|
this.koboldProcess = null;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -13,6 +13,7 @@
|
||||||
color: #333;
|
color: #333;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
@ -63,12 +64,53 @@
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
.primary {
|
.primary {
|
||||||
background: #0366d6;
|
background: #228be6;
|
||||||
color: white;
|
color: white;
|
||||||
border-color: #0366d6;
|
border-color: #228be6;
|
||||||
}
|
}
|
||||||
.primary:hover {
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import { ipcMain, dialog } from 'electron';
|
import { ipcMain, dialog } from 'electron';
|
||||||
import { shell, app } 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 { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
import { LogManager } from '@/main/managers/LogManager';
|
||||||
|
|
@ -255,115 +252,5 @@ export class IPCHandlers {
|
||||||
this.logManager.logError(message, error);
|
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<void>((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 };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import type {
|
||||||
AppAPI,
|
AppAPI,
|
||||||
ConfigAPI,
|
ConfigAPI,
|
||||||
LogsAPI,
|
LogsAPI,
|
||||||
SystemAPI,
|
|
||||||
UpdateInfo,
|
UpdateInfo,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
import type { GPUCapabilities } from '@/types/hardware';
|
import type { GPUCapabilities } from '@/types/hardware';
|
||||||
|
|
@ -149,14 +148,9 @@ const logsAPI: LogsAPI = {
|
||||||
ipcRenderer.invoke('logs:logError', message, error),
|
ipcRenderer.invoke('logs:logError', message, error),
|
||||||
};
|
};
|
||||||
|
|
||||||
const systemAPI: SystemAPI = {
|
|
||||||
installIcon: () => ipcRenderer.invoke('system:installIcon'),
|
|
||||||
};
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
kobold: koboldAPI,
|
kobold: koboldAPI,
|
||||||
app: appAPI,
|
app: appAPI,
|
||||||
config: configAPI,
|
config: configAPI,
|
||||||
logs: logsAPI,
|
logs: logsAPI,
|
||||||
system: systemAPI,
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ export const InterfaceScreen = ({
|
||||||
(url: string) => {
|
(url: string) => {
|
||||||
setServerUrl(url);
|
setServerUrl(url);
|
||||||
setIsServerReady(true);
|
setIsServerReady(true);
|
||||||
|
|
||||||
if (onTabChange) {
|
if (onTabChange) {
|
||||||
onTabChange(isImageGenerationMode ? 'image' : 'chat');
|
onTabChange(isImageGenerationMode ? 'image' : 'chat');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
import {
|
import { Text, Group, Select, Badge, ActionIcon, Tooltip } from '@mantine/core';
|
||||||
Text,
|
|
||||||
Group,
|
|
||||||
Select,
|
|
||||||
Badge,
|
|
||||||
Card,
|
|
||||||
useMantineColorScheme,
|
|
||||||
ActionIcon,
|
|
||||||
Tooltip,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { AlertTriangle } from 'lucide-react';
|
import { AlertTriangle, Info } from 'lucide-react';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
|
|
||||||
interface BackendSelectorProps {
|
interface BackendSelectorProps {
|
||||||
|
|
@ -29,9 +20,6 @@ export const BackendSelector = ({
|
||||||
noavx2 = false,
|
noavx2 = false,
|
||||||
failsafe = false,
|
failsafe = false,
|
||||||
}: BackendSelectorProps) => {
|
}: BackendSelectorProps) => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
|
||||||
const isDark = colorScheme === 'dark';
|
|
||||||
|
|
||||||
const [availableBackends, setAvailableBackends] = useState<
|
const [availableBackends, setAvailableBackends] = useState<
|
||||||
Array<{ value: string; label: string; devices?: string[] }>
|
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;
|
return warnings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -131,6 +130,7 @@ export const BackendSelector = ({
|
||||||
</Text>
|
</Text>
|
||||||
<InfoTooltip label="Select a backend to use. CUDA runs on Nvidia GPUs, and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast works on all GPUs but are somewhat slower." />
|
<InfoTooltip label="Select a backend to use. CUDA runs on Nvidia GPUs, and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast works on all GPUs but are somewhat slower." />
|
||||||
{!isLoadingBackends &&
|
{!isLoadingBackends &&
|
||||||
|
availableBackends.length > 0 &&
|
||||||
getWarnings().map((warning, index) => (
|
getWarnings().map((warning, index) => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
key={index}
|
key={index}
|
||||||
|
|
@ -139,8 +139,16 @@ export const BackendSelector = ({
|
||||||
w={300}
|
w={300}
|
||||||
withArrow
|
withArrow
|
||||||
>
|
>
|
||||||
<ActionIcon size="sm" color="orange" variant="light">
|
<ActionIcon
|
||||||
<AlertTriangle size={14} />
|
size="sm"
|
||||||
|
color={warning.type === 'warning' ? 'orange' : 'blue'}
|
||||||
|
variant="light"
|
||||||
|
>
|
||||||
|
{warning.type === 'warning' ? (
|
||||||
|
<AlertTriangle size={14} />
|
||||||
|
) : (
|
||||||
|
<Info size={14} />
|
||||||
|
)}
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
|
|
@ -207,20 +215,6 @@ export const BackendSelector = ({
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{!isLoadingBackends &&
|
|
||||||
backend === 'cpu' &&
|
|
||||||
availableBackends.length > 0 && (
|
|
||||||
<Card withBorder p="sm" mt="xs" bg={isDark ? 'blue.9' : 'blue.0'}>
|
|
||||||
<Text size="sm" c={isDark ? 'blue.2' : 'blue.8'}>
|
|
||||||
<Text component="span" fw={600}>
|
|
||||||
Performance Note:
|
|
||||||
</Text>{' '}
|
|
||||||
LLMs run significantly faster on GPU-accelerated systems. Consider
|
|
||||||
using NVIDIA's CUDA or AMD's ROCm backends for optimal
|
|
||||||
performance.
|
|
||||||
</Text>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -392,7 +392,7 @@ export const LaunchScreen = ({
|
||||||
args.push('--port', actualPort.toString());
|
args.push('--port', actualPort.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host !== 'localhost') {
|
if (host !== 'localhost' && host !== '') {
|
||||||
args.push('--host', host);
|
args.push('--host', host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
12
src/types/electron.d.ts
vendored
12
src/types/electron.d.ts
vendored
|
|
@ -4,7 +4,6 @@ import type {
|
||||||
BasicGPUInfo,
|
BasicGPUInfo,
|
||||||
HardwareInfo,
|
HardwareInfo,
|
||||||
PlatformInfo,
|
PlatformInfo,
|
||||||
SystemCapabilities,
|
|
||||||
} from '@/types/hardware';
|
} from '@/types/hardware';
|
||||||
|
|
||||||
interface GitHubAsset {
|
interface GitHubAsset {
|
||||||
|
|
@ -14,7 +13,7 @@ interface GitHubAsset {
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitHubRelease {
|
export interface GitHubRelease {
|
||||||
tag_name: string;
|
tag_name: string;
|
||||||
name: string;
|
name: string;
|
||||||
published_at: string;
|
published_at: string;
|
||||||
|
|
@ -22,7 +21,7 @@ interface GitHubRelease {
|
||||||
assets: GitHubAsset[];
|
assets: GitHubAsset[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateInfo {
|
export interface UpdateInfo {
|
||||||
currentVersion: string;
|
currentVersion: string;
|
||||||
latestVersion: string;
|
latestVersion: string;
|
||||||
releaseInfo: GitHubRelease;
|
releaseInfo: GitHubRelease;
|
||||||
|
|
@ -38,7 +37,7 @@ interface ReleaseWithStatus {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InstalledVersion {
|
export interface InstalledVersion {
|
||||||
version: string;
|
version: string;
|
||||||
path: string;
|
path: string;
|
||||||
type: 'github' | 'rocm';
|
type: 'github' | 'rocm';
|
||||||
|
|
@ -168,10 +167,6 @@ export interface LogsAPI {
|
||||||
logError: (message: string, error?: Error) => Promise<void>;
|
logError: (message: string, error?: Error) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SystemAPI {
|
|
||||||
installIcon: () => Promise<{ success: boolean; error?: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electronAPI: {
|
electronAPI: {
|
||||||
|
|
@ -179,7 +174,6 @@ declare global {
|
||||||
app: AppAPI;
|
app: AppAPI;
|
||||||
config: ConfigAPI;
|
config: ConfigAPI;
|
||||||
logs: LogsAPI;
|
logs: LogsAPI;
|
||||||
system: SystemAPI;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
179
src/types/electron.ts
Normal file
179
src/types/electron.ts
Normal file
|
|
@ -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<string | undefined>;
|
||||||
|
getInstalledVersions: () => Promise<InstalledVersion[]>;
|
||||||
|
getCurrentVersion: () => Promise<InstalledVersion | null>;
|
||||||
|
getCurrentBinaryInfo: () => Promise<{
|
||||||
|
path: string;
|
||||||
|
filename: string;
|
||||||
|
} | null>;
|
||||||
|
setCurrentVersion: (version: string) => Promise<boolean>;
|
||||||
|
getVersionFromBinary: (binaryPath: string) => Promise<string | null>;
|
||||||
|
getLatestRelease: () => Promise<GitHubRelease>;
|
||||||
|
getAllReleases: () => Promise<GitHubRelease[]>;
|
||||||
|
getPlatform: () => Promise<PlatformInfo>;
|
||||||
|
detectGPU: () => Promise<BasicGPUInfo>;
|
||||||
|
detectCPU: () => Promise<CPUCapabilities>;
|
||||||
|
detectGPUCapabilities: () => Promise<GPUCapabilities>;
|
||||||
|
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
|
||||||
|
detectHardware: () => Promise<HardwareInfo>;
|
||||||
|
detectAllCapabilities: () => Promise<HardwareInfo>;
|
||||||
|
detectBackendSupport: (binaryPath: string) => Promise<{
|
||||||
|
rocm: boolean;
|
||||||
|
vulkan: boolean;
|
||||||
|
clblast: boolean;
|
||||||
|
noavx2: boolean;
|
||||||
|
failsafe: boolean;
|
||||||
|
cuda: boolean;
|
||||||
|
}>;
|
||||||
|
getAvailableBackends: (
|
||||||
|
binaryPath: string,
|
||||||
|
hardwareCapabilities: GPUCapabilities
|
||||||
|
) => Promise<Array<{ value: string; label: string; devices?: string[] }>>;
|
||||||
|
clearBinaryCache: () => Promise<void>;
|
||||||
|
getCurrentInstallDir: () => Promise<string>;
|
||||||
|
selectInstallDirectory: () => Promise<string | null>;
|
||||||
|
downloadRelease: (
|
||||||
|
asset: GitHubAsset
|
||||||
|
) => Promise<{ success: boolean; path?: string; error?: string }>;
|
||||||
|
downloadROCm: () => Promise<{
|
||||||
|
success: boolean;
|
||||||
|
path?: string;
|
||||||
|
error?: string;
|
||||||
|
}>;
|
||||||
|
getROCmDownload: () => Promise<ROCmDownload | null>;
|
||||||
|
checkForUpdates: () => Promise<UpdateInfo | null>;
|
||||||
|
getLatestReleaseWithStatus: () => Promise<ReleaseWithStatus | null>;
|
||||||
|
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<boolean>;
|
||||||
|
getSelectedConfig: () => Promise<string | null>;
|
||||||
|
setSelectedConfig: (configName: string) => Promise<boolean>;
|
||||||
|
parseConfigFile: (filePath: string) => Promise<{
|
||||||
|
gpulayers?: number;
|
||||||
|
contextsize?: number;
|
||||||
|
model_param?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
} | null>;
|
||||||
|
selectModelFile: () => Promise<string | null>;
|
||||||
|
stopKoboldCpp: () => void;
|
||||||
|
confirmEject: () => Promise<boolean>;
|
||||||
|
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<string>;
|
||||||
|
openExternal: (url: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigAPI {
|
||||||
|
get: (key: string) => Promise<unknown>;
|
||||||
|
set: (key: string, value: unknown) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogsAPI {
|
||||||
|
logError: (message: string, error?: Error) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
electronAPI: {
|
||||||
|
kobold: KoboldAPI;
|
||||||
|
app: AppAPI;
|
||||||
|
config: ConfigAPI;
|
||||||
|
logs: LogsAPI;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/types/index.ts → src/types/index.d.ts
vendored
0
src/types/index.ts → src/types/index.d.ts
vendored
31
src/types/vite-env.d.ts
vendored
Normal file
31
src/types/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
20
src/utils/sounds.ts
Normal file
20
src/utils/sounds.ts
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
46
src/vite-env.d.ts
vendored
Normal file
46
src/vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue