mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
clean up previously installed kcpp versions after an update, bring back a needed monkey patch, small refactors
This commit is contained in:
parent
4fd87a619a
commit
03b62d3499
11 changed files with 101 additions and 126 deletions
|
|
@ -13,7 +13,8 @@ import { useState } from 'react';
|
|||
import type { DownloadItem } from '@/types/electron';
|
||||
import type { BinaryUpdateInfo } from '@/hooks/useUpdateChecker';
|
||||
import { useKoboldVersionsStore } from '@/stores/koboldVersions';
|
||||
import { getDisplayNameFromPath } from '@/utils/version';
|
||||
import { pretifyBinName } from '@/utils/assets';
|
||||
import { formatDownloadSize } from '@/utils/format';
|
||||
import { GITHUB_API } from '@/constants';
|
||||
import { safeExecute } from '@/utils/logger';
|
||||
import { Modal } from '@/components/Modal';
|
||||
|
|
@ -60,7 +61,7 @@ export const UpdateAvailableModal = ({
|
|||
opened={opened}
|
||||
onClose={onClose}
|
||||
size="sm"
|
||||
title="An update is available"
|
||||
title="An Update is Available"
|
||||
closeOnEscape={!isDownloading && !isUpdating}
|
||||
>
|
||||
<Stack gap="md">
|
||||
|
|
@ -92,26 +93,28 @@ export const UpdateAvailableModal = ({
|
|||
|
||||
{currentVersion && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Binary: {getDisplayNameFromPath(currentVersion)}
|
||||
Binary Type: {pretifyBinName(currentVersion.filename)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Group gap="xs" align="center" mt="xs">
|
||||
{availableUpdate?.size && (
|
||||
<Text size="xs" c="dimmed">
|
||||
View release notes:
|
||||
Update Size:{' '}
|
||||
{formatDownloadSize(availableUpdate.size, availableUpdate.url)}
|
||||
</Text>
|
||||
<Anchor
|
||||
size="xs"
|
||||
href={`${GITHUB_API.GITHUB_BASE_URL}/${GITHUB_API.KOBOLDCPP_REPO}/releases/tag/v${availableUpdate?.version}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Group gap={4} align="center">
|
||||
<span>v{availableUpdate?.version}</span>
|
||||
<ExternalLink size={12} />
|
||||
</Group>
|
||||
</Anchor>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
<Anchor
|
||||
size="xs"
|
||||
href={`${GITHUB_API.GITHUB_BASE_URL}/${GITHUB_API.KOBOLDCPP_REPO}/releases/tag/v${availableUpdate?.version}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Group gap={4} align="center">
|
||||
<span>View Release Notes</span>
|
||||
<ExternalLink size={12} />
|
||||
</Group>
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
|
|
|
|||
|
|
@ -90,15 +90,13 @@ export const App = () => {
|
|||
};
|
||||
|
||||
const handleBinaryUpdate = async (download: DownloadItem) => {
|
||||
const success = await handleDownload({
|
||||
await handleDownload({
|
||||
item: download,
|
||||
isUpdate: true,
|
||||
wasCurrentBinary: true,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
closeModal();
|
||||
}
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const handleEject = async () => {
|
||||
|
|
|
|||
|
|
@ -31,21 +31,17 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
|||
async (download: DownloadItem) => {
|
||||
setDownloadingAsset(download.name);
|
||||
|
||||
const success = await handleDownloadFromStore({
|
||||
await handleDownloadFromStore({
|
||||
item: download,
|
||||
isUpdate: false,
|
||||
wasCurrentBinary: false,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
onDownloadComplete();
|
||||
onDownloadComplete();
|
||||
|
||||
setTimeout(() => {
|
||||
setDownloadingAsset(null);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
setDownloadingAsset(null);
|
||||
setTimeout(() => {
|
||||
setDownloadingAsset(null);
|
||||
}, 200);
|
||||
},
|
||||
[handleDownloadFromStore, onDownloadComplete]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export const AboutTab = () => {
|
|||
</Card>
|
||||
|
||||
<Card withBorder radius="md" p="xs" style={{ position: 'relative' }}>
|
||||
<Tooltip label="Copy Version Info">
|
||||
<Tooltip label="Copy Info">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -171,30 +171,26 @@ export const VersionsTab = () => {
|
|||
const download = availableDownloads.find((d) => d.name === version.name);
|
||||
if (!download) return;
|
||||
|
||||
const success = await handleDownloadFromStore({
|
||||
await handleDownloadFromStore({
|
||||
item: download,
|
||||
isUpdate: false,
|
||||
wasCurrentBinary: false,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
await loadInstalledVersions();
|
||||
}
|
||||
await loadInstalledVersions();
|
||||
};
|
||||
|
||||
const handleUpdate = async (version: VersionInfo) => {
|
||||
const download = availableDownloads.find((d) => d.name === version.name);
|
||||
if (!download) return;
|
||||
|
||||
const success = await handleDownloadFromStore({
|
||||
await handleDownloadFromStore({
|
||||
item: download,
|
||||
isUpdate: true,
|
||||
wasCurrentBinary: version.isCurrent,
|
||||
});
|
||||
|
||||
if (success) {
|
||||
await loadInstalledVersions();
|
||||
}
|
||||
await loadInstalledVersions();
|
||||
};
|
||||
|
||||
const makeCurrent = async (version: VersionInfo) => {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@ export const KLITE_CSS_OVERRIDE = `
|
|||
padding: 0 10px;
|
||||
}
|
||||
|
||||
|
||||
#navbarNavDropdown {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#actionmenuitems {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#inputrow > :nth-child(1) {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ipcMain, shell, app } from 'electron';
|
||||
import { ipcMain, app } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { release } from 'os';
|
||||
import { platform, versions, arch } from 'process';
|
||||
|
|
@ -30,8 +30,8 @@ import {
|
|||
getColorScheme,
|
||||
setColorScheme,
|
||||
} from '@/main/modules/config';
|
||||
import { getConfigDir } from '@/utils/node/path';
|
||||
import { logError, safeExecute } from '@/utils/node/logging';
|
||||
import { getConfigDir, openPathHandler, openUrl } from '@/utils/node/path';
|
||||
import { logError } from '@/utils/node/logging';
|
||||
import { stopFrontend as stopSillyTavernFrontend } from '@/main/modules/sillytavern';
|
||||
import { stopFrontend as stopOpenWebUIFrontend } from '@/main/modules/openwebui';
|
||||
import { stopFrontend as stopComfyUIFrontend } from '@/main/modules/comfyui';
|
||||
|
|
@ -81,10 +81,9 @@ import type { NotepadState } from '@/types/electron';
|
|||
export function setupIPCHandlers() {
|
||||
const mainWindow = getMainWindow();
|
||||
|
||||
ipcMain.handle('kobold:downloadRelease', async (_, asset) => ({
|
||||
success: true,
|
||||
path: await downloadRelease(asset),
|
||||
}));
|
||||
ipcMain.handle('kobold:downloadRelease', async (_, asset) =>
|
||||
downloadRelease(asset)
|
||||
);
|
||||
|
||||
ipcMain.handle('kobold:getInstalledVersions', () => getInstalledVersions());
|
||||
|
||||
|
|
@ -183,26 +182,13 @@ export function setupIPCHandlers() {
|
|||
};
|
||||
});
|
||||
|
||||
const openPathHandler = async (path: string) =>
|
||||
(await safeExecute(async () => {
|
||||
await shell.openPath(path);
|
||||
return { success: true };
|
||||
}, 'Failed to open path')) || {
|
||||
success: false,
|
||||
error: 'Failed to open path',
|
||||
};
|
||||
|
||||
ipcMain.handle('app:openPath', (_, path: string) => openPathHandler(path));
|
||||
|
||||
ipcMain.handle('app:showLogsFolder', () => {
|
||||
const logsDir = join(app.getPath('userData'), 'logs');
|
||||
return openPathHandler(logsDir);
|
||||
});
|
||||
ipcMain.handle('app:showLogsFolder', () =>
|
||||
openPathHandler(join(app.getPath('userData'), 'logs'))
|
||||
);
|
||||
|
||||
ipcMain.handle('app:viewConfigFile', () => {
|
||||
const configDir = getConfigDir();
|
||||
return openPathHandler(configDir);
|
||||
});
|
||||
ipcMain.handle('app:viewConfigFile', () => openPathHandler(getConfigDir()));
|
||||
|
||||
ipcMain.handle('app:minimizeWindow', () => mainWindow.minimize());
|
||||
|
||||
|
|
@ -233,17 +219,7 @@ export function setupIPCHandlers() {
|
|||
setColorScheme(colorScheme)
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
'app:openExternal',
|
||||
async (_, url: string) =>
|
||||
(await safeExecute(async () => {
|
||||
await shell.openExternal(url);
|
||||
return { success: true };
|
||||
}, 'Failed to open external URL')) || {
|
||||
success: false,
|
||||
error: 'Failed to open external URL',
|
||||
}
|
||||
);
|
||||
ipcMain.handle('app:openExternal', async (_, url: string) => openUrl(url));
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', async () => {
|
||||
const savedZoomLevel = await getConfig('zoomLevel');
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { createWriteStream } from 'fs';
|
|||
import { join } from 'path';
|
||||
import { platform } from 'process';
|
||||
import { rm, unlink, rename, mkdir, chmod } from 'fs/promises';
|
||||
|
||||
import { execa } from 'execa';
|
||||
|
||||
import {
|
||||
getInstallDir,
|
||||
getCurrentKoboldBinary,
|
||||
|
|
@ -37,27 +37,6 @@ async function removeDirectoryWithRetry(
|
|||
}
|
||||
}
|
||||
|
||||
async function handleExistingDirectory(
|
||||
unpackedDirPath: string,
|
||||
isUpdate: boolean,
|
||||
wasCurrentBinary: boolean
|
||||
) {
|
||||
if (await pathExists(unpackedDirPath)) {
|
||||
if (isUpdate || wasCurrentBinary) {
|
||||
try {
|
||||
await removeDirectoryWithRetry(unpackedDirPath);
|
||||
} catch (error) {
|
||||
logError('Failed to remove existing directory:', error as Error);
|
||||
throw new Error('Failed to remove existing installation');
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
'Installation directory already exists. Please uninstall the existing version first.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(asset: GitHubAsset, tempPackedFilePath: string) {
|
||||
const writer = createWriteStream(tempPackedFilePath);
|
||||
const mainWindow = getMainWindow();
|
||||
|
|
@ -177,12 +156,16 @@ export async function downloadRelease(asset: GitHubAsset) {
|
|||
: baseFilename;
|
||||
const unpackedDirPath = join(getInstallDir(), folderName);
|
||||
|
||||
let currentBinaryPath: string | null = null;
|
||||
if (asset.isUpdate && asset.wasCurrentBinary) {
|
||||
currentBinaryPath = getCurrentKoboldBinary() || null;
|
||||
}
|
||||
|
||||
try {
|
||||
await handleExistingDirectory(
|
||||
unpackedDirPath,
|
||||
Boolean(asset.isUpdate),
|
||||
Boolean(asset.wasCurrentBinary)
|
||||
);
|
||||
if (await pathExists(unpackedDirPath)) {
|
||||
await removeDirectoryWithRetry(unpackedDirPath);
|
||||
}
|
||||
|
||||
await downloadFile(asset, tempPackedFilePath);
|
||||
|
||||
await mkdir(unpackedDirPath, { recursive: true });
|
||||
|
|
@ -197,8 +180,15 @@ export async function downloadRelease(asset: GitHubAsset) {
|
|||
await setCurrentKoboldBinary(launcherPath);
|
||||
}
|
||||
|
||||
if (currentBinaryPath && asset.isUpdate && asset.wasCurrentBinary) {
|
||||
const oldInstallDir = join(currentBinaryPath, '..');
|
||||
|
||||
if (oldInstallDir !== unpackedDirPath) {
|
||||
await removeDirectoryWithRetry(oldInstallDir);
|
||||
}
|
||||
}
|
||||
|
||||
sendToRenderer('versions-updated');
|
||||
return launcherPath;
|
||||
} catch (error) {
|
||||
logError('Failed to download or unpack binary:', error as Error);
|
||||
throw new Error('Failed to download or unpack binary');
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ interface KoboldVersionsState {
|
|||
downloadProgress: Record<string, number>;
|
||||
|
||||
initialize: () => Promise<void>;
|
||||
handleDownload: (params: HandleDownloadParams) => Promise<boolean>;
|
||||
handleDownload: (params: HandleDownloadParams) => Promise<void>;
|
||||
getLatestReleaseWithDownloadStatus: () => Promise<ReleaseWithStatus | null>;
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
|||
const { downloading } = get();
|
||||
|
||||
if (downloading) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
set({ downloading: item.name, downloadProgress: { [item.name]: 0 } });
|
||||
|
|
@ -111,25 +111,19 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
|||
}
|
||||
);
|
||||
|
||||
try {
|
||||
const asset: GitHubAsset = {
|
||||
name: item.name,
|
||||
browser_download_url: item.url,
|
||||
size: item.size,
|
||||
version: item.version,
|
||||
isUpdate,
|
||||
wasCurrentBinary,
|
||||
};
|
||||
const asset: GitHubAsset = {
|
||||
name: item.name,
|
||||
browser_download_url: item.url,
|
||||
size: item.size,
|
||||
version: item.version,
|
||||
isUpdate,
|
||||
wasCurrentBinary,
|
||||
};
|
||||
|
||||
const result = await window.electronAPI.kobold.downloadRelease(asset);
|
||||
return result.success;
|
||||
} catch (err) {
|
||||
logError('Download failed:', err as Error);
|
||||
return false;
|
||||
} finally {
|
||||
progressCleanup();
|
||||
set({ downloading: null, downloadProgress: {} });
|
||||
}
|
||||
await window.electronAPI.kobold.downloadRelease(asset);
|
||||
|
||||
progressCleanup();
|
||||
set({ downloading: null, downloadProgress: {} });
|
||||
},
|
||||
getLatestReleaseWithDownloadStatus: async () =>
|
||||
safeExecute(async () => {
|
||||
|
|
|
|||
4
src/types/electron.d.ts
vendored
4
src/types/electron.d.ts
vendored
|
|
@ -113,9 +113,7 @@ export interface KoboldAPI {
|
|||
getAvailableBackends: (includeDisabled?: boolean) => Promise<BackendOption[]>;
|
||||
getCurrentInstallDir: () => Promise<string>;
|
||||
selectInstallDirectory: () => Promise<string | null>;
|
||||
downloadRelease: (
|
||||
asset: GitHubAsset
|
||||
) => Promise<{ success: boolean; path?: string; error?: string }>;
|
||||
downloadRelease: (asset: GitHubAsset) => Promise<void>;
|
||||
launchKoboldCpp: (
|
||||
args?: string[]
|
||||
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { platform, resourcesPath } from 'process';
|
||||
import { shell } from 'electron';
|
||||
|
||||
import { safeExecute } from '@/utils/logger';
|
||||
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
||||
import { isDevelopment } from './environment';
|
||||
import { pathExists } from './fs';
|
||||
|
|
@ -37,3 +40,21 @@ export async function getLauncherPath(unpackedDir: string) {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
export const openPathHandler = async (path: string) =>
|
||||
(await safeExecute(async () => {
|
||||
await shell.openPath(path);
|
||||
return { success: true };
|
||||
}, 'Failed to open path')) || {
|
||||
success: false,
|
||||
error: 'Failed to open path',
|
||||
};
|
||||
|
||||
export const openUrl = (url: string) =>
|
||||
safeExecute(async () => {
|
||||
await shell.openExternal(url);
|
||||
return { success: true };
|
||||
}, 'Failed to open external URL') || {
|
||||
success: false,
|
||||
error: 'Failed to open external URL',
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue