(
- []
- );
- const [isLoadingBackends, setIsLoadingBackends] = useState(false);
+ const [availableAccelerations, setAvailableAccelerations] = useState<
+ AccelerationOption[]
+ >([]);
+ const [isLoadingAccelerations, setIsLoadingAccelerations] = useState(false);
const [isCalculatingLayers, setIsCalculatingLayers] = useState(false);
+ const [isMac, setIsMac] = useState(false);
const hasInitialized = useRef(false);
useEffect(() => {
- const loadBackends = async () => {
- setIsLoadingBackends(true);
+ const loadAccelerations = async () => {
+ setIsLoadingAccelerations(true);
- const backends =
- await window.electronAPI.kobold.getAvailableBackends(true);
+ const [accelerations, platform] = await Promise.all([
+ window.electronAPI.kobold.getAvailableAccelerations(true),
+ window.electronAPI.kobold.getPlatform(),
+ ]);
- setAvailableBackends(backends || []);
- setIsLoadingBackends(false);
+ setAvailableAccelerations(accelerations || []);
+ setIsMac(platform === 'darwin');
+ setIsLoadingAccelerations(false);
hasInitialized.current = true;
};
if (!hasInitialized.current) {
- loadBackends();
+ loadAccelerations();
}
const cleanup = window.electronAPI.kobold.onVersionsUpdated(() => {
hasInitialized.current = false;
- loadBackends();
+ loadAccelerations();
});
return cleanup;
}, []);
useEffect(() => {
- if (availableBackends.length > 0 && backend) {
- const isBackendAvailable = availableBackends.some(
- (b) => b.value === backend && !b.disabled
+ if (availableAccelerations.length > 0 && backend) {
+ const isAccelerationAvailable = availableAccelerations.some(
+ (a) => a.value === backend && !a.disabled
);
- if (!isBackendAvailable) {
- const fallbackBackend = availableBackends.find((b) => !b.disabled);
- if (fallbackBackend) {
- handleBackendChange(fallbackBackend.value);
+ if (!isAccelerationAvailable) {
+ const fallbackAcceleration = availableAccelerations.find(
+ (a) => !a.disabled
+ );
+ if (fallbackAcceleration) {
+ handleBackendChange(fallbackAcceleration.value);
}
}
}
- }, [availableBackends, backend, handleBackendChange]);
+ }, [availableAccelerations, backend, handleBackendChange]);
useEffect(() => {
const calculateLayers = async () => {
+ const isCpuOnly = backend === 'cpu' && !isMac;
if (
!autoGpuLayers ||
!model ||
!contextSize ||
- backend === 'cpu' ||
- isLoadingBackends
+ isCpuOnly ||
+ isLoadingAccelerations
) {
return;
}
@@ -133,7 +140,8 @@ export const BackendSelector = () => {
backend,
gpuDeviceSelection,
flashattention,
- isLoadingBackends,
+ isLoadingAccelerations,
+ isMac,
handleGpuLayersChange,
]);
@@ -143,16 +151,20 @@ export const BackendSelector = () => {
- Backend
+ Acceleration
-
+
-
+
);
};
diff --git a/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx b/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx
index 0212cfa..aab096b 100644
--- a/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx
+++ b/src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx
@@ -2,14 +2,14 @@ import { Text, Group, TextInput } from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
import { Select } from '@/components/Select';
-import type { BackendOption } from '@/types';
+import type { AccelerationOption } from '@/types';
interface GpuDeviceSelectorProps {
- availableBackends: BackendOption[];
+ availableAccelerations: AccelerationOption[];
}
export const GpuDeviceSelector = ({
- availableBackends,
+ availableAccelerations,
}: GpuDeviceSelectorProps) => {
const {
backend,
@@ -19,21 +19,23 @@ export const GpuDeviceSelector = ({
handleTensorSplitChange,
} = useLaunchConfig();
- const selectedBackend = availableBackends.find((b) => b.value === backend);
- const isGpuBackend =
+ const selectedAcceleration = availableAccelerations.find(
+ (a) => a.value === backend
+ );
+ const isGpuAcceleration =
backend === 'cuda' ||
backend === 'rocm' ||
backend === 'vulkan' ||
backend === 'clblast';
const getDiscreteDeviceCount = () => {
- if (!selectedBackend?.devices) return 0;
+ if (!selectedAcceleration?.devices) return 0;
if (backend === 'clblast' || backend === 'vulkan' || backend === 'rocm') {
- return selectedBackend.devices.filter(
+ return selectedAcceleration.devices.filter(
(device) => typeof device === 'string' || !device.isIntegrated
).length;
}
- return selectedBackend.devices.length;
+ return selectedAcceleration.devices.length;
};
const hasMultipleDevices = getDiscreteDeviceCount() > 1;
@@ -42,15 +44,15 @@ export const GpuDeviceSelector = ({
hasMultipleDevices &&
gpuDeviceSelection === 'all';
- if (!isGpuBackend || !hasMultipleDevices) {
+ if (!isGpuAcceleration || !hasMultipleDevices) {
return null;
}
const deviceOptions = (() => {
- if (!selectedBackend?.devices) return [];
+ if (!selectedAcceleration?.devices) return [];
if (backend === 'clblast') {
- return selectedBackend.devices
+ return selectedAcceleration.devices
.map((device, index) => {
if (typeof device === 'object' && device.isIntegrated) {
return null;
@@ -67,7 +69,7 @@ export const GpuDeviceSelector = ({
}
if (backend === 'vulkan' || backend === 'rocm') {
- const discreteDeviceOptions = selectedBackend.devices
+ const discreteDeviceOptions = selectedAcceleration.devices
.map((device, index) => {
if (typeof device === 'object' && device.isIntegrated) {
return null;
@@ -87,7 +89,7 @@ export const GpuDeviceSelector = ({
return [
{ value: 'all', label: 'All GPUs' },
- ...selectedBackend.devices.map((device, index) => {
+ ...selectedAcceleration.devices.map((device, index) => {
const deviceName =
typeof device === 'string'
? device
diff --git a/src/components/screens/Launch/GeneralTab/index.tsx b/src/components/screens/Launch/GeneralTab/index.tsx
index e10df56..c6a2b6a 100644
--- a/src/components/screens/Launch/GeneralTab/index.tsx
+++ b/src/components/screens/Launch/GeneralTab/index.tsx
@@ -7,7 +7,7 @@ import {
Transition,
} from '@mantine/core';
import { InfoTooltip } from '@/components/InfoTooltip';
-import { BackendSelector } from '@/components/screens/Launch/GeneralTab/BackendSelector';
+import { AccelerationSelector } from '@/components/screens/Launch/GeneralTab/AccelerationSelector';
import { ModelFileField } from '@/components/screens/Launch/ModelFileField';
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
@@ -33,7 +33,7 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => {
>
{(styles) => (
-
+
{
});
const setHappyDefaults = useCallback(async () => {
- const backends = await window.electronAPI.kobold.getAvailableBackends();
+ const accelerations =
+ await window.electronAPI.kobold.getAvailableAccelerations();
- if (!backend && backends && backends.length > 0) {
- handleBackendChange(backends[0].value);
+ if (!backend && accelerations && accelerations.length > 0) {
+ handleBackendChange(accelerations[0].value);
}
}, [backend, handleBackendChange]);
diff --git a/src/components/settings/VersionsTab.tsx b/src/components/settings/BackendsTab.tsx
similarity index 63%
rename from src/components/settings/VersionsTab.tsx
rename to src/components/settings/BackendsTab.tsx
index 0b393c3..9f08b78 100644
--- a/src/components/settings/VersionsTab.tsx
+++ b/src/components/settings/BackendsTab.tsx
@@ -19,10 +19,10 @@ import {
import { formatDownloadSize } from '@/utils/format';
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
-import type { InstalledVersion, ReleaseWithStatus } from '@/types/electron';
+import type { InstalledBackend, ReleaseWithStatus } from '@/types/electron';
import type { VersionInfo } from '@/types';
-export const VersionsTab = () => {
+export const BackendsTab = () => {
const {
availableDownloads,
loadingPlatform,
@@ -33,10 +33,10 @@ export const VersionsTab = () => {
initialize,
} = useKoboldVersionsStore();
- const [installedVersions, setInstalledVersions] = useState<
- InstalledVersion[]
+ const [installedBackends, setInstalledBackends] = useState<
+ InstalledBackend[]
>([]);
- const [currentVersion, setCurrentVersion] = useState(
+ const [currentBackend, setCurrentBackend] = useState(
null
);
const [loadingInstalled, setLoadingInstalled] = useState(true);
@@ -45,16 +45,16 @@ export const VersionsTab = () => {
);
const downloadingItemRef = useRef(null);
- const loadInstalledVersions = useCallback(async () => {
+ const loadInstalledBackends = useCallback(async () => {
setLoadingInstalled(true);
- const [versions, currentVersion] = await Promise.all([
- window.electronAPI.kobold.getInstalledVersions(),
- window.electronAPI.kobold.getCurrentVersion(),
+ const [backends, current] = await Promise.all([
+ window.electronAPI.kobold.getInstalledBackends(),
+ window.electronAPI.kobold.getCurrentBackend(),
]);
- setInstalledVersions(versions);
- setCurrentVersion(currentVersion);
+ setInstalledBackends(backends);
+ setCurrentBackend(current);
setLoadingInstalled(false);
}, []);
@@ -72,10 +72,10 @@ export const VersionsTab = () => {
}
// eslint-disable-next-line react-hooks/set-state-in-effect
- loadInstalledVersions();
+ loadInstalledBackends();
loadLatestRelease();
}, [
- loadInstalledVersions,
+ loadInstalledBackends,
loadLatestRelease,
availableDownloads.length,
loadingRemote,
@@ -83,47 +83,47 @@ export const VersionsTab = () => {
initialize,
]);
- const allVersions = useMemo((): VersionInfo[] => {
- const versions: VersionInfo[] = [];
+ const allBackends = useMemo((): VersionInfo[] => {
+ const backends: VersionInfo[] = [];
const processedInstalled = new Set();
availableDownloads.forEach((download) => {
const downloadBaseName = stripAssetExtensions(download.name);
- const installedVersion = installedVersions.find((v) => {
- const displayName = getDisplayNameFromPath(v);
+ const installedBackend = installedBackends.find((b) => {
+ const displayName = getDisplayNameFromPath(b);
return displayName === downloadBaseName;
});
const isCurrent = Boolean(
- installedVersion &&
- currentVersion &&
- currentVersion.path === installedVersion.path
+ installedBackend &&
+ currentBackend &&
+ currentBackend.path === installedBackend.path
);
- if (installedVersion) {
- processedInstalled.add(installedVersion.path);
+ if (installedBackend) {
+ processedInstalled.add(installedBackend.path);
const hasUpdate =
compareVersions(
download.version || 'unknown',
- installedVersion.version
+ installedBackend.version
) > 0;
- versions.push({
+ backends.push({
name: download.name,
- version: installedVersion.version,
+ version: installedBackend.version,
size: undefined,
isInstalled: true,
isCurrent,
downloadUrl: download.url,
- installedPath: installedVersion.path,
+ installedPath: installedBackend.path,
hasUpdate,
newerVersion: hasUpdate ? download.version : undefined,
- actualVersion: installedVersion.actualVersion,
+ actualVersion: installedBackend.actualVersion,
});
} else {
- versions.push({
+ backends.push({
name: download.name,
version: download.version || 'unknown',
size: download.size,
@@ -134,14 +134,14 @@ export const VersionsTab = () => {
}
});
- installedVersions.forEach((installed) => {
+ installedBackends.forEach((installed) => {
if (!processedInstalled.has(installed.path)) {
const displayName = getDisplayNameFromPath(installed);
const isCurrent = Boolean(
- currentVersion && currentVersion.path === installed.path
+ currentBackend && currentBackend.path === installed.path
);
- versions.push({
+ backends.push({
name: displayName,
version: installed.version,
size: undefined,
@@ -153,13 +153,13 @@ export const VersionsTab = () => {
}
});
- return versions.sort((a, b) => {
+ return backends.sort((a, b) => {
if (a.isInstalled && !b.isInstalled) return -1;
if (!a.isInstalled && b.isInstalled) return 1;
return 0;
});
- }, [availableDownloads, installedVersions, currentVersion]);
+ }, [availableDownloads, installedBackends, currentBackend]);
useEffect(() => {
if (downloading && downloadingItemRef.current) {
@@ -170,8 +170,8 @@ export const VersionsTab = () => {
}
}, [downloading]);
- const handleDownload = async (version: VersionInfo) => {
- const download = availableDownloads.find((d) => d.name === version.name);
+ const handleDownload = async (backend: VersionInfo) => {
+ const download = availableDownloads.find((d) => d.name === backend.name);
if (!download) return;
await handleDownloadFromStore({
@@ -180,59 +180,59 @@ export const VersionsTab = () => {
wasCurrentBinary: false,
});
- await loadInstalledVersions();
+ await loadInstalledBackends();
};
- const handleUpdate = async (version: VersionInfo) => {
- const download = availableDownloads.find((d) => d.name === version.name);
+ const handleUpdate = async (backend: VersionInfo) => {
+ const download = availableDownloads.find((d) => d.name === backend.name);
if (!download) return;
await handleDownloadFromStore({
item: download,
isUpdate: true,
- wasCurrentBinary: version.isCurrent,
- oldVersionPath: version.installedPath,
+ wasCurrentBinary: backend.isCurrent,
+ oldVersionPath: backend.installedPath,
});
- await loadInstalledVersions();
+ await loadInstalledBackends();
};
- const handleRedownload = async (version: VersionInfo) => {
- const download = availableDownloads.find((d) => d.name === version.name);
+ const handleRedownload = async (backend: VersionInfo) => {
+ const download = availableDownloads.find((d) => d.name === backend.name);
if (!download) return;
await handleDownloadFromStore({
item: download,
isUpdate: true,
- wasCurrentBinary: version.isCurrent,
- oldVersionPath: version.installedPath,
+ wasCurrentBinary: backend.isCurrent,
+ oldVersionPath: backend.installedPath,
});
- await loadInstalledVersions();
+ await loadInstalledBackends();
};
- const handleDelete = async (version: VersionInfo) => {
- if (!version.installedPath || version.isCurrent) return;
+ const handleDelete = async (backend: VersionInfo) => {
+ if (!backend.installedPath || backend.isCurrent) return;
const result = await window.electronAPI.kobold.deleteRelease(
- version.installedPath
+ backend.installedPath
);
if (result.success) {
- await loadInstalledVersions();
+ await loadInstalledBackends();
}
};
- const makeCurrent = (version: VersionInfo) => {
- if (!version.installedPath) return;
+ const makeCurrent = (backend: VersionInfo) => {
+ if (!backend.installedPath) return;
- const targetInstalledVersion = installedVersions.find(
- (v) => v.path === version.installedPath
+ const targetBackend = installedBackends.find(
+ (b) => b.path === backend.installedPath
);
- if (targetInstalledVersion) {
- setCurrentVersion(targetInstalledVersion);
+ if (targetBackend) {
+ setCurrentBackend(targetBackend);
}
- window.electronAPI.kobold.setCurrentVersion(version.installedPath);
+ window.electronAPI.kobold.setCurrentBackend(backend.installedPath);
};
if (loadingInstalled || loadingPlatform || loadingRemote) {
@@ -242,9 +242,9 @@ export const VersionsTab = () => {
{loadingInstalled && (loadingPlatform || loadingRemote)
- ? 'Loading versions...'
+ ? 'Loading backends...'
: loadingInstalled
- ? 'Scanning installed versions...'
+ ? 'Scanning installed backends...'
: 'Checking for updates...'}
@@ -276,50 +276,50 @@ export const VersionsTab = () => {
)}
- {allVersions.map((version, index) => {
- const isDownloading = downloading === version.name;
+ {allBackends.map((backend, index) => {
+ const isDownloading = downloading === backend.name;
return (
{
e.stopPropagation();
- handleDownload(version);
+ handleDownload(backend);
}}
onUpdate={(e) => {
e.stopPropagation();
- handleUpdate(version);
+ handleUpdate(backend);
}}
onRedownload={(e) => {
e.stopPropagation();
- handleRedownload(version);
+ handleRedownload(backend);
}}
onDelete={(e) => {
e.stopPropagation();
- handleDelete(version);
+ handleDelete(backend);
}}
- onMakeCurrent={() => makeCurrent(version)}
+ onMakeCurrent={() => makeCurrent(backend)}
/>
);
})}
- {allVersions.length === 0 && (
+ {allBackends.length === 0 && (
- No versions found
+ No backends found
)}
diff --git a/src/components/settings/SettingsModal.tsx b/src/components/settings/SettingsModal.tsx
index 8e48afc..234a9d4 100644
--- a/src/components/settings/SettingsModal.tsx
+++ b/src/components/settings/SettingsModal.tsx
@@ -10,7 +10,7 @@ import {
Wrench,
} from 'lucide-react';
import { GeneralTab } from '@/components/settings/GeneralTab';
-import { VersionsTab } from '@/components/settings/VersionsTab';
+import { BackendsTab } from '@/components/settings/BackendsTab';
import { AppearanceTab } from '@/components/settings/AppearanceTab';
import { SystemTab } from '@/components/settings/SystemTab';
import { TroubleshootingTab } from '@/components/settings/TroubleshootingTab';
@@ -33,11 +33,11 @@ export const SettingsModal = ({
}: SettingsModalProps) => {
const [activeTab, setActiveTab] = useState('general');
- const showVersionsTab =
+ const showBackendsTab =
currentScreen !== 'download' && currentScreen !== 'welcome';
const effectiveActiveTab =
- !showVersionsTab && activeTab === 'versions' ? 'general' : activeTab;
+ !showBackendsTab && activeTab === 'backends' ? 'general' : activeTab;
useEffect(() => {
if (opened) {
@@ -110,14 +110,14 @@ export const SettingsModal = ({
>
General
- {showVersionsTab && (
+ {showBackendsTab && (
}
>
- Versions
+ Backends
)}
- {showVersionsTab && (
-
-
+ {showBackendsTab && (
+
+
)}
diff --git a/src/components/settings/SystemTab.tsx b/src/components/settings/SystemTab.tsx
index c5356a1..4aed50b 100644
--- a/src/components/settings/SystemTab.tsx
+++ b/src/components/settings/SystemTab.tsx
@@ -26,7 +26,7 @@ export const SystemTab = () => {
const loadKoboldVersion = async () => {
const currentVersion =
- await window.electronAPI.kobold.getCurrentVersion();
+ await window.electronAPI.kobold.getCurrentBackend();
if (currentVersion) {
setKoboldVersion(currentVersion.version);
}
diff --git a/src/hooks/useLaunchLogic.ts b/src/hooks/useLaunchLogic.ts
index bafe504..2b67718 100644
--- a/src/hooks/useLaunchLogic.ts
+++ b/src/hooks/useLaunchLogic.ts
@@ -206,11 +206,11 @@ const addTensorSplitArgs = (args: string[], launchArgs: LaunchArgs) => {
}
};
-const buildBackendArgs = (launchArgs: LaunchArgs) => {
+const buildBackendArgs = (launchArgs: LaunchArgs, platform: string) => {
const args: string[] = [];
if (!launchArgs.backend || launchArgs.backend === 'cpu') {
- if (launchArgs.backend === 'cpu') {
+ if (launchArgs.backend === 'cpu' && platform !== 'darwin') {
args.push('--usecpu');
}
return args;
@@ -271,10 +271,12 @@ export const useLaunchLogic = ({
onLaunch();
+ const platform = await window.electronAPI.kobold.getPlatform();
+
const args: string[] = [
...buildModelArgs(model, sdmodel, launchArgs),
...buildConfigArgs(hasImageModel, launchArgs),
- ...buildBackendArgs(launchArgs),
+ ...buildBackendArgs(launchArgs, platform),
];
if (launchArgs.additionalArguments.trim()) {
diff --git a/src/hooks/useUpdateChecker.ts b/src/hooks/useUpdateChecker.ts
index 5d1cd31..1f6d156 100644
--- a/src/hooks/useUpdateChecker.ts
+++ b/src/hooks/useUpdateChecker.ts
@@ -6,11 +6,11 @@ import {
} from '@/utils/version';
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
import { getROCmDownload } from '@/utils/rocm';
-import type { InstalledVersion, DownloadItem } from '@/types/electron';
+import type { InstalledBackend, DownloadItem } from '@/types/electron';
import type { DismissedUpdate } from '@/types';
export interface BinaryUpdateInfo {
- currentVersion: InstalledVersion;
+ currentBackend: InstalledBackend;
availableUpdate: DownloadItem;
}
@@ -44,12 +44,12 @@ export const useUpdateChecker = () => {
}
setIsChecking(true);
- const [currentVersion, rocmDownload] = await Promise.all([
- window.electronAPI.kobold.getCurrentVersion(),
+ const [currentBackend, rocmDownload] = await Promise.all([
+ window.electronAPI.kobold.getCurrentBackend(),
getROCmDownload(),
]);
- if (!currentVersion) {
+ if (!currentBackend) {
setIsChecking(false);
return;
}
@@ -59,7 +59,7 @@ export const useUpdateChecker = () => {
availableDownloads.push(rocmDownload);
}
- const currentDisplayName = getDisplayNameFromPath(currentVersion);
+ const currentDisplayName = getDisplayNameFromPath(currentBackend);
const matchingDownload = availableDownloads.find(
(download: DownloadItem) => {
@@ -70,18 +70,18 @@ export const useUpdateChecker = () => {
if (matchingDownload && matchingDownload.version) {
const hasUpdate =
- compareVersions(matchingDownload.version, currentVersion.version) > 0;
+ compareVersions(matchingDownload.version, currentBackend.version) > 0;
if (hasUpdate) {
const isUpdateDismissed = dismissedUpdates.some(
(dismissedUpdate) =>
- dismissedUpdate.currentVersionPath === currentVersion.path &&
+ dismissedUpdate.currentVersionPath === currentBackend.path &&
dismissedUpdate.targetVersion === matchingDownload.version
);
if (!isUpdateDismissed) {
setUpdateInfo({
- currentVersion,
+ currentBackend,
availableUpdate: matchingDownload,
});
setShowUpdateModal(true);
@@ -95,7 +95,7 @@ export const useUpdateChecker = () => {
const skipUpdate = useCallback(() => {
if (updateInfo && updateInfo.availableUpdate.version) {
const newDismissedUpdate: DismissedUpdate = {
- currentVersionPath: updateInfo.currentVersion.path,
+ currentVersionPath: updateInfo.currentBackend.path,
targetVersion: updateInfo.availableUpdate.version,
};
diff --git a/src/hooks/useWarnings.ts b/src/hooks/useWarnings.ts
index 54acd74..0127e6a 100644
--- a/src/hooks/useWarnings.ts
+++ b/src/hooks/useWarnings.ts
@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback, useMemo } from 'react';
import { CPUCapabilities, GPUDevice } from '@/types/hardware';
-import type { BackendOption, BackendSupport } from '@/types';
+import type { AccelerationOption, AccelerationSupport } from '@/types';
export interface Warning {
type: 'warning' | 'info';
@@ -58,14 +58,14 @@ interface GpuInfo {
}
const checkGpuWarnings = async (
- backendSupport: BackendSupport,
+ accelerationSupport: AccelerationSupport,
gpuCapabilities: GpuCapabilities,
gpuInfo: GpuInfo
) => {
const warnings: Warning[] = [];
if (
- backendSupport.cuda &&
+ accelerationSupport.cuda &&
gpuCapabilities.cuda.devices.length === 0 &&
gpuInfo.hasNVIDIA
) {
@@ -77,7 +77,7 @@ const checkGpuWarnings = async (
}
if (
- backendSupport.rocm &&
+ accelerationSupport.rocm &&
gpuCapabilities.rocm.devices.length === 0 &&
gpuInfo.hasAMD
) {
@@ -134,7 +134,7 @@ const checkVramWarnings = async (backend: string): Promise => {
const checkCpuWarnings = (
backend: string,
- availableBackends: BackendOption[]
+ availableAccelerations: AccelerationOption[]
) => {
const warnings: Warning[] = [];
@@ -143,8 +143,8 @@ const checkCpuWarnings = (
}
if (
- availableBackends.length > 0 &&
- availableBackends.some((b) => b.value === 'cpu')
+ availableAccelerations.length > 0 &&
+ availableAccelerations.some((a) => a.value === 'cpu')
) {
warnings.push({
type: 'info',
@@ -159,35 +159,35 @@ const checkCpuWarnings = (
const checkBackendWarnings = async (params?: {
backend: string;
cpuCapabilities: CPUCapabilities | null;
- availableBackends: BackendOption[];
+ availableAccelerations: AccelerationOption[];
}) => {
const warnings: Warning[] = [];
- const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([
- window.electronAPI.kobold.detectBackendSupport(),
+ const [accelerationSupport, gpuCapabilities, gpuInfo] = await Promise.all([
+ window.electronAPI.kobold.detectAccelerationSupport(),
window.electronAPI.kobold.detectGPUCapabilities(),
window.electronAPI.kobold.detectGPU(),
]);
- if (!backendSupport) {
+ if (!accelerationSupport) {
return warnings;
}
const gpuWarnings = await checkGpuWarnings(
- backendSupport,
+ accelerationSupport,
gpuCapabilities,
gpuInfo
);
warnings.push(...gpuWarnings);
if (params) {
- const { backend, cpuCapabilities, availableBackends } = params;
+ const { backend, cpuCapabilities, availableAccelerations } = params;
const vramWarnings = await checkVramWarnings(backend);
warnings.push(...vramWarnings);
if (cpuCapabilities) {
- const cpuWarnings = checkCpuWarnings(backend, availableBackends);
+ const cpuWarnings = checkCpuWarnings(backend, availableAccelerations);
warnings.push(...cpuWarnings);
}
}
@@ -214,15 +214,15 @@ export const useWarnings = ({
return;
}
- const [cpuCapabilitiesResult, availableBackends] = await Promise.all([
+ const [cpuCapabilitiesResult, availableAccelerations] = await Promise.all([
window.electronAPI.kobold.detectCPU(),
- window.electronAPI.kobold.getAvailableBackends(),
+ window.electronAPI.kobold.getAvailableAccelerations(),
]);
const result = await checkBackendWarnings({
backend,
cpuCapabilities: cpuCapabilitiesResult,
- availableBackends,
+ availableAccelerations,
});
setBackendWarnings(result);
diff --git a/src/main/ipc.ts b/src/main/ipc.ts
index 109a1a6..a2c08fb 100644
--- a/src/main/ipc.ts
+++ b/src/main/ipc.ts
@@ -8,11 +8,11 @@ import {
} from '@/main/modules/koboldcpp/launcher';
import { downloadRelease } from '@/main/modules/koboldcpp/download';
import {
- getInstalledVersions,
- getCurrentVersion,
- setCurrentVersion,
+ getInstalledBackends,
+ getCurrentBackend,
+ setCurrentBackend,
deleteRelease,
-} from '@/main/modules/koboldcpp/version';
+} from '@/main/modules/koboldcpp/backend';
import {
getConfigFiles,
saveConfigFile,
@@ -61,9 +61,9 @@ import {
detectSystemMemory,
} from '@/main/modules/hardware';
import {
- detectBackendSupport,
- getAvailableBackends,
-} from '@/main/modules/koboldcpp/backend';
+ detectAccelerationSupport,
+ getAvailableAccelerations,
+} from '@/main/modules/koboldcpp/acceleration';
import {
openPerformanceManager,
startMonitoring,
@@ -85,9 +85,9 @@ export function setupIPCHandlers() {
downloadRelease(asset, options)
);
- ipcMain.handle('kobold:getInstalledVersions', () => getInstalledVersions());
+ ipcMain.handle('kobold:getInstalledBackends', () => getInstalledBackends());
- ipcMain.handle('kobold:getCurrentVersion', () => getCurrentVersion());
+ ipcMain.handle('kobold:getCurrentBackend', () => getCurrentBackend());
ipcMain.handle('kobold:getConfigFiles', () => getConfigFiles());
@@ -105,8 +105,8 @@ export function setupIPCHandlers() {
setConfig('selectedConfig', configName)
);
- ipcMain.handle('kobold:setCurrentVersion', (_, version) =>
- setCurrentVersion(version)
+ ipcMain.handle('kobold:setCurrentBackend', (_, version) =>
+ setCurrentBackend(version)
);
ipcMain.handle('kobold:getCurrentInstallDir', () => getInstallDir());
@@ -127,10 +127,13 @@ export function setupIPCHandlers() {
ipcMain.handle('kobold:detectROCm', () => detectROCm());
- ipcMain.handle('kobold:detectBackendSupport', () => detectBackendSupport());
+ ipcMain.handle('kobold:detectAccelerationSupport', () =>
+ detectAccelerationSupport()
+ );
- ipcMain.handle('kobold:getAvailableBackends', (_, includeDisabled = false) =>
- getAvailableBackends(includeDisabled)
+ ipcMain.handle(
+ 'kobold:getAvailableAccelerations',
+ (_, includeDisabled = false) => getAvailableAccelerations(includeDisabled)
);
ipcMain.handle('kobold:getPlatform', () => platform);
diff --git a/src/main/modules/koboldcpp/acceleration.ts b/src/main/modules/koboldcpp/acceleration.ts
new file mode 100644
index 0000000..3752032
--- /dev/null
+++ b/src/main/modules/koboldcpp/acceleration.ts
@@ -0,0 +1,184 @@
+import { join, dirname } from 'path';
+import { platform } from 'process';
+import { pathExists } from '@/utils/node/fs';
+import { getCurrentBinaryInfo } from './backend';
+import { detectGPUCapabilities, detectCPU } from '../hardware';
+import { tryExecute, safeExecute } from '@/utils/node/logging';
+import type { AccelerationOption, AccelerationSupport } from '@/types';
+
+const accelerationSupportCache = new Map();
+const availableAccelerationsCache = new Map();
+
+const CPU_LABEL = platform === 'darwin' ? 'Metal' : 'CPU';
+
+async function detectAccelerationSupportFromPath(koboldBinaryPath: string) {
+ if (accelerationSupportCache.has(koboldBinaryPath)) {
+ return accelerationSupportCache.get(koboldBinaryPath)!;
+ }
+
+ const support: AccelerationSupport = {
+ rocm: false,
+ vulkan: false,
+ clblast: false,
+ noavx2: false,
+ failsafe: false,
+ cuda: false,
+ };
+
+ await tryExecute(async () => {
+ const binaryDir = dirname(koboldBinaryPath);
+ const internalDir = join(binaryDir, '_internal');
+
+ const libExtension = platform === 'win32' ? '.dll' : '.so';
+
+ const hasKoboldCppLib = async (name: string): Promise => {
+ const filename = `${name}${libExtension}`;
+
+ if (platform === 'win32') {
+ return (
+ (await pathExists(join(binaryDir, filename))) ||
+ (await pathExists(join(internalDir, filename)))
+ );
+ } else {
+ return (
+ (await pathExists(join(internalDir, filename))) ||
+ (await pathExists(join(binaryDir, filename)))
+ );
+ }
+ };
+
+ const [rocm, vulkan, clblast, noavx2, failsafe, cuda] = await Promise.all([
+ hasKoboldCppLib('koboldcpp_hipblas'),
+ hasKoboldCppLib('koboldcpp_vulkan'),
+ hasKoboldCppLib('koboldcpp_clblast'),
+ hasKoboldCppLib('koboldcpp_noavx2'),
+ hasKoboldCppLib('koboldcpp_failsafe'),
+ hasKoboldCppLib('koboldcpp_cublas'),
+ ]);
+
+ support.rocm = rocm;
+ support.vulkan = vulkan;
+ support.clblast = clblast;
+ support.noavx2 = noavx2;
+ support.failsafe = failsafe;
+ support.cuda = cuda;
+ }, 'Error detecting acceleration support');
+
+ accelerationSupportCache.set(koboldBinaryPath, support);
+ return support;
+}
+
+export const detectAccelerationSupport = async () =>
+ (await safeExecute(async () => {
+ const currentBinaryInfo = await getCurrentBinaryInfo();
+
+ if (!currentBinaryInfo?.path) {
+ return null;
+ }
+
+ return detectAccelerationSupportFromPath(currentBinaryInfo.path);
+ }, 'Error detecting current binary acceleration support')) || null;
+
+export async function getAvailableAccelerations(includeDisabled = false) {
+ if (platform === 'darwin') {
+ return [{ value: 'cpu', label: CPU_LABEL }];
+ }
+
+ // eslint-disable-next-line sonarjs/cognitive-complexity
+ const result = await safeExecute(async () => {
+ const [currentBinaryInfo, hardwareCapabilities, cpuCapabilities] =
+ await Promise.all([
+ getCurrentBinaryInfo(),
+ detectGPUCapabilities(),
+ includeDisabled ? detectCPU() : Promise.resolve(null),
+ ]);
+
+ if (!currentBinaryInfo?.path) {
+ return [{ value: 'cpu', label: CPU_LABEL }];
+ }
+
+ const cacheKey = `${currentBinaryInfo.path}:${includeDisabled}`;
+
+ if (availableAccelerationsCache.has(cacheKey)) {
+ return availableAccelerationsCache.get(cacheKey)!;
+ }
+
+ const accelerationSupport = await detectAccelerationSupport();
+
+ if (!accelerationSupport) {
+ return [];
+ }
+
+ const accelerations: AccelerationOption[] = [];
+
+ if (accelerationSupport.cuda) {
+ const isSupported = hardwareCapabilities.cuda.devices.length > 0;
+ if (isSupported || includeDisabled) {
+ accelerations.push({
+ value: 'cuda',
+ label: 'CUDA',
+ devices: hardwareCapabilities.cuda.devices,
+ disabled: includeDisabled ? !isSupported : undefined,
+ });
+ }
+ }
+
+ if (accelerationSupport.rocm) {
+ const isSupported = hardwareCapabilities.rocm.devices.length > 0;
+ if (isSupported || includeDisabled) {
+ accelerations.push({
+ value: 'rocm',
+ label: 'ROCm',
+ devices: hardwareCapabilities.rocm.devices,
+ disabled: includeDisabled ? !isSupported : undefined,
+ });
+ }
+ }
+
+ if (accelerationSupport.vulkan) {
+ const isSupported = hardwareCapabilities.vulkan.devices.length > 0;
+ if (isSupported || includeDisabled) {
+ accelerations.push({
+ value: 'vulkan',
+ label: 'Vulkan',
+ devices: hardwareCapabilities.vulkan.devices,
+ disabled: includeDisabled ? !isSupported : undefined,
+ });
+ }
+ }
+
+ if (accelerationSupport.clblast) {
+ const discreteDevices = hardwareCapabilities.clblast.devices.filter(
+ (device) => typeof device === 'string' || !device.isIntegrated
+ );
+ const isSupported = discreteDevices.length > 0;
+ if (isSupported || includeDisabled) {
+ accelerations.push({
+ value: 'clblast',
+ label: 'CLBlast',
+ devices: hardwareCapabilities.clblast.devices,
+ disabled: includeDisabled ? !isSupported : undefined,
+ });
+ }
+ }
+
+ accelerations.push({
+ value: 'cpu',
+ label: CPU_LABEL,
+ devices: cpuCapabilities?.devices.map((device) => device.name) || [],
+ disabled: false,
+ });
+
+ if (includeDisabled) {
+ accelerations.sort((a, b) => {
+ if (a.disabled === b.disabled) return 0;
+ return a.disabled ? 1 : -1;
+ });
+ }
+
+ availableAccelerationsCache.set(cacheKey, accelerations);
+ return accelerations;
+ }, 'Failed to get available accelerations');
+
+ return result || [{ value: 'cpu', label: CPU_LABEL }];
+}
diff --git a/src/main/modules/koboldcpp/backend.ts b/src/main/modules/koboldcpp/backend.ts
index b7cb8df..aef5ec8 100644
--- a/src/main/modules/koboldcpp/backend.ts
+++ b/src/main/modules/koboldcpp/backend.ts
@@ -1,178 +1,233 @@
-import { join, dirname } from 'path';
-import { platform } from 'process';
+import { readdir, stat, rm } from 'fs/promises';
+import { join } from 'path';
+import { execa } from 'execa';
+
+import {
+ getCurrentKoboldBinary,
+ setCurrentKoboldBinary,
+ getInstallDir,
+} from '../config';
+import { sendToRenderer } from '../window';
import { pathExists } from '@/utils/node/fs';
-import { getCurrentBinaryInfo } from './version';
-import { detectGPUCapabilities, detectCPU } from '../hardware';
-import { tryExecute, safeExecute } from '@/utils/node/logging';
-import type { BackendOption, BackendSupport } from '@/types';
+import { logError } from '@/utils/node/logging';
+import { getLauncherPath } from '@/utils/node/path';
+import type { InstalledBackend } from '@/types/electron';
-const backendSupportCache = new Map();
-const availableBackendsCache = new Map();
+const versionCache = new Map<
+ string,
+ { version: string; actualVersion?: string } | null
+>();
-async function detectBackendSupportFromPath(koboldBinaryPath: string) {
- if (backendSupportCache.has(koboldBinaryPath)) {
- return backendSupportCache.get(koboldBinaryPath)!;
+export function clearVersionCache(path?: string) {
+ if (path) {
+ versionCache.delete(path);
+ } else {
+ versionCache.clear();
}
-
- const support: BackendSupport = {
- rocm: false,
- vulkan: false,
- clblast: false,
- noavx2: false,
- failsafe: false,
- cuda: false,
- };
-
- await tryExecute(async () => {
- const binaryDir = dirname(koboldBinaryPath);
- const internalDir = join(binaryDir, '_internal');
-
- const libExtension = platform === 'win32' ? '.dll' : '.so';
-
- const hasKoboldCppLib = async (name: string): Promise => {
- const filename = `${name}${libExtension}`;
-
- if (platform === 'win32') {
- return (
- (await pathExists(join(binaryDir, filename))) ||
- (await pathExists(join(internalDir, filename)))
- );
- } else {
- return (
- (await pathExists(join(internalDir, filename))) ||
- (await pathExists(join(binaryDir, filename)))
- );
- }
- };
-
- const [rocm, vulkan, clblast, noavx2, failsafe, cuda] = await Promise.all([
- hasKoboldCppLib('koboldcpp_hipblas'),
- hasKoboldCppLib('koboldcpp_vulkan'),
- hasKoboldCppLib('koboldcpp_clblast'),
- hasKoboldCppLib('koboldcpp_noavx2'),
- hasKoboldCppLib('koboldcpp_failsafe'),
- hasKoboldCppLib('koboldcpp_cublas'),
- ]);
-
- support.rocm = rocm;
- support.vulkan = vulkan;
- support.clblast = clblast;
- support.noavx2 = noavx2;
- support.failsafe = failsafe;
- support.cuda = cuda;
- }, 'Error detecting backend support');
-
- backendSupportCache.set(koboldBinaryPath, support);
- return support;
}
-export const detectBackendSupport = async () =>
- (await safeExecute(async () => {
- const currentBinaryInfo = await getCurrentBinaryInfo();
-
- if (!currentBinaryInfo?.path) {
- return null;
- }
-
- return detectBackendSupportFromPath(currentBinaryInfo.path);
- }, 'Error detecting current binary backend support')) || null;
-
-export async function getAvailableBackends(includeDisabled = false) {
- // eslint-disable-next-line sonarjs/cognitive-complexity
- const result = await safeExecute(async () => {
- const [currentBinaryInfo, hardwareCapabilities, cpuCapabilities] =
- await Promise.all([
- getCurrentBinaryInfo(),
- detectGPUCapabilities(),
- includeDisabled ? detectCPU() : Promise.resolve(null),
- ]);
-
- if (!currentBinaryInfo?.path) {
- return [{ value: 'cpu', label: 'CPU' }];
- }
-
- const cacheKey = `${currentBinaryInfo.path}:${includeDisabled}`;
-
- if (availableBackendsCache.has(cacheKey)) {
- return availableBackendsCache.get(cacheKey)!;
- }
-
- const backendSupport = await detectBackendSupport();
-
- if (!backendSupport) {
+export async function getInstalledBackends() {
+ try {
+ const installDir = getInstallDir();
+ if (!(await pathExists(installDir))) {
return [];
}
- const backends: BackendOption[] = [];
+ const items = await readdir(installDir);
+ const launchers: { path: string; filename: string; size: number }[] = [];
- if (backendSupport.cuda) {
- const isSupported = hardwareCapabilities.cuda.devices.length > 0;
- if (isSupported || includeDisabled) {
- backends.push({
- value: 'cuda',
- label: 'CUDA',
- devices: hardwareCapabilities.cuda.devices,
- disabled: includeDisabled ? !isSupported : undefined,
- });
+ for (const item of items) {
+ const itemPath = join(installDir, item);
+ const stats = await stat(itemPath);
+
+ if (stats.isDirectory()) {
+ const launcherPath = await getLauncherPath(itemPath);
+ if (launcherPath && (await pathExists(launcherPath))) {
+ const launcherStats = await stat(launcherPath);
+ const launcherFilename = launcherPath.split(/[/\\]/).pop() || '';
+ launchers.push({
+ path: launcherPath,
+ filename: launcherFilename,
+ size: launcherStats.size,
+ });
+ }
}
}
- if (backendSupport.rocm) {
- const isSupported = hardwareCapabilities.rocm.devices.length > 0;
- if (isSupported || includeDisabled) {
- backends.push({
- value: 'rocm',
- label: 'ROCm',
- devices: hardwareCapabilities.rocm.devices,
- disabled: includeDisabled ? !isSupported : undefined,
- });
- }
- }
+ const versionPromises = launchers.map(async (launcher) => {
+ try {
+ const versionInfo = await getVersionFromBinary(launcher.path);
- if (backendSupport.vulkan) {
- const isSupported = hardwareCapabilities.vulkan.devices.length > 0;
- if (isSupported || includeDisabled) {
- backends.push({
- value: 'vulkan',
- label: 'Vulkan',
- devices: hardwareCapabilities.vulkan.devices,
- disabled: includeDisabled ? !isSupported : undefined,
- });
- }
- }
+ if (!versionInfo) {
+ return null;
+ }
- if (backendSupport.clblast) {
- const discreteDevices = hardwareCapabilities.clblast.devices.filter(
- (device) => typeof device === 'string' || !device.isIntegrated
- );
- const isSupported = discreteDevices.length > 0;
- if (isSupported || includeDisabled) {
- backends.push({
- value: 'clblast',
- label: 'CLBlast',
- devices: hardwareCapabilities.clblast.devices,
- disabled: includeDisabled ? !isSupported : undefined,
- });
+ return {
+ version: versionInfo.version,
+ path: launcher.path,
+ filename: launcher.filename,
+ size: launcher.size,
+ actualVersion: versionInfo.actualVersion,
+ } as InstalledBackend;
+ } catch (error) {
+ logError(
+ `Could not detect version for ${launcher.filename}:`,
+ error as Error
+ );
+ return null;
}
- }
-
- backends.push({
- value: 'cpu',
- label: 'CPU',
- devices: cpuCapabilities?.devices.map((device) => device.name) || [],
- disabled: false,
});
- if (includeDisabled) {
- backends.sort((a, b) => {
- if (a.disabled === b.disabled) return 0;
- return a.disabled ? 1 : -1;
- });
+ const results = await Promise.all(versionPromises);
+ return results.filter(
+ (version): version is InstalledBackend => version !== null
+ );
+ } catch (error) {
+ logError('Error scanning install directory:', error as Error);
+ return [];
+ }
+}
+
+export async function getCurrentBackend() {
+ const currentBinaryPath = getCurrentKoboldBinary();
+ const backends = await getInstalledBackends();
+
+ if (currentBinaryPath && (await pathExists(currentBinaryPath))) {
+ const currentBackend = backends.find(
+ (b: InstalledBackend) => b.path === currentBinaryPath
+ );
+ if (currentBackend) {
+ return currentBackend;
+ }
+ }
+
+ const firstBackend = backends[0];
+ if (firstBackend) {
+ await setCurrentKoboldBinary(firstBackend.path);
+ return firstBackend;
+ }
+
+ if (currentBinaryPath) {
+ await setCurrentKoboldBinary('');
+ }
+
+ return null;
+}
+
+export async function getCurrentBinaryInfo() {
+ const currentBackend = await getCurrentBackend();
+
+ if (currentBackend) {
+ const pathParts = currentBackend.path.split(/[/\\]/);
+ const filename = pathParts[pathParts.length - 2] || currentBackend.filename;
+
+ return {
+ path: currentBackend.path,
+ filename,
+ };
+ }
+
+ return null;
+}
+
+export async function setCurrentBackend(binaryPath: string) {
+ if (await pathExists(binaryPath)) {
+ await setCurrentKoboldBinary(binaryPath);
+
+ sendToRenderer('versions-updated');
+
+ return true;
+ }
+
+ return false;
+}
+
+export async function deleteRelease(binaryPath: string) {
+ try {
+ if (!(await pathExists(binaryPath))) {
+ return { success: false, error: 'Release not found' };
}
- availableBackendsCache.set(cacheKey, backends);
- return backends;
- }, 'Failed to get available backends');
+ const currentBinaryPath = getCurrentKoboldBinary();
+ if (currentBinaryPath === binaryPath) {
+ return {
+ success: false,
+ error: 'Cannot delete the currently active release',
+ };
+ }
- return result || [{ value: 'cpu', label: 'CPU' }];
+ const releaseDir = binaryPath.split(/[/\\]/).slice(0, -1).join('/');
+
+ if (await pathExists(releaseDir)) {
+ 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;
+
+ const folderName = launcherPath.split(/[/\\]/).slice(-2, -1)[0];
+ if (folderName) {
+ const versionMatch = folderName.match(
+ /-(\d+\.\d+(?:\.\d+)?(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*)$/
+ );
+ if (versionMatch) {
+ folderVersion = versionMatch[1];
+ }
+ }
+
+ try {
+ const result = await execa(launcherPath, ['--version'], {
+ timeout: 30000,
+ stdio: ['ignore', 'pipe', 'pipe'],
+ });
+
+ const allOutput = (result.stdout + result.stderr).trim();
+ const lines = allOutput.split('\n').filter((line) => line.trim());
+
+ 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 {}
+
+ 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;
+ }
}
diff --git a/src/main/modules/koboldcpp/download.ts b/src/main/modules/koboldcpp/download.ts
index 8b610cf..223c4bc 100644
--- a/src/main/modules/koboldcpp/download.ts
+++ b/src/main/modules/koboldcpp/download.ts
@@ -15,7 +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';
+import { clearVersionCache } from './backend';
async function removeDirectoryWithRetry(
dirPath: string,
diff --git a/src/main/modules/koboldcpp/launcher/index.ts b/src/main/modules/koboldcpp/launcher/index.ts
index c8f2e51..523696d 100644
--- a/src/main/modules/koboldcpp/launcher/index.ts
+++ b/src/main/modules/koboldcpp/launcher/index.ts
@@ -7,7 +7,7 @@ import { sendKoboldOutput } from '@/main/modules/window';
import { SERVER_READY_SIGNALS } from '@/constants';
import { pathExists } from '@/utils/node/fs';
import { parseKoboldConfig } from '@/utils/node/kobold';
-import { getCurrentVersion } from '../version';
+import { getCurrentBackend } from '../backend';
import {
getCurrentKoboldBinary,
get as getConfig,
@@ -153,15 +153,15 @@ export async function launchKoboldCpp(
spawnPreLaunchCommands(preLaunchCommands);
}
- const currentVersion = await getCurrentVersion();
- if (!currentVersion || !(await pathExists(currentVersion.path))) {
+ const currentBackend = await getCurrentBackend();
+ if (!currentBackend || !(await pathExists(currentBackend.path))) {
const rawPath = getCurrentKoboldBinary();
- const error = currentVersion
- ? `Binary file does not exist at path: ${currentVersion.path}`
- : 'No version configured';
+ const error = currentBackend
+ ? `Binary file does not exist at path: ${currentBackend.path}`
+ : 'No backend configured';
logError(
- `Launch failed: ${error}. Raw config path: "${rawPath}", Current version: ${JSON.stringify(currentVersion)}`
+ `Launch failed: ${error}. Raw config path: "${rawPath}", Current backend: ${JSON.stringify(currentBackend)}`
);
return {
@@ -170,7 +170,7 @@ export async function launchKoboldCpp(
};
}
- const binaryDir = currentVersion.path.split(/[/\\]/).slice(0, -1).join('/');
+ const binaryDir = currentBackend.path.split(/[/\\]/).slice(0, -1).join('/');
const { isImageMode, isTextMode, debugmode } = parseKoboldConfig(args);
@@ -191,14 +191,14 @@ export async function launchKoboldCpp(
await startProxy(koboldHost, koboldPort);
- const child = spawn(currentVersion.path, finalArgs, {
+ const child = spawn(currentBackend.path, finalArgs, {
stdio: ['pipe', 'pipe', 'pipe'],
detached: false,
});
koboldProcess = child;
- const commandLine = `${currentVersion.path} ${finalArgs.join(' ')}`;
+ const commandLine = `${currentBackend.path} ${finalArgs.join(' ')}`;
sendKoboldOutput(commandLine);
diff --git a/src/main/modules/koboldcpp/version.ts b/src/main/modules/koboldcpp/version.ts
deleted file mode 100644
index 18fa70b..0000000
--- a/src/main/modules/koboldcpp/version.ts
+++ /dev/null
@@ -1,233 +0,0 @@
-import { readdir, stat, rm } from 'fs/promises';
-import { join } from 'path';
-import { execa } from 'execa';
-
-import {
- getCurrentKoboldBinary,
- setCurrentKoboldBinary,
- getInstallDir,
-} from '../config';
-import { sendToRenderer } from '../window';
-import { pathExists } from '@/utils/node/fs';
-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();
- if (!(await pathExists(installDir))) {
- return [];
- }
-
- const items = await readdir(installDir);
- const launchers: { path: string; filename: string; size: number }[] = [];
-
- for (const item of items) {
- const itemPath = join(installDir, item);
- const stats = await stat(itemPath);
-
- if (stats.isDirectory()) {
- const launcherPath = await getLauncherPath(itemPath);
- if (launcherPath && (await pathExists(launcherPath))) {
- const launcherStats = await stat(launcherPath);
- const launcherFilename = launcherPath.split(/[/\\]/).pop() || '';
- launchers.push({
- path: launcherPath,
- filename: launcherFilename,
- size: launcherStats.size,
- });
- }
- }
- }
-
- const versionPromises = launchers.map(async (launcher) => {
- try {
- const versionInfo = await getVersionFromBinary(launcher.path);
-
- if (!versionInfo) {
- return null;
- }
-
- return {
- version: versionInfo.version,
- path: launcher.path,
- filename: launcher.filename,
- size: launcher.size,
- actualVersion: versionInfo.actualVersion,
- } as InstalledVersion;
- } catch (error) {
- logError(
- `Could not detect version for ${launcher.filename}:`,
- error as Error
- );
- return null;
- }
- });
-
- const results = await Promise.all(versionPromises);
- return results.filter(
- (version): version is InstalledVersion => version !== null
- );
- } catch (error) {
- logError('Error scanning install directory:', error as Error);
- return [];
- }
-}
-
-export async function getCurrentVersion() {
- const currentBinaryPath = getCurrentKoboldBinary();
- const versions = await getInstalledVersions();
-
- if (currentBinaryPath && (await pathExists(currentBinaryPath))) {
- const currentVersion = versions.find(
- (v: InstalledVersion) => v.path === currentBinaryPath
- );
- if (currentVersion) {
- return currentVersion;
- }
- }
-
- const firstVersion = versions[0];
- if (firstVersion) {
- await setCurrentKoboldBinary(firstVersion.path);
- return firstVersion;
- }
-
- if (currentBinaryPath) {
- await setCurrentKoboldBinary('');
- }
-
- return null;
-}
-
-export async function getCurrentBinaryInfo() {
- const currentVersion = await getCurrentVersion();
-
- if (currentVersion) {
- const pathParts = currentVersion.path.split(/[/\\]/);
- const filename = pathParts[pathParts.length - 2] || currentVersion.filename;
-
- return {
- path: currentVersion.path,
- filename,
- };
- }
-
- return null;
-}
-
-export async function setCurrentVersion(binaryPath: string) {
- if (await pathExists(binaryPath)) {
- await setCurrentKoboldBinary(binaryPath);
-
- sendToRenderer('versions-updated');
-
- return true;
- }
-
- 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)) {
- 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;
-
- const folderName = launcherPath.split(/[/\\]/).slice(-2, -1)[0];
- if (folderName) {
- const versionMatch = folderName.match(
- /-(\d+\.\d+(?:\.\d+)?(?:\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*)$/
- );
- if (versionMatch) {
- folderVersion = versionMatch[1];
- }
- }
-
- try {
- const result = await execa(launcherPath, ['--version'], {
- timeout: 30000,
- stdio: ['ignore', 'pipe', 'pipe'],
- });
-
- const allOutput = (result.stdout + result.stderr).trim();
- const lines = allOutput.split('\n').filter((line) => line.trim());
-
- 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 {}
-
- 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;
- }
-}
diff --git a/src/preload/index.ts b/src/preload/index.ts
index 370d09a..b530106 100644
--- a/src/preload/index.ts
+++ b/src/preload/index.ts
@@ -16,10 +16,10 @@ import type {
} from '@/main/modules/monitoring';
const koboldAPI: KoboldAPI = {
- getInstalledVersions: () => ipcRenderer.invoke('kobold:getInstalledVersions'),
- getCurrentVersion: () => ipcRenderer.invoke('kobold:getCurrentVersion'),
- setCurrentVersion: (version) =>
- ipcRenderer.invoke('kobold:setCurrentVersion', version),
+ getInstalledBackends: () => ipcRenderer.invoke('kobold:getInstalledBackends'),
+ getCurrentBackend: () => ipcRenderer.invoke('kobold:getCurrentBackend'),
+ setCurrentBackend: (version) =>
+ ipcRenderer.invoke('kobold:setCurrentBackend', version),
getPlatform: () => ipcRenderer.invoke('kobold:getPlatform'),
detectGPU: () => ipcRenderer.invoke('kobold:detectGPU'),
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
@@ -28,9 +28,10 @@ const koboldAPI: KoboldAPI = {
detectGPUMemory: () => ipcRenderer.invoke('kobold:detectGPUMemory'),
detectSystemMemory: () => ipcRenderer.invoke('kobold:detectSystemMemory'),
detectROCm: () => ipcRenderer.invoke('kobold:detectROCm'),
- detectBackendSupport: () => ipcRenderer.invoke('kobold:detectBackendSupport'),
- getAvailableBackends: (includeDisabled) =>
- ipcRenderer.invoke('kobold:getAvailableBackends', includeDisabled),
+ detectAccelerationSupport: () =>
+ ipcRenderer.invoke('kobold:detectAccelerationSupport'),
+ getAvailableAccelerations: (includeDisabled) =>
+ ipcRenderer.invoke('kobold:getAvailableAccelerations', includeDisabled),
getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'),
selectInstallDirectory: () =>
ipcRenderer.invoke('kobold:selectInstallDirectory'),
diff --git a/src/stores/koboldVersions.ts b/src/stores/koboldVersions.ts
index 8e64dbf..90f43b8 100644
--- a/src/stores/koboldVersions.ts
+++ b/src/stores/koboldVersions.ts
@@ -8,7 +8,7 @@ import type {
GitHubRelease,
ReleaseWithStatus,
GitHubAsset,
- InstalledVersion,
+ InstalledBackend,
} from '@/types/electron';
import { sortDownloadsByType } from '@/utils/assets';
@@ -137,7 +137,7 @@ export const useKoboldVersionsStore = create(
safeExecute(async () => {
const [response, installedVersions] = await Promise.all([
fetch(GITHUB_API.LATEST_RELEASE_URL),
- window.electronAPI.kobold.getInstalledVersions(),
+ window.electronAPI.kobold.getInstalledBackends(),
]);
if (!response.ok) return null;
@@ -147,11 +147,11 @@ export const useKoboldVersionsStore = create(
const availableAssets = latestRelease.assets.map(
(asset: GitHubAsset) => {
- const installedVersion = installedVersions.find(
- (v: InstalledVersion) => {
+ const installedBackend = installedVersions.find(
+ (v: InstalledBackend) => {
const pathParts = v.path.split(/[/\\]/);
const launcherIndex = pathParts.findIndex(
- (part) =>
+ (part: string) =>
part === 'koboldcpp-launcher' ||
part === 'koboldcpp-launcher.exe'
);
@@ -167,8 +167,8 @@ export const useKoboldVersionsStore = create(
return {
asset,
- isDownloaded: !!installedVersion,
- installedVersion: installedVersion?.version,
+ isDownloaded: !!installedBackend,
+ installedVersion: installedBackend?.version,
};
}
);
diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts
index 65c0ee8..bd373df 100644
--- a/src/types/electron.d.ts
+++ b/src/types/electron.d.ts
@@ -6,8 +6,8 @@ import type {
SystemMemoryInfo,
} from '@/types/hardware';
import type {
- BackendOption,
- BackendSupport,
+ AccelerationOption,
+ AccelerationSupport,
Screen,
ModelAnalysis,
CachedModel,
@@ -57,7 +57,7 @@ export interface ReleaseWithStatus {
}[];
}
-export interface InstalledVersion {
+export interface InstalledBackend {
version: string;
path: string;
filename: string;
@@ -125,9 +125,9 @@ export interface KoboldConfig {
}
export interface KoboldAPI {
- getInstalledVersions: () => Promise;
- getCurrentVersion: () => Promise;
- setCurrentVersion: (version: string) => Promise;
+ getInstalledBackends: () => Promise;
+ getCurrentBackend: () => Promise;
+ setCurrentBackend: (version: string) => Promise;
getPlatform: () => Promise;
detectGPU: () => Promise;
detectCPU: () => Promise;
@@ -135,8 +135,10 @@ export interface KoboldAPI {
detectGPUMemory: () => Promise;
detectSystemMemory: () => Promise;
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
- detectBackendSupport: () => Promise;
- getAvailableBackends: (includeDisabled?: boolean) => Promise;
+ detectAccelerationSupport: () => Promise;
+ getAvailableAccelerations: (
+ includeDisabled?: boolean
+ ) => Promise;
getCurrentInstallDir: () => Promise;
selectInstallDirectory: () => Promise;
downloadRelease: (
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index d0633fe..7399e78 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -66,14 +66,6 @@ export interface UpdateInfo {
hasUpdate: boolean;
}
-export interface InstalledVersion {
- version: string;
- path: string;
- filename: string;
- size?: number;
- actualVersion?: string;
-}
-
export interface VersionInfo {
name: string;
version: string;
@@ -97,12 +89,12 @@ export interface SelectOption {
label: string;
}
-export interface BackendOption extends SelectOption {
+export interface AccelerationOption extends SelectOption {
readonly devices?: readonly (string | GPUDevice)[];
readonly disabled?: boolean;
}
-export interface BackendSupport {
+export interface AccelerationSupport {
rocm: boolean;
vulkan: boolean;
clblast: boolean;
diff --git a/src/utils/version.ts b/src/utils/version.ts
index 9da5ab1..c3dfdcd 100644
--- a/src/utils/version.ts
+++ b/src/utils/version.ts
@@ -1,16 +1,17 @@
-import type { InstalledVersion } from '@/types';
+import type { InstalledBackend } from '@/types/electron';
-export const getDisplayNameFromPath = (installedVersion: InstalledVersion) => {
- const pathParts = installedVersion.path.split(/[/\\]/);
+export const getDisplayNameFromPath = (installedBackend: InstalledBackend) => {
+ const pathParts = installedBackend.path.split(/[/\\]/);
const launcherIndex = pathParts.findIndex(
- (part) => part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
+ (part: string) =>
+ part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
);
if (launcherIndex > 0) {
return stripVersionSuffix(pathParts[launcherIndex - 1]);
}
- return installedVersion.filename;
+ return installedBackend.filename;
};
export const stripAssetExtensions = (assetName: string) =>
diff --git a/yarn.lock b/yarn.lock
index 69fcf39..7cf968e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3736,7 +3736,7 @@ __metadata:
jiti: "npm:^2.6.1"
lucide-react: "npm:^0.555.0"
mime-types: "npm:^3.0.2"
- prettier: "npm:^3.7.1"
+ prettier: "npm:^3.7.2"
react: "npm:^19.2.0"
react-dom: "npm:^19.2.0"
react-error-boundary: "npm:^6.0.0"
@@ -5506,12 +5506,12 @@ __metadata:
languageName: node
linkType: hard
-"prettier@npm:^3.7.1":
- version: 3.7.1
- resolution: "prettier@npm:3.7.1"
+"prettier@npm:^3.7.2":
+ version: 3.7.2
+ resolution: "prettier@npm:3.7.2"
bin:
prettier: bin/prettier.cjs
- checksum: 10c0/a6610043ee0a64a3251a948bf82fad3e59d984a8e8dea206400cfa190585417e3343b32c1f6ae7d8f40798a9b4bd91affc08fa7795dd99a9dec5c9bccdf31500
+ checksum: 10c0/df3d658df301face0918f8ecbd4354f32e1151d83a3a4720c7f252342baf631466568f708e0e57beea55bbc56415c40208adc76a91d5f1a88f3e743d0d775dc0
languageName: node
linkType: hard