From 03b62d34996c9cfd616cbfaf1153fc7e456a0736 Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 24 Sep 2025 14:33:44 -0700 Subject: [PATCH] clean up previously installed kcpp versions after an update, bring back a needed monkey patch, small refactors --- src/components/App/UpdateAvailableModal.tsx | 37 +++++++++-------- src/components/App/index.tsx | 6 +-- src/components/screens/Download.tsx | 14 +++---- src/components/settings/AboutTab.tsx | 2 +- src/components/settings/VersionsTab.tsx | 12 ++---- src/constants/patches.ts | 5 ++- src/main/ipc.ts | 46 +++++---------------- src/main/modules/koboldcpp/download.ts | 46 ++++++++------------- src/stores/koboldVersions.ts | 34 +++++++-------- src/types/electron.d.ts | 4 +- src/utils/node/path.ts | 21 ++++++++++ 11 files changed, 101 insertions(+), 126 deletions(-) diff --git a/src/components/App/UpdateAvailableModal.tsx b/src/components/App/UpdateAvailableModal.tsx index ce12d07..e734a0e 100644 --- a/src/components/App/UpdateAvailableModal.tsx +++ b/src/components/App/UpdateAvailableModal.tsx @@ -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} > @@ -92,26 +93,28 @@ export const UpdateAvailableModal = ({ {currentVersion && ( - Binary: {getDisplayNameFromPath(currentVersion)} + Binary Type: {pretifyBinName(currentVersion.filename)} )} - + {availableUpdate?.size && ( - View release notes: + Update Size:{' '} + {formatDownloadSize(availableUpdate.size, availableUpdate.url)} - - - v{availableUpdate?.version} - - - - + )} + + + + View Release Notes + + + diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index a31a70d..ec6d643 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -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 () => { diff --git a/src/components/screens/Download.tsx b/src/components/screens/Download.tsx index 0e5bf68..60e6b0c 100644 --- a/src/components/screens/Download.tsx +++ b/src/components/screens/Download.tsx @@ -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] ); diff --git a/src/components/settings/AboutTab.tsx b/src/components/settings/AboutTab.tsx index 9cbef93..a41481f 100644 --- a/src/components/settings/AboutTab.tsx +++ b/src/components/settings/AboutTab.tsx @@ -158,7 +158,7 @@ export const AboutTab = () => { - + { 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) => { diff --git a/src/constants/patches.ts b/src/constants/patches.ts index 2a12974..6aac2af 100644 --- a/src/constants/patches.ts +++ b/src/constants/patches.ts @@ -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; } diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 8a60ff8..cf795b9 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -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'); diff --git a/src/main/modules/koboldcpp/download.ts b/src/main/modules/koboldcpp/download.ts index 10b7785..6d0438e 100644 --- a/src/main/modules/koboldcpp/download.ts +++ b/src/main/modules/koboldcpp/download.ts @@ -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'); diff --git a/src/stores/koboldVersions.ts b/src/stores/koboldVersions.ts index d017f24..6ba884e 100644 --- a/src/stores/koboldVersions.ts +++ b/src/stores/koboldVersions.ts @@ -56,7 +56,7 @@ interface KoboldVersionsState { downloadProgress: Record; initialize: () => Promise; - handleDownload: (params: HandleDownloadParams) => Promise; + handleDownload: (params: HandleDownloadParams) => Promise; getLatestReleaseWithDownloadStatus: () => Promise; } @@ -100,7 +100,7 @@ export const useKoboldVersionsStore = create( const { downloading } = get(); if (downloading) { - return false; + return; } set({ downloading: item.name, downloadProgress: { [item.name]: 0 } }); @@ -111,25 +111,19 @@ export const useKoboldVersionsStore = create( } ); - 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 () => { diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 57810d9..3ff981a 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -113,9 +113,7 @@ export interface KoboldAPI { getAvailableBackends: (includeDisabled?: boolean) => Promise; getCurrentInstallDir: () => Promise; selectInstallDirectory: () => Promise; - downloadRelease: ( - asset: GitHubAsset - ) => Promise<{ success: boolean; path?: string; error?: string }>; + downloadRelease: (asset: GitHubAsset) => Promise; launchKoboldCpp: ( args?: string[] ) => Promise<{ success: boolean; pid?: number; error?: string }>; diff --git a/src/utils/node/path.ts b/src/utils/node/path.ts index 1c39221..1cc325a 100644 --- a/src/utils/node/path.ts +++ b/src/utils/node/path.ts @@ -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', + };