mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
allow gerbil to work fully offline by allowing kcpp binary imports on the initial Download screen, more version -> backend renames
This commit is contained in:
parent
4b2e9b2ae9
commit
05cb3b8c05
16 changed files with 346 additions and 198 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.13.0",
|
"version": "1.14.0",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
@ -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.2",
|
"prettier": "^3.7.3",
|
||||||
"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"
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"@mantine/hooks": "^8.3.9",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"@uiw/react-codemirror": "^4.25.3",
|
"@uiw/react-codemirror": "^4.25.3",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.1",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^0.555.0",
|
||||||
"mime-types": "^3.0.2",
|
"mime-types": "^3.0.2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
"winston": "^3.18.3",
|
"winston": "^3.18.3",
|
||||||
"winston-daily-rotate-file": "^5.0.0",
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
"yauzl": "^3.2.0",
|
"yauzl": "^3.2.0",
|
||||||
"zustand": "^5.0.8"
|
"zustand": "^5.0.9"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.gerbil.app",
|
"appId": "com.gerbil.app",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { Download, X, ExternalLink } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import type { DownloadItem } from '@/types/electron';
|
||||||
import type { BinaryUpdateInfo } from '@/hooks/useUpdateChecker';
|
import type { BinaryUpdateInfo } from '@/hooks/useUpdateChecker';
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldBackendsStore } from '@/stores/koboldBackends';
|
||||||
import { pretifyBinName } from '@/utils/assets';
|
import { pretifyBinName } from '@/utils/assets';
|
||||||
import { formatDownloadSize } from '@/utils/format';
|
import { formatDownloadSize } from '@/utils/format';
|
||||||
import { GITHUB_API } from '@/constants';
|
import { GITHUB_API } from '@/constants';
|
||||||
|
|
@ -34,7 +34,7 @@ export const UpdateAvailableModal = ({
|
||||||
updateInfo,
|
updateInfo,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
}: UpdateAvailableModalProps) => {
|
}: UpdateAvailableModalProps) => {
|
||||||
const { downloading, downloadProgress } = useKoboldVersionsStore();
|
const { downloading, downloadProgress } = useKoboldBackendsStore();
|
||||||
const currentBackend = updateInfo?.currentBackend;
|
const currentBackend = updateInfo?.currentBackend;
|
||||||
const availableUpdate = updateInfo?.availableUpdate;
|
const availableUpdate = updateInfo?.availableUpdate;
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import { ErrorBoundary } from '@/components/App/ErrorBoundary';
|
||||||
import { AppRouter } from '@/components/App/Router';
|
import { AppRouter } from '@/components/App/Router';
|
||||||
import { NotepadContainer } from '@/components/Notepad/Container';
|
import { NotepadContainer } from '@/components/Notepad/Container';
|
||||||
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldBackendsStore } from '@/stores/koboldBackends';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants';
|
import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants';
|
||||||
|
|
@ -81,7 +81,7 @@ export const App = () => {
|
||||||
closeModal,
|
closeModal,
|
||||||
} = useUpdateChecker();
|
} = useUpdateChecker();
|
||||||
|
|
||||||
const { handleDownload, loadingRemote } = useKoboldVersionsStore();
|
const { handleDownload, loadingRemote } = useKoboldBackendsStore();
|
||||||
|
|
||||||
const determineScreen = (
|
const determineScreen = (
|
||||||
currentVersion: unknown,
|
currentVersion: unknown,
|
||||||
|
|
@ -142,7 +142,7 @@ export const App = () => {
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: true,
|
wasCurrentBinary: true,
|
||||||
oldVersionPath: currentBackend?.path,
|
oldBackendPath: currentBackend?.path,
|
||||||
});
|
});
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import { Download, Trash2 } from 'lucide-react';
|
||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
import { pretifyBinName, isWindowsROCmBuild } from '@/utils/assets';
|
import { pretifyBinName, isWindowsROCmBuild } from '@/utils/assets';
|
||||||
import { usePreferencesStore } from '@/stores/preferences';
|
import { usePreferencesStore } from '@/stores/preferences';
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldBackendsStore } from '@/stores/koboldBackends';
|
||||||
import type { VersionInfo } from '@/types';
|
import type { BackendInfo } from '@/types';
|
||||||
|
|
||||||
interface DownloadCardProps {
|
interface DownloadCardProps {
|
||||||
version: VersionInfo;
|
backend: BackendInfo;
|
||||||
size: string;
|
size: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|
@ -29,7 +29,7 @@ interface DownloadCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DownloadCard = ({
|
export const DownloadCard = ({
|
||||||
version: versionInfo,
|
backend,
|
||||||
size,
|
size,
|
||||||
description,
|
description,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
|
@ -40,23 +40,23 @@ export const DownloadCard = ({
|
||||||
onDelete,
|
onDelete,
|
||||||
}: DownloadCardProps) => {
|
}: DownloadCardProps) => {
|
||||||
const { resolvedColorScheme: colorScheme } = usePreferencesStore();
|
const { resolvedColorScheme: colorScheme } = usePreferencesStore();
|
||||||
const { downloading, downloadProgress } = useKoboldVersionsStore();
|
const { downloading, downloadProgress } = useKoboldBackendsStore();
|
||||||
|
|
||||||
const isLoading = downloading === versionInfo.name;
|
const isLoading = downloading === backend.name;
|
||||||
const currentProgress = isLoading
|
const currentProgress = isLoading
|
||||||
? Math.min(downloadProgress[versionInfo.name], 100) || 0
|
? Math.min(downloadProgress[backend.name], 100) || 0
|
||||||
: 0;
|
: 0;
|
||||||
const hasVersionMismatch = Boolean(
|
const hasVersionMismatch = Boolean(
|
||||||
versionInfo.version &&
|
backend.version &&
|
||||||
versionInfo.actualVersion &&
|
backend.actualVersion &&
|
||||||
versionInfo.version !== versionInfo.actualVersion
|
backend.version !== backend.actualVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const renderActionButtons = () => {
|
const renderActionButtons = () => {
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
if (!versionInfo.isInstalled) {
|
if (!backend.isInstalled) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key="download"
|
key="download"
|
||||||
|
|
@ -78,7 +78,7 @@ export const DownloadCard = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!versionInfo.isCurrent && onMakeCurrent) {
|
if (!backend.isCurrent && onMakeCurrent) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key="makeCurrent"
|
key="makeCurrent"
|
||||||
|
|
@ -92,7 +92,7 @@ export const DownloadCard = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (versionInfo.hasUpdate && onUpdate) {
|
if (backend.hasUpdate && onUpdate) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key="update"
|
key="update"
|
||||||
|
|
@ -110,7 +110,7 @@ export const DownloadCard = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? 'Updating...' : `Update to ${versionInfo.newerVersion}`}
|
{isLoading ? 'Updating...' : `Update to ${backend.newerVersion}`}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +138,7 @@ export const DownloadCard = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onDelete && versionInfo.isInstalled && !versionInfo.isCurrent) {
|
if (onDelete && backend.isInstalled && !backend.isCurrent) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
key="delete"
|
key="delete"
|
||||||
|
|
@ -169,7 +169,7 @@ export const DownloadCard = ({
|
||||||
withBorder
|
withBorder
|
||||||
radius="sm"
|
radius="sm"
|
||||||
padding="sm"
|
padding="sm"
|
||||||
{...(versionInfo.isCurrent && {
|
{...(backend.isCurrent && {
|
||||||
bg: colorScheme === 'dark' ? 'dark.6' : 'gray.0',
|
bg: colorScheme === 'dark' ? 'dark.6' : 'gray.0',
|
||||||
bd: `2px solid var(--mantine-color-${colorScheme === 'dark' ? 'blue-4' : 'blue-6'})`,
|
bd: `2px solid var(--mantine-color-${colorScheme === 'dark' ? 'blue-4' : 'blue-6'})`,
|
||||||
})}
|
})}
|
||||||
|
|
@ -178,19 +178,19 @@ export const DownloadCard = ({
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<Group gap="xs" align="center" mb="xs">
|
<Group gap="xs" align="center" mb="xs">
|
||||||
<Text fw={500} size="sm">
|
<Text fw={500} size="sm">
|
||||||
{pretifyBinName(versionInfo.name)}
|
{pretifyBinName(backend.name)}
|
||||||
</Text>
|
</Text>
|
||||||
{versionInfo.isCurrent && (
|
{backend.isCurrent && (
|
||||||
<Badge variant="light" color="blue" size="sm">
|
<Badge variant="light" color="blue" size="sm">
|
||||||
Current
|
Current
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{versionInfo.hasUpdate && (
|
{backend.hasUpdate && (
|
||||||
<Badge variant="light" color="orange" size="sm">
|
<Badge variant="light" color="orange" size="sm">
|
||||||
Update Available
|
Update Available
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{isWindowsROCmBuild(versionInfo.name) && (
|
{isWindowsROCmBuild(backend.name) && (
|
||||||
<Badge variant="light" color="yellow" size="sm">
|
<Badge variant="light" color="yellow" size="sm">
|
||||||
Experimental
|
Experimental
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -202,13 +202,13 @@ export const DownloadCard = ({
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
{versionInfo.version && (
|
{backend.version && (
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
Version {versionInfo.version}
|
Version {backend.version}
|
||||||
{hasVersionMismatch && versionInfo.actualVersion && (
|
{hasVersionMismatch && backend.actualVersion && (
|
||||||
<span style={{ color: 'var(--mantine-color-red-6)' }}>
|
<span style={{ color: 'var(--mantine-color-red-6)' }}>
|
||||||
{' '}
|
{' '}
|
||||||
(actual: {versionInfo.actualVersion})
|
(actual: {backend.actualVersion})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
|
import {
|
||||||
|
Card,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Loader,
|
||||||
|
Stack,
|
||||||
|
Container,
|
||||||
|
Anchor,
|
||||||
|
} from '@mantine/core';
|
||||||
import { DownloadCard } from '@/components/DownloadCard';
|
import { DownloadCard } from '@/components/DownloadCard';
|
||||||
import { getPlatformDisplayName } from '@/utils/platform';
|
import { getPlatformDisplayName } from '@/utils/platform';
|
||||||
import { formatDownloadSize } from '@/utils/format';
|
import { formatDownloadSize } from '@/utils/format';
|
||||||
import { getAssetDescription } from '@/utils/assets';
|
import { getAssetDescription } from '@/utils/assets';
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldBackendsStore } from '@/stores/koboldBackends';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import type { DownloadItem } from '@/types/electron';
|
||||||
|
|
||||||
interface DownloadScreenProps {
|
interface DownloadScreenProps {
|
||||||
|
|
@ -19,9 +27,11 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
loadingRemote,
|
loadingRemote,
|
||||||
downloading,
|
downloading,
|
||||||
handleDownload: handleDownloadFromStore,
|
handleDownload: handleDownloadFromStore,
|
||||||
} = useKoboldVersionsStore();
|
} = useKoboldBackendsStore();
|
||||||
|
|
||||||
const [downloadingAsset, setDownloadingAsset] = useState<string | null>(null);
|
const [downloadingAsset, setDownloadingAsset] = useState<string | null>(null);
|
||||||
|
const [importError, setImportError] = useState<string | null>(null);
|
||||||
|
const [importing, setImporting] = useState(false);
|
||||||
const downloadingItemRef = useRef<HTMLDivElement>(null);
|
const downloadingItemRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const loading = loadingPlatform || loadingRemote;
|
const loading = loadingPlatform || loadingRemote;
|
||||||
|
|
@ -56,75 +66,101 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="sm" mt="md">
|
<Container size="sm" mt="md">
|
||||||
<Stack gap="xl">
|
<Card withBorder radius="md" shadow="sm">
|
||||||
<Card withBorder radius="md" shadow="sm">
|
<Stack gap="lg">
|
||||||
<Stack gap="lg">
|
<Title order={3}>Select a Backend</Title>
|
||||||
<Title order={3}>Select a Backend</Title>
|
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Stack align="center" gap="md" py="xl">
|
<Stack align="center" gap="md" py="xl">
|
||||||
<Loader color="blue" />
|
<Loader color="blue" />
|
||||||
<Text c="dimmed">Preparing download options...</Text>
|
<Text c="dimmed">Preparing download options...</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{availableDownloads.length > 0 ? (
|
{availableDownloads.length > 0 ? (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{availableDownloads.map((download) => {
|
{availableDownloads.map((download) => {
|
||||||
const isDownloading =
|
const isDownloading =
|
||||||
Boolean(downloading) &&
|
Boolean(downloading) &&
|
||||||
downloadingAsset === download.name;
|
downloadingAsset === download.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={download.name}
|
key={download.name}
|
||||||
ref={isDownloading ? downloadingItemRef : null}
|
ref={isDownloading ? downloadingItemRef : null}
|
||||||
>
|
>
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
version={{
|
backend={{
|
||||||
name: download.name,
|
name: download.name,
|
||||||
version: download.version || '',
|
version: download.version || '',
|
||||||
size: download.size,
|
size: download.size,
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
isCurrent: false,
|
isCurrent: false,
|
||||||
downloadUrl: download.url,
|
downloadUrl: download.url,
|
||||||
hasUpdate: false,
|
hasUpdate: false,
|
||||||
}}
|
}}
|
||||||
size={formatDownloadSize(
|
size={formatDownloadSize(download.size, download.url)}
|
||||||
download.size,
|
description={getAssetDescription(download.name)}
|
||||||
download.url
|
disabled={
|
||||||
)}
|
importing ||
|
||||||
description={getAssetDescription(download.name)}
|
(Boolean(downloading) &&
|
||||||
disabled={
|
downloadingAsset !== download.name)
|
||||||
Boolean(downloading) &&
|
}
|
||||||
downloadingAsset !== download.name
|
onDownload={(e) => {
|
||||||
}
|
e.stopPropagation();
|
||||||
onDownload={(e) => {
|
handleDownload(download);
|
||||||
e.stopPropagation();
|
}}
|
||||||
handleDownload(download);
|
/>
|
||||||
}}
|
</div>
|
||||||
/>
|
);
|
||||||
</div>
|
})}
|
||||||
);
|
</Stack>
|
||||||
})}
|
) : (
|
||||||
</Stack>
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
) : (
|
Unable to fetch downloads for your platform (
|
||||||
<Card withBorder p="md" bg="red.0" c="red.9">
|
{getPlatformDisplayName(platform)}). Check your internet
|
||||||
<Stack gap="xs">
|
connection and try again.
|
||||||
<Text fw={500}>No downloads available</Text>
|
</Text>
|
||||||
<Text size="sm">
|
)}
|
||||||
Unable to fetch downloads for your platform (
|
|
||||||
{getPlatformDisplayName(platform)}). Please check your
|
{importError && (
|
||||||
internet connection and try again.
|
<Text size="sm" c="red" ta="center">
|
||||||
</Text>
|
{importError}
|
||||||
</Stack>
|
</Text>
|
||||||
</Card>
|
)}
|
||||||
)}
|
|
||||||
</>
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
)}
|
Already have a backend downloaded?{' '}
|
||||||
</Stack>
|
<Anchor
|
||||||
</Card>
|
component="button"
|
||||||
</Stack>
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
disabled={importing || Boolean(downloading)}
|
||||||
|
onClick={async () => {
|
||||||
|
setImportError(null);
|
||||||
|
setImporting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result =
|
||||||
|
await window.electronAPI.kobold.importLocalBackend();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
onDownloadComplete();
|
||||||
|
} else if (result.error) {
|
||||||
|
setImportError(result.error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setImporting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{importing ? 'Importing...' : 'Select a local file'}
|
||||||
|
</Anchor>
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ import {
|
||||||
} from '@/utils/version';
|
} from '@/utils/version';
|
||||||
import { formatDownloadSize } from '@/utils/format';
|
import { formatDownloadSize } from '@/utils/format';
|
||||||
|
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldBackendsStore } from '@/stores/koboldBackends';
|
||||||
import type { InstalledBackend, ReleaseWithStatus } from '@/types/electron';
|
import type { InstalledBackend, ReleaseWithStatus } from '@/types/electron';
|
||||||
import type { VersionInfo } from '@/types';
|
import type { BackendInfo } from '@/types';
|
||||||
|
|
||||||
export const BackendsTab = () => {
|
export const BackendsTab = () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -31,7 +31,7 @@ export const BackendsTab = () => {
|
||||||
handleDownload: handleDownloadFromStore,
|
handleDownload: handleDownloadFromStore,
|
||||||
getLatestReleaseWithDownloadStatus,
|
getLatestReleaseWithDownloadStatus,
|
||||||
initialize,
|
initialize,
|
||||||
} = useKoboldVersionsStore();
|
} = useKoboldBackendsStore();
|
||||||
|
|
||||||
const [installedBackends, setInstalledBackends] = useState<
|
const [installedBackends, setInstalledBackends] = useState<
|
||||||
InstalledBackend[]
|
InstalledBackend[]
|
||||||
|
|
@ -83,8 +83,8 @@ export const BackendsTab = () => {
|
||||||
initialize,
|
initialize,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const allBackends = useMemo((): VersionInfo[] => {
|
const allBackends = useMemo((): BackendInfo[] => {
|
||||||
const backends: VersionInfo[] = [];
|
const backends: BackendInfo[] = [];
|
||||||
const processedInstalled = new Set<string>();
|
const processedInstalled = new Set<string>();
|
||||||
|
|
||||||
availableDownloads.forEach((download) => {
|
availableDownloads.forEach((download) => {
|
||||||
|
|
@ -170,7 +170,7 @@ export const BackendsTab = () => {
|
||||||
}
|
}
|
||||||
}, [downloading]);
|
}, [downloading]);
|
||||||
|
|
||||||
const handleDownload = async (backend: VersionInfo) => {
|
const handleDownload = async (backend: BackendInfo) => {
|
||||||
const download = availableDownloads.find((d) => d.name === backend.name);
|
const download = availableDownloads.find((d) => d.name === backend.name);
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
|
|
@ -183,7 +183,7 @@ export const BackendsTab = () => {
|
||||||
await loadInstalledBackends();
|
await loadInstalledBackends();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = async (backend: VersionInfo) => {
|
const handleUpdate = async (backend: BackendInfo) => {
|
||||||
const download = availableDownloads.find((d) => d.name === backend.name);
|
const download = availableDownloads.find((d) => d.name === backend.name);
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
|
|
@ -191,13 +191,13 @@ export const BackendsTab = () => {
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: backend.isCurrent,
|
wasCurrentBinary: backend.isCurrent,
|
||||||
oldVersionPath: backend.installedPath,
|
oldBackendPath: backend.installedPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadInstalledBackends();
|
await loadInstalledBackends();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRedownload = async (backend: VersionInfo) => {
|
const handleRedownload = async (backend: BackendInfo) => {
|
||||||
const download = availableDownloads.find((d) => d.name === backend.name);
|
const download = availableDownloads.find((d) => d.name === backend.name);
|
||||||
if (!download) return;
|
if (!download) return;
|
||||||
|
|
||||||
|
|
@ -205,13 +205,13 @@ export const BackendsTab = () => {
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: backend.isCurrent,
|
wasCurrentBinary: backend.isCurrent,
|
||||||
oldVersionPath: backend.installedPath,
|
oldBackendPath: backend.installedPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
await loadInstalledBackends();
|
await loadInstalledBackends();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (backend: VersionInfo) => {
|
const handleDelete = async (backend: BackendInfo) => {
|
||||||
if (!backend.installedPath || backend.isCurrent) return;
|
if (!backend.installedPath || backend.isCurrent) return;
|
||||||
|
|
||||||
const result = await window.electronAPI.kobold.deleteRelease(
|
const result = await window.electronAPI.kobold.deleteRelease(
|
||||||
|
|
@ -222,7 +222,7 @@ export const BackendsTab = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeCurrent = (backend: VersionInfo) => {
|
const makeCurrent = (backend: BackendInfo) => {
|
||||||
if (!backend.installedPath) return;
|
if (!backend.installedPath) return;
|
||||||
|
|
||||||
const targetBackend = installedBackends.find(
|
const targetBackend = installedBackends.find(
|
||||||
|
|
@ -286,7 +286,7 @@ export const BackendsTab = () => {
|
||||||
ref={isDownloading ? downloadingItemRef : null}
|
ref={isDownloading ? downloadingItemRef : null}
|
||||||
>
|
>
|
||||||
<DownloadCard
|
<DownloadCard
|
||||||
version={backend}
|
backend={backend}
|
||||||
size={
|
size={
|
||||||
backend.size
|
backend.size
|
||||||
? formatDownloadSize(backend.size, backend.downloadUrl)
|
? formatDownloadSize(backend.size, backend.downloadUrl)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
compareVersions,
|
compareVersions,
|
||||||
stripAssetExtensions,
|
stripAssetExtensions,
|
||||||
} from '@/utils/version';
|
} from '@/utils/version';
|
||||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
import { useKoboldBackendsStore } from '@/stores/koboldBackends';
|
||||||
import { getROCmDownload } from '@/utils/rocm';
|
import { getROCmDownload } from '@/utils/rocm';
|
||||||
import type { InstalledBackend, DownloadItem } from '@/types/electron';
|
import type { InstalledBackend, DownloadItem } from '@/types/electron';
|
||||||
import type { DismissedUpdate } from '@/types';
|
import type { DismissedUpdate } from '@/types';
|
||||||
|
|
@ -23,7 +23,7 @@ export const useUpdateChecker = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const { availableDownloads: releases, loadingRemote } =
|
const { availableDownloads: releases, loadingRemote } =
|
||||||
useKoboldVersionsStore();
|
useKoboldBackendsStore();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDismissedUpdates = async () => {
|
const loadDismissedUpdates = async () => {
|
||||||
const dismissed = (await window.electronAPI.config.get(
|
const dismissed = (await window.electronAPI.config.get(
|
||||||
|
|
@ -75,7 +75,7 @@ export const useUpdateChecker = () => {
|
||||||
if (hasUpdate) {
|
if (hasUpdate) {
|
||||||
const isUpdateDismissed = dismissedUpdates.some(
|
const isUpdateDismissed = dismissedUpdates.some(
|
||||||
(dismissedUpdate) =>
|
(dismissedUpdate) =>
|
||||||
dismissedUpdate.currentVersionPath === currentBackend.path &&
|
dismissedUpdate.currentBackendPath === currentBackend.path &&
|
||||||
dismissedUpdate.targetVersion === matchingDownload.version
|
dismissedUpdate.targetVersion === matchingDownload.version
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -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.currentBackend.path,
|
currentBackendPath: updateInfo.currentBackend.path,
|
||||||
targetVersion: updateInfo.availableUpdate.version,
|
targetVersion: updateInfo.availableUpdate.version,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ import {
|
||||||
stopKoboldCpp,
|
stopKoboldCpp,
|
||||||
launchKoboldCppWithCustomFrontends,
|
launchKoboldCppWithCustomFrontends,
|
||||||
} from '@/main/modules/koboldcpp/launcher';
|
} from '@/main/modules/koboldcpp/launcher';
|
||||||
import { downloadRelease } from '@/main/modules/koboldcpp/download';
|
import {
|
||||||
|
downloadRelease,
|
||||||
|
importLocalBackend,
|
||||||
|
} from '@/main/modules/koboldcpp/download';
|
||||||
import {
|
import {
|
||||||
getInstalledBackends,
|
getInstalledBackends,
|
||||||
getCurrentBackend,
|
getCurrentBackend,
|
||||||
|
|
@ -160,6 +163,8 @@ export function setupIPCHandlers() {
|
||||||
selectModelFile(title)
|
selectModelFile(title)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle('kobold:importLocalBackend', () => importLocalBackend());
|
||||||
|
|
||||||
ipcMain.handle('kobold:getLocalModels', (_, paramType: string) =>
|
ipcMain.handle('kobold:getLocalModels', (_, paramType: string) =>
|
||||||
getLocalModelsForType(
|
getLocalModelsForType(
|
||||||
paramType as Parameters<typeof getLocalModelsForType>[0]
|
paramType as Parameters<typeof getLocalModelsForType>[0]
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,16 @@ import { logError } from '@/utils/node/logging';
|
||||||
import { getLauncherPath } from '@/utils/node/path';
|
import { getLauncherPath } from '@/utils/node/path';
|
||||||
import type { InstalledBackend } from '@/types/electron';
|
import type { InstalledBackend } from '@/types/electron';
|
||||||
|
|
||||||
const versionCache = new Map<
|
const backendVersionCache = new Map<
|
||||||
string,
|
string,
|
||||||
{ version: string; actualVersion?: string } | null
|
{ version: string; actualVersion?: string } | null
|
||||||
>();
|
>();
|
||||||
|
|
||||||
export function clearVersionCache(path?: string) {
|
export function clearBackendVersionCache(path?: string) {
|
||||||
if (path) {
|
if (path) {
|
||||||
versionCache.delete(path);
|
backendVersionCache.delete(path);
|
||||||
} else {
|
} else {
|
||||||
versionCache.clear();
|
backendVersionCache.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +161,7 @@ export async function deleteRelease(binaryPath: string) {
|
||||||
if (await pathExists(releaseDir)) {
|
if (await pathExists(releaseDir)) {
|
||||||
await rm(releaseDir, { recursive: true, force: true });
|
await rm(releaseDir, { recursive: true, force: true });
|
||||||
|
|
||||||
clearVersionCache(binaryPath);
|
clearBackendVersionCache(binaryPath);
|
||||||
sendToRenderer('versions-updated');
|
sendToRenderer('versions-updated');
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
|
|
@ -179,8 +179,8 @@ export async function getVersionFromBinary(launcherPath: string) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (versionCache.has(launcherPath)) {
|
if (backendVersionCache.has(launcherPath)) {
|
||||||
return versionCache.get(launcherPath);
|
return backendVersionCache.get(launcherPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
let folderVersion: string | null = null;
|
let folderVersion: string | null = null;
|
||||||
|
|
@ -224,10 +224,10 @@ export async function getVersionFromBinary(launcherPath: string) {
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
versionCache.set(launcherPath, result);
|
backendVersionCache.set(launcherPath, result);
|
||||||
return result;
|
return result;
|
||||||
} catch {
|
} catch {
|
||||||
versionCache.set(launcherPath, null);
|
backendVersionCache.set(launcherPath, null);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { createWriteStream } from 'fs';
|
import { createWriteStream } from 'fs';
|
||||||
import { join } from 'path';
|
import { join, basename } from 'path';
|
||||||
import { platform } from 'process';
|
import { platform } from 'process';
|
||||||
import { rm, unlink, rename, mkdir, chmod } from 'fs/promises';
|
import { rm, unlink, rename, mkdir, chmod, copyFile } from 'fs/promises';
|
||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
|
import { dialog } from 'electron';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getInstallDir,
|
getInstallDir,
|
||||||
|
|
@ -15,7 +16,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 './backend';
|
import { clearBackendVersionCache, getVersionFromBinary } from './backend';
|
||||||
|
|
||||||
async function removeDirectoryWithRetry(
|
async function removeDirectoryWithRetry(
|
||||||
dirPath: string,
|
dirPath: string,
|
||||||
|
|
@ -150,6 +151,59 @@ async function unpackKoboldCpp(packedPath: string, unpackDir: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InstallBackendOptions {
|
||||||
|
packedFilePath: string;
|
||||||
|
unpackedDirPath: string;
|
||||||
|
isUpdate?: boolean;
|
||||||
|
wasCurrentBinary?: boolean;
|
||||||
|
oldBackendPath?: string;
|
||||||
|
skipUnpackError?: boolean;
|
||||||
|
skipCleanup?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installBackend({
|
||||||
|
packedFilePath,
|
||||||
|
unpackedDirPath,
|
||||||
|
isUpdate = false,
|
||||||
|
wasCurrentBinary = false,
|
||||||
|
oldBackendPath,
|
||||||
|
skipUnpackError = false,
|
||||||
|
skipCleanup = false,
|
||||||
|
}: InstallBackendOptions) {
|
||||||
|
if (!skipCleanup && (await pathExists(unpackedDirPath))) {
|
||||||
|
await removeDirectoryWithRetry(unpackedDirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mkdir(unpackedDirPath, { recursive: true });
|
||||||
|
|
||||||
|
if (skipUnpackError) {
|
||||||
|
try {
|
||||||
|
await unpackKoboldCpp(packedFilePath, unpackedDirPath);
|
||||||
|
} catch {}
|
||||||
|
} else {
|
||||||
|
await unpackKoboldCpp(packedFilePath, unpackedDirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const launcherPath = await setupLauncher(packedFilePath, unpackedDirPath);
|
||||||
|
|
||||||
|
clearBackendVersionCache(launcherPath);
|
||||||
|
|
||||||
|
if (oldBackendPath && isUpdate) {
|
||||||
|
const oldInstallDir = join(oldBackendPath, '..');
|
||||||
|
if (oldInstallDir !== unpackedDirPath) {
|
||||||
|
await removeDirectoryWithRetry(oldInstallDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getCurrentKoboldBinary() || (isUpdate && wasCurrentBinary)) {
|
||||||
|
await setCurrentKoboldBinary(launcherPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRenderer('versions-updated');
|
||||||
|
|
||||||
|
return launcherPath;
|
||||||
|
}
|
||||||
|
|
||||||
export async function downloadRelease(
|
export async function downloadRelease(
|
||||||
asset: GitHubAsset,
|
asset: GitHubAsset,
|
||||||
options: DownloadReleaseOptions
|
options: DownloadReleaseOptions
|
||||||
|
|
@ -162,39 +216,73 @@ export async function downloadRelease(
|
||||||
const unpackedDirPath = join(getInstallDir(), folderName);
|
const unpackedDirPath = join(getInstallDir(), folderName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await pathExists(unpackedDirPath)) {
|
|
||||||
await removeDirectoryWithRetry(unpackedDirPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
await downloadFile(asset, tempPackedFilePath);
|
await downloadFile(asset, tempPackedFilePath);
|
||||||
|
|
||||||
await unpackKoboldCpp(tempPackedFilePath, unpackedDirPath);
|
await installBackend({
|
||||||
|
packedFilePath: tempPackedFilePath,
|
||||||
const launcherPath = await setupLauncher(
|
unpackedDirPath,
|
||||||
tempPackedFilePath,
|
isUpdate: options.isUpdate,
|
||||||
unpackedDirPath
|
wasCurrentBinary: options.wasCurrentBinary,
|
||||||
);
|
oldBackendPath: options.oldBackendPath,
|
||||||
|
});
|
||||||
clearVersionCache(launcherPath);
|
|
||||||
|
|
||||||
if (options.oldVersionPath && options.isUpdate) {
|
|
||||||
const oldInstallDir = join(options.oldVersionPath, '..');
|
|
||||||
|
|
||||||
if (oldInstallDir !== unpackedDirPath) {
|
|
||||||
await removeDirectoryWithRetry(oldInstallDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!getCurrentKoboldBinary() ||
|
|
||||||
(options.isUpdate && options.wasCurrentBinary)
|
|
||||||
) {
|
|
||||||
await setCurrentKoboldBinary(launcherPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToRenderer('versions-updated');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('Failed to download or unpack binary:', error as Error);
|
logError('Failed to download or unpack binary:', error as Error);
|
||||||
throw new Error('Failed to download or unpack binary');
|
throw new Error('Failed to download or unpack binary');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function importLocalBackend() {
|
||||||
|
const result = await dialog.showOpenDialog(getMainWindow(), {
|
||||||
|
title: 'Select Backend Executable',
|
||||||
|
filters:
|
||||||
|
platform === 'win32'
|
||||||
|
? [
|
||||||
|
{ name: 'Executable Files', extensions: ['exe'] },
|
||||||
|
{ name: 'All Files', extensions: ['*'] },
|
||||||
|
]
|
||||||
|
: [{ name: 'All Files', extensions: ['*'] }],
|
||||||
|
properties: ['openFile'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || result.filePaths.length === 0) {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPath = result.filePaths[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (platform !== 'win32') {
|
||||||
|
await chmod(selectedPath, 0o755);
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendVersion = await getVersionFromBinary(selectedPath);
|
||||||
|
|
||||||
|
if (!backendVersion || backendVersion.version === 'unknown') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error:
|
||||||
|
'Invalid backend executable. Could not determine version information.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = backendVersion.actualVersion || backendVersion.version;
|
||||||
|
const filename = basename(selectedPath);
|
||||||
|
const baseFilename = stripAssetExtensions(filename);
|
||||||
|
const folderName = `${baseFilename}-${version}`;
|
||||||
|
const installDir = join(getInstallDir(), folderName);
|
||||||
|
const packedFilePath = join(getInstallDir(), `${filename}.packed`);
|
||||||
|
|
||||||
|
await copyFile(selectedPath, packedFilePath);
|
||||||
|
|
||||||
|
await installBackend({
|
||||||
|
packedFilePath,
|
||||||
|
unpackedDirPath: installDir,
|
||||||
|
skipUnpackError: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to import local backend:', error as Error);
|
||||||
|
return { success: false, error: (error as Error).message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import type {
|
||||||
const koboldAPI: KoboldAPI = {
|
const koboldAPI: KoboldAPI = {
|
||||||
getInstalledBackends: () => ipcRenderer.invoke('kobold:getInstalledBackends'),
|
getInstalledBackends: () => ipcRenderer.invoke('kobold:getInstalledBackends'),
|
||||||
getCurrentBackend: () => ipcRenderer.invoke('kobold:getCurrentBackend'),
|
getCurrentBackend: () => ipcRenderer.invoke('kobold:getCurrentBackend'),
|
||||||
setCurrentBackend: (version) =>
|
setCurrentBackend: (backend) =>
|
||||||
ipcRenderer.invoke('kobold:setCurrentBackend', version),
|
ipcRenderer.invoke('kobold:setCurrentBackend', backend),
|
||||||
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'),
|
||||||
|
|
@ -53,6 +53,7 @@ const koboldAPI: KoboldAPI = {
|
||||||
ipcRenderer.invoke('kobold:parseConfigFile', filePath),
|
ipcRenderer.invoke('kobold:parseConfigFile', filePath),
|
||||||
selectModelFile: (title) =>
|
selectModelFile: (title) =>
|
||||||
ipcRenderer.invoke('kobold:selectModelFile', title),
|
ipcRenderer.invoke('kobold:selectModelFile', title),
|
||||||
|
importLocalBackend: () => ipcRenderer.invoke('kobold:importLocalBackend'),
|
||||||
getLocalModels: (paramType) =>
|
getLocalModels: (paramType) =>
|
||||||
ipcRenderer.invoke('kobold:getLocalModels', paramType),
|
ipcRenderer.invoke('kobold:getLocalModels', paramType),
|
||||||
analyzeModel: (filePath) =>
|
analyzeModel: (filePath) =>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ interface HandleDownloadParams {
|
||||||
item: DownloadItem;
|
item: DownloadItem;
|
||||||
isUpdate?: boolean;
|
isUpdate?: boolean;
|
||||||
wasCurrentBinary?: boolean;
|
wasCurrentBinary?: boolean;
|
||||||
oldVersionPath?: string;
|
oldBackendPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformReleaseToDownloadItems = (
|
const transformReleaseToDownloadItems = (
|
||||||
|
|
@ -48,7 +48,7 @@ const fetchLatestReleaseFromAPI = async (platform: string) => {
|
||||||
return transformReleaseToDownloadItems(release, platform);
|
return transformReleaseToDownloadItems(release, platform);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface KoboldVersionsState {
|
interface KoboldBackendsState {
|
||||||
platform: string;
|
platform: string;
|
||||||
availableDownloads: DownloadItem[];
|
availableDownloads: DownloadItem[];
|
||||||
loadingPlatform: boolean;
|
loadingPlatform: boolean;
|
||||||
|
|
@ -61,7 +61,7 @@ interface KoboldVersionsState {
|
||||||
getLatestReleaseWithDownloadStatus: () => Promise<ReleaseWithStatus | null>;
|
getLatestReleaseWithDownloadStatus: () => Promise<ReleaseWithStatus | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
export const useKoboldBackendsStore = create<KoboldBackendsState>(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
platform: '',
|
platform: '',
|
||||||
availableDownloads: [],
|
availableDownloads: [],
|
||||||
|
|
@ -101,7 +101,7 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
item,
|
item,
|
||||||
isUpdate = false,
|
isUpdate = false,
|
||||||
wasCurrentBinary = false,
|
wasCurrentBinary = false,
|
||||||
oldVersionPath,
|
oldBackendPath,
|
||||||
} = params;
|
} = params;
|
||||||
const { downloading } = get();
|
const { downloading } = get();
|
||||||
|
|
||||||
|
|
@ -127,15 +127,16 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
await window.electronAPI.kobold.downloadRelease(asset, {
|
await window.electronAPI.kobold.downloadRelease(asset, {
|
||||||
isUpdate,
|
isUpdate,
|
||||||
wasCurrentBinary,
|
wasCurrentBinary,
|
||||||
oldVersionPath,
|
oldBackendPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
progressCleanup();
|
progressCleanup();
|
||||||
set({ downloading: null, downloadProgress: {} });
|
set({ downloading: null, downloadProgress: {} });
|
||||||
},
|
},
|
||||||
|
|
||||||
getLatestReleaseWithDownloadStatus: async () =>
|
getLatestReleaseWithDownloadStatus: async () =>
|
||||||
safeExecute(async () => {
|
safeExecute(async () => {
|
||||||
const [response, installedVersions] = await Promise.all([
|
const [response, installedBackends] = await Promise.all([
|
||||||
fetch(GITHUB_API.LATEST_RELEASE_URL),
|
fetch(GITHUB_API.LATEST_RELEASE_URL),
|
||||||
window.electronAPI.kobold.getInstalledBackends(),
|
window.electronAPI.kobold.getInstalledBackends(),
|
||||||
]);
|
]);
|
||||||
|
|
@ -147,9 +148,9 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
|
|
||||||
const availableAssets = latestRelease.assets.map(
|
const availableAssets = latestRelease.assets.map(
|
||||||
(asset: GitHubAsset) => {
|
(asset: GitHubAsset) => {
|
||||||
const installedBackend = installedVersions.find(
|
const installedBackend = installedBackends.find(
|
||||||
(v: InstalledBackend) => {
|
(b: InstalledBackend) => {
|
||||||
const pathParts = v.path.split(/[/\\]/);
|
const pathParts = b.path.split(/[/\\]/);
|
||||||
const launcherIndex = pathParts.findIndex(
|
const launcherIndex = pathParts.findIndex(
|
||||||
(part: string) =>
|
(part: string) =>
|
||||||
part === 'koboldcpp-launcher' ||
|
part === 'koboldcpp-launcher' ||
|
||||||
|
|
@ -168,7 +169,7 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
return {
|
return {
|
||||||
asset,
|
asset,
|
||||||
isDownloaded: !!installedBackend,
|
isDownloaded: !!installedBackend,
|
||||||
installedVersion: installedBackend?.version,
|
installedBackendVersion: installedBackend?.version,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -181,4 +182,4 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
useKoboldVersionsStore.getState().initialize();
|
useKoboldBackendsStore.getState().initialize();
|
||||||
5
src/types/electron.d.ts
vendored
5
src/types/electron.d.ts
vendored
|
|
@ -29,7 +29,7 @@ export interface GitHubAsset {
|
||||||
export interface DownloadReleaseOptions {
|
export interface DownloadReleaseOptions {
|
||||||
isUpdate?: boolean;
|
isUpdate?: boolean;
|
||||||
wasCurrentBinary?: boolean;
|
wasCurrentBinary?: boolean;
|
||||||
oldVersionPath?: string;
|
oldBackendPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubRelease {
|
export interface GitHubRelease {
|
||||||
|
|
@ -53,7 +53,7 @@ export interface ReleaseWithStatus {
|
||||||
availableAssets: {
|
availableAssets: {
|
||||||
asset: GitHubAsset;
|
asset: GitHubAsset;
|
||||||
isDownloaded: boolean;
|
isDownloaded: boolean;
|
||||||
installedVersion?: string;
|
installedBackendVersion?: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,6 +162,7 @@ export interface KoboldAPI {
|
||||||
setSelectedConfig: (configName: string) => Promise<boolean>;
|
setSelectedConfig: (configName: string) => Promise<boolean>;
|
||||||
parseConfigFile: (filePath: string) => Promise<KoboldConfig | null>;
|
parseConfigFile: (filePath: string) => Promise<KoboldConfig | null>;
|
||||||
selectModelFile: (title?: string) => Promise<string | null>;
|
selectModelFile: (title?: string) => Promise<string | null>;
|
||||||
|
importLocalBackend: () => Promise<{ success: boolean; error?: string }>;
|
||||||
getLocalModels: (paramType: string) => Promise<CachedModel[]>;
|
getLocalModels: (paramType: string) => Promise<CachedModel[]>;
|
||||||
analyzeModel: (filePath: string) => Promise<ModelAnalysis>;
|
analyzeModel: (filePath: string) => Promise<ModelAnalysis>;
|
||||||
calculateOptimalLayers: (
|
calculateOptimalLayers: (
|
||||||
|
|
|
||||||
4
src/types/index.d.ts
vendored
4
src/types/index.d.ts
vendored
|
|
@ -66,7 +66,7 @@ export interface UpdateInfo {
|
||||||
hasUpdate: boolean;
|
hasUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VersionInfo {
|
export interface BackendInfo {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
|
|
@ -80,7 +80,7 @@ export interface VersionInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DismissedUpdate {
|
export interface DismissedUpdate {
|
||||||
currentVersionPath: string;
|
currentBackendPath: string;
|
||||||
targetVersion: string;
|
targetVersion: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,22 @@ export const getAssetDescription = (assetName: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.endsWith(ASSET_SUFFIXES.OLDPC)) {
|
if (name.endsWith(ASSET_SUFFIXES.OLDPC)) {
|
||||||
return 'Meant for old PCs with outdated CPUs that may not work with the standard build. Does not support modern AVX2 CPU architectures or modern CUDA.';
|
return 'Meant for old PCs with outdated CPUs that may not work with the standard backend. Does not support modern AVX2 CPU architectures or modern CUDA.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.endsWith(ASSET_SUFFIXES.NOCUDA)) {
|
if (name.endsWith(ASSET_SUFFIXES.NOCUDA)) {
|
||||||
return 'Standard build with NVIDIA CUDA support removed for minimal file size.';
|
return 'Standard backend with NVIDIA CUDA support removed for minimal file size.';
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Standard build that's ideal for most cases.";
|
if (
|
||||||
|
name === 'koboldcpp-linux-x64' ||
|
||||||
|
name === 'koboldcpp-mac-arm64' ||
|
||||||
|
name === 'koboldcpp'
|
||||||
|
) {
|
||||||
|
return "Standard backend that's ideal for most cases.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Custom backend.';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isWindowsROCmBuild = (assetName: string) => {
|
export const isWindowsROCmBuild = (assetName: string) => {
|
||||||
|
|
@ -78,5 +86,13 @@ export const pretifyBinName = (binName: string) => {
|
||||||
return 'No CUDA';
|
return 'No CUDA';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Standard';
|
if (
|
||||||
|
cleanName === 'koboldcpp-linux-x64' ||
|
||||||
|
cleanName === 'koboldcpp-mac-arm64' ||
|
||||||
|
cleanName === 'koboldcpp'
|
||||||
|
) {
|
||||||
|
return 'Standard';
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanName;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
30
yarn.lock
30
yarn.lock
|
|
@ -3358,9 +3358,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"execa@npm:^9.6.0":
|
"execa@npm:^9.6.1":
|
||||||
version: 9.6.0
|
version: 9.6.1
|
||||||
resolution: "execa@npm:9.6.0"
|
resolution: "execa@npm:9.6.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sindresorhus/merge-streams": "npm:^4.0.0"
|
"@sindresorhus/merge-streams": "npm:^4.0.0"
|
||||||
cross-spawn: "npm:^7.0.6"
|
cross-spawn: "npm:^7.0.6"
|
||||||
|
|
@ -3374,7 +3374,7 @@ __metadata:
|
||||||
signal-exit: "npm:^4.1.0"
|
signal-exit: "npm:^4.1.0"
|
||||||
strip-final-newline: "npm:^4.0.0"
|
strip-final-newline: "npm:^4.0.0"
|
||||||
yoctocolors: "npm:^2.1.1"
|
yoctocolors: "npm:^2.1.1"
|
||||||
checksum: 10c0/2c44a33142f77d3a6a590a3b769b49b27029a76768593bac1f26fed4dd1330e9c189ee61eba6a8c990fb77e37286c68c7445472ebf24c22b31e9ff320e73d7ac
|
checksum: 10c0/636b36585306a3c8bc3a9d7b25d2d915fb06d8c9b9b02a804280d62562de3b34535affc1b7702b039320e0953daa6545a073f3c4b63fe974c1fe11336c56b467
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3731,12 +3731,12 @@ __metadata:
|
||||||
eslint-plugin-react: "npm:^7.37.5"
|
eslint-plugin-react: "npm:^7.37.5"
|
||||||
eslint-plugin-react-hooks: "npm:^7.0.1"
|
eslint-plugin-react-hooks: "npm:^7.0.1"
|
||||||
eslint-plugin-sonarjs: "npm:^3.0.5"
|
eslint-plugin-sonarjs: "npm:^3.0.5"
|
||||||
execa: "npm:^9.6.0"
|
execa: "npm:^9.6.1"
|
||||||
globals: "npm:^16.5.0"
|
globals: "npm:^16.5.0"
|
||||||
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.2"
|
prettier: "npm:^3.7.3"
|
||||||
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"
|
||||||
|
|
@ -3747,7 +3747,7 @@ __metadata:
|
||||||
winston: "npm:^3.18.3"
|
winston: "npm:^3.18.3"
|
||||||
winston-daily-rotate-file: "npm:^5.0.0"
|
winston-daily-rotate-file: "npm:^5.0.0"
|
||||||
yauzl: "npm:^3.2.0"
|
yauzl: "npm:^3.2.0"
|
||||||
zustand: "npm:^5.0.8"
|
zustand: "npm:^5.0.9"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
|
@ -5506,12 +5506,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"prettier@npm:^3.7.2":
|
"prettier@npm:^3.7.3":
|
||||||
version: 3.7.2
|
version: 3.7.3
|
||||||
resolution: "prettier@npm:3.7.2"
|
resolution: "prettier@npm:3.7.3"
|
||||||
bin:
|
bin:
|
||||||
prettier: bin/prettier.cjs
|
prettier: bin/prettier.cjs
|
||||||
checksum: 10c0/df3d658df301face0918f8ecbd4354f32e1151d83a3a4720c7f252342baf631466568f708e0e57beea55bbc56415c40208adc76a91d5f1a88f3e743d0d775dc0
|
checksum: 10c0/ee86bb06121c74dadc54f30b6f99aff6288966d9b842ce501d6991e20d20c6ce2d45028651b3b0955ca6e5fa89c1bee1e72b6f810243a93cef8bc69737972ef7
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -7308,9 +7308,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"zustand@npm:^5.0.8":
|
"zustand@npm:^5.0.9":
|
||||||
version: 5.0.8
|
version: 5.0.9
|
||||||
resolution: "zustand@npm:5.0.8"
|
resolution: "zustand@npm:5.0.9"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/react": ">=18.0.0"
|
"@types/react": ">=18.0.0"
|
||||||
immer: ">=9.0.6"
|
immer: ">=9.0.6"
|
||||||
|
|
@ -7325,6 +7325,6 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
use-sync-external-store:
|
use-sync-external-store:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/e865a6f7f1c0e03571701db5904151aaa7acefd6f85541a117085e129bf16e8f60b11f8cc82f277472de5c7ad5dcb201e1b0a89035ea0b40c9d9aab3cd24c8ad
|
checksum: 10c0/552849e4546c7760704d6509a5c412d57c62a1fa9e53169c939ba5e3d75f8cb3df50a64c3a22e6c3f1c8cc00de7543e4edd61ab5ae0c9169ba9a98e28303aba6
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue