mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
versions -> update and check for updates on app boot
This commit is contained in:
parent
45a095d20b
commit
6894614b54
13 changed files with 579 additions and 53 deletions
|
|
@ -55,10 +55,10 @@
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
||||||
"@typescript-eslint/parser": "^8.40.0",
|
"@typescript-eslint/parser": "^8.40.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.1",
|
||||||
"cross-env": "^10.0.0",
|
"cross-env": "^10.0.0",
|
||||||
"cspell": "^9.2.0",
|
"cspell": "^9.2.0",
|
||||||
"electron": "^37.3.0",
|
"electron": "^37.3.1",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
"electron-vite": "^4.0.0",
|
"electron-vite": "^4.0.0",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.33.0",
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"rollup-plugin-visualizer": "^6.0.3",
|
"rollup-plugin-visualizer": "^6.0.3",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vite": "^7.1.2",
|
"vite": "^7.1.3",
|
||||||
"wait-on": "^8.0.4"
|
"wait-on": "^8.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
62
src/App.tsx
62
src/App.tsx
|
|
@ -4,11 +4,15 @@ import { DownloadScreen } from '@/components/screens/Download';
|
||||||
import { LaunchScreen } from '@/components/screens/Launch';
|
import { LaunchScreen } from '@/components/screens/Launch';
|
||||||
import { InterfaceScreen } from '@/components/screens/Interface';
|
import { InterfaceScreen } from '@/components/screens/Interface';
|
||||||
import { UpdateDialog } from '@/components/UpdateDialog';
|
import { UpdateDialog } from '@/components/UpdateDialog';
|
||||||
|
import { UpdateAvailableModal } from '@/components/UpdateAvailableModal';
|
||||||
import { SettingsModal } from '@/components/settings/SettingsModal';
|
import { SettingsModal } from '@/components/settings/SettingsModal';
|
||||||
import { ScreenTransition } from '@/components/ScreenTransition';
|
import { ScreenTransition } from '@/components/ScreenTransition';
|
||||||
import { AppHeader } from '@/components/AppHeader';
|
import { AppHeader } from '@/components/AppHeader';
|
||||||
|
import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
||||||
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
import { UI } from '@/constants';
|
import { UI } from '@/constants';
|
||||||
import type { UpdateInfo } from '@/types';
|
import type { UpdateInfo } from '@/types';
|
||||||
|
import type { DownloadItem } from '@/types/electron';
|
||||||
|
|
||||||
type Screen = 'download' | 'launch' | 'interface';
|
type Screen = 'download' | 'launch' | 'interface';
|
||||||
|
|
||||||
|
|
@ -24,6 +28,19 @@ export const App = () => {
|
||||||
const [isImageGenerationMode, setIsImageGenerationMode] =
|
const [isImageGenerationMode, setIsImageGenerationMode] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
updateInfo: binaryUpdateInfo,
|
||||||
|
showUpdateModal,
|
||||||
|
checkForUpdates,
|
||||||
|
dismissUpdate,
|
||||||
|
} = useUpdateChecker();
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleDownload: sharedHandleDownload,
|
||||||
|
downloading,
|
||||||
|
downloadProgress,
|
||||||
|
} = useKoboldVersions();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkInstallation = async () => {
|
const checkInstallation = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -54,6 +71,12 @@ export const App = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setHasInitialized(true);
|
setHasInitialized(true);
|
||||||
|
|
||||||
|
if (versions.length > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
checkForUpdates();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.electronAPI.logs.logError(
|
window.electronAPI.logs.logError(
|
||||||
'Error checking installation:',
|
'Error checking installation:',
|
||||||
|
|
@ -86,7 +109,28 @@ export const App = () => {
|
||||||
cleanupInstallDirListener();
|
cleanupInstallDirListener();
|
||||||
cleanupVersionsListener();
|
cleanupVersionsListener();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [checkForUpdates]);
|
||||||
|
|
||||||
|
const handleBinaryUpdate = async (download: DownloadItem) => {
|
||||||
|
try {
|
||||||
|
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset';
|
||||||
|
const success = await sharedHandleDownload(
|
||||||
|
downloadType,
|
||||||
|
download,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
dismissUpdate();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError(
|
||||||
|
'Failed to update binary:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDownloadComplete = async () => {
|
const handleDownloadComplete = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -235,6 +279,22 @@ export const App = () => {
|
||||||
onAccept={handleUpdateAccept}
|
onAccept={handleUpdateAccept}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showUpdateModal && binaryUpdateInfo && (
|
||||||
|
<UpdateAvailableModal
|
||||||
|
opened={showUpdateModal}
|
||||||
|
onClose={dismissUpdate}
|
||||||
|
currentVersion={binaryUpdateInfo.currentVersion}
|
||||||
|
availableUpdate={binaryUpdateInfo.availableUpdate}
|
||||||
|
onUpdate={handleBinaryUpdate}
|
||||||
|
isDownloading={
|
||||||
|
downloading === binaryUpdateInfo.availableUpdate.name
|
||||||
|
}
|
||||||
|
downloadProgress={
|
||||||
|
downloadProgress[binaryUpdateInfo.availableUpdate.name] || 0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
<SettingsModal
|
<SettingsModal
|
||||||
opened={settingsOpened}
|
opened={settingsOpened}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,11 @@ interface DownloadCardProps {
|
||||||
isDownloading?: boolean;
|
isDownloading?: boolean;
|
||||||
downloadProgress?: number;
|
downloadProgress?: number;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
hasUpdate?: boolean;
|
||||||
|
newerVersion?: string;
|
||||||
onDownload?: (e: MouseEvent<HTMLButtonElement>) => void;
|
onDownload?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
onMakeCurrent?: () => void;
|
onMakeCurrent?: () => void;
|
||||||
|
onUpdate?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DownloadCard = ({
|
export const DownloadCard = ({
|
||||||
|
|
@ -39,10 +42,35 @@ export const DownloadCard = ({
|
||||||
isDownloading = false,
|
isDownloading = false,
|
||||||
downloadProgress = 0,
|
downloadProgress = 0,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
hasUpdate = false,
|
||||||
|
newerVersion,
|
||||||
onDownload,
|
onDownload,
|
||||||
onMakeCurrent,
|
onMakeCurrent,
|
||||||
|
onUpdate,
|
||||||
}: DownloadCardProps) => {
|
}: DownloadCardProps) => {
|
||||||
const renderActionButton = () => {
|
const renderActionButton = () => {
|
||||||
|
if (hasUpdate && onUpdate) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
size="xs"
|
||||||
|
onClick={onUpdate}
|
||||||
|
loading={isDownloading}
|
||||||
|
disabled={disabled}
|
||||||
|
color="orange"
|
||||||
|
leftSection={
|
||||||
|
isDownloading ? (
|
||||||
|
<Loader size="1rem" />
|
||||||
|
) : (
|
||||||
|
<Download style={{ width: rem(14), height: rem(14) }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isDownloading ? 'Updating...' : `Update to ${newerVersion}`}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isInstalled && onDownload) {
|
if (!isInstalled && onDownload) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -99,6 +127,11 @@ export const DownloadCard = ({
|
||||||
Recommended
|
Recommended
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{hasUpdate && (
|
||||||
|
<Badge variant="light" color="orange" size="sm">
|
||||||
|
Update Available
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
{description && (
|
{description && (
|
||||||
<Text size="xs" c="dimmed" mb="xs">
|
<Text size="xs" c="dimmed" mb="xs">
|
||||||
|
|
|
||||||
192
src/components/UpdateAvailableModal.tsx
Normal file
192
src/components/UpdateAvailableModal.tsx
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Group,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Loader,
|
||||||
|
useMantineColorScheme,
|
||||||
|
Anchor,
|
||||||
|
Progress,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { Download, X, ExternalLink } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
||||||
|
import { getDisplayNameFromPath } from '@/utils/versionUtils';
|
||||||
|
import { GITHUB_API } from '@/constants';
|
||||||
|
|
||||||
|
interface UpdateAvailableModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
currentVersion: InstalledVersion;
|
||||||
|
availableUpdate: DownloadItem;
|
||||||
|
onUpdate: (download: DownloadItem) => Promise<void>;
|
||||||
|
isDownloading?: boolean;
|
||||||
|
downloadProgress?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UpdateAvailableModal = ({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
currentVersion,
|
||||||
|
availableUpdate,
|
||||||
|
onUpdate,
|
||||||
|
isDownloading = false,
|
||||||
|
downloadProgress = 0,
|
||||||
|
}: UpdateAvailableModalProps) => {
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
try {
|
||||||
|
setIsUpdating(true);
|
||||||
|
await onUpdate(availableUpdate);
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError('Failed to update:', error as Error);
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
size="sm"
|
||||||
|
title="A newer version is available"
|
||||||
|
centered
|
||||||
|
closeOnClickOutside={false}
|
||||||
|
closeOnEscape={!isDownloading && !isUpdating}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Card
|
||||||
|
withBorder
|
||||||
|
radius="md"
|
||||||
|
p="md"
|
||||||
|
bg={colorScheme === 'dark' ? 'dark.6' : 'gray.0'}
|
||||||
|
style={{
|
||||||
|
borderColor:
|
||||||
|
colorScheme === 'dark'
|
||||||
|
? 'var(--mantine-color-orange-7)'
|
||||||
|
: 'var(--mantine-color-orange-3)',
|
||||||
|
borderWidth: '2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Group gap="md" align="center">
|
||||||
|
<div>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Current Version
|
||||||
|
</Text>
|
||||||
|
<Text fw={500} size="sm">
|
||||||
|
{currentVersion.version}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
size="lg"
|
||||||
|
c={colorScheme === 'dark' ? 'orange.4' : 'orange.6'}
|
||||||
|
fw={500}
|
||||||
|
>
|
||||||
|
→
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Available Version
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
fw={500}
|
||||||
|
size="sm"
|
||||||
|
c={colorScheme === 'dark' ? 'orange.4' : 'orange.6'}
|
||||||
|
>
|
||||||
|
{availableUpdate.version}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Binary: {getDisplayNameFromPath(currentVersion)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group gap="xs" align="center" mt="xs">
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
View release notes:
|
||||||
|
</Text>
|
||||||
|
<Anchor
|
||||||
|
size="xs"
|
||||||
|
href={`https://github.com/${GITHUB_API.KOBOLDCPP_REPO}/releases/tag/v${availableUpdate.version}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={() =>
|
||||||
|
window.electronAPI.app.openExternal(
|
||||||
|
`https://github.com/${GITHUB_API.KOBOLDCPP_REPO}/releases/tag/v${availableUpdate.version}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Group gap={4} align="center">
|
||||||
|
<span>v{availableUpdate.version}</span>
|
||||||
|
<ExternalLink size={12} />
|
||||||
|
</Group>
|
||||||
|
</Anchor>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Stack gap="xs" style={{ minHeight: '44px' }}>
|
||||||
|
<Progress
|
||||||
|
value={isDownloading ? Math.min(downloadProgress, 100) : 0}
|
||||||
|
color="orange"
|
||||||
|
radius="xl"
|
||||||
|
style={{
|
||||||
|
opacity: isDownloading || isUpdating ? 1 : 0,
|
||||||
|
transition: 'opacity 200ms ease',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
c="dimmed"
|
||||||
|
ta="center"
|
||||||
|
style={{
|
||||||
|
opacity: isDownloading || isUpdating ? 1 : 0,
|
||||||
|
transition: 'opacity 200ms ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isDownloading
|
||||||
|
? `${Math.min(downloadProgress, 100).toFixed(1)}% complete`
|
||||||
|
: 'Preparing update...'}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Group justify="flex-end" gap="sm">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={isDownloading || isUpdating}
|
||||||
|
leftSection={<X size={16} />}
|
||||||
|
>
|
||||||
|
Skip
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleUpdate}
|
||||||
|
loading={isDownloading || isUpdating}
|
||||||
|
disabled={isDownloading || isUpdating}
|
||||||
|
leftSection={
|
||||||
|
isDownloading || isUpdating ? (
|
||||||
|
<Loader size="1rem" />
|
||||||
|
) : (
|
||||||
|
<Download size={16} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
color="orange"
|
||||||
|
>
|
||||||
|
{isDownloading || isUpdating ? 'Updating...' : 'Update Now'}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -52,7 +52,12 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
setDownloadingAsset(type === 'asset' ? download!.name : null);
|
setDownloadingAsset(type === 'asset' ? download!.name : null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const success = await sharedHandleDownload(type, download);
|
const success = await sharedHandleDownload(
|
||||||
|
type,
|
||||||
|
download,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
onDownloadComplete();
|
onDownloadComplete();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,31 @@ import { getDisplayNameFromPath, formatFileSizeInMB } from '@/utils';
|
||||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
import type { InstalledVersion, ReleaseWithStatus } from '@/types/electron';
|
import type { InstalledVersion, ReleaseWithStatus } from '@/types/electron';
|
||||||
|
|
||||||
|
const compareVersions = (versionA: string, versionB: string): number => {
|
||||||
|
const cleanVersion = (version: string): string =>
|
||||||
|
version.replace(/^v/, '').replace(/[^0-9.]/g, '');
|
||||||
|
|
||||||
|
const parseVersion = (version: string): number[] =>
|
||||||
|
cleanVersion(version)
|
||||||
|
.split('.')
|
||||||
|
.map((num) => parseInt(num, 10) || 0);
|
||||||
|
|
||||||
|
const a = parseVersion(versionA);
|
||||||
|
const b = parseVersion(versionB);
|
||||||
|
const maxLength = Math.max(a.length, b.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const aVal = a[i] || 0;
|
||||||
|
const bVal = b[i] || 0;
|
||||||
|
|
||||||
|
if (aVal !== bVal) {
|
||||||
|
return aVal - bVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
interface VersionInfo {
|
interface VersionInfo {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
|
@ -30,6 +55,8 @@ interface VersionInfo {
|
||||||
downloadUrl?: string;
|
downloadUrl?: string;
|
||||||
installedPath?: string;
|
installedPath?: string;
|
||||||
isROCm?: boolean;
|
isROCm?: boolean;
|
||||||
|
hasUpdate?: boolean;
|
||||||
|
newerVersion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VersionsTab = () => {
|
export const VersionsTab = () => {
|
||||||
|
|
@ -68,6 +95,7 @@ export const VersionsTab = () => {
|
||||||
|
|
||||||
if (currentBinaryPath && versions.length > 0) {
|
if (currentBinaryPath && versions.length > 0) {
|
||||||
const current = versions.find((v) => v.path === currentBinaryPath);
|
const current = versions.find((v) => v.path === currentBinaryPath);
|
||||||
|
|
||||||
if (current) {
|
if (current) {
|
||||||
setCurrentVersion(current);
|
setCurrentVersion(current);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -79,6 +107,7 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
} else if (versions.length > 0) {
|
} else if (versions.length > 0) {
|
||||||
setCurrentVersion(versions[0]);
|
setCurrentVersion(versions[0]);
|
||||||
|
|
||||||
await window.electronAPI.config.set(
|
await window.electronAPI.config.set(
|
||||||
'currentKoboldBinary',
|
'currentKoboldBinary',
|
||||||
versions[0].path
|
versions[0].path
|
||||||
|
|
@ -119,13 +148,15 @@ export const VersionsTab = () => {
|
||||||
|
|
||||||
const getAllVersions = (): VersionInfo[] => {
|
const getAllVersions = (): VersionInfo[] => {
|
||||||
const versions: VersionInfo[] = [];
|
const versions: VersionInfo[] = [];
|
||||||
|
const processedInstalled = new Set<string>();
|
||||||
|
|
||||||
availableDownloads.forEach((download) => {
|
availableDownloads.forEach((download) => {
|
||||||
|
const downloadBaseName = download.name
|
||||||
|
.replace(/\.(tar\.gz|zip|exe)$/i, '')
|
||||||
|
.replace(/\.packed$/, '');
|
||||||
|
|
||||||
const installedVersion = installedVersions.find((v) => {
|
const installedVersion = installedVersions.find((v) => {
|
||||||
const displayName = getDisplayNameFromPath(v);
|
const displayName = getDisplayNameFromPath(v);
|
||||||
const downloadBaseName = download.name
|
|
||||||
.replace(/\.(tar\.gz|zip|exe)$/i, '')
|
|
||||||
.replace(/\.packed$/, '');
|
|
||||||
return displayName === downloadBaseName;
|
return displayName === downloadBaseName;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -135,23 +166,43 @@ export const VersionsTab = () => {
|
||||||
currentVersion.path === installedVersion.path
|
currentVersion.path === installedVersion.path
|
||||||
);
|
);
|
||||||
|
|
||||||
versions.push({
|
if (installedVersion) {
|
||||||
name: download.name,
|
processedInstalled.add(installedVersion.path);
|
||||||
version: installedVersion?.version || download.version || 'unknown',
|
|
||||||
size: installedVersion ? undefined : download.size,
|
const hasUpdate =
|
||||||
isInstalled: Boolean(installedVersion),
|
compareVersions(
|
||||||
isCurrent,
|
download.version || 'unknown',
|
||||||
downloadUrl: download.url,
|
installedVersion.version
|
||||||
installedPath: installedVersion?.path,
|
) > 0;
|
||||||
isROCm: download.type === 'rocm',
|
|
||||||
});
|
versions.push({
|
||||||
|
name: download.name,
|
||||||
|
version: installedVersion.version,
|
||||||
|
size: undefined,
|
||||||
|
isInstalled: true,
|
||||||
|
isCurrent,
|
||||||
|
downloadUrl: download.url,
|
||||||
|
installedPath: installedVersion.path,
|
||||||
|
isROCm: download.type === 'rocm',
|
||||||
|
hasUpdate,
|
||||||
|
newerVersion: hasUpdate ? download.version : undefined,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
versions.push({
|
||||||
|
name: download.name,
|
||||||
|
version: download.version || 'unknown',
|
||||||
|
size: download.size,
|
||||||
|
isInstalled: false,
|
||||||
|
isCurrent: false,
|
||||||
|
downloadUrl: download.url,
|
||||||
|
isROCm: download.type === 'rocm',
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
installedVersions.forEach((installed) => {
|
installedVersions.forEach((installed) => {
|
||||||
const displayName = getDisplayNameFromPath(installed);
|
if (!processedInstalled.has(installed.path)) {
|
||||||
const existsInRemote = versions.some((v) => v.name === displayName);
|
const displayName = getDisplayNameFromPath(installed);
|
||||||
|
|
||||||
if (!existsInRemote) {
|
|
||||||
const isCurrent = Boolean(
|
const isCurrent = Boolean(
|
||||||
currentVersion && currentVersion.path === installed.path
|
currentVersion && currentVersion.path === installed.path
|
||||||
);
|
);
|
||||||
|
|
@ -179,7 +230,12 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset';
|
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset';
|
||||||
const success = await sharedHandleDownload(downloadType, download);
|
const success = await sharedHandleDownload(
|
||||||
|
downloadType,
|
||||||
|
download,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await loadInstalledVersions();
|
await loadInstalledVersions();
|
||||||
|
|
@ -189,6 +245,30 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async (version: VersionInfo) => {
|
||||||
|
try {
|
||||||
|
const download = availableDownloads.find((d) => d.name === version.name);
|
||||||
|
if (!download) {
|
||||||
|
throw new Error('Download not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset';
|
||||||
|
const wasCurrentBinary = version.isCurrent;
|
||||||
|
const success = await sharedHandleDownload(
|
||||||
|
downloadType,
|
||||||
|
download,
|
||||||
|
true,
|
||||||
|
wasCurrentBinary
|
||||||
|
);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await loadInstalledVersions();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError('Failed to update:', error as Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const makeCurrent = async (version: VersionInfo) => {
|
const makeCurrent = async (version: VersionInfo) => {
|
||||||
if (!version.installedPath) return;
|
if (!version.installedPath) return;
|
||||||
|
|
||||||
|
|
@ -310,6 +390,8 @@ export const VersionsTab = () => {
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
downloadProgress={downloadProgress[version.name]}
|
downloadProgress={downloadProgress[version.name]}
|
||||||
disabled={downloading !== null}
|
disabled={downloading !== null}
|
||||||
|
hasUpdate={version.hasUpdate}
|
||||||
|
newerVersion={version.newerVersion}
|
||||||
onDownload={
|
onDownload={
|
||||||
!version.isInstalled
|
!version.isInstalled
|
||||||
? (e) => {
|
? (e) => {
|
||||||
|
|
@ -318,8 +400,16 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
onUpdate={
|
||||||
|
version.hasUpdate
|
||||||
|
? (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleUpdate(version);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onMakeCurrent={
|
onMakeCurrent={
|
||||||
version.isInstalled && !version.isCurrent
|
version.isInstalled && !version.isCurrent && !version.hasUpdate
|
||||||
? () => makeCurrent(version)
|
? () => makeCurrent(version)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export const GITHUB_API = {
|
||||||
BASE_URL: 'https://api.github.com',
|
BASE_URL: 'https://api.github.com',
|
||||||
KOBOLDCPP_REPO: 'LostRuins/koboldcpp',
|
KOBOLDCPP_REPO: 'LostRuins/koboldcpp',
|
||||||
KOBOLDCPP_ROCM_REPO: 'YellowRoseCx/koboldcpp-rocm',
|
KOBOLDCPP_ROCM_REPO: 'YellowRoseCx/koboldcpp-rocm',
|
||||||
|
FRIENDLY_KOBOLD_REPO: 'lone-cloud/friendly-kobold',
|
||||||
get LATEST_RELEASE_URL() {
|
get LATEST_RELEASE_URL() {
|
||||||
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest`;
|
return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest`;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ interface UseKoboldVersionsReturn {
|
||||||
loadRemoteVersions: () => Promise<void>;
|
loadRemoteVersions: () => Promise<void>;
|
||||||
handleDownload: (
|
handleDownload: (
|
||||||
type: 'asset' | 'rocm',
|
type: 'asset' | 'rocm',
|
||||||
item?: DownloadItem
|
item?: DownloadItem,
|
||||||
|
isUpdate?: boolean,
|
||||||
|
wasCurrentBinary?: boolean
|
||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
setDownloading: (value: string | null) => void;
|
setDownloading: (value: string | null) => void;
|
||||||
setDownloadProgress: (
|
setDownloadProgress: (
|
||||||
|
|
@ -113,7 +115,12 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
}, [platformInfo.platform]);
|
}, [platformInfo.platform]);
|
||||||
|
|
||||||
const handleDownload = useCallback(
|
const handleDownload = useCallback(
|
||||||
async (type: 'asset' | 'rocm', item?: DownloadItem): Promise<boolean> => {
|
async (
|
||||||
|
type: 'asset' | 'rocm',
|
||||||
|
item?: DownloadItem,
|
||||||
|
isUpdate = false,
|
||||||
|
wasCurrentBinary = false
|
||||||
|
): Promise<boolean> => {
|
||||||
if (type === 'asset' && !item) return false;
|
if (type === 'asset' && !item) return false;
|
||||||
|
|
||||||
const downloadName = item?.name || 'download';
|
const downloadName = item?.name || 'download';
|
||||||
|
|
@ -130,6 +137,8 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
browser_download_url: item!.url,
|
browser_download_url: item!.url,
|
||||||
size: item!.size,
|
size: item!.size,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
|
isUpdate,
|
||||||
|
wasCurrentBinary,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.success !== false;
|
return result.success !== false;
|
||||||
|
|
|
||||||
115
src/hooks/useUpdateChecker.ts
Normal file
115
src/hooks/useUpdateChecker.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import { getDisplayNameFromPath } from '@/utils/versionUtils';
|
||||||
|
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
||||||
|
|
||||||
|
const compareVersions = (versionA: string, versionB: string): number => {
|
||||||
|
const cleanVersion = (version: string): string =>
|
||||||
|
version.replace(/^v/, '').replace(/[^0-9.]/g, '');
|
||||||
|
|
||||||
|
const parseVersion = (version: string): number[] =>
|
||||||
|
cleanVersion(version)
|
||||||
|
.split('.')
|
||||||
|
.map((num) => parseInt(num, 10) || 0);
|
||||||
|
|
||||||
|
const a = parseVersion(versionA);
|
||||||
|
const b = parseVersion(versionB);
|
||||||
|
const maxLength = Math.max(a.length, b.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const aVal = a[i] || 0;
|
||||||
|
const bVal = b[i] || 0;
|
||||||
|
|
||||||
|
if (aVal !== bVal) {
|
||||||
|
return aVal - bVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UpdateInfo {
|
||||||
|
currentVersion: InstalledVersion;
|
||||||
|
availableUpdate: DownloadItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUpdateChecker = () => {
|
||||||
|
const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
||||||
|
|
||||||
|
const checkForUpdates = useCallback(async () => {
|
||||||
|
setIsChecking(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [currentBinaryPath, installedVersions, releases, rocm] =
|
||||||
|
await Promise.all([
|
||||||
|
window.electronAPI.config.get(
|
||||||
|
'currentKoboldBinary'
|
||||||
|
) as Promise<string>,
|
||||||
|
window.electronAPI.kobold.getInstalledVersions(),
|
||||||
|
window.electronAPI.kobold.getLatestRelease(),
|
||||||
|
window.electronAPI.kobold.getROCmDownload(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!currentBinaryPath || installedVersions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVersion = installedVersions.find(
|
||||||
|
(v: InstalledVersion) => v.path === currentBinaryPath
|
||||||
|
);
|
||||||
|
if (!currentVersion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableDownloads: DownloadItem[] = [...releases];
|
||||||
|
if (rocm) {
|
||||||
|
availableDownloads.push(rocm);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDisplayName = getDisplayNameFromPath(currentVersion);
|
||||||
|
|
||||||
|
const matchingDownload = availableDownloads.find(
|
||||||
|
(download: DownloadItem) => {
|
||||||
|
const downloadBaseName = download.name
|
||||||
|
.replace(/\.(tar\.gz|zip|exe)$/i, '')
|
||||||
|
.replace(/\.packed$/, '');
|
||||||
|
return downloadBaseName === currentDisplayName;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingDownload && matchingDownload.version) {
|
||||||
|
const hasUpdate =
|
||||||
|
compareVersions(matchingDownload.version, currentVersion.version) > 0;
|
||||||
|
|
||||||
|
if (hasUpdate) {
|
||||||
|
setUpdateInfo({
|
||||||
|
currentVersion,
|
||||||
|
availableUpdate: matchingDownload,
|
||||||
|
});
|
||||||
|
setShowUpdateModal(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError(
|
||||||
|
'Failed to check for updates:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const dismissUpdate = useCallback(() => {
|
||||||
|
setShowUpdateModal(false);
|
||||||
|
setUpdateInfo(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateInfo,
|
||||||
|
showUpdateModal,
|
||||||
|
isChecking,
|
||||||
|
checkForUpdates,
|
||||||
|
dismissUpdate,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
unlinkSync,
|
unlinkSync,
|
||||||
} from 'fs';
|
} from 'fs';
|
||||||
|
import { rm } from 'fs/promises';
|
||||||
import { dialog } from 'electron';
|
import { dialog } from 'electron';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
import { GitHubService } from '@/main/services/GitHubService';
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
|
|
@ -23,6 +24,8 @@ interface GitHubAsset {
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
size: number;
|
size: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
isUpdate?: boolean;
|
||||||
|
wasCurrentBinary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GitHubRelease {
|
interface GitHubRelease {
|
||||||
|
|
@ -85,6 +88,17 @@ export class KoboldCppManager {
|
||||||
const baseFilename = asset.name.replace(/\.exe$/, '');
|
const baseFilename = asset.name.replace(/\.exe$/, '');
|
||||||
const unpackedDirPath = join(this.installDir, baseFilename);
|
const unpackedDirPath = join(this.installDir, baseFilename);
|
||||||
|
|
||||||
|
if (asset.isUpdate && existsSync(unpackedDirPath)) {
|
||||||
|
try {
|
||||||
|
await rm(unpackedDirPath, { recursive: true, force: true });
|
||||||
|
} catch (error) {
|
||||||
|
this.logManager.logError(
|
||||||
|
'Failed to remove existing directory for update:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(asset.browser_download_url);
|
const response = await fetch(asset.browser_download_url);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
@ -150,7 +164,7 @@ export class KoboldCppManager {
|
||||||
const launcherPath = this.getLauncherPath(unpackedDirPath);
|
const launcherPath = this.getLauncherPath(unpackedDirPath);
|
||||||
if (launcherPath && existsSync(launcherPath)) {
|
if (launcherPath && existsSync(launcherPath)) {
|
||||||
const currentBinary = this.configManager.getCurrentKoboldBinary();
|
const currentBinary = this.configManager.getCurrentKoboldBinary();
|
||||||
if (!currentBinary) {
|
if (!currentBinary || (asset.isUpdate && asset.wasCurrentBinary)) {
|
||||||
this.configManager.setCurrentKoboldBinary(launcherPath);
|
this.configManager.setCurrentKoboldBinary(launcherPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { GITHUB_API } from '../../constants';
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
private mainWindow: BrowserWindow | null = null;
|
private mainWindow: BrowserWindow | null = null;
|
||||||
|
|
@ -255,7 +256,9 @@ export class WindowManager {
|
||||||
{
|
{
|
||||||
label: 'KoboldCpp Wiki',
|
label: 'KoboldCpp Wiki',
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal('https://github.com/LostRuins/koboldcpp/wiki');
|
shell.openExternal(
|
||||||
|
`https://github.com/${GITHUB_API.KOBOLDCPP_REPO}/wiki`
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -327,7 +330,9 @@ OS: ${osInfo}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.once('open-github', () => {
|
ipcMain.once('open-github', () => {
|
||||||
shell.openExternal('https://github.com/lone-cloud/friendly-kobold');
|
shell.openExternal(
|
||||||
|
`https://github.com/${GITHUB_API.FRIENDLY_KOBOLD_REPO}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.once('close-about-dialog', () => {
|
ipcMain.once('close-about-dialog', () => {
|
||||||
|
|
|
||||||
2
src/types/electron.d.ts
vendored
2
src/types/electron.d.ts
vendored
|
|
@ -11,6 +11,8 @@ interface GitHubAsset {
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
size: number;
|
size: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
isUpdate?: boolean;
|
||||||
|
wasCurrentBinary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubRelease {
|
export interface GitHubRelease {
|
||||||
|
|
|
||||||
48
yarn.lock
48
yarn.lock
|
|
@ -40,7 +40,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/core@npm:^7.27.7, @babel/core@npm:^7.28.0":
|
"@babel/core@npm:^7.27.7, @babel/core@npm:^7.28.3":
|
||||||
version: 7.28.3
|
version: 7.28.3
|
||||||
resolution: "@babel/core@npm:7.28.3"
|
resolution: "@babel/core@npm:7.28.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -1661,10 +1661,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/pluginutils@npm:1.0.0-beta.30":
|
"@rolldown/pluginutils@npm:1.0.0-beta.32":
|
||||||
version: 1.0.0-beta.30
|
version: 1.0.0-beta.32
|
||||||
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.30"
|
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.32"
|
||||||
checksum: 10c0/aff8b532cb9d82d94c9a4101fa12ecb10620ad47d52dbb9135a5c65bde1ad19895b41026b821f4d607083699239a5d0010198401b6a6a54ab6a10d0015302768
|
checksum: 10c0/ba3582fc3c35c8eb57b0df2d22d0733b1be83d37edcc258203364773f094f58fc0cb7a056d604603573a69dd0105a466506cad467f59074e1e53d0dc26191f06
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -2191,19 +2191,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@vitejs/plugin-react@npm:^5.0.0":
|
"@vitejs/plugin-react@npm:^5.0.1":
|
||||||
version: 5.0.0
|
version: 5.0.1
|
||||||
resolution: "@vitejs/plugin-react@npm:5.0.0"
|
resolution: "@vitejs/plugin-react@npm:5.0.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core": "npm:^7.28.0"
|
"@babel/core": "npm:^7.28.3"
|
||||||
"@babel/plugin-transform-react-jsx-self": "npm:^7.27.1"
|
"@babel/plugin-transform-react-jsx-self": "npm:^7.27.1"
|
||||||
"@babel/plugin-transform-react-jsx-source": "npm:^7.27.1"
|
"@babel/plugin-transform-react-jsx-source": "npm:^7.27.1"
|
||||||
"@rolldown/pluginutils": "npm:1.0.0-beta.30"
|
"@rolldown/pluginutils": "npm:1.0.0-beta.32"
|
||||||
"@types/babel__core": "npm:^7.20.5"
|
"@types/babel__core": "npm:^7.20.5"
|
||||||
react-refresh: "npm:^0.17.0"
|
react-refresh: "npm:^0.17.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
checksum: 10c0/e5813839d319ab5dc1b90cab40b6c08388f26e456166ba9df10ffc3c3f4ecc594cec06715b5c93390bba56140ca5f68a18f2233f7d275d77e5bbfeb979e4fd9b
|
checksum: 10c0/2641171beedfc38edc5671abb47706906f9af2a79a6dfff4e946106c9550de4f83ccae41c164f3ee26a3edf07127ecc0e415fe5cddbf7abc71fbb2540016c27d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3615,16 +3615,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"electron@npm:^37.3.0":
|
"electron@npm:^37.3.1":
|
||||||
version: 37.3.0
|
version: 37.3.1
|
||||||
resolution: "electron@npm:37.3.0"
|
resolution: "electron@npm:37.3.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@electron/get": "npm:^2.0.0"
|
"@electron/get": "npm:^2.0.0"
|
||||||
"@types/node": "npm:^22.7.7"
|
"@types/node": "npm:^22.7.7"
|
||||||
extract-zip: "npm:^2.0.1"
|
extract-zip: "npm:^2.0.1"
|
||||||
bin:
|
bin:
|
||||||
electron: cli.js
|
electron: cli.js
|
||||||
checksum: 10c0/6f08ca2955c65591368cb0dadbd637d86749040c8d4cd616d7265e1c71ece854f23c678b7cfc23746100327dcdb34f4815504745e97ca3ac2f40c72b951e1044
|
checksum: 10c0/1b171804646f229768c29413629b70b968c4c06686913b69e8615da09c58d63a95817021c31ed3c3bdbba0ad360ad4469773b9dda2662884d7df2866559f95f1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -4304,7 +4304,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"fdir@npm:^6.4.4, fdir@npm:^6.4.6":
|
"fdir@npm:^6.4.4, fdir@npm:^6.5.0":
|
||||||
version: 6.5.0
|
version: 6.5.0
|
||||||
resolution: "fdir@npm:6.5.0"
|
resolution: "fdir@npm:6.5.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -4433,10 +4433,10 @@ __metadata:
|
||||||
"@types/react-dom": "npm:^19.1.7"
|
"@types/react-dom": "npm:^19.1.7"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
|
"@typescript-eslint/eslint-plugin": "npm:^8.40.0"
|
||||||
"@typescript-eslint/parser": "npm:^8.40.0"
|
"@typescript-eslint/parser": "npm:^8.40.0"
|
||||||
"@vitejs/plugin-react": "npm:^5.0.0"
|
"@vitejs/plugin-react": "npm:^5.0.1"
|
||||||
cross-env: "npm:^10.0.0"
|
cross-env: "npm:^10.0.0"
|
||||||
cspell: "npm:^9.2.0"
|
cspell: "npm:^9.2.0"
|
||||||
electron: "npm:^37.3.0"
|
electron: "npm:^37.3.1"
|
||||||
electron-builder: "npm:^26.0.12"
|
electron-builder: "npm:^26.0.12"
|
||||||
electron-vite: "npm:^4.0.0"
|
electron-vite: "npm:^4.0.0"
|
||||||
eslint: "npm:^9.33.0"
|
eslint: "npm:^9.33.0"
|
||||||
|
|
@ -4457,7 +4457,7 @@ __metadata:
|
||||||
rollup-plugin-visualizer: "npm:^6.0.3"
|
rollup-plugin-visualizer: "npm:^6.0.3"
|
||||||
systeminformation: "npm:^5.27.7"
|
systeminformation: "npm:^5.27.7"
|
||||||
typescript: "npm:^5.9.2"
|
typescript: "npm:^5.9.2"
|
||||||
vite: "npm:^7.1.2"
|
vite: "npm:^7.1.3"
|
||||||
wait-on: "npm:^8.0.4"
|
wait-on: "npm:^8.0.4"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
@ -8180,12 +8180,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"vite@npm:^7.1.2":
|
"vite@npm:^7.1.3":
|
||||||
version: 7.1.2
|
version: 7.1.3
|
||||||
resolution: "vite@npm:7.1.2"
|
resolution: "vite@npm:7.1.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: "npm:^0.25.0"
|
esbuild: "npm:^0.25.0"
|
||||||
fdir: "npm:^6.4.6"
|
fdir: "npm:^6.5.0"
|
||||||
fsevents: "npm:~2.3.3"
|
fsevents: "npm:~2.3.3"
|
||||||
picomatch: "npm:^4.0.3"
|
picomatch: "npm:^4.0.3"
|
||||||
postcss: "npm:^8.5.6"
|
postcss: "npm:^8.5.6"
|
||||||
|
|
@ -8231,7 +8231,7 @@ __metadata:
|
||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
vite: bin/vite.js
|
vite: bin/vite.js
|
||||||
checksum: 10c0/4ed825b20bc0f49db99cd382de9506b2721ccd47dcebd4a68e0ef65e3cdd2347fded52b306c34178308e0fd7fe78fd5ff517623002cb00710182ad3012c92ced
|
checksum: 10c0/a0aa418beab80673dc9a3e9d1fa49472955d6ef9d41a4c9c6bd402953f411346f612864dae267adfb2bb8ceeb894482369316ffae5816c84fd45990e352b727d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue