mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
rename version -> backend and backend -> acceleration, display "metal" for mac users and don't show other options, allow "metal" users to select GPU layers
This commit is contained in:
parent
3c2d987769
commit
72d4aacb5a
28 changed files with 677 additions and 652 deletions
|
|
@ -60,7 +60,7 @@
|
||||||
"eslint-plugin-sonarjs": "^3.0.5",
|
"eslint-plugin-sonarjs": "^3.0.5",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
"prettier": "^3.7.1",
|
"prettier": "^3.7.2",
|
||||||
"rollup-plugin-visualizer": "^6.0.5",
|
"rollup-plugin-visualizer": "^6.0.5",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.2.4"
|
"vite": "^7.2.4"
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export const UpdateAvailableModal = ({
|
||||||
onUpdate,
|
onUpdate,
|
||||||
}: UpdateAvailableModalProps) => {
|
}: UpdateAvailableModalProps) => {
|
||||||
const { downloading, downloadProgress } = useKoboldVersionsStore();
|
const { downloading, downloadProgress } = useKoboldVersionsStore();
|
||||||
const currentVersion = updateInfo?.currentVersion;
|
const currentBackend = updateInfo?.currentBackend;
|
||||||
const availableUpdate = updateInfo?.availableUpdate;
|
const availableUpdate = updateInfo?.availableUpdate;
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ export const UpdateAvailableModal = ({
|
||||||
Current Version
|
Current Version
|
||||||
</Text>
|
</Text>
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{currentVersion?.version}
|
{currentBackend?.version}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,12 +98,12 @@ export const App = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkInstallation = async () => {
|
const checkInstallation = async () => {
|
||||||
const [currentVersion, hasSeenWelcome] = await Promise.all([
|
const [currentBackend, hasSeenWelcome] = await Promise.all([
|
||||||
window.electronAPI.kobold.getCurrentVersion(),
|
window.electronAPI.kobold.getCurrentBackend(),
|
||||||
window.electronAPI.config.get('hasSeenWelcome') as Promise<boolean>,
|
window.electronAPI.config.get('hasSeenWelcome') as Promise<boolean>,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
determineScreen(currentVersion, hasSeenWelcome);
|
determineScreen(currentBackend, hasSeenWelcome);
|
||||||
setHasInitialized(true);
|
setHasInitialized(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -114,9 +114,9 @@ export const App = () => {
|
||||||
const runUpdateCheck = async () => {
|
const runUpdateCheck = async () => {
|
||||||
if (loadingRemote || !hasInitialized) return;
|
if (loadingRemote || !hasInitialized) return;
|
||||||
|
|
||||||
const currentVersion =
|
const currentBackend =
|
||||||
await window.electronAPI.kobold.getCurrentVersion();
|
await window.electronAPI.kobold.getCurrentBackend();
|
||||||
if (currentVersion) {
|
if (currentBackend) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
@ -136,13 +136,13 @@ export const App = () => {
|
||||||
}, [loadingRemote, hasInitialized, checkForUpdates]);
|
}, [loadingRemote, hasInitialized, checkForUpdates]);
|
||||||
|
|
||||||
const handleBinaryUpdate = async (download: DownloadItem) => {
|
const handleBinaryUpdate = async (download: DownloadItem) => {
|
||||||
const currentVersion = await window.electronAPI.kobold.getCurrentVersion();
|
const currentBackend = await window.electronAPI.kobold.getCurrentBackend();
|
||||||
|
|
||||||
await handleDownload({
|
await handleDownload({
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: true,
|
wasCurrentBinary: true,
|
||||||
oldVersionPath: currentVersion?.path,
|
oldVersionPath: currentBackend?.path,
|
||||||
});
|
});
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|
@ -170,8 +170,8 @@ export const App = () => {
|
||||||
const handleWelcomeComplete = async () => {
|
const handleWelcomeComplete = async () => {
|
||||||
window.electronAPI.config.set('hasSeenWelcome', true);
|
window.electronAPI.config.set('hasSeenWelcome', true);
|
||||||
|
|
||||||
const currentVersion = await window.electronAPI.kobold.getCurrentVersion();
|
const currentBackend = await window.electronAPI.kobold.getCurrentBackend();
|
||||||
determineScreen(currentVersion, true);
|
determineScreen(currentBackend, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadComplete = () => setCurrentScreen('launch');
|
const handleDownloadComplete = () => setCurrentScreen('launch');
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
<Card withBorder radius="md" shadow="sm">
|
<Card withBorder radius="md" shadow="sm">
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<Title order={3}>Available Binaries for Your Platform</Title>
|
<Title order={3}>Select a Backend</Title>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Stack align="center" gap="md" py="xl">
|
<Stack align="center" gap="md" py="xl">
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ export const AdvancedTab = () => {
|
||||||
const isGpuBackend = backend === 'cuda' || backend === 'rocm';
|
const isGpuBackend = backend === 'cuda' || backend === 'rocm';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const detectBackendSupport = async () => {
|
const detectAccelerationSupport = async () => {
|
||||||
const support = await window.electronAPI.kobold.detectBackendSupport();
|
const support =
|
||||||
|
await window.electronAPI.kobold.detectAccelerationSupport();
|
||||||
|
|
||||||
if (support) {
|
if (support) {
|
||||||
setBackendSupport({
|
setBackendSupport({
|
||||||
|
|
@ -76,7 +77,7 @@ export const AdvancedTab = () => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
void detectBackendSupport();
|
void detectAccelerationSupport();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { Text, Group, Badge, Box } from '@mantine/core';
|
import { Text, Group, Badge, Box } from '@mantine/core';
|
||||||
import type { BackendOption } from '@/types';
|
import type { AccelerationOption } from '@/types';
|
||||||
import { GPUDevice } from '@/types/hardware';
|
import { GPUDevice } from '@/types/hardware';
|
||||||
|
|
||||||
type BackendSelectItemProps = Omit<BackendOption, 'value'>;
|
type AccelerationSelectItemProps = Omit<AccelerationOption, 'value'>;
|
||||||
|
|
||||||
export const BackendSelectItem = ({
|
export const AccelerationSelectItem = ({
|
||||||
label,
|
label,
|
||||||
devices,
|
devices,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: BackendSelectItemProps) => {
|
}: AccelerationSelectItemProps) => {
|
||||||
const renderDeviceName = (device: string | GPUDevice) => {
|
const renderDeviceName = (device: string | GPUDevice) => {
|
||||||
const deviceName = typeof device === 'string' ? device : device.name;
|
const deviceName = typeof device === 'string' ? device : device.name;
|
||||||
return deviceName.length > 25
|
return deviceName.length > 25
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { Text, Group, Checkbox, TextInput } from '@mantine/core';
|
import { Text, Group, Checkbox, TextInput } from '@mantine/core';
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem';
|
import { AccelerationSelectItem } from '@/components/screens/Launch/GeneralTab/AccelerationSelectItem';
|
||||||
import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector';
|
import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
import type { BackendOption } from '@/types';
|
import type { AccelerationOption } from '@/types';
|
||||||
import { Select } from '@/components/Select';
|
import { Select } from '@/components/Select';
|
||||||
|
|
||||||
export const BackendSelector = () => {
|
export const AccelerationSelector = () => {
|
||||||
const {
|
const {
|
||||||
backend,
|
backend,
|
||||||
gpuLayers,
|
gpuLayers,
|
||||||
|
|
@ -21,60 +21,67 @@ export const BackendSelector = () => {
|
||||||
handleAutoGpuLayersChange,
|
handleAutoGpuLayersChange,
|
||||||
} = useLaunchConfig();
|
} = useLaunchConfig();
|
||||||
|
|
||||||
const [availableBackends, setAvailableBackends] = useState<BackendOption[]>(
|
const [availableAccelerations, setAvailableAccelerations] = useState<
|
||||||
[]
|
AccelerationOption[]
|
||||||
);
|
>([]);
|
||||||
const [isLoadingBackends, setIsLoadingBackends] = useState(false);
|
const [isLoadingAccelerations, setIsLoadingAccelerations] = useState(false);
|
||||||
const [isCalculatingLayers, setIsCalculatingLayers] = useState(false);
|
const [isCalculatingLayers, setIsCalculatingLayers] = useState(false);
|
||||||
|
const [isMac, setIsMac] = useState(false);
|
||||||
const hasInitialized = useRef(false);
|
const hasInitialized = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadBackends = async () => {
|
const loadAccelerations = async () => {
|
||||||
setIsLoadingBackends(true);
|
setIsLoadingAccelerations(true);
|
||||||
|
|
||||||
const backends =
|
const [accelerations, platform] = await Promise.all([
|
||||||
await window.electronAPI.kobold.getAvailableBackends(true);
|
window.electronAPI.kobold.getAvailableAccelerations(true),
|
||||||
|
window.electronAPI.kobold.getPlatform(),
|
||||||
|
]);
|
||||||
|
|
||||||
setAvailableBackends(backends || []);
|
setAvailableAccelerations(accelerations || []);
|
||||||
setIsLoadingBackends(false);
|
setIsMac(platform === 'darwin');
|
||||||
|
setIsLoadingAccelerations(false);
|
||||||
hasInitialized.current = true;
|
hasInitialized.current = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!hasInitialized.current) {
|
if (!hasInitialized.current) {
|
||||||
loadBackends();
|
loadAccelerations();
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanup = window.electronAPI.kobold.onVersionsUpdated(() => {
|
const cleanup = window.electronAPI.kobold.onVersionsUpdated(() => {
|
||||||
hasInitialized.current = false;
|
hasInitialized.current = false;
|
||||||
loadBackends();
|
loadAccelerations();
|
||||||
});
|
});
|
||||||
|
|
||||||
return cleanup;
|
return cleanup;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (availableBackends.length > 0 && backend) {
|
if (availableAccelerations.length > 0 && backend) {
|
||||||
const isBackendAvailable = availableBackends.some(
|
const isAccelerationAvailable = availableAccelerations.some(
|
||||||
(b) => b.value === backend && !b.disabled
|
(a) => a.value === backend && !a.disabled
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isBackendAvailable) {
|
if (!isAccelerationAvailable) {
|
||||||
const fallbackBackend = availableBackends.find((b) => !b.disabled);
|
const fallbackAcceleration = availableAccelerations.find(
|
||||||
if (fallbackBackend) {
|
(a) => !a.disabled
|
||||||
handleBackendChange(fallbackBackend.value);
|
);
|
||||||
|
if (fallbackAcceleration) {
|
||||||
|
handleBackendChange(fallbackAcceleration.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [availableBackends, backend, handleBackendChange]);
|
}, [availableAccelerations, backend, handleBackendChange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const calculateLayers = async () => {
|
const calculateLayers = async () => {
|
||||||
|
const isCpuOnly = backend === 'cpu' && !isMac;
|
||||||
if (
|
if (
|
||||||
!autoGpuLayers ||
|
!autoGpuLayers ||
|
||||||
!model ||
|
!model ||
|
||||||
!contextSize ||
|
!contextSize ||
|
||||||
backend === 'cpu' ||
|
isCpuOnly ||
|
||||||
isLoadingBackends
|
isLoadingAccelerations
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -133,7 +140,8 @@ export const BackendSelector = () => {
|
||||||
backend,
|
backend,
|
||||||
gpuDeviceSelection,
|
gpuDeviceSelection,
|
||||||
flashattention,
|
flashattention,
|
||||||
isLoadingBackends,
|
isLoadingAccelerations,
|
||||||
|
isMac,
|
||||||
handleGpuLayersChange,
|
handleGpuLayersChange,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -143,16 +151,20 @@ export const BackendSelector = () => {
|
||||||
<div style={{ flex: 1, marginRight: '1rem' }}>
|
<div style={{ flex: 1, marginRight: '1rem' }}>
|
||||||
<Group gap="xs" align="center" mb="xs">
|
<Group gap="xs" align="center" mb="xs">
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
Backend
|
Acceleration
|
||||||
</Text>
|
</Text>
|
||||||
<InfoTooltip label="Select a backend to use to run LLMs. CUDA runs on NVIDIA GPUs and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast work on all GPUs." />
|
<InfoTooltip label="Select an acceleration mode to run LLMs. CUDA runs on NVIDIA GPUs and is much faster. ROCm is the AMD equivalent. Vulkan and CLBlast work on all GPUs." />
|
||||||
</Group>
|
</Group>
|
||||||
<Select
|
<Select
|
||||||
placeholder={
|
placeholder={
|
||||||
isLoadingBackends ? 'Loading backends...' : 'Select backend'
|
isLoadingAccelerations
|
||||||
|
? 'Loading accelerations...'
|
||||||
|
: 'Select acceleration'
|
||||||
}
|
}
|
||||||
value={
|
value={
|
||||||
availableBackends.some((b) => b.value === backend && !b.disabled)
|
availableAccelerations.some(
|
||||||
|
(a) => a.value === backend && !a.disabled
|
||||||
|
)
|
||||||
? backend
|
? backend
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
@ -161,22 +173,24 @@ export const BackendSelector = () => {
|
||||||
handleBackendChange(value);
|
handleBackendChange(value);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
data={availableBackends.map((b) => ({
|
data={availableAccelerations.map((a) => ({
|
||||||
value: b.value,
|
value: a.value,
|
||||||
label: b.label,
|
label: a.label,
|
||||||
disabled: b.disabled,
|
disabled: a.disabled,
|
||||||
}))}
|
}))}
|
||||||
disabled={isLoadingBackends || availableBackends.length === 0}
|
disabled={
|
||||||
|
isLoadingAccelerations || availableAccelerations.length === 0
|
||||||
|
}
|
||||||
renderOption={({ option }) => {
|
renderOption={({ option }) => {
|
||||||
const backendData = availableBackends.find(
|
const accelerationData = availableAccelerations.find(
|
||||||
(b) => b.value === option.value
|
(a) => a.value === option.value
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BackendSelectItem
|
<AccelerationSelectItem
|
||||||
label={backendData?.label || option.label.split(' (')[0]}
|
label={accelerationData?.label || option.label.split(' (')[0]}
|
||||||
devices={backendData?.devices}
|
devices={accelerationData?.devices}
|
||||||
disabled={backendData?.disabled}
|
disabled={accelerationData?.disabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
@ -209,7 +223,7 @@ export const BackendSelector = () => {
|
||||||
step={1}
|
step={1}
|
||||||
size="sm"
|
size="sm"
|
||||||
w={80}
|
w={80}
|
||||||
disabled={autoGpuLayers || backend === 'cpu'}
|
disabled={autoGpuLayers || (backend === 'cpu' && !isMac)}
|
||||||
/>
|
/>
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
@ -219,7 +233,7 @@ export const BackendSelector = () => {
|
||||||
handleAutoGpuLayersChange(event.currentTarget.checked)
|
handleAutoGpuLayersChange(event.currentTarget.checked)
|
||||||
}
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={backend === 'cpu'}
|
disabled={backend === 'cpu' && !isMac}
|
||||||
/>
|
/>
|
||||||
<InfoTooltip label="Automatically calculate optimal GPU layers based on available VRAM. The calculation accounts for model size, context size and flash attention." />
|
<InfoTooltip label="Automatically calculate optimal GPU layers based on available VRAM. The calculation accounts for model size, context size and flash attention." />
|
||||||
</Group>
|
</Group>
|
||||||
|
|
@ -227,7 +241,7 @@ export const BackendSelector = () => {
|
||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<GpuDeviceSelector availableBackends={availableBackends} />
|
<GpuDeviceSelector availableAccelerations={availableAccelerations} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -2,14 +2,14 @@ import { Text, Group, TextInput } from '@mantine/core';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
import { Select } from '@/components/Select';
|
import { Select } from '@/components/Select';
|
||||||
import type { BackendOption } from '@/types';
|
import type { AccelerationOption } from '@/types';
|
||||||
|
|
||||||
interface GpuDeviceSelectorProps {
|
interface GpuDeviceSelectorProps {
|
||||||
availableBackends: BackendOption[];
|
availableAccelerations: AccelerationOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GpuDeviceSelector = ({
|
export const GpuDeviceSelector = ({
|
||||||
availableBackends,
|
availableAccelerations,
|
||||||
}: GpuDeviceSelectorProps) => {
|
}: GpuDeviceSelectorProps) => {
|
||||||
const {
|
const {
|
||||||
backend,
|
backend,
|
||||||
|
|
@ -19,21 +19,23 @@ export const GpuDeviceSelector = ({
|
||||||
handleTensorSplitChange,
|
handleTensorSplitChange,
|
||||||
} = useLaunchConfig();
|
} = useLaunchConfig();
|
||||||
|
|
||||||
const selectedBackend = availableBackends.find((b) => b.value === backend);
|
const selectedAcceleration = availableAccelerations.find(
|
||||||
const isGpuBackend =
|
(a) => a.value === backend
|
||||||
|
);
|
||||||
|
const isGpuAcceleration =
|
||||||
backend === 'cuda' ||
|
backend === 'cuda' ||
|
||||||
backend === 'rocm' ||
|
backend === 'rocm' ||
|
||||||
backend === 'vulkan' ||
|
backend === 'vulkan' ||
|
||||||
backend === 'clblast';
|
backend === 'clblast';
|
||||||
|
|
||||||
const getDiscreteDeviceCount = () => {
|
const getDiscreteDeviceCount = () => {
|
||||||
if (!selectedBackend?.devices) return 0;
|
if (!selectedAcceleration?.devices) return 0;
|
||||||
if (backend === 'clblast' || backend === 'vulkan' || backend === 'rocm') {
|
if (backend === 'clblast' || backend === 'vulkan' || backend === 'rocm') {
|
||||||
return selectedBackend.devices.filter(
|
return selectedAcceleration.devices.filter(
|
||||||
(device) => typeof device === 'string' || !device.isIntegrated
|
(device) => typeof device === 'string' || !device.isIntegrated
|
||||||
).length;
|
).length;
|
||||||
}
|
}
|
||||||
return selectedBackend.devices.length;
|
return selectedAcceleration.devices.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasMultipleDevices = getDiscreteDeviceCount() > 1;
|
const hasMultipleDevices = getDiscreteDeviceCount() > 1;
|
||||||
|
|
@ -42,15 +44,15 @@ export const GpuDeviceSelector = ({
|
||||||
hasMultipleDevices &&
|
hasMultipleDevices &&
|
||||||
gpuDeviceSelection === 'all';
|
gpuDeviceSelection === 'all';
|
||||||
|
|
||||||
if (!isGpuBackend || !hasMultipleDevices) {
|
if (!isGpuAcceleration || !hasMultipleDevices) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceOptions = (() => {
|
const deviceOptions = (() => {
|
||||||
if (!selectedBackend?.devices) return [];
|
if (!selectedAcceleration?.devices) return [];
|
||||||
|
|
||||||
if (backend === 'clblast') {
|
if (backend === 'clblast') {
|
||||||
return selectedBackend.devices
|
return selectedAcceleration.devices
|
||||||
.map((device, index) => {
|
.map((device, index) => {
|
||||||
if (typeof device === 'object' && device.isIntegrated) {
|
if (typeof device === 'object' && device.isIntegrated) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -67,7 +69,7 @@ export const GpuDeviceSelector = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backend === 'vulkan' || backend === 'rocm') {
|
if (backend === 'vulkan' || backend === 'rocm') {
|
||||||
const discreteDeviceOptions = selectedBackend.devices
|
const discreteDeviceOptions = selectedAcceleration.devices
|
||||||
.map((device, index) => {
|
.map((device, index) => {
|
||||||
if (typeof device === 'object' && device.isIntegrated) {
|
if (typeof device === 'object' && device.isIntegrated) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -87,7 +89,7 @@ export const GpuDeviceSelector = ({
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ value: 'all', label: 'All GPUs' },
|
{ value: 'all', label: 'All GPUs' },
|
||||||
...selectedBackend.devices.map((device, index) => {
|
...selectedAcceleration.devices.map((device, index) => {
|
||||||
const deviceName =
|
const deviceName =
|
||||||
typeof device === 'string'
|
typeof device === 'string'
|
||||||
? device
|
? device
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Transition,
|
Transition,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
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 { ModelFileField } from '@/components/screens/Launch/ModelFileField';
|
||||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => {
|
||||||
>
|
>
|
||||||
{(styles) => (
|
{(styles) => (
|
||||||
<Stack gap="md" style={styles}>
|
<Stack gap="md" style={styles}>
|
||||||
<BackendSelector />
|
<AccelerationSelector />
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
label="Text Model File"
|
label="Text Model File"
|
||||||
|
|
|
||||||
|
|
@ -83,10 +83,11 @@ export const LaunchScreen = ({ onLaunch }: LaunchScreenProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const setHappyDefaults = useCallback(async () => {
|
const setHappyDefaults = useCallback(async () => {
|
||||||
const backends = await window.electronAPI.kobold.getAvailableBackends();
|
const accelerations =
|
||||||
|
await window.electronAPI.kobold.getAvailableAccelerations();
|
||||||
|
|
||||||
if (!backend && backends && backends.length > 0) {
|
if (!backend && accelerations && accelerations.length > 0) {
|
||||||
handleBackendChange(backends[0].value);
|
handleBackendChange(accelerations[0].value);
|
||||||
}
|
}
|
||||||
}, [backend, handleBackendChange]);
|
}, [backend, handleBackendChange]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ import {
|
||||||
import { formatDownloadSize } from '@/utils/format';
|
import { formatDownloadSize } from '@/utils/format';
|
||||||
|
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
||||||
import type { InstalledVersion, ReleaseWithStatus } from '@/types/electron';
|
import type { InstalledBackend, ReleaseWithStatus } from '@/types/electron';
|
||||||
import type { VersionInfo } from '@/types';
|
import type { VersionInfo } from '@/types';
|
||||||
|
|
||||||
export const VersionsTab = () => {
|
export const BackendsTab = () => {
|
||||||
const {
|
const {
|
||||||
availableDownloads,
|
availableDownloads,
|
||||||
loadingPlatform,
|
loadingPlatform,
|
||||||
|
|
@ -33,10 +33,10 @@ export const VersionsTab = () => {
|
||||||
initialize,
|
initialize,
|
||||||
} = useKoboldVersionsStore();
|
} = useKoboldVersionsStore();
|
||||||
|
|
||||||
const [installedVersions, setInstalledVersions] = useState<
|
const [installedBackends, setInstalledBackends] = useState<
|
||||||
InstalledVersion[]
|
InstalledBackend[]
|
||||||
>([]);
|
>([]);
|
||||||
const [currentVersion, setCurrentVersion] = useState<InstalledVersion | null>(
|
const [currentBackend, setCurrentBackend] = useState<InstalledBackend | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [loadingInstalled, setLoadingInstalled] = useState(true);
|
const [loadingInstalled, setLoadingInstalled] = useState(true);
|
||||||
|
|
@ -45,16 +45,16 @@ export const VersionsTab = () => {
|
||||||
);
|
);
|
||||||
const downloadingItemRef = useRef<HTMLDivElement>(null);
|
const downloadingItemRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const loadInstalledVersions = useCallback(async () => {
|
const loadInstalledBackends = useCallback(async () => {
|
||||||
setLoadingInstalled(true);
|
setLoadingInstalled(true);
|
||||||
|
|
||||||
const [versions, currentVersion] = await Promise.all([
|
const [backends, current] = await Promise.all([
|
||||||
window.electronAPI.kobold.getInstalledVersions(),
|
window.electronAPI.kobold.getInstalledBackends(),
|
||||||
window.electronAPI.kobold.getCurrentVersion(),
|
window.electronAPI.kobold.getCurrentBackend(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setInstalledVersions(versions);
|
setInstalledBackends(backends);
|
||||||
setCurrentVersion(currentVersion);
|
setCurrentBackend(current);
|
||||||
|
|
||||||
setLoadingInstalled(false);
|
setLoadingInstalled(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -72,10 +72,10 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
loadInstalledVersions();
|
loadInstalledBackends();
|
||||||
loadLatestRelease();
|
loadLatestRelease();
|
||||||
}, [
|
}, [
|
||||||
loadInstalledVersions,
|
loadInstalledBackends,
|
||||||
loadLatestRelease,
|
loadLatestRelease,
|
||||||
availableDownloads.length,
|
availableDownloads.length,
|
||||||
loadingRemote,
|
loadingRemote,
|
||||||
|
|
@ -83,47 +83,47 @@ export const VersionsTab = () => {
|
||||||
initialize,
|
initialize,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const allVersions = useMemo((): VersionInfo[] => {
|
const allBackends = useMemo((): VersionInfo[] => {
|
||||||
const versions: VersionInfo[] = [];
|
const backends: VersionInfo[] = [];
|
||||||
const processedInstalled = new Set<string>();
|
const processedInstalled = new Set<string>();
|
||||||
|
|
||||||
availableDownloads.forEach((download) => {
|
availableDownloads.forEach((download) => {
|
||||||
const downloadBaseName = stripAssetExtensions(download.name);
|
const downloadBaseName = stripAssetExtensions(download.name);
|
||||||
|
|
||||||
const installedVersion = installedVersions.find((v) => {
|
const installedBackend = installedBackends.find((b) => {
|
||||||
const displayName = getDisplayNameFromPath(v);
|
const displayName = getDisplayNameFromPath(b);
|
||||||
return displayName === downloadBaseName;
|
return displayName === downloadBaseName;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCurrent = Boolean(
|
const isCurrent = Boolean(
|
||||||
installedVersion &&
|
installedBackend &&
|
||||||
currentVersion &&
|
currentBackend &&
|
||||||
currentVersion.path === installedVersion.path
|
currentBackend.path === installedBackend.path
|
||||||
);
|
);
|
||||||
|
|
||||||
if (installedVersion) {
|
if (installedBackend) {
|
||||||
processedInstalled.add(installedVersion.path);
|
processedInstalled.add(installedBackend.path);
|
||||||
|
|
||||||
const hasUpdate =
|
const hasUpdate =
|
||||||
compareVersions(
|
compareVersions(
|
||||||
download.version || 'unknown',
|
download.version || 'unknown',
|
||||||
installedVersion.version
|
installedBackend.version
|
||||||
) > 0;
|
) > 0;
|
||||||
|
|
||||||
versions.push({
|
backends.push({
|
||||||
name: download.name,
|
name: download.name,
|
||||||
version: installedVersion.version,
|
version: installedBackend.version,
|
||||||
size: undefined,
|
size: undefined,
|
||||||
isInstalled: true,
|
isInstalled: true,
|
||||||
isCurrent,
|
isCurrent,
|
||||||
downloadUrl: download.url,
|
downloadUrl: download.url,
|
||||||
installedPath: installedVersion.path,
|
installedPath: installedBackend.path,
|
||||||
hasUpdate,
|
hasUpdate,
|
||||||
newerVersion: hasUpdate ? download.version : undefined,
|
newerVersion: hasUpdate ? download.version : undefined,
|
||||||
actualVersion: installedVersion.actualVersion,
|
actualVersion: installedBackend.actualVersion,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
versions.push({
|
backends.push({
|
||||||
name: download.name,
|
name: download.name,
|
||||||
version: download.version || 'unknown',
|
version: download.version || 'unknown',
|
||||||
size: download.size,
|
size: download.size,
|
||||||
|
|
@ -134,14 +134,14 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
installedVersions.forEach((installed) => {
|
installedBackends.forEach((installed) => {
|
||||||
if (!processedInstalled.has(installed.path)) {
|
if (!processedInstalled.has(installed.path)) {
|
||||||
const displayName = getDisplayNameFromPath(installed);
|
const displayName = getDisplayNameFromPath(installed);
|
||||||
const isCurrent = Boolean(
|
const isCurrent = Boolean(
|
||||||
currentVersion && currentVersion.path === installed.path
|
currentBackend && currentBackend.path === installed.path
|
||||||
);
|
);
|
||||||
|
|
||||||
versions.push({
|
backends.push({
|
||||||
name: displayName,
|
name: displayName,
|
||||||
version: installed.version,
|
version: installed.version,
|
||||||
size: undefined,
|
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;
|
||||||
if (!a.isInstalled && b.isInstalled) return 1;
|
if (!a.isInstalled && b.isInstalled) return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}, [availableDownloads, installedVersions, currentVersion]);
|
}, [availableDownloads, installedBackends, currentBackend]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (downloading && downloadingItemRef.current) {
|
if (downloading && downloadingItemRef.current) {
|
||||||
|
|
@ -170,8 +170,8 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
}, [downloading]);
|
}, [downloading]);
|
||||||
|
|
||||||
const handleDownload = async (version: VersionInfo) => {
|
const handleDownload = async (backend: VersionInfo) => {
|
||||||
const download = availableDownloads.find((d) => d.name === version.name);
|
const download = availableDownloads.find((d) => d.name === backend.name);
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
await handleDownloadFromStore({
|
await handleDownloadFromStore({
|
||||||
|
|
@ -180,59 +180,59 @@ export const VersionsTab = () => {
|
||||||
wasCurrentBinary: false,
|
wasCurrentBinary: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadInstalledVersions();
|
await loadInstalledBackends();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = async (version: VersionInfo) => {
|
const handleUpdate = async (backend: VersionInfo) => {
|
||||||
const download = availableDownloads.find((d) => d.name === version.name);
|
const download = availableDownloads.find((d) => d.name === backend.name);
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
await handleDownloadFromStore({
|
await handleDownloadFromStore({
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: version.isCurrent,
|
wasCurrentBinary: backend.isCurrent,
|
||||||
oldVersionPath: version.installedPath,
|
oldVersionPath: backend.installedPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadInstalledVersions();
|
await loadInstalledBackends();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRedownload = async (version: VersionInfo) => {
|
const handleRedownload = async (backend: VersionInfo) => {
|
||||||
const download = availableDownloads.find((d) => d.name === version.name);
|
const download = availableDownloads.find((d) => d.name === backend.name);
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
await handleDownloadFromStore({
|
await handleDownloadFromStore({
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: version.isCurrent,
|
wasCurrentBinary: backend.isCurrent,
|
||||||
oldVersionPath: version.installedPath,
|
oldVersionPath: backend.installedPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadInstalledVersions();
|
await loadInstalledBackends();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (version: VersionInfo) => {
|
const handleDelete = async (backend: VersionInfo) => {
|
||||||
if (!version.installedPath || version.isCurrent) return;
|
if (!backend.installedPath || backend.isCurrent) return;
|
||||||
|
|
||||||
const result = await window.electronAPI.kobold.deleteRelease(
|
const result = await window.electronAPI.kobold.deleteRelease(
|
||||||
version.installedPath
|
backend.installedPath
|
||||||
);
|
);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
await loadInstalledVersions();
|
await loadInstalledBackends();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeCurrent = (version: VersionInfo) => {
|
const makeCurrent = (backend: VersionInfo) => {
|
||||||
if (!version.installedPath) return;
|
if (!backend.installedPath) return;
|
||||||
|
|
||||||
const targetInstalledVersion = installedVersions.find(
|
const targetBackend = installedBackends.find(
|
||||||
(v) => v.path === version.installedPath
|
(b) => b.path === backend.installedPath
|
||||||
);
|
);
|
||||||
if (targetInstalledVersion) {
|
if (targetBackend) {
|
||||||
setCurrentVersion(targetInstalledVersion);
|
setCurrentBackend(targetBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electronAPI.kobold.setCurrentVersion(version.installedPath);
|
window.electronAPI.kobold.setCurrentBackend(backend.installedPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loadingInstalled || loadingPlatform || loadingRemote) {
|
if (loadingInstalled || loadingPlatform || loadingRemote) {
|
||||||
|
|
@ -242,9 +242,9 @@ export const VersionsTab = () => {
|
||||||
<Loader size="lg" />
|
<Loader size="lg" />
|
||||||
<Text c="dimmed">
|
<Text c="dimmed">
|
||||||
{loadingInstalled && (loadingPlatform || loadingRemote)
|
{loadingInstalled && (loadingPlatform || loadingRemote)
|
||||||
? 'Loading versions...'
|
? 'Loading backends...'
|
||||||
: loadingInstalled
|
: loadingInstalled
|
||||||
? 'Scanning installed versions...'
|
? 'Scanning installed backends...'
|
||||||
: 'Checking for updates...'}
|
: 'Checking for updates...'}
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
@ -276,50 +276,50 @@ export const VersionsTab = () => {
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{allVersions.map((version, index) => {
|
{allBackends.map((backend, index) => {
|
||||||
const isDownloading = downloading === version.name;
|
const isDownloading = downloading === backend.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${version.name}-${version.version}-${index}`}
|
key={`${backend.name}-${backend.version}-${index}`}
|
||||||
style={{ paddingBottom: '0.5rem' }}
|
style={{ paddingBottom: '0.5rem' }}
|
||||||
ref={isDownloading ? downloadingItemRef : null}
|
ref={isDownloading ? downloadingItemRef : null}
|
||||||
>
|
>
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
version={version}
|
version={backend}
|
||||||
size={
|
size={
|
||||||
version.size
|
backend.size
|
||||||
? formatDownloadSize(version.size, version.downloadUrl)
|
? formatDownloadSize(backend.size, backend.downloadUrl)
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
description={getAssetDescription(version.name)}
|
description={getAssetDescription(backend.name)}
|
||||||
disabled={downloading !== null}
|
disabled={downloading !== null}
|
||||||
onDownload={(e) => {
|
onDownload={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDownload(version);
|
handleDownload(backend);
|
||||||
}}
|
}}
|
||||||
onUpdate={(e) => {
|
onUpdate={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleUpdate(version);
|
handleUpdate(backend);
|
||||||
}}
|
}}
|
||||||
onRedownload={(e) => {
|
onRedownload={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleRedownload(version);
|
handleRedownload(backend);
|
||||||
}}
|
}}
|
||||||
onDelete={(e) => {
|
onDelete={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDelete(version);
|
handleDelete(backend);
|
||||||
}}
|
}}
|
||||||
onMakeCurrent={() => makeCurrent(version)}
|
onMakeCurrent={() => makeCurrent(backend)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{allVersions.length === 0 && (
|
{allBackends.length === 0 && (
|
||||||
<Card withBorder radius="md" padding="md">
|
<Card withBorder radius="md" padding="md">
|
||||||
<Text size="sm" c="dimmed" ta="center">
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
No versions found
|
No backends found
|
||||||
</Text>
|
</Text>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
Wrench,
|
Wrench,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { GeneralTab } from '@/components/settings/GeneralTab';
|
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 { AppearanceTab } from '@/components/settings/AppearanceTab';
|
||||||
import { SystemTab } from '@/components/settings/SystemTab';
|
import { SystemTab } from '@/components/settings/SystemTab';
|
||||||
import { TroubleshootingTab } from '@/components/settings/TroubleshootingTab';
|
import { TroubleshootingTab } from '@/components/settings/TroubleshootingTab';
|
||||||
|
|
@ -33,11 +33,11 @@ export const SettingsModal = ({
|
||||||
}: SettingsModalProps) => {
|
}: SettingsModalProps) => {
|
||||||
const [activeTab, setActiveTab] = useState('general');
|
const [activeTab, setActiveTab] = useState('general');
|
||||||
|
|
||||||
const showVersionsTab =
|
const showBackendsTab =
|
||||||
currentScreen !== 'download' && currentScreen !== 'welcome';
|
currentScreen !== 'download' && currentScreen !== 'welcome';
|
||||||
|
|
||||||
const effectiveActiveTab =
|
const effectiveActiveTab =
|
||||||
!showVersionsTab && activeTab === 'versions' ? 'general' : activeTab;
|
!showBackendsTab && activeTab === 'backends' ? 'general' : activeTab;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (opened) {
|
if (opened) {
|
||||||
|
|
@ -110,14 +110,14 @@ export const SettingsModal = ({
|
||||||
>
|
>
|
||||||
General
|
General
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
{showVersionsTab && (
|
{showBackendsTab && (
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
value="versions"
|
value="backends"
|
||||||
leftSection={
|
leftSection={
|
||||||
<GitBranch style={{ width: rem(16), height: rem(16) }} />
|
<GitBranch style={{ width: rem(16), height: rem(16) }} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Versions
|
Backends
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
)}
|
)}
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
|
|
@ -156,9 +156,9 @@ export const SettingsModal = ({
|
||||||
<GeneralTab />
|
<GeneralTab />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
{showVersionsTab && (
|
{showBackendsTab && (
|
||||||
<Tabs.Panel value="versions">
|
<Tabs.Panel value="backends">
|
||||||
<VersionsTab />
|
<BackendsTab />
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export const SystemTab = () => {
|
||||||
|
|
||||||
const loadKoboldVersion = async () => {
|
const loadKoboldVersion = async () => {
|
||||||
const currentVersion =
|
const currentVersion =
|
||||||
await window.electronAPI.kobold.getCurrentVersion();
|
await window.electronAPI.kobold.getCurrentBackend();
|
||||||
if (currentVersion) {
|
if (currentVersion) {
|
||||||
setKoboldVersion(currentVersion.version);
|
setKoboldVersion(currentVersion.version);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,11 +206,11 @@ const addTensorSplitArgs = (args: string[], launchArgs: LaunchArgs) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildBackendArgs = (launchArgs: LaunchArgs) => {
|
const buildBackendArgs = (launchArgs: LaunchArgs, platform: string) => {
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
|
|
||||||
if (!launchArgs.backend || launchArgs.backend === 'cpu') {
|
if (!launchArgs.backend || launchArgs.backend === 'cpu') {
|
||||||
if (launchArgs.backend === 'cpu') {
|
if (launchArgs.backend === 'cpu' && platform !== 'darwin') {
|
||||||
args.push('--usecpu');
|
args.push('--usecpu');
|
||||||
}
|
}
|
||||||
return args;
|
return args;
|
||||||
|
|
@ -271,10 +271,12 @@ export const useLaunchLogic = ({
|
||||||
|
|
||||||
onLaunch();
|
onLaunch();
|
||||||
|
|
||||||
|
const platform = await window.electronAPI.kobold.getPlatform();
|
||||||
|
|
||||||
const args: string[] = [
|
const args: string[] = [
|
||||||
...buildModelArgs(model, sdmodel, launchArgs),
|
...buildModelArgs(model, sdmodel, launchArgs),
|
||||||
...buildConfigArgs(hasImageModel, launchArgs),
|
...buildConfigArgs(hasImageModel, launchArgs),
|
||||||
...buildBackendArgs(launchArgs),
|
...buildBackendArgs(launchArgs, platform),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (launchArgs.additionalArguments.trim()) {
|
if (launchArgs.additionalArguments.trim()) {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import {
|
||||||
} from '@/utils/version';
|
} from '@/utils/version';
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
||||||
import { getROCmDownload } from '@/utils/rocm';
|
import { getROCmDownload } from '@/utils/rocm';
|
||||||
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
import type { InstalledBackend, DownloadItem } from '@/types/electron';
|
||||||
import type { DismissedUpdate } from '@/types';
|
import type { DismissedUpdate } from '@/types';
|
||||||
|
|
||||||
export interface BinaryUpdateInfo {
|
export interface BinaryUpdateInfo {
|
||||||
currentVersion: InstalledVersion;
|
currentBackend: InstalledBackend;
|
||||||
availableUpdate: DownloadItem;
|
availableUpdate: DownloadItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,12 +44,12 @@ export const useUpdateChecker = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsChecking(true);
|
setIsChecking(true);
|
||||||
const [currentVersion, rocmDownload] = await Promise.all([
|
const [currentBackend, rocmDownload] = await Promise.all([
|
||||||
window.electronAPI.kobold.getCurrentVersion(),
|
window.electronAPI.kobold.getCurrentBackend(),
|
||||||
getROCmDownload(),
|
getROCmDownload(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!currentVersion) {
|
if (!currentBackend) {
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ export const useUpdateChecker = () => {
|
||||||
availableDownloads.push(rocmDownload);
|
availableDownloads.push(rocmDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDisplayName = getDisplayNameFromPath(currentVersion);
|
const currentDisplayName = getDisplayNameFromPath(currentBackend);
|
||||||
|
|
||||||
const matchingDownload = availableDownloads.find(
|
const matchingDownload = availableDownloads.find(
|
||||||
(download: DownloadItem) => {
|
(download: DownloadItem) => {
|
||||||
|
|
@ -70,18 +70,18 @@ export const useUpdateChecker = () => {
|
||||||
|
|
||||||
if (matchingDownload && matchingDownload.version) {
|
if (matchingDownload && matchingDownload.version) {
|
||||||
const hasUpdate =
|
const hasUpdate =
|
||||||
compareVersions(matchingDownload.version, currentVersion.version) > 0;
|
compareVersions(matchingDownload.version, currentBackend.version) > 0;
|
||||||
|
|
||||||
if (hasUpdate) {
|
if (hasUpdate) {
|
||||||
const isUpdateDismissed = dismissedUpdates.some(
|
const isUpdateDismissed = dismissedUpdates.some(
|
||||||
(dismissedUpdate) =>
|
(dismissedUpdate) =>
|
||||||
dismissedUpdate.currentVersionPath === currentVersion.path &&
|
dismissedUpdate.currentVersionPath === currentBackend.path &&
|
||||||
dismissedUpdate.targetVersion === matchingDownload.version
|
dismissedUpdate.targetVersion === matchingDownload.version
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isUpdateDismissed) {
|
if (!isUpdateDismissed) {
|
||||||
setUpdateInfo({
|
setUpdateInfo({
|
||||||
currentVersion,
|
currentBackend,
|
||||||
availableUpdate: matchingDownload,
|
availableUpdate: matchingDownload,
|
||||||
});
|
});
|
||||||
setShowUpdateModal(true);
|
setShowUpdateModal(true);
|
||||||
|
|
@ -95,7 +95,7 @@ export const useUpdateChecker = () => {
|
||||||
const skipUpdate = useCallback(() => {
|
const skipUpdate = useCallback(() => {
|
||||||
if (updateInfo && updateInfo.availableUpdate.version) {
|
if (updateInfo && updateInfo.availableUpdate.version) {
|
||||||
const newDismissedUpdate: DismissedUpdate = {
|
const newDismissedUpdate: DismissedUpdate = {
|
||||||
currentVersionPath: updateInfo.currentVersion.path,
|
currentVersionPath: updateInfo.currentBackend.path,
|
||||||
targetVersion: updateInfo.availableUpdate.version,
|
targetVersion: updateInfo.availableUpdate.version,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||||
import { CPUCapabilities, GPUDevice } from '@/types/hardware';
|
import { CPUCapabilities, GPUDevice } from '@/types/hardware';
|
||||||
import type { BackendOption, BackendSupport } from '@/types';
|
import type { AccelerationOption, AccelerationSupport } from '@/types';
|
||||||
|
|
||||||
export interface Warning {
|
export interface Warning {
|
||||||
type: 'warning' | 'info';
|
type: 'warning' | 'info';
|
||||||
|
|
@ -58,14 +58,14 @@ interface GpuInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkGpuWarnings = async (
|
const checkGpuWarnings = async (
|
||||||
backendSupport: BackendSupport,
|
accelerationSupport: AccelerationSupport,
|
||||||
gpuCapabilities: GpuCapabilities,
|
gpuCapabilities: GpuCapabilities,
|
||||||
gpuInfo: GpuInfo
|
gpuInfo: GpuInfo
|
||||||
) => {
|
) => {
|
||||||
const warnings: Warning[] = [];
|
const warnings: Warning[] = [];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
backendSupport.cuda &&
|
accelerationSupport.cuda &&
|
||||||
gpuCapabilities.cuda.devices.length === 0 &&
|
gpuCapabilities.cuda.devices.length === 0 &&
|
||||||
gpuInfo.hasNVIDIA
|
gpuInfo.hasNVIDIA
|
||||||
) {
|
) {
|
||||||
|
|
@ -77,7 +77,7 @@ const checkGpuWarnings = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
backendSupport.rocm &&
|
accelerationSupport.rocm &&
|
||||||
gpuCapabilities.rocm.devices.length === 0 &&
|
gpuCapabilities.rocm.devices.length === 0 &&
|
||||||
gpuInfo.hasAMD
|
gpuInfo.hasAMD
|
||||||
) {
|
) {
|
||||||
|
|
@ -134,7 +134,7 @@ const checkVramWarnings = async (backend: string): Promise<Warning[]> => {
|
||||||
|
|
||||||
const checkCpuWarnings = (
|
const checkCpuWarnings = (
|
||||||
backend: string,
|
backend: string,
|
||||||
availableBackends: BackendOption[]
|
availableAccelerations: AccelerationOption[]
|
||||||
) => {
|
) => {
|
||||||
const warnings: Warning[] = [];
|
const warnings: Warning[] = [];
|
||||||
|
|
||||||
|
|
@ -143,8 +143,8 @@ const checkCpuWarnings = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
availableBackends.length > 0 &&
|
availableAccelerations.length > 0 &&
|
||||||
availableBackends.some((b) => b.value === 'cpu')
|
availableAccelerations.some((a) => a.value === 'cpu')
|
||||||
) {
|
) {
|
||||||
warnings.push({
|
warnings.push({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
|
|
@ -159,35 +159,35 @@ const checkCpuWarnings = (
|
||||||
const checkBackendWarnings = async (params?: {
|
const checkBackendWarnings = async (params?: {
|
||||||
backend: string;
|
backend: string;
|
||||||
cpuCapabilities: CPUCapabilities | null;
|
cpuCapabilities: CPUCapabilities | null;
|
||||||
availableBackends: BackendOption[];
|
availableAccelerations: AccelerationOption[];
|
||||||
}) => {
|
}) => {
|
||||||
const warnings: Warning[] = [];
|
const warnings: Warning[] = [];
|
||||||
|
|
||||||
const [backendSupport, gpuCapabilities, gpuInfo] = await Promise.all([
|
const [accelerationSupport, gpuCapabilities, gpuInfo] = await Promise.all([
|
||||||
window.electronAPI.kobold.detectBackendSupport(),
|
window.electronAPI.kobold.detectAccelerationSupport(),
|
||||||
window.electronAPI.kobold.detectGPUCapabilities(),
|
window.electronAPI.kobold.detectGPUCapabilities(),
|
||||||
window.electronAPI.kobold.detectGPU(),
|
window.electronAPI.kobold.detectGPU(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!backendSupport) {
|
if (!accelerationSupport) {
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gpuWarnings = await checkGpuWarnings(
|
const gpuWarnings = await checkGpuWarnings(
|
||||||
backendSupport,
|
accelerationSupport,
|
||||||
gpuCapabilities,
|
gpuCapabilities,
|
||||||
gpuInfo
|
gpuInfo
|
||||||
);
|
);
|
||||||
warnings.push(...gpuWarnings);
|
warnings.push(...gpuWarnings);
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
const { backend, cpuCapabilities, availableBackends } = params;
|
const { backend, cpuCapabilities, availableAccelerations } = params;
|
||||||
|
|
||||||
const vramWarnings = await checkVramWarnings(backend);
|
const vramWarnings = await checkVramWarnings(backend);
|
||||||
warnings.push(...vramWarnings);
|
warnings.push(...vramWarnings);
|
||||||
|
|
||||||
if (cpuCapabilities) {
|
if (cpuCapabilities) {
|
||||||
const cpuWarnings = checkCpuWarnings(backend, availableBackends);
|
const cpuWarnings = checkCpuWarnings(backend, availableAccelerations);
|
||||||
warnings.push(...cpuWarnings);
|
warnings.push(...cpuWarnings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -214,15 +214,15 @@ export const useWarnings = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [cpuCapabilitiesResult, availableBackends] = await Promise.all([
|
const [cpuCapabilitiesResult, availableAccelerations] = await Promise.all([
|
||||||
window.electronAPI.kobold.detectCPU(),
|
window.electronAPI.kobold.detectCPU(),
|
||||||
window.electronAPI.kobold.getAvailableBackends(),
|
window.electronAPI.kobold.getAvailableAccelerations(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = await checkBackendWarnings({
|
const result = await checkBackendWarnings({
|
||||||
backend,
|
backend,
|
||||||
cpuCapabilities: cpuCapabilitiesResult,
|
cpuCapabilities: cpuCapabilitiesResult,
|
||||||
availableBackends,
|
availableAccelerations,
|
||||||
});
|
});
|
||||||
|
|
||||||
setBackendWarnings(result);
|
setBackendWarnings(result);
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import {
|
||||||
} from '@/main/modules/koboldcpp/launcher';
|
} from '@/main/modules/koboldcpp/launcher';
|
||||||
import { downloadRelease } from '@/main/modules/koboldcpp/download';
|
import { downloadRelease } from '@/main/modules/koboldcpp/download';
|
||||||
import {
|
import {
|
||||||
getInstalledVersions,
|
getInstalledBackends,
|
||||||
getCurrentVersion,
|
getCurrentBackend,
|
||||||
setCurrentVersion,
|
setCurrentBackend,
|
||||||
deleteRelease,
|
deleteRelease,
|
||||||
} from '@/main/modules/koboldcpp/version';
|
} from '@/main/modules/koboldcpp/backend';
|
||||||
import {
|
import {
|
||||||
getConfigFiles,
|
getConfigFiles,
|
||||||
saveConfigFile,
|
saveConfigFile,
|
||||||
|
|
@ -61,9 +61,9 @@ import {
|
||||||
detectSystemMemory,
|
detectSystemMemory,
|
||||||
} from '@/main/modules/hardware';
|
} from '@/main/modules/hardware';
|
||||||
import {
|
import {
|
||||||
detectBackendSupport,
|
detectAccelerationSupport,
|
||||||
getAvailableBackends,
|
getAvailableAccelerations,
|
||||||
} from '@/main/modules/koboldcpp/backend';
|
} from '@/main/modules/koboldcpp/acceleration';
|
||||||
import {
|
import {
|
||||||
openPerformanceManager,
|
openPerformanceManager,
|
||||||
startMonitoring,
|
startMonitoring,
|
||||||
|
|
@ -85,9 +85,9 @@ export function setupIPCHandlers() {
|
||||||
downloadRelease(asset, options)
|
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());
|
ipcMain.handle('kobold:getConfigFiles', () => getConfigFiles());
|
||||||
|
|
||||||
|
|
@ -105,8 +105,8 @@ export function setupIPCHandlers() {
|
||||||
setConfig('selectedConfig', configName)
|
setConfig('selectedConfig', configName)
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:setCurrentVersion', (_, version) =>
|
ipcMain.handle('kobold:setCurrentBackend', (_, version) =>
|
||||||
setCurrentVersion(version)
|
setCurrentBackend(version)
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:getCurrentInstallDir', () => getInstallDir());
|
ipcMain.handle('kobold:getCurrentInstallDir', () => getInstallDir());
|
||||||
|
|
@ -127,10 +127,13 @@ export function setupIPCHandlers() {
|
||||||
|
|
||||||
ipcMain.handle('kobold:detectROCm', () => detectROCm());
|
ipcMain.handle('kobold:detectROCm', () => detectROCm());
|
||||||
|
|
||||||
ipcMain.handle('kobold:detectBackendSupport', () => detectBackendSupport());
|
ipcMain.handle('kobold:detectAccelerationSupport', () =>
|
||||||
|
detectAccelerationSupport()
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:getAvailableBackends', (_, includeDisabled = false) =>
|
ipcMain.handle(
|
||||||
getAvailableBackends(includeDisabled)
|
'kobold:getAvailableAccelerations',
|
||||||
|
(_, includeDisabled = false) => getAvailableAccelerations(includeDisabled)
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:getPlatform', () => platform);
|
ipcMain.handle('kobold:getPlatform', () => platform);
|
||||||
|
|
|
||||||
184
src/main/modules/koboldcpp/acceleration.ts
Normal file
184
src/main/modules/koboldcpp/acceleration.ts
Normal file
|
|
@ -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<string, AccelerationSupport>();
|
||||||
|
const availableAccelerationsCache = new Map<string, AccelerationOption[]>();
|
||||||
|
|
||||||
|
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<boolean> => {
|
||||||
|
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 }];
|
||||||
|
}
|
||||||
|
|
@ -1,178 +1,233 @@
|
||||||
import { join, dirname } from 'path';
|
import { readdir, stat, rm } from 'fs/promises';
|
||||||
import { platform } from 'process';
|
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 { pathExists } from '@/utils/node/fs';
|
||||||
import { getCurrentBinaryInfo } from './version';
|
import { logError } from '@/utils/node/logging';
|
||||||
import { detectGPUCapabilities, detectCPU } from '../hardware';
|
import { getLauncherPath } from '@/utils/node/path';
|
||||||
import { tryExecute, safeExecute } from '@/utils/node/logging';
|
import type { InstalledBackend } from '@/types/electron';
|
||||||
import type { BackendOption, BackendSupport } from '@/types';
|
|
||||||
|
|
||||||
const backendSupportCache = new Map<string, BackendSupport>();
|
const versionCache = new Map<
|
||||||
const availableBackendsCache = new Map<string, BackendOption[]>();
|
string,
|
||||||
|
{ version: string; actualVersion?: string } | null
|
||||||
|
>();
|
||||||
|
|
||||||
async function detectBackendSupportFromPath(koboldBinaryPath: string) {
|
export function clearVersionCache(path?: string) {
|
||||||
if (backendSupportCache.has(koboldBinaryPath)) {
|
if (path) {
|
||||||
return backendSupportCache.get(koboldBinaryPath)!;
|
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<boolean> => {
|
|
||||||
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 () =>
|
export async function getInstalledBackends() {
|
||||||
(await safeExecute(async () => {
|
try {
|
||||||
const currentBinaryInfo = await getCurrentBinaryInfo();
|
const installDir = getInstallDir();
|
||||||
|
if (!(await pathExists(installDir))) {
|
||||||
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) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const backends: BackendOption[] = [];
|
const items = await readdir(installDir);
|
||||||
|
const launchers: { path: string; filename: string; size: number }[] = [];
|
||||||
|
|
||||||
if (backendSupport.cuda) {
|
for (const item of items) {
|
||||||
const isSupported = hardwareCapabilities.cuda.devices.length > 0;
|
const itemPath = join(installDir, item);
|
||||||
if (isSupported || includeDisabled) {
|
const stats = await stat(itemPath);
|
||||||
backends.push({
|
|
||||||
value: 'cuda',
|
if (stats.isDirectory()) {
|
||||||
label: 'CUDA',
|
const launcherPath = await getLauncherPath(itemPath);
|
||||||
devices: hardwareCapabilities.cuda.devices,
|
if (launcherPath && (await pathExists(launcherPath))) {
|
||||||
disabled: includeDisabled ? !isSupported : undefined,
|
const launcherStats = await stat(launcherPath);
|
||||||
});
|
const launcherFilename = launcherPath.split(/[/\\]/).pop() || '';
|
||||||
|
launchers.push({
|
||||||
|
path: launcherPath,
|
||||||
|
filename: launcherFilename,
|
||||||
|
size: launcherStats.size,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backendSupport.rocm) {
|
const versionPromises = launchers.map(async (launcher) => {
|
||||||
const isSupported = hardwareCapabilities.rocm.devices.length > 0;
|
try {
|
||||||
if (isSupported || includeDisabled) {
|
const versionInfo = await getVersionFromBinary(launcher.path);
|
||||||
backends.push({
|
|
||||||
value: 'rocm',
|
|
||||||
label: 'ROCm',
|
|
||||||
devices: hardwareCapabilities.rocm.devices,
|
|
||||||
disabled: includeDisabled ? !isSupported : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backendSupport.vulkan) {
|
if (!versionInfo) {
|
||||||
const isSupported = hardwareCapabilities.vulkan.devices.length > 0;
|
return null;
|
||||||
if (isSupported || includeDisabled) {
|
}
|
||||||
backends.push({
|
|
||||||
value: 'vulkan',
|
|
||||||
label: 'Vulkan',
|
|
||||||
devices: hardwareCapabilities.vulkan.devices,
|
|
||||||
disabled: includeDisabled ? !isSupported : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backendSupport.clblast) {
|
return {
|
||||||
const discreteDevices = hardwareCapabilities.clblast.devices.filter(
|
version: versionInfo.version,
|
||||||
(device) => typeof device === 'string' || !device.isIntegrated
|
path: launcher.path,
|
||||||
);
|
filename: launcher.filename,
|
||||||
const isSupported = discreteDevices.length > 0;
|
size: launcher.size,
|
||||||
if (isSupported || includeDisabled) {
|
actualVersion: versionInfo.actualVersion,
|
||||||
backends.push({
|
} as InstalledBackend;
|
||||||
value: 'clblast',
|
} catch (error) {
|
||||||
label: 'CLBlast',
|
logError(
|
||||||
devices: hardwareCapabilities.clblast.devices,
|
`Could not detect version for ${launcher.filename}:`,
|
||||||
disabled: includeDisabled ? !isSupported : undefined,
|
error as Error
|
||||||
});
|
);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
backends.push({
|
|
||||||
value: 'cpu',
|
|
||||||
label: 'CPU',
|
|
||||||
devices: cpuCapabilities?.devices.map((device) => device.name) || [],
|
|
||||||
disabled: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (includeDisabled) {
|
const results = await Promise.all(versionPromises);
|
||||||
backends.sort((a, b) => {
|
return results.filter(
|
||||||
if (a.disabled === b.disabled) return 0;
|
(version): version is InstalledBackend => version !== null
|
||||||
return a.disabled ? 1 : -1;
|
);
|
||||||
});
|
} 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);
|
const currentBinaryPath = getCurrentKoboldBinary();
|
||||||
return backends;
|
if (currentBinaryPath === binaryPath) {
|
||||||
}, 'Failed to get available backends');
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +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';
|
import { clearVersionCache } from './backend';
|
||||||
|
|
||||||
async function removeDirectoryWithRetry(
|
async function removeDirectoryWithRetry(
|
||||||
dirPath: string,
|
dirPath: string,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { sendKoboldOutput } from '@/main/modules/window';
|
||||||
import { SERVER_READY_SIGNALS } from '@/constants';
|
import { SERVER_READY_SIGNALS } from '@/constants';
|
||||||
import { pathExists } from '@/utils/node/fs';
|
import { pathExists } from '@/utils/node/fs';
|
||||||
import { parseKoboldConfig } from '@/utils/node/kobold';
|
import { parseKoboldConfig } from '@/utils/node/kobold';
|
||||||
import { getCurrentVersion } from '../version';
|
import { getCurrentBackend } from '../backend';
|
||||||
import {
|
import {
|
||||||
getCurrentKoboldBinary,
|
getCurrentKoboldBinary,
|
||||||
get as getConfig,
|
get as getConfig,
|
||||||
|
|
@ -153,15 +153,15 @@ export async function launchKoboldCpp(
|
||||||
spawnPreLaunchCommands(preLaunchCommands);
|
spawnPreLaunchCommands(preLaunchCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentVersion = await getCurrentVersion();
|
const currentBackend = await getCurrentBackend();
|
||||||
if (!currentVersion || !(await pathExists(currentVersion.path))) {
|
if (!currentBackend || !(await pathExists(currentBackend.path))) {
|
||||||
const rawPath = getCurrentKoboldBinary();
|
const rawPath = getCurrentKoboldBinary();
|
||||||
const error = currentVersion
|
const error = currentBackend
|
||||||
? `Binary file does not exist at path: ${currentVersion.path}`
|
? `Binary file does not exist at path: ${currentBackend.path}`
|
||||||
: 'No version configured';
|
: 'No backend configured';
|
||||||
|
|
||||||
logError(
|
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 {
|
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);
|
const { isImageMode, isTextMode, debugmode } = parseKoboldConfig(args);
|
||||||
|
|
||||||
|
|
@ -191,14 +191,14 @@ export async function launchKoboldCpp(
|
||||||
|
|
||||||
await startProxy(koboldHost, koboldPort);
|
await startProxy(koboldHost, koboldPort);
|
||||||
|
|
||||||
const child = spawn(currentVersion.path, finalArgs, {
|
const child = spawn(currentBackend.path, finalArgs, {
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
detached: false,
|
detached: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
koboldProcess = child;
|
koboldProcess = child;
|
||||||
|
|
||||||
const commandLine = `${currentVersion.path} ${finalArgs.join(' ')}`;
|
const commandLine = `${currentBackend.path} ${finalArgs.join(' ')}`;
|
||||||
|
|
||||||
sendKoboldOutput(commandLine);
|
sendKoboldOutput(commandLine);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,10 +16,10 @@ import type {
|
||||||
} from '@/main/modules/monitoring';
|
} from '@/main/modules/monitoring';
|
||||||
|
|
||||||
const koboldAPI: KoboldAPI = {
|
const koboldAPI: KoboldAPI = {
|
||||||
getInstalledVersions: () => ipcRenderer.invoke('kobold:getInstalledVersions'),
|
getInstalledBackends: () => ipcRenderer.invoke('kobold:getInstalledBackends'),
|
||||||
getCurrentVersion: () => ipcRenderer.invoke('kobold:getCurrentVersion'),
|
getCurrentBackend: () => ipcRenderer.invoke('kobold:getCurrentBackend'),
|
||||||
setCurrentVersion: (version) =>
|
setCurrentBackend: (version) =>
|
||||||
ipcRenderer.invoke('kobold:setCurrentVersion', version),
|
ipcRenderer.invoke('kobold:setCurrentBackend', version),
|
||||||
getPlatform: () => ipcRenderer.invoke('kobold:getPlatform'),
|
getPlatform: () => ipcRenderer.invoke('kobold:getPlatform'),
|
||||||
detectGPU: () => ipcRenderer.invoke('kobold:detectGPU'),
|
detectGPU: () => ipcRenderer.invoke('kobold:detectGPU'),
|
||||||
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
|
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
|
||||||
|
|
@ -28,9 +28,10 @@ const koboldAPI: KoboldAPI = {
|
||||||
detectGPUMemory: () => ipcRenderer.invoke('kobold:detectGPUMemory'),
|
detectGPUMemory: () => ipcRenderer.invoke('kobold:detectGPUMemory'),
|
||||||
detectSystemMemory: () => ipcRenderer.invoke('kobold:detectSystemMemory'),
|
detectSystemMemory: () => ipcRenderer.invoke('kobold:detectSystemMemory'),
|
||||||
detectROCm: () => ipcRenderer.invoke('kobold:detectROCm'),
|
detectROCm: () => ipcRenderer.invoke('kobold:detectROCm'),
|
||||||
detectBackendSupport: () => ipcRenderer.invoke('kobold:detectBackendSupport'),
|
detectAccelerationSupport: () =>
|
||||||
getAvailableBackends: (includeDisabled) =>
|
ipcRenderer.invoke('kobold:detectAccelerationSupport'),
|
||||||
ipcRenderer.invoke('kobold:getAvailableBackends', includeDisabled),
|
getAvailableAccelerations: (includeDisabled) =>
|
||||||
|
ipcRenderer.invoke('kobold:getAvailableAccelerations', includeDisabled),
|
||||||
getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'),
|
getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'),
|
||||||
selectInstallDirectory: () =>
|
selectInstallDirectory: () =>
|
||||||
ipcRenderer.invoke('kobold:selectInstallDirectory'),
|
ipcRenderer.invoke('kobold:selectInstallDirectory'),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type {
|
||||||
GitHubRelease,
|
GitHubRelease,
|
||||||
ReleaseWithStatus,
|
ReleaseWithStatus,
|
||||||
GitHubAsset,
|
GitHubAsset,
|
||||||
InstalledVersion,
|
InstalledBackend,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
import { sortDownloadsByType } from '@/utils/assets';
|
import { sortDownloadsByType } from '@/utils/assets';
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
safeExecute(async () => {
|
safeExecute(async () => {
|
||||||
const [response, installedVersions] = await Promise.all([
|
const [response, installedVersions] = await Promise.all([
|
||||||
fetch(GITHUB_API.LATEST_RELEASE_URL),
|
fetch(GITHUB_API.LATEST_RELEASE_URL),
|
||||||
window.electronAPI.kobold.getInstalledVersions(),
|
window.electronAPI.kobold.getInstalledBackends(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!response.ok) return null;
|
if (!response.ok) return null;
|
||||||
|
|
@ -147,11 +147,11 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
|
|
||||||
const availableAssets = latestRelease.assets.map(
|
const availableAssets = latestRelease.assets.map(
|
||||||
(asset: GitHubAsset) => {
|
(asset: GitHubAsset) => {
|
||||||
const installedVersion = installedVersions.find(
|
const installedBackend = installedVersions.find(
|
||||||
(v: InstalledVersion) => {
|
(v: InstalledBackend) => {
|
||||||
const pathParts = v.path.split(/[/\\]/);
|
const pathParts = v.path.split(/[/\\]/);
|
||||||
const launcherIndex = pathParts.findIndex(
|
const launcherIndex = pathParts.findIndex(
|
||||||
(part) =>
|
(part: string) =>
|
||||||
part === 'koboldcpp-launcher' ||
|
part === 'koboldcpp-launcher' ||
|
||||||
part === 'koboldcpp-launcher.exe'
|
part === 'koboldcpp-launcher.exe'
|
||||||
);
|
);
|
||||||
|
|
@ -167,8 +167,8 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asset,
|
asset,
|
||||||
isDownloaded: !!installedVersion,
|
isDownloaded: !!installedBackend,
|
||||||
installedVersion: installedVersion?.version,
|
installedVersion: installedBackend?.version,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
18
src/types/electron.d.ts
vendored
18
src/types/electron.d.ts
vendored
|
|
@ -6,8 +6,8 @@ import type {
|
||||||
SystemMemoryInfo,
|
SystemMemoryInfo,
|
||||||
} from '@/types/hardware';
|
} from '@/types/hardware';
|
||||||
import type {
|
import type {
|
||||||
BackendOption,
|
AccelerationOption,
|
||||||
BackendSupport,
|
AccelerationSupport,
|
||||||
Screen,
|
Screen,
|
||||||
ModelAnalysis,
|
ModelAnalysis,
|
||||||
CachedModel,
|
CachedModel,
|
||||||
|
|
@ -57,7 +57,7 @@ export interface ReleaseWithStatus {
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstalledVersion {
|
export interface InstalledBackend {
|
||||||
version: string;
|
version: string;
|
||||||
path: string;
|
path: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
|
@ -125,9 +125,9 @@ export interface KoboldConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KoboldAPI {
|
export interface KoboldAPI {
|
||||||
getInstalledVersions: () => Promise<InstalledVersion[]>;
|
getInstalledBackends: () => Promise<InstalledBackend[]>;
|
||||||
getCurrentVersion: () => Promise<InstalledVersion | null>;
|
getCurrentBackend: () => Promise<InstalledBackend | null>;
|
||||||
setCurrentVersion: (version: string) => Promise<boolean>;
|
setCurrentBackend: (version: string) => Promise<boolean>;
|
||||||
getPlatform: () => Promise<string>;
|
getPlatform: () => Promise<string>;
|
||||||
detectGPU: () => Promise<BasicGPUInfo>;
|
detectGPU: () => Promise<BasicGPUInfo>;
|
||||||
detectCPU: () => Promise<CPUCapabilities>;
|
detectCPU: () => Promise<CPUCapabilities>;
|
||||||
|
|
@ -135,8 +135,10 @@ export interface KoboldAPI {
|
||||||
detectGPUMemory: () => Promise<GPUMemoryInfo[]>;
|
detectGPUMemory: () => Promise<GPUMemoryInfo[]>;
|
||||||
detectSystemMemory: () => Promise<SystemMemoryInfo>;
|
detectSystemMemory: () => Promise<SystemMemoryInfo>;
|
||||||
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
|
detectROCm: () => Promise<{ supported: boolean; devices: string[] }>;
|
||||||
detectBackendSupport: () => Promise<BackendSupport | null>;
|
detectAccelerationSupport: () => Promise<AccelerationSupport | null>;
|
||||||
getAvailableBackends: (includeDisabled?: boolean) => Promise<BackendOption[]>;
|
getAvailableAccelerations: (
|
||||||
|
includeDisabled?: boolean
|
||||||
|
) => Promise<AccelerationOption[]>;
|
||||||
getCurrentInstallDir: () => Promise<string>;
|
getCurrentInstallDir: () => Promise<string>;
|
||||||
selectInstallDirectory: () => Promise<string | null>;
|
selectInstallDirectory: () => Promise<string | null>;
|
||||||
downloadRelease: (
|
downloadRelease: (
|
||||||
|
|
|
||||||
12
src/types/index.d.ts
vendored
12
src/types/index.d.ts
vendored
|
|
@ -66,14 +66,6 @@ export interface UpdateInfo {
|
||||||
hasUpdate: boolean;
|
hasUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstalledVersion {
|
|
||||||
version: string;
|
|
||||||
path: string;
|
|
||||||
filename: string;
|
|
||||||
size?: number;
|
|
||||||
actualVersion?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VersionInfo {
|
export interface VersionInfo {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
|
@ -97,12 +89,12 @@ export interface SelectOption {
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackendOption extends SelectOption {
|
export interface AccelerationOption extends SelectOption {
|
||||||
readonly devices?: readonly (string | GPUDevice)[];
|
readonly devices?: readonly (string | GPUDevice)[];
|
||||||
readonly disabled?: boolean;
|
readonly disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackendSupport {
|
export interface AccelerationSupport {
|
||||||
rocm: boolean;
|
rocm: boolean;
|
||||||
vulkan: boolean;
|
vulkan: boolean;
|
||||||
clblast: boolean;
|
clblast: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import type { InstalledVersion } from '@/types';
|
import type { InstalledBackend } from '@/types/electron';
|
||||||
|
|
||||||
export const getDisplayNameFromPath = (installedVersion: InstalledVersion) => {
|
export const getDisplayNameFromPath = (installedBackend: InstalledBackend) => {
|
||||||
const pathParts = installedVersion.path.split(/[/\\]/);
|
const pathParts = installedBackend.path.split(/[/\\]/);
|
||||||
const launcherIndex = pathParts.findIndex(
|
const launcherIndex = pathParts.findIndex(
|
||||||
(part) => part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
|
(part: string) =>
|
||||||
|
part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (launcherIndex > 0) {
|
if (launcherIndex > 0) {
|
||||||
return stripVersionSuffix(pathParts[launcherIndex - 1]);
|
return stripVersionSuffix(pathParts[launcherIndex - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return installedVersion.filename;
|
return installedBackend.filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stripAssetExtensions = (assetName: string) =>
|
export const stripAssetExtensions = (assetName: string) =>
|
||||||
|
|
|
||||||
10
yarn.lock
10
yarn.lock
|
|
@ -3736,7 +3736,7 @@ __metadata:
|
||||||
jiti: "npm:^2.6.1"
|
jiti: "npm:^2.6.1"
|
||||||
lucide-react: "npm:^0.555.0"
|
lucide-react: "npm:^0.555.0"
|
||||||
mime-types: "npm:^3.0.2"
|
mime-types: "npm:^3.0.2"
|
||||||
prettier: "npm:^3.7.1"
|
prettier: "npm:^3.7.2"
|
||||||
react: "npm:^19.2.0"
|
react: "npm:^19.2.0"
|
||||||
react-dom: "npm:^19.2.0"
|
react-dom: "npm:^19.2.0"
|
||||||
react-error-boundary: "npm:^6.0.0"
|
react-error-boundary: "npm:^6.0.0"
|
||||||
|
|
@ -5506,12 +5506,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"prettier@npm:^3.7.1":
|
"prettier@npm:^3.7.2":
|
||||||
version: 3.7.1
|
version: 3.7.2
|
||||||
resolution: "prettier@npm:3.7.1"
|
resolution: "prettier@npm:3.7.2"
|
||||||
bin:
|
bin:
|
||||||
prettier: bin/prettier.cjs
|
prettier: bin/prettier.cjs
|
||||||
checksum: 10c0/a6610043ee0a64a3251a948bf82fad3e59d984a8e8dea206400cfa190585417e3343b32c1f6ae7d8f40798a9b4bd91affc08fa7795dd99a9dec5c9bccdf31500
|
checksum: 10c0/df3d658df301face0918f8ecbd4354f32e1151d83a3a4720c7f252342baf631466568f708e0e57beea55bbc56415c40208adc76a91d5f1a88f3e743d0d775dc0
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue