bug fixes and improvements

This commit is contained in:
lone-cloud 2025-08-17 03:47:12 -07:00
parent 92bbcd1fda
commit 91b25622d4
24 changed files with 531 additions and 270 deletions

View file

@ -27,6 +27,7 @@ export default defineConfig({
}, },
renderer: { renderer: {
root: '.', root: '.',
publicDir: 'assets',
build: { build: {
outDir: 'dist', outDir: 'dist',
rollupOptions: { rollupOptions: {

View file

@ -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;

View file

@ -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>

View file

@ -122,6 +122,7 @@ export const App = () => {
}; };
const handleLaunch = () => { const handleLaunch = () => {
setActiveInterfaceTab('terminal');
setCurrentScreen('interface'); setCurrentScreen('interface');
}; };

View file

@ -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}

View file

@ -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>
); );
}; };

View file

@ -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>
); );
}; };

View file

@ -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);

View file

@ -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

View file

@ -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>

View file

@ -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 };
}
});
} }
} }

View file

@ -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,
}); });

View file

@ -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');
} }

View file

@ -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&apos;s CUDA or AMD&apos;s ROCm backends for optimal
performance.
</Text>
</Card>
)}
</div> </div>
); );
}; };

View file

@ -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);
} }

View file

@ -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
View 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;
};
}
}

31
src/types/vite-env.d.ts vendored Normal file
View 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
View 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
View 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;
}