fighting rocm on windows, allow deleting kcpp binaries from versions tab

This commit is contained in:
Egor 2025-09-26 17:35:05 -07:00
parent b2533659c2
commit ec2410c0e4
14 changed files with 286 additions and 216 deletions

View file

@ -9,7 +9,7 @@ import {
Progress, Progress,
rem, rem,
} from '@mantine/core'; } from '@mantine/core';
import { Download } from 'lucide-react'; import { Download, Trash2 } from 'lucide-react';
import { MouseEvent } from 'react'; import { MouseEvent } from 'react';
import { pretifyBinName, isWindowsROCmBuild } from '@/utils/assets'; import { pretifyBinName, isWindowsROCmBuild } from '@/utils/assets';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
@ -19,26 +19,28 @@ interface DownloadCardProps {
version: VersionInfo; version: VersionInfo;
size: string; size: string;
description?: string; description?: string;
isDownloading?: boolean; isLoading?: boolean;
downloadProgress?: number; downloadProgress?: number;
disabled?: boolean; disabled?: boolean;
onDownload: (e: MouseEvent<HTMLButtonElement>) => void; onDownload: (e: MouseEvent<HTMLButtonElement>) => void;
onMakeCurrent?: () => void; onMakeCurrent?: () => void;
onUpdate?: (e: MouseEvent<HTMLButtonElement>) => void; onUpdate?: (e: MouseEvent<HTMLButtonElement>) => void;
onRedownload?: (e: MouseEvent<HTMLButtonElement>) => void; onRedownload?: (e: MouseEvent<HTMLButtonElement>) => void;
onDelete?: (e: MouseEvent<HTMLButtonElement>) => void;
} }
export const DownloadCard = ({ export const DownloadCard = ({
version: versionInfo, version: versionInfo,
size, size,
description, description,
isDownloading = false, isLoading = false,
downloadProgress = 0, downloadProgress = 0,
disabled = false, disabled = false,
onDownload, onDownload,
onMakeCurrent, onMakeCurrent,
onUpdate, onUpdate,
onRedownload, onRedownload,
onDelete,
}: DownloadCardProps) => { }: DownloadCardProps) => {
const { resolvedColorScheme: colorScheme } = usePreferencesStore(); const { resolvedColorScheme: colorScheme } = usePreferencesStore();
const hasVersionMismatch = Boolean( const hasVersionMismatch = Boolean(
@ -46,6 +48,8 @@ export const DownloadCard = ({
versionInfo.actualVersion && versionInfo.actualVersion &&
versionInfo.version !== versionInfo.actualVersion versionInfo.version !== versionInfo.actualVersion
); );
// eslint-disable-next-line sonarjs/cognitive-complexity
const renderActionButtons = () => { const renderActionButtons = () => {
const buttons = []; const buttons = [];
@ -56,17 +60,17 @@ export const DownloadCard = ({
variant="filled" variant="filled"
size="xs" size="xs"
onClick={onDownload} onClick={onDownload}
loading={isDownloading} loading={isLoading}
disabled={disabled} disabled={disabled}
leftSection={ leftSection={
isDownloading ? ( isLoading ? (
<Loader size="1rem" /> <Loader size="1rem" />
) : ( ) : (
<Download style={{ width: rem(14), height: rem(14) }} /> <Download style={{ width: rem(14), height: rem(14) }} />
) )
} }
> >
{isDownloading ? 'Downloading...' : 'Download'} {isLoading ? 'Downloading...' : 'Download'}
</Button> </Button>
); );
} }
@ -92,20 +96,18 @@ export const DownloadCard = ({
variant="filled" variant="filled"
size="xs" size="xs"
onClick={onUpdate} onClick={onUpdate}
loading={isDownloading} loading={isLoading}
disabled={disabled} disabled={disabled}
color="orange" color="orange"
leftSection={ leftSection={
isDownloading ? ( isLoading ? (
<Loader size="1rem" /> <Loader size="1rem" />
) : ( ) : (
<Download style={{ width: rem(14), height: rem(14) }} /> <Download style={{ width: rem(14), height: rem(14) }} />
) )
} }
> >
{isDownloading {isLoading ? 'Updating...' : `Update to ${versionInfo.newerVersion}`}
? 'Updating...'
: `Update to ${versionInfo.newerVersion}`}
</Button> </Button>
); );
} }
@ -117,18 +119,41 @@ export const DownloadCard = ({
variant="filled" variant="filled"
size="xs" size="xs"
onClick={onRedownload} onClick={onRedownload}
loading={isDownloading} loading={isLoading}
disabled={disabled} disabled={disabled}
color="red" color="red"
leftSection={ leftSection={
isDownloading ? ( isLoading ? (
<Loader size="1rem" /> <Loader size="1rem" />
) : ( ) : (
<Download style={{ width: rem(14), height: rem(14) }} /> <Download style={{ width: rem(14), height: rem(14) }} />
) )
} }
> >
{isDownloading ? 'Re-downloading...' : 'Re-download'} {isLoading ? 'Re-downloading...' : 'Re-download'}
</Button>
);
}
if (onDelete && versionInfo.isInstalled && !versionInfo.isCurrent) {
buttons.push(
<Button
key="delete"
variant="light"
size="xs"
onClick={onDelete}
loading={isLoading}
disabled={disabled}
color="red"
leftSection={
isLoading ? (
<Loader size="1rem" />
) : (
<Trash2 style={{ width: rem(14), height: rem(14) }} />
)
}
>
{isLoading ? 'Deleting...' : 'Delete'}
</Button> </Button>
); );
} }
@ -201,7 +226,7 @@ export const DownloadCard = ({
{renderActionButtons()} {renderActionButtons()}
</Group> </Group>
{isDownloading && downloadProgress !== undefined && ( {isLoading && downloadProgress !== undefined && (
<Stack gap="xs" mt="sm"> <Stack gap="xs" mt="sm">
<Progress <Progress
value={Math.min(downloadProgress, 100)} value={Math.min(downloadProgress, 100)}

View file

@ -96,7 +96,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
download.url download.url
)} )}
description={getAssetDescription(download.name)} description={getAssetDescription(download.name)}
isDownloading={isDownloading} isLoading={isDownloading}
downloadProgress={ downloadProgress={
isDownloading isDownloading
? downloadProgress[download.name] || 0 ? downloadProgress[download.name] || 0

View file

@ -26,8 +26,10 @@ export const BackendSelector = () => {
useEffect(() => { useEffect(() => {
const loadBackends = async () => { const loadBackends = async () => {
setIsLoadingBackends(true); setIsLoadingBackends(true);
const backends = const backends =
await window.electronAPI.kobold.getAvailableBackends(true); await window.electronAPI.kobold.getAvailableBackends(true);
setAvailableBackends(backends || []); setAvailableBackends(backends || []);
setIsLoadingBackends(false); setIsLoadingBackends(false);
hasInitialized.current = true; hasInitialized.current = true;

View file

@ -3,7 +3,6 @@ import {
createSoftwareItems, createSoftwareItems,
createDriverItems, createDriverItems,
createHardwareItems, createHardwareItems,
type HardwareInfo,
} from '@/utils/systemInfo'; } from '@/utils/systemInfo';
import { import {
Text, Text,
@ -18,9 +17,10 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { Github, FolderOpen, FileText } from 'lucide-react'; import { Github, FolderOpen, FileText } from 'lucide-react';
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds'; import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
import type { SystemVersionInfo } from '@/types/electron';
import { PRODUCT_NAME, GITHUB_API } from '@/constants'; import { PRODUCT_NAME, GITHUB_API } from '@/constants';
import { InfoCard } from '@/components/InfoCard'; import { InfoCard } from '@/components/InfoCard';
import type { HardwareInfo } from '@/types/hardware';
import type { SystemVersionInfo } from '@/types/electron';
import icon from '/icon.png'; import icon from '/icon.png';

View file

@ -199,6 +199,17 @@ export const VersionsTab = () => {
await loadInstalledVersions(); await loadInstalledVersions();
}; };
const handleDelete = async (version: VersionInfo) => {
if (!version.installedPath || version.isCurrent) return;
const result = await window.electronAPI.kobold.deleteRelease(
version.installedPath
);
if (result.success) {
await loadInstalledVersions();
}
};
const makeCurrent = (version: VersionInfo) => { const makeCurrent = (version: VersionInfo) => {
if (!version.installedPath) return; if (!version.installedPath) return;
@ -270,7 +281,7 @@ export const VersionsTab = () => {
: '' : ''
} }
description={getAssetDescription(version.name)} description={getAssetDescription(version.name)}
isDownloading={isDownloading} isLoading={isDownloading}
downloadProgress={downloadProgress[version.name]} downloadProgress={downloadProgress[version.name]}
disabled={downloading !== null} disabled={downloading !== null}
onDownload={(e) => { onDownload={(e) => {
@ -285,6 +296,10 @@ export const VersionsTab = () => {
e.stopPropagation(); e.stopPropagation();
handleRedownload(version); handleRedownload(version);
}} }}
onDelete={(e) => {
e.stopPropagation();
handleDelete(version);
}}
onMakeCurrent={() => makeCurrent(version)} onMakeCurrent={() => makeCurrent(version)}
/> />
</div> </div>

View file

@ -11,6 +11,7 @@ import {
getInstalledVersions, getInstalledVersions,
getCurrentVersion, getCurrentVersion,
setCurrentVersion, setCurrentVersion,
deleteRelease,
} from '@/main/modules/koboldcpp/version'; } from '@/main/modules/koboldcpp/version';
import { import {
getConfigFiles, getConfigFiles,
@ -138,6 +139,10 @@ export function setupIPCHandlers() {
launchKoboldCppWithCustomFrontends(args) launchKoboldCppWithCustomFrontends(args)
); );
ipcMain.handle('kobold:deleteRelease', (_, binaryPath) =>
deleteRelease(binaryPath)
);
ipcMain.handle('kobold:stopKoboldCpp', () => { ipcMain.handle('kobold:stopKoboldCpp', () => {
stopKoboldCpp(); stopKoboldCpp();
stopSillyTavernFrontend(); stopSillyTavernFrontend();

View file

@ -110,7 +110,7 @@ async function detectVulkan() {
const isIntegrated = gpu.isIntegrated; const isIntegrated = gpu.isIntegrated;
devices.push({ devices.push({
name: isIntegrated ? gpu.deviceName : formatDeviceName(gpu.deviceName), name: isIntegrated ? gpu.name : formatDeviceName(gpu.name),
isIntegrated, isIntegrated,
}); });
} }
@ -192,155 +192,30 @@ async function detectCUDA() {
export async function detectROCm() { export async function detectROCm() {
try { try {
const rocminfoCommand = platform === 'win32' ? 'hipInfo' : 'rocminfo'; const rocminfoCommand = platform === 'win32' ? 'hipInfo' : 'rocminfo';
const { stdout } = await execa(rocminfoCommand, [], COMMON_EXEC_OPTIONS); const [rocminfoResult, vulkanInfo, hipccVersion] = await Promise.all([
execa(rocminfoCommand, [], COMMON_EXEC_OPTIONS),
if (stdout.trim()) { getVulkanInfo(),
const devices: GPUDevice[] = []; execa('hipcc', ['--version'], COMMON_EXEC_OPTIONS),
]);
if (platform === 'win32') { const { stdout } = rocminfoResult;
const lines = stdout.split('\n'); const { stdout: hipccOutput } = hipccVersion;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('Marketing Name:')) {
const name = line.split('Marketing Name:')[1]?.trim();
if (name && !name.toLowerCase().includes('cpu')) {
let deviceType = '';
const searchRangeLines = 20;
const searchStartIndex = Math.max(0, i - searchRangeLines);
const searchEndIndex = Math.min(
lines.length,
i + searchRangeLines
);
for (
let searchIndex = searchStartIndex;
searchIndex < searchEndIndex;
searchIndex++
) {
if (lines[searchIndex].includes('Device Type:')) {
deviceType =
lines[searchIndex].split('Device Type:')[1]?.trim() || '';
break;
}
}
if (deviceType !== 'CPU') {
let isIntegrated = true;
try {
const vulkanInfo = await getVulkanInfo();
const matchingGPU = vulkanInfo.allGPUs.find(
(gpu) =>
gpu.deviceName.includes(name) ||
name.includes(gpu.deviceName)
);
isIntegrated = matchingGPU ? matchingGPU.isIntegrated : false;
} catch {
isIntegrated = false;
}
devices.push({
name: isIntegrated ? name : formatDeviceName(name),
isIntegrated,
});
}
}
}
}
} else {
const lines = stdout.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('Marketing Name:')) {
const name = line.split('Marketing Name:')[1]?.trim();
if (name) {
let deviceType = '';
const searchRangeLines = 20;
const searchStartIndex = Math.max(0, i - searchRangeLines);
const searchEndIndex = Math.min(
lines.length,
i + searchRangeLines
);
for (
let searchIndex = searchStartIndex;
searchIndex < searchEndIndex;
searchIndex++
) {
if (lines[searchIndex].includes('Device Type:')) {
deviceType =
lines[searchIndex].split('Device Type:')[1]?.trim() || '';
break;
}
}
if (deviceType !== 'CPU') {
// Check if integrated by cross-referencing with vulkan GPU list
let isIntegrated = true;
try {
const vulkanInfo = await getVulkanInfo();
const matchingGPU = vulkanInfo.allGPUs.find(
(gpu) =>
gpu.deviceName.includes(name) ||
name.includes(gpu.deviceName)
);
isIntegrated = matchingGPU ? matchingGPU.isIntegrated : false;
} catch {
isIntegrated = false;
}
devices.push({
name: isIntegrated ? name : formatDeviceName(name),
isIntegrated,
});
}
}
}
}
}
let version: string | undefined; let version: string | undefined;
let driverVersion: string | undefined;
if (platform === 'linux' || platform === 'darwin') {
try { try {
const { stdout: amdSmiOutput } = await execa(
'amd-smi',
COMMON_EXEC_OPTIONS
);
if (amdSmiOutput.trim()) {
const match =
amdSmiOutput.match(/ROCm version:\s*(\d+\.\d+\.\d+)/i) ||
amdSmiOutput.match(/version\s*(\d+\.\d+\.\d+)/i) ||
amdSmiOutput.match(/(\d+\.\d+\.\d+)/);
if (match) {
version = match[1];
}
}
} catch {}
} else {
try {
const { stdout: hipccOutput } = await execa(
'hipcc',
['--version'],
COMMON_EXEC_OPTIONS
);
if (hipccOutput.trim()) { if (hipccOutput.trim()) {
const hipVersionMatch = hipccOutput.match( const hipVersionMatch = hipccOutput.match(
/HIP version:\s*(\d+\.\d+(?:\.\d+)?)/i /HIP version:\s*(\d+\.\d+(?:\.\d+)?)/i
); );
if (hipVersionMatch) { if (hipVersionMatch) {
version = hipVersionMatch[1]; version = hipVersionMatch[1];
} }
} }
} catch {} } catch {}
}
let driverVersion: string | undefined; if (stdout.trim()) {
const devices = parseRocmOutput(stdout, vulkanInfo);
if (platform === 'win32') { if (platform === 'win32') {
try { try {
@ -357,10 +232,8 @@ export async function detectROCm() {
driverVersion = driverOutput.trim(); driverVersion = driverOutput.trim();
} }
} catch {} } catch {}
} else if (platform === 'linux') { } else {
try { try {
const vulkanInfo = await getVulkanInfo();
for (const gpu of vulkanInfo.allGPUs) { for (const gpu of vulkanInfo.allGPUs) {
if (gpu.driverInfo && !gpu.isIntegrated) { if (gpu.driverInfo && !gpu.isIntegrated) {
driverVersion = gpu.driverInfo; driverVersion = gpu.driverInfo;
@ -467,6 +340,119 @@ function findComputeUnitsInClInfo(lines: string[], startIndex: number) {
const isDiscreteGPU = (computeUnits: number) => computeUnits > 12; const isDiscreteGPU = (computeUnits: number) => computeUnits > 12;
function parseRocmOutput(output: string, vulkanInfo: { allGPUs: GPUDevice[] }) {
const devices: GPUDevice[] = [];
const lines = output.split('\n');
let currentDevice: Partial<GPUDevice> | null = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmedLine = line.trim();
// Handle hipInfo format
if (handleHipInfoLine(trimmedLine, currentDevice, devices)) {
currentDevice = trimmedLine.startsWith('device#') ? {} : currentDevice;
continue;
}
// Handle rocminfo format
if (line.includes('Marketing Name:')) {
const device = parseRocmInfoDevice(line, lines, i, vulkanInfo);
if (device) {
devices.push(device);
}
}
}
// Add the last device for hipInfo format
if (currentDevice?.name) {
devices.push(
createDevice(currentDevice.name, currentDevice.isIntegrated || false)
);
}
return devices;
}
function handleHipInfoLine(
trimmedLine: string,
currentDevice: Partial<GPUDevice> | null,
devices: GPUDevice[]
): boolean {
if (trimmedLine.startsWith('device#')) {
if (currentDevice?.name) {
devices.push(
createDevice(currentDevice.name, currentDevice.isIntegrated || false)
);
}
return true;
}
if (currentDevice) {
if (trimmedLine.startsWith('Name:')) {
currentDevice.name = trimmedLine.split('Name:')[1]?.trim();
} else if (trimmedLine.startsWith('isIntegrated:')) {
const value = trimmedLine.split('isIntegrated:')[1]?.trim();
currentDevice.isIntegrated = value === '1';
}
return true;
}
return false;
}
function parseRocmInfoDevice(
line: string,
lines: string[],
index: number,
vulkanInfo: { allGPUs: GPUDevice[] }
) {
const name = line.split('Marketing Name:')[1]?.trim();
if (!name) return null;
const deviceType = findDeviceType(lines, index);
if (deviceType === 'CPU') return null;
const isIntegrated = determineIfIntegrated(name, vulkanInfo);
return createDevice(name, isIntegrated);
}
const createDevice = (name: string, isIntegrated: boolean) => ({
name: isIntegrated ? name : formatDeviceName(name),
isIntegrated,
});
function findDeviceType(lines: string[], startIndex: number) {
const searchRangeLines = 20;
const searchStartIndex = Math.max(0, startIndex - searchRangeLines);
const searchEndIndex = Math.min(lines.length, startIndex + searchRangeLines);
for (
let searchIndex = searchStartIndex;
searchIndex < searchEndIndex;
searchIndex++
) {
if (lines[searchIndex].includes('Device Type:')) {
return lines[searchIndex].split('Device Type:')[1]?.trim() || '';
}
}
return '';
}
function determineIfIntegrated(
name: string,
vulkanInfo: { allGPUs: GPUDevice[] }
): boolean {
try {
const matchingGPU = vulkanInfo.allGPUs.find(
(gpu) => gpu.name.includes(name) || name.includes(gpu.name)
);
return matchingGPU ? matchingGPU.isIntegrated : false;
} catch {
return false;
}
}
async function detectCLBlast() { async function detectCLBlast() {
try { try {
const { stdout } = await execa('clinfo', [], COMMON_EXEC_OPTIONS); const { stdout } = await execa('clinfo', [], COMMON_EXEC_OPTIONS);

View file

@ -15,6 +15,7 @@ import { pathExists } from '@/utils/node/fs';
import { stripAssetExtensions } from '@/utils/version'; import { stripAssetExtensions } from '@/utils/version';
import { getLauncherPath } from '@/utils/node/path'; import { getLauncherPath } from '@/utils/node/path';
import type { DownloadReleaseOptions, GitHubAsset } from '@/types/electron'; import type { DownloadReleaseOptions, GitHubAsset } from '@/types/electron';
import { clearVersionCache } from './version';
async function removeDirectoryWithRetry( async function removeDirectoryWithRetry(
dirPath: string, dirPath: string,
@ -174,6 +175,8 @@ export async function downloadRelease(
unpackedDirPath unpackedDirPath
); );
clearVersionCache(launcherPath);
if (options.oldVersionPath && options.isUpdate) { if (options.oldVersionPath && options.isUpdate) {
const oldInstallDir = join(options.oldVersionPath, '..'); const oldInstallDir = join(options.oldVersionPath, '..');

View file

@ -13,6 +13,19 @@ import { logError } from '@/utils/node/logging';
import { getLauncherPath } from '@/utils/node/path'; import { getLauncherPath } from '@/utils/node/path';
import type { InstalledVersion } from '@/types/electron'; import type { InstalledVersion } from '@/types/electron';
const versionCache = new Map<
string,
{ version: string; actualVersion?: string } | null
>();
export function clearVersionCache(path?: string) {
if (path) {
versionCache.delete(path);
} else {
versionCache.clear();
}
}
export async function getInstalledVersions() { export async function getInstalledVersions() {
try { try {
const installDir = getInstallDir(); const installDir = getInstallDir();
@ -129,12 +142,48 @@ export async function setCurrentVersion(binaryPath: string) {
return false; return false;
} }
export async function deleteRelease(binaryPath: string) {
try {
if (!(await pathExists(binaryPath))) {
return { success: false, error: 'Release not found' };
}
const currentBinaryPath = getCurrentKoboldBinary();
if (currentBinaryPath === binaryPath) {
return {
success: false,
error: 'Cannot delete the currently active release',
};
}
const releaseDir = binaryPath.split(/[/\\]/).slice(0, -1).join('/');
if (await pathExists(releaseDir)) {
const { rm } = await import('fs/promises');
await rm(releaseDir, { recursive: true, force: true });
clearVersionCache(binaryPath);
sendToRenderer('versions-updated');
return { success: true };
}
return { success: false, error: 'Release directory not found' };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}
export async function getVersionFromBinary(launcherPath: string) { export async function getVersionFromBinary(launcherPath: string) {
try { try {
if (!(await pathExists(launcherPath))) { if (!(await pathExists(launcherPath))) {
return null; return null;
} }
if (versionCache.has(launcherPath)) {
return versionCache.get(launcherPath);
}
let folderVersion: string | null = null; let folderVersion: string | null = null;
let actualVersion: string | null = null; let actualVersion: string | null = null;
@ -155,37 +204,31 @@ export async function getVersionFromBinary(launcherPath: string) {
}); });
const allOutput = (result.stdout + result.stderr).trim(); const allOutput = (result.stdout + result.stderr).trim();
const lines = allOutput.split('\n').filter((line) => line.trim());
if (/^\d+\.\d+/.test(allOutput)) { if (lines.length > 0) {
const versionParts = allOutput.split(/\s+/)[0]; const lastLine = lines[lines.length - 1].trim();
if (versionParts && /^\d+\.\d+/.test(versionParts)) { const versionMatch = lastLine.match(
actualVersion = versionParts; /^(\d+\.\d+(?:\.\d+)?(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*)$/
} );
} if (versionMatch) {
actualVersion = versionMatch[1];
if (!actualVersion) {
const lines = allOutput.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
if (/^\d+\.\d+/.test(trimmedLine)) {
const versionPart = trimmedLine.split(/\s+/)[0];
if (versionPart) {
actualVersion = versionPart;
break;
}
}
} }
} }
} catch {} } catch {}
return { const result = {
version: folderVersion || actualVersion || 'unknown', version: folderVersion || actualVersion || 'unknown',
actualVersion: actualVersion:
folderVersion && actualVersion && folderVersion !== actualVersion folderVersion && actualVersion && folderVersion !== actualVersion
? actualVersion ? actualVersion
: undefined, : undefined,
}; };
versionCache.set(launcherPath, result);
return result;
} catch { } catch {
versionCache.set(launcherPath, null);
return null; return null;
} }
} }

View file

@ -36,6 +36,8 @@ const koboldAPI: KoboldAPI = {
ipcRenderer.invoke('kobold:selectInstallDirectory'), ipcRenderer.invoke('kobold:selectInstallDirectory'),
downloadRelease: (asset, options) => downloadRelease: (asset, options) =>
ipcRenderer.invoke('kobold:downloadRelease', asset, options), ipcRenderer.invoke('kobold:downloadRelease', asset, options),
deleteRelease: (binaryPath) =>
ipcRenderer.invoke('kobold:deleteRelease', binaryPath),
launchKoboldCpp: (args) => ipcRenderer.invoke('kobold:launchKoboldCpp', args), launchKoboldCpp: (args) => ipcRenderer.invoke('kobold:launchKoboldCpp', args),
getConfigFiles: () => ipcRenderer.invoke('kobold:getConfigFiles'), getConfigFiles: () => ipcRenderer.invoke('kobold:getConfigFiles'),
saveConfigFile: (configName, configData) => saveConfigFile: (configName, configData) =>

View file

@ -124,6 +124,9 @@ export interface KoboldAPI {
asset: GitHubAsset, asset: GitHubAsset,
options: DownloadReleaseOptions options: DownloadReleaseOptions
) => Promise<void>; ) => Promise<void>;
deleteRelease: (
binaryPath: string
) => Promise<{ success: boolean; error?: string }>;
launchKoboldCpp: ( launchKoboldCpp: (
args?: string[] args?: string[]
) => Promise<{ success: boolean; pid?: number; error?: string }>; ) => Promise<{ success: boolean; pid?: number; error?: string }>;

View file

@ -13,8 +13,8 @@ export interface SystemMemoryInfo {
} }
export interface GPUDevice { export interface GPUDevice {
readonly name: string; name: string;
readonly isIntegrated: boolean; isIntegrated: boolean;
} }
export interface GPUCapabilities { export interface GPUCapabilities {
@ -52,7 +52,7 @@ export interface HardwareDetectionResult {
export interface HardwareInfo { export interface HardwareInfo {
cpu: CPUCapabilities; cpu: CPUCapabilities;
gpu: BasicGPUInfo; gpu: BasicGPUInfo;
gpuCapabilities?: GPUCapabilities; gpuCapabilities: GPUCapabilities;
gpuMemory?: GPUMemoryInfo[]; gpuMemory: GPUMemoryInfo[];
systemMemory?: SystemMemoryInfo; systemMemory: SystemMemoryInfo;
} }

View file

@ -4,7 +4,7 @@ import { formatDeviceName } from '@/utils/format';
let vulkanInfoCache: { let vulkanInfoCache: {
allGPUs: { allGPUs: {
deviceName: string; name: string;
driverInfo?: string; driverInfo?: string;
apiVersion?: string; apiVersion?: string;
hasAMD: boolean; hasAMD: boolean;
@ -27,7 +27,7 @@ export async function getVulkanInfo() {
}); });
const allGPUs: { const allGPUs: {
deviceName: string; name: string;
driverInfo?: string; driverInfo?: string;
apiVersion?: string; apiVersion?: string;
hasAMD: boolean; hasAMD: boolean;
@ -62,7 +62,7 @@ export async function getVulkanInfo() {
'PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU' 'PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU'
); );
currentGPU = { currentGPU = {
deviceName: '', name: '',
hasAMD: false, hasAMD: false,
hasNVIDIA: false, hasNVIDIA: false,
isIntegrated, isIntegrated,
@ -77,7 +77,7 @@ export async function getVulkanInfo() {
if (parts.length >= 2) { if (parts.length >= 2) {
const name = parts[1]?.trim(); const name = parts[1]?.trim();
if (name) { if (name) {
currentGPU.deviceName = name; currentGPU.name = name;
currentGPU.hasAMD = currentGPU.hasAMD =
name.toLowerCase().includes('amd') || name.toLowerCase().includes('amd') ||
name.toLowerCase().includes('radeon'); name.toLowerCase().includes('radeon');
@ -107,7 +107,7 @@ export async function getVulkanInfo() {
} }
} }
} else if (foundGPU && currentGPU && line.includes('GPU')) { } else if (foundGPU && currentGPU && line.includes('GPU')) {
if (currentGPU.deviceName) { if (currentGPU.name) {
allGPUs.push(currentGPU); allGPUs.push(currentGPU);
} }
foundGPU = false; foundGPU = false;
@ -115,7 +115,7 @@ export async function getVulkanInfo() {
} }
} }
if (foundGPU && currentGPU && currentGPU.deviceName) { if (foundGPU && currentGPU && currentGPU.name) {
allGPUs.push(currentGPU); allGPUs.push(currentGPU);
} }
} }
@ -143,7 +143,7 @@ export async function detectGPUViaVulkan() {
const gpuInfo: string[] = []; const gpuInfo: string[] = [];
for (const gpu of vulkanInfo.allGPUs.filter((g) => !g.isIntegrated)) { for (const gpu of vulkanInfo.allGPUs.filter((g) => !g.isIntegrated)) {
gpuInfo.push(formatDeviceName(gpu.deviceName)); gpuInfo.push(formatDeviceName(gpu.name));
if (gpu.hasAMD) { if (gpu.hasAMD) {
hasAMD = true; hasAMD = true;

View file

@ -1,21 +1,7 @@
import type { SystemVersionInfo } from '@/types/electron'; import type { SystemVersionInfo } from '@/types/electron';
import type {
CPUCapabilities,
GPUCapabilities,
BasicGPUInfo,
GPUMemoryInfo,
SystemMemoryInfo,
} from '@/types/hardware';
import { PRODUCT_NAME } from '@/constants'; import { PRODUCT_NAME } from '@/constants';
import type { InfoItem } from '@/components/InfoCard'; import type { InfoItem } from '@/components/InfoCard';
import type { HardwareInfo } from '@/types/hardware';
export interface HardwareInfo {
cpu: CPUCapabilities;
gpu: BasicGPUInfo;
gpuCapabilities: GPUCapabilities;
gpuMemory: GPUMemoryInfo[];
systemMemory: SystemMemoryInfo;
}
export const createSoftwareItems = (versionInfo: SystemVersionInfo) => [ export const createSoftwareItems = (versionInfo: SystemVersionInfo) => [
{ {