mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
fighting rocm on windows, allow deleting kcpp binaries from versions tab
This commit is contained in:
parent
b2533659c2
commit
ec2410c0e4
14 changed files with 286 additions and 216 deletions
|
|
@ -9,7 +9,7 @@ import {
|
|||
Progress,
|
||||
rem,
|
||||
} from '@mantine/core';
|
||||
import { Download } from 'lucide-react';
|
||||
import { Download, Trash2 } from 'lucide-react';
|
||||
import { MouseEvent } from 'react';
|
||||
import { pretifyBinName, isWindowsROCmBuild } from '@/utils/assets';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
|
|
@ -19,26 +19,28 @@ interface DownloadCardProps {
|
|||
version: VersionInfo;
|
||||
size: string;
|
||||
description?: string;
|
||||
isDownloading?: boolean;
|
||||
isLoading?: boolean;
|
||||
downloadProgress?: number;
|
||||
disabled?: boolean;
|
||||
onDownload: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
onMakeCurrent?: () => void;
|
||||
onUpdate?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
onRedownload?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
onDelete?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
export const DownloadCard = ({
|
||||
version: versionInfo,
|
||||
size,
|
||||
description,
|
||||
isDownloading = false,
|
||||
isLoading = false,
|
||||
downloadProgress = 0,
|
||||
disabled = false,
|
||||
onDownload,
|
||||
onMakeCurrent,
|
||||
onUpdate,
|
||||
onRedownload,
|
||||
onDelete,
|
||||
}: DownloadCardProps) => {
|
||||
const { resolvedColorScheme: colorScheme } = usePreferencesStore();
|
||||
const hasVersionMismatch = Boolean(
|
||||
|
|
@ -46,6 +48,8 @@ export const DownloadCard = ({
|
|||
versionInfo.actualVersion &&
|
||||
versionInfo.version !== versionInfo.actualVersion
|
||||
);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const renderActionButtons = () => {
|
||||
const buttons = [];
|
||||
|
||||
|
|
@ -56,17 +60,17 @@ export const DownloadCard = ({
|
|||
variant="filled"
|
||||
size="xs"
|
||||
onClick={onDownload}
|
||||
loading={isDownloading}
|
||||
loading={isLoading}
|
||||
disabled={disabled}
|
||||
leftSection={
|
||||
isDownloading ? (
|
||||
isLoading ? (
|
||||
<Loader size="1rem" />
|
||||
) : (
|
||||
<Download style={{ width: rem(14), height: rem(14) }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{isDownloading ? 'Downloading...' : 'Download'}
|
||||
{isLoading ? 'Downloading...' : 'Download'}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
@ -92,20 +96,18 @@ export const DownloadCard = ({
|
|||
variant="filled"
|
||||
size="xs"
|
||||
onClick={onUpdate}
|
||||
loading={isDownloading}
|
||||
loading={isLoading}
|
||||
disabled={disabled}
|
||||
color="orange"
|
||||
leftSection={
|
||||
isDownloading ? (
|
||||
isLoading ? (
|
||||
<Loader size="1rem" />
|
||||
) : (
|
||||
<Download style={{ width: rem(14), height: rem(14) }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{isDownloading
|
||||
? 'Updating...'
|
||||
: `Update to ${versionInfo.newerVersion}`}
|
||||
{isLoading ? 'Updating...' : `Update to ${versionInfo.newerVersion}`}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
@ -117,18 +119,41 @@ export const DownloadCard = ({
|
|||
variant="filled"
|
||||
size="xs"
|
||||
onClick={onRedownload}
|
||||
loading={isDownloading}
|
||||
loading={isLoading}
|
||||
disabled={disabled}
|
||||
color="red"
|
||||
leftSection={
|
||||
isDownloading ? (
|
||||
isLoading ? (
|
||||
<Loader size="1rem" />
|
||||
) : (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
@ -201,7 +226,7 @@ export const DownloadCard = ({
|
|||
{renderActionButtons()}
|
||||
</Group>
|
||||
|
||||
{isDownloading && downloadProgress !== undefined && (
|
||||
{isLoading && downloadProgress !== undefined && (
|
||||
<Stack gap="xs" mt="sm">
|
||||
<Progress
|
||||
value={Math.min(downloadProgress, 100)}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
|||
download.url
|
||||
)}
|
||||
description={getAssetDescription(download.name)}
|
||||
isDownloading={isDownloading}
|
||||
isLoading={isDownloading}
|
||||
downloadProgress={
|
||||
isDownloading
|
||||
? downloadProgress[download.name] || 0
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ export const BackendSelector = () => {
|
|||
useEffect(() => {
|
||||
const loadBackends = async () => {
|
||||
setIsLoadingBackends(true);
|
||||
|
||||
const backends =
|
||||
await window.electronAPI.kobold.getAvailableBackends(true);
|
||||
|
||||
setAvailableBackends(backends || []);
|
||||
setIsLoadingBackends(false);
|
||||
hasInitialized.current = true;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
createSoftwareItems,
|
||||
createDriverItems,
|
||||
createHardwareItems,
|
||||
type HardwareInfo,
|
||||
} from '@/utils/systemInfo';
|
||||
import {
|
||||
Text,
|
||||
|
|
@ -18,9 +17,10 @@ import {
|
|||
} from '@mantine/core';
|
||||
import { Github, FolderOpen, FileText } from 'lucide-react';
|
||||
import { useLogoClickSounds } from '@/hooks/useLogoClickSounds';
|
||||
import type { SystemVersionInfo } from '@/types/electron';
|
||||
import { PRODUCT_NAME, GITHUB_API } from '@/constants';
|
||||
import { InfoCard } from '@/components/InfoCard';
|
||||
import type { HardwareInfo } from '@/types/hardware';
|
||||
import type { SystemVersionInfo } from '@/types/electron';
|
||||
|
||||
import icon from '/icon.png';
|
||||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,17 @@ export const VersionsTab = () => {
|
|||
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) => {
|
||||
if (!version.installedPath) return;
|
||||
|
||||
|
|
@ -270,7 +281,7 @@ export const VersionsTab = () => {
|
|||
: ''
|
||||
}
|
||||
description={getAssetDescription(version.name)}
|
||||
isDownloading={isDownloading}
|
||||
isLoading={isDownloading}
|
||||
downloadProgress={downloadProgress[version.name]}
|
||||
disabled={downloading !== null}
|
||||
onDownload={(e) => {
|
||||
|
|
@ -285,6 +296,10 @@ export const VersionsTab = () => {
|
|||
e.stopPropagation();
|
||||
handleRedownload(version);
|
||||
}}
|
||||
onDelete={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDelete(version);
|
||||
}}
|
||||
onMakeCurrent={() => makeCurrent(version)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
getInstalledVersions,
|
||||
getCurrentVersion,
|
||||
setCurrentVersion,
|
||||
deleteRelease,
|
||||
} from '@/main/modules/koboldcpp/version';
|
||||
import {
|
||||
getConfigFiles,
|
||||
|
|
@ -138,6 +139,10 @@ export function setupIPCHandlers() {
|
|||
launchKoboldCppWithCustomFrontends(args)
|
||||
);
|
||||
|
||||
ipcMain.handle('kobold:deleteRelease', (_, binaryPath) =>
|
||||
deleteRelease(binaryPath)
|
||||
);
|
||||
|
||||
ipcMain.handle('kobold:stopKoboldCpp', () => {
|
||||
stopKoboldCpp();
|
||||
stopSillyTavernFrontend();
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ async function detectVulkan() {
|
|||
const isIntegrated = gpu.isIntegrated;
|
||||
|
||||
devices.push({
|
||||
name: isIntegrated ? gpu.deviceName : formatDeviceName(gpu.deviceName),
|
||||
name: isIntegrated ? gpu.name : formatDeviceName(gpu.name),
|
||||
isIntegrated,
|
||||
});
|
||||
}
|
||||
|
|
@ -192,155 +192,30 @@ async function detectCUDA() {
|
|||
export async function detectROCm() {
|
||||
try {
|
||||
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),
|
||||
getVulkanInfo(),
|
||||
execa('hipcc', ['--version'], COMMON_EXEC_OPTIONS),
|
||||
]);
|
||||
const { stdout } = rocminfoResult;
|
||||
const { stdout: hipccOutput } = hipccVersion;
|
||||
let version: string | undefined;
|
||||
let driverVersion: string | undefined;
|
||||
|
||||
try {
|
||||
if (hipccOutput.trim()) {
|
||||
const hipVersionMatch = hipccOutput.match(
|
||||
/HIP version:\s*(\d+\.\d+(?:\.\d+)?)/i
|
||||
);
|
||||
|
||||
if (hipVersionMatch) {
|
||||
version = hipVersionMatch[1];
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (stdout.trim()) {
|
||||
const devices: GPUDevice[] = [];
|
||||
|
||||
if (platform === 'win32') {
|
||||
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 && !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;
|
||||
|
||||
if (platform === 'linux' || platform === 'darwin') {
|
||||
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()) {
|
||||
const hipVersionMatch = hipccOutput.match(
|
||||
/HIP version:\s*(\d+\.\d+(?:\.\d+)?)/i
|
||||
);
|
||||
if (hipVersionMatch) {
|
||||
version = hipVersionMatch[1];
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
let driverVersion: string | undefined;
|
||||
const devices = parseRocmOutput(stdout, vulkanInfo);
|
||||
|
||||
if (platform === 'win32') {
|
||||
try {
|
||||
|
|
@ -357,10 +232,8 @@ export async function detectROCm() {
|
|||
driverVersion = driverOutput.trim();
|
||||
}
|
||||
} catch {}
|
||||
} else if (platform === 'linux') {
|
||||
} else {
|
||||
try {
|
||||
const vulkanInfo = await getVulkanInfo();
|
||||
|
||||
for (const gpu of vulkanInfo.allGPUs) {
|
||||
if (gpu.driverInfo && !gpu.isIntegrated) {
|
||||
driverVersion = gpu.driverInfo;
|
||||
|
|
@ -467,6 +340,119 @@ function findComputeUnitsInClInfo(lines: string[], startIndex: number) {
|
|||
|
||||
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() {
|
||||
try {
|
||||
const { stdout } = await execa('clinfo', [], COMMON_EXEC_OPTIONS);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { pathExists } from '@/utils/node/fs';
|
|||
import { stripAssetExtensions } from '@/utils/version';
|
||||
import { getLauncherPath } from '@/utils/node/path';
|
||||
import type { DownloadReleaseOptions, GitHubAsset } from '@/types/electron';
|
||||
import { clearVersionCache } from './version';
|
||||
|
||||
async function removeDirectoryWithRetry(
|
||||
dirPath: string,
|
||||
|
|
@ -174,6 +175,8 @@ export async function downloadRelease(
|
|||
unpackedDirPath
|
||||
);
|
||||
|
||||
clearVersionCache(launcherPath);
|
||||
|
||||
if (options.oldVersionPath && options.isUpdate) {
|
||||
const oldInstallDir = join(options.oldVersionPath, '..');
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,19 @@ import { logError } from '@/utils/node/logging';
|
|||
import { getLauncherPath } from '@/utils/node/path';
|
||||
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() {
|
||||
try {
|
||||
const installDir = getInstallDir();
|
||||
|
|
@ -129,12 +142,48 @@ export async function setCurrentVersion(binaryPath: string) {
|
|||
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) {
|
||||
try {
|
||||
if (!(await pathExists(launcherPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (versionCache.has(launcherPath)) {
|
||||
return versionCache.get(launcherPath);
|
||||
}
|
||||
|
||||
let folderVersion: 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 lines = allOutput.split('\n').filter((line) => line.trim());
|
||||
|
||||
if (/^\d+\.\d+/.test(allOutput)) {
|
||||
const versionParts = allOutput.split(/\s+/)[0];
|
||||
if (versionParts && /^\d+\.\d+/.test(versionParts)) {
|
||||
actualVersion = versionParts;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (lines.length > 0) {
|
||||
const lastLine = lines[lines.length - 1].trim();
|
||||
const versionMatch = lastLine.match(
|
||||
/^(\d+\.\d+(?:\.\d+)?(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*)$/
|
||||
);
|
||||
if (versionMatch) {
|
||||
actualVersion = versionMatch[1];
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
const result = {
|
||||
version: folderVersion || actualVersion || 'unknown',
|
||||
actualVersion:
|
||||
folderVersion && actualVersion && folderVersion !== actualVersion
|
||||
? actualVersion
|
||||
: undefined,
|
||||
};
|
||||
|
||||
versionCache.set(launcherPath, result);
|
||||
return result;
|
||||
} catch {
|
||||
versionCache.set(launcherPath, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ const koboldAPI: KoboldAPI = {
|
|||
ipcRenderer.invoke('kobold:selectInstallDirectory'),
|
||||
downloadRelease: (asset, options) =>
|
||||
ipcRenderer.invoke('kobold:downloadRelease', asset, options),
|
||||
deleteRelease: (binaryPath) =>
|
||||
ipcRenderer.invoke('kobold:deleteRelease', binaryPath),
|
||||
launchKoboldCpp: (args) => ipcRenderer.invoke('kobold:launchKoboldCpp', args),
|
||||
getConfigFiles: () => ipcRenderer.invoke('kobold:getConfigFiles'),
|
||||
saveConfigFile: (configName, configData) =>
|
||||
|
|
|
|||
3
src/types/electron.d.ts
vendored
3
src/types/electron.d.ts
vendored
|
|
@ -124,6 +124,9 @@ export interface KoboldAPI {
|
|||
asset: GitHubAsset,
|
||||
options: DownloadReleaseOptions
|
||||
) => Promise<void>;
|
||||
deleteRelease: (
|
||||
binaryPath: string
|
||||
) => Promise<{ success: boolean; error?: string }>;
|
||||
launchKoboldCpp: (
|
||||
args?: string[]
|
||||
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
||||
|
|
|
|||
10
src/types/hardware.d.ts
vendored
10
src/types/hardware.d.ts
vendored
|
|
@ -13,8 +13,8 @@ export interface SystemMemoryInfo {
|
|||
}
|
||||
|
||||
export interface GPUDevice {
|
||||
readonly name: string;
|
||||
readonly isIntegrated: boolean;
|
||||
name: string;
|
||||
isIntegrated: boolean;
|
||||
}
|
||||
|
||||
export interface GPUCapabilities {
|
||||
|
|
@ -52,7 +52,7 @@ export interface HardwareDetectionResult {
|
|||
export interface HardwareInfo {
|
||||
cpu: CPUCapabilities;
|
||||
gpu: BasicGPUInfo;
|
||||
gpuCapabilities?: GPUCapabilities;
|
||||
gpuMemory?: GPUMemoryInfo[];
|
||||
systemMemory?: SystemMemoryInfo;
|
||||
gpuCapabilities: GPUCapabilities;
|
||||
gpuMemory: GPUMemoryInfo[];
|
||||
systemMemory: SystemMemoryInfo;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { formatDeviceName } from '@/utils/format';
|
|||
|
||||
let vulkanInfoCache: {
|
||||
allGPUs: {
|
||||
deviceName: string;
|
||||
name: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
|
|
@ -27,7 +27,7 @@ export async function getVulkanInfo() {
|
|||
});
|
||||
|
||||
const allGPUs: {
|
||||
deviceName: string;
|
||||
name: string;
|
||||
driverInfo?: string;
|
||||
apiVersion?: string;
|
||||
hasAMD: boolean;
|
||||
|
|
@ -62,7 +62,7 @@ export async function getVulkanInfo() {
|
|||
'PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU'
|
||||
);
|
||||
currentGPU = {
|
||||
deviceName: '',
|
||||
name: '',
|
||||
hasAMD: false,
|
||||
hasNVIDIA: false,
|
||||
isIntegrated,
|
||||
|
|
@ -77,7 +77,7 @@ export async function getVulkanInfo() {
|
|||
if (parts.length >= 2) {
|
||||
const name = parts[1]?.trim();
|
||||
if (name) {
|
||||
currentGPU.deviceName = name;
|
||||
currentGPU.name = name;
|
||||
currentGPU.hasAMD =
|
||||
name.toLowerCase().includes('amd') ||
|
||||
name.toLowerCase().includes('radeon');
|
||||
|
|
@ -107,7 +107,7 @@ export async function getVulkanInfo() {
|
|||
}
|
||||
}
|
||||
} else if (foundGPU && currentGPU && line.includes('GPU')) {
|
||||
if (currentGPU.deviceName) {
|
||||
if (currentGPU.name) {
|
||||
allGPUs.push(currentGPU);
|
||||
}
|
||||
foundGPU = false;
|
||||
|
|
@ -115,7 +115,7 @@ export async function getVulkanInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
if (foundGPU && currentGPU && currentGPU.deviceName) {
|
||||
if (foundGPU && currentGPU && currentGPU.name) {
|
||||
allGPUs.push(currentGPU);
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ export async function detectGPUViaVulkan() {
|
|||
const gpuInfo: string[] = [];
|
||||
|
||||
for (const gpu of vulkanInfo.allGPUs.filter((g) => !g.isIntegrated)) {
|
||||
gpuInfo.push(formatDeviceName(gpu.deviceName));
|
||||
gpuInfo.push(formatDeviceName(gpu.name));
|
||||
|
||||
if (gpu.hasAMD) {
|
||||
hasAMD = true;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,7 @@
|
|||
import type { SystemVersionInfo } from '@/types/electron';
|
||||
import type {
|
||||
CPUCapabilities,
|
||||
GPUCapabilities,
|
||||
BasicGPUInfo,
|
||||
GPUMemoryInfo,
|
||||
SystemMemoryInfo,
|
||||
} from '@/types/hardware';
|
||||
import { PRODUCT_NAME } from '@/constants';
|
||||
import type { InfoItem } from '@/components/InfoCard';
|
||||
|
||||
export interface HardwareInfo {
|
||||
cpu: CPUCapabilities;
|
||||
gpu: BasicGPUInfo;
|
||||
gpuCapabilities: GPUCapabilities;
|
||||
gpuMemory: GPUMemoryInfo[];
|
||||
systemMemory: SystemMemoryInfo;
|
||||
}
|
||||
import type { HardwareInfo } from '@/types/hardware';
|
||||
|
||||
export const createSoftwareItems = (versionInfo: SystemVersionInfo) => [
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue