mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
better old window state recreation, more distrinct select option highlights in light mode, better updated version cleanup
This commit is contained in:
parent
03b62d3499
commit
7cef3ce90d
20 changed files with 133 additions and 181 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gerbil",
|
||||
"productName": "Gerbil",
|
||||
"version": "1.5.2",
|
||||
"version": "1.5.3",
|
||||
"description": "Run Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"build": "electron-vite build",
|
||||
"package": "electron-vite build && electron-builder --publish=never",
|
||||
"analyze": "cross-env ANALYZE=true electron-vite build && yarn dlx open-cli dist/stats.html",
|
||||
"format": "prettier --write . --ignore-path .gitignore",
|
||||
"format": "prettier --write . --ignore-path .gitignore | grep -v '(unchanged)' || true",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"compile": "tsc --noEmit",
|
||||
|
|
@ -68,8 +68,8 @@
|
|||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.38.3",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@mantine/core": "^8.3.1",
|
||||
"@mantine/hooks": "^8.3.1",
|
||||
"@mantine/core": "8.3.1",
|
||||
"@mantine/hooks": "8.3.1",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"@uiw/react-codemirror": "^4.25.2",
|
||||
"electron-updater": "6.6.2",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
ActionIcon,
|
||||
Box,
|
||||
Image,
|
||||
Select,
|
||||
AppShell,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
|
|
@ -18,6 +17,7 @@ import { UpdateButton } from '@/components/App/UpdateButton';
|
|||
import icon from '/icon.png';
|
||||
import { PRODUCT_NAME, TITLEBAR_HEIGHT } from '@/constants';
|
||||
import type { InterfaceTab, Screen, SelectOption } from '@/types';
|
||||
import { Select } from '@/components/Select';
|
||||
|
||||
interface TitleBarProps {
|
||||
currentScreen: Screen;
|
||||
|
|
@ -135,9 +135,7 @@ export const TitleBar = ({
|
|||
onDropdownClose={() => setIsSelectOpen(false)}
|
||||
data={interfaceOptions}
|
||||
renderOption={renderOption}
|
||||
allowDeselect={false}
|
||||
variant="unstyled"
|
||||
size="sm"
|
||||
style={{ textAlign: 'center', minWidth: '7.5rem' }}
|
||||
styles={{
|
||||
input: {
|
||||
|
|
|
|||
|
|
@ -91,9 +91,9 @@ export const UpdateAvailableModal = ({
|
|||
</div>
|
||||
</Group>
|
||||
|
||||
{currentVersion && (
|
||||
{availableUpdate?.name && (
|
||||
<Text size="xs" c="dimmed">
|
||||
Binary Type: {pretifyBinName(currentVersion.filename)}
|
||||
Binary Type: {pretifyBinName(availableUpdate?.name)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,10 +90,13 @@ export const App = () => {
|
|||
};
|
||||
|
||||
const handleBinaryUpdate = async (download: DownloadItem) => {
|
||||
const currentVersion = await window.electronAPI.kobold.getCurrentVersion();
|
||||
|
||||
await handleDownload({
|
||||
item: download,
|
||||
isUpdate: true,
|
||||
wasCurrentBinary: true,
|
||||
oldVersionPath: currentVersion?.path,
|
||||
});
|
||||
|
||||
closeModal();
|
||||
|
|
|
|||
16
src/components/Select.tsx
Normal file
16
src/components/Select.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Select as MantineSelect } from '@mantine/core';
|
||||
import type { SelectProps } from '@mantine/core';
|
||||
|
||||
export const Select = ({
|
||||
allowDeselect = false,
|
||||
clearable = false,
|
||||
size = 'sm',
|
||||
...props
|
||||
}: SelectProps) => (
|
||||
<MantineSelect
|
||||
allowDeselect={allowDeselect}
|
||||
clearable={clearable}
|
||||
size={size}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import type { CSSProperties } from 'react';
|
||||
import { Select } from '@mantine/core';
|
||||
import type { ComboboxItem } from '@mantine/core';
|
||||
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
|
||||
import type { SelectOption } from '@/types';
|
||||
import { Select } from '@/components/Select';
|
||||
|
||||
interface SelectWithTooltipProps {
|
||||
label: string;
|
||||
|
|
@ -36,7 +36,6 @@ export const SelectWithTooltip = ({
|
|||
data={data}
|
||||
disabled={disabled}
|
||||
clearable={clearable}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Stack, Text, Group, Button, Select, Tooltip } from '@mantine/core';
|
||||
import { Stack, Text, Group, Button, Tooltip } from '@mantine/core';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Save, File, Plus, Check, Trash2 } from 'lucide-react';
|
||||
import type { ConfigFile } from '@/types';
|
||||
import { CreateConfigModal } from './CreateConfigModal';
|
||||
import { DeleteConfigModal } from './DeleteConfigModal';
|
||||
import { Select } from '@/components/Select';
|
||||
|
||||
interface ConfigFileManagerProps {
|
||||
configFiles: ConfigFile[];
|
||||
|
|
@ -108,8 +109,6 @@ export const ConfigFileManager = ({
|
|||
data={selectData}
|
||||
leftSection={<File size={16} />}
|
||||
searchable
|
||||
clearable={false}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { Text, Group, Select, Checkbox, TextInput } from '@mantine/core';
|
||||
import { Text, Group, Checkbox, TextInput } from '@mantine/core';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||
import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem';
|
||||
import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
import type { BackendOption } from '@/types';
|
||||
import { Select } from '@/components/Select';
|
||||
|
||||
export const BackendSelector = () => {
|
||||
const {
|
||||
|
|
@ -89,11 +90,11 @@ export const BackendSelector = () => {
|
|||
disabled: b.disabled,
|
||||
}))}
|
||||
disabled={isLoadingBackends || availableBackends.length === 0}
|
||||
allowDeselect={false}
|
||||
renderOption={({ option }) => {
|
||||
const backendData = availableBackends.find(
|
||||
(b) => b.value === option.value
|
||||
);
|
||||
|
||||
return (
|
||||
<BackendSelectItem
|
||||
label={backendData?.label || option.label.split(' (')[0]}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Text, Group, Select, TextInput } from '@mantine/core';
|
||||
import { Text, Group, TextInput } from '@mantine/core';
|
||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
import { Select } from '@/components/Select';
|
||||
import type { BackendOption } from '@/types';
|
||||
|
||||
interface GpuDeviceSelectorProps {
|
||||
|
|
@ -68,7 +69,6 @@ export const GpuDeviceSelector = ({
|
|||
}
|
||||
}}
|
||||
data={deviceOptions}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import {
|
|||
TextInput,
|
||||
Button,
|
||||
rem,
|
||||
Select,
|
||||
Box,
|
||||
Anchor,
|
||||
} from '@mantine/core';
|
||||
|
|
@ -14,6 +13,7 @@ import { Folder, FolderOpen, Monitor, ExternalLink } from 'lucide-react';
|
|||
import type { FrontendPreference } from '@/types';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { FRONTENDS } from '@/constants';
|
||||
import { Select } from '@/components/Select';
|
||||
|
||||
interface FrontendRequirement {
|
||||
id: string;
|
||||
|
|
@ -267,7 +267,6 @@ export const GeneralTab = ({
|
|||
disabled: !isFrontendAvailable(config.value),
|
||||
}))}
|
||||
leftSection={<Monitor style={{ width: rem(16), height: rem(16) }} />}
|
||||
allowDeselect={false}
|
||||
/>
|
||||
|
||||
<Box mt="sm">
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ export const VersionsTab = () => {
|
|||
item: download,
|
||||
isUpdate: true,
|
||||
wasCurrentBinary: version.isCurrent,
|
||||
oldVersionPath: version.installedPath,
|
||||
});
|
||||
|
||||
await loadInstalledVersions();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { ipcMain, app } from 'electron';
|
|||
import { join } from 'path';
|
||||
import { release } from 'os';
|
||||
import { platform, versions, arch } from 'process';
|
||||
import type { MantineColorScheme } from '@mantine/core';
|
||||
import {
|
||||
stopKoboldCpp,
|
||||
launchKoboldCppWithCustomFrontends,
|
||||
|
|
@ -76,13 +75,12 @@ import {
|
|||
canAutoUpdate,
|
||||
} from '@/main/modules/autoUpdater';
|
||||
import { getAppVersion } from '@/utils/node/fs';
|
||||
import type { NotepadState } from '@/types/electron';
|
||||
|
||||
export function setupIPCHandlers() {
|
||||
const mainWindow = getMainWindow();
|
||||
|
||||
ipcMain.handle('kobold:downloadRelease', async (_, asset) =>
|
||||
downloadRelease(asset)
|
||||
ipcMain.handle('kobold:downloadRelease', async (_, asset, options) =>
|
||||
downloadRelease(asset, options)
|
||||
);
|
||||
|
||||
ipcMain.handle('kobold:getInstalledVersions', () => getInstalledVersions());
|
||||
|
|
@ -182,7 +180,7 @@ export function setupIPCHandlers() {
|
|||
};
|
||||
});
|
||||
|
||||
ipcMain.handle('app:openPath', (_, path: string) => openPathHandler(path));
|
||||
ipcMain.handle('app:openPath', (_, path) => openPathHandler(path));
|
||||
|
||||
ipcMain.handle('app:showLogsFolder', () =>
|
||||
openPathHandler(join(app.getPath('userData'), 'logs'))
|
||||
|
|
@ -208,18 +206,18 @@ export function setupIPCHandlers() {
|
|||
mainWindow.webContents.getZoomLevel()
|
||||
);
|
||||
|
||||
ipcMain.handle('app:setZoomLevel', (_, level: number) => {
|
||||
ipcMain.handle('app:setZoomLevel', (_, level) => {
|
||||
mainWindow.webContents.setZoomLevel(level);
|
||||
setConfig('zoomLevel', level);
|
||||
});
|
||||
|
||||
ipcMain.handle('app:getColorScheme', () => getColorScheme());
|
||||
|
||||
ipcMain.handle('app:setColorScheme', (_, colorScheme: MantineColorScheme) =>
|
||||
ipcMain.handle('app:setColorScheme', (_, colorScheme) =>
|
||||
setColorScheme(colorScheme)
|
||||
);
|
||||
|
||||
ipcMain.handle('app:openExternal', async (_, url: string) => openUrl(url));
|
||||
ipcMain.handle('app:openExternal', async (_, url) => openUrl(url));
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', async () => {
|
||||
const savedZoomLevel = await getConfig('zoomLevel');
|
||||
|
|
@ -230,9 +228,7 @@ export function setupIPCHandlers() {
|
|||
|
||||
ipcMain.handle('app:openPerformanceManager', () => openPerformanceManager());
|
||||
|
||||
ipcMain.on('logs:logError', (_, message: string, error?: Error) =>
|
||||
logError(message, error)
|
||||
);
|
||||
ipcMain.on('logs:logError', (_, message, error?) => logError(message, error));
|
||||
|
||||
ipcMain.handle('dependencies:isNpxAvailable', () => isNpxAvailable());
|
||||
|
||||
|
|
@ -257,30 +253,21 @@ export function setupIPCHandlers() {
|
|||
return aurPackageVersion !== null;
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
'notepad:saveTabContent',
|
||||
(_, title: string, content: string) => saveTabContent(title, content)
|
||||
ipcMain.handle('notepad:saveTabContent', (_, title, content) =>
|
||||
saveTabContent(title, content)
|
||||
);
|
||||
|
||||
ipcMain.handle('notepad:loadTabContent', (_, title: string) =>
|
||||
loadTabContent(title)
|
||||
);
|
||||
ipcMain.handle('notepad:loadTabContent', (_, title) => loadTabContent(title));
|
||||
|
||||
ipcMain.handle('notepad:renameTab', (_, oldTitle: string, newTitle: string) =>
|
||||
ipcMain.handle('notepad:renameTab', (_, oldTitle, newTitle) =>
|
||||
renameTab(oldTitle, newTitle)
|
||||
);
|
||||
|
||||
ipcMain.handle('notepad:saveState', (_, state: NotepadState) =>
|
||||
saveNotepadState(state)
|
||||
);
|
||||
ipcMain.handle('notepad:saveState', (_, state) => saveNotepadState(state));
|
||||
|
||||
ipcMain.handle('notepad:loadState', () => loadNotepadState());
|
||||
|
||||
ipcMain.handle('notepad:deleteTab', (_, title: string) =>
|
||||
deleteTabFile(title)
|
||||
);
|
||||
ipcMain.handle('notepad:deleteTab', (_, title) => deleteTabFile(title));
|
||||
|
||||
ipcMain.handle('notepad:createNewTab', (_, title?: string) =>
|
||||
createNewTab(title)
|
||||
);
|
||||
ipcMain.handle('notepad:createNewTab', (_, title) => createNewTab(title));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import type { FrontendPreference } from '@/types';
|
|||
import type { MantineColorScheme } from '@mantine/core';
|
||||
import type { SavedNotepadState } from '@/types/electron';
|
||||
|
||||
interface WindowBounds {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
export interface WindowBounds {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
isMaximized: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { getMainWindow, sendToRenderer } from '../window';
|
|||
import { pathExists } from '@/utils/node/fs';
|
||||
import { stripAssetExtensions } from '@/utils/version';
|
||||
import { getLauncherPath } from '@/utils/node/path';
|
||||
import type { GitHubAsset } from '@/types/electron';
|
||||
import type { DownloadReleaseOptions, GitHubAsset } from '@/types/electron';
|
||||
|
||||
async function removeDirectoryWithRetry(
|
||||
dirPath: string,
|
||||
|
|
@ -132,6 +132,7 @@ async function setupLauncher(
|
|||
|
||||
async function unpackKoboldCpp(packedPath: string, unpackDir: string) {
|
||||
try {
|
||||
await mkdir(unpackDir, { recursive: true });
|
||||
await execa(packedPath, ['--unpack', unpackDir], {
|
||||
timeout: 60000,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
|
|
@ -148,7 +149,10 @@ async function unpackKoboldCpp(packedPath: string, unpackDir: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function downloadRelease(asset: GitHubAsset) {
|
||||
export async function downloadRelease(
|
||||
asset: GitHubAsset,
|
||||
options: DownloadReleaseOptions
|
||||
) {
|
||||
const tempPackedFilePath = join(getInstallDir(), `${asset.name}.packed`);
|
||||
const baseFilename = stripAssetExtensions(asset.name);
|
||||
const folderName = asset.version
|
||||
|
|
@ -156,11 +160,6 @@ 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 {
|
||||
if (await pathExists(unpackedDirPath)) {
|
||||
await removeDirectoryWithRetry(unpackedDirPath);
|
||||
|
|
@ -168,26 +167,28 @@ export async function downloadRelease(asset: GitHubAsset) {
|
|||
|
||||
await downloadFile(asset, tempPackedFilePath);
|
||||
|
||||
await mkdir(unpackedDirPath, { recursive: true });
|
||||
await unpackKoboldCpp(tempPackedFilePath, unpackedDirPath);
|
||||
|
||||
const launcherPath = await setupLauncher(
|
||||
tempPackedFilePath,
|
||||
unpackedDirPath
|
||||
);
|
||||
|
||||
const currentBinary = getCurrentKoboldBinary();
|
||||
if (!currentBinary || (asset.isUpdate && asset.wasCurrentBinary)) {
|
||||
await setCurrentKoboldBinary(launcherPath);
|
||||
}
|
||||
|
||||
if (currentBinaryPath && asset.isUpdate && asset.wasCurrentBinary) {
|
||||
const oldInstallDir = join(currentBinaryPath, '..');
|
||||
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) {
|
||||
logError('Failed to download or unpack binary:', error as Error);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@ import { BrowserWindow, app, shell, screen, Menu, clipboard } from 'electron';
|
|||
import type { BrowserWindowConstructorOptions } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { stripVTControlCharacters } from 'util';
|
||||
import { PRODUCT_NAME } from '../../constants';
|
||||
import { PRODUCT_NAME } from '@/constants';
|
||||
import type { IPCChannel, IPCChannelPayloads } from '@/types/ipc';
|
||||
import { isDevelopment } from '@/utils/node/environment';
|
||||
import { getBackgroundColor, getWindowBounds, setWindowBounds } from './config';
|
||||
import {
|
||||
getBackgroundColor,
|
||||
getWindowBounds,
|
||||
setWindowBounds,
|
||||
WindowBounds,
|
||||
} from './config';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
|
|
@ -42,35 +47,23 @@ export function createMainWindow() {
|
|||
},
|
||||
} as BrowserWindowConstructorOptions;
|
||||
|
||||
if (savedBounds) {
|
||||
const isPseudoMaximized =
|
||||
(savedBounds.width >= size.width * 0.95 ||
|
||||
savedBounds.height >= size.height * 0.95) &&
|
||||
!savedBounds.isMaximized;
|
||||
|
||||
if (isPseudoMaximized) {
|
||||
windowOptions.width = defaultWidth;
|
||||
windowOptions.height = defaultHeight;
|
||||
windowOptions.x = Math.floor((size.width - defaultWidth) / 2);
|
||||
windowOptions.y = Math.floor((size.height - defaultHeight) / 2);
|
||||
if (savedBounds?.x !== undefined && savedBounds?.y !== undefined) {
|
||||
const minVisibleSize = 100;
|
||||
if (
|
||||
savedBounds.x >= -minVisibleSize &&
|
||||
savedBounds.y >= -minVisibleSize &&
|
||||
savedBounds.x < size.width - minVisibleSize &&
|
||||
savedBounds.y < size.height - minVisibleSize
|
||||
) {
|
||||
windowOptions.x = savedBounds.x;
|
||||
windowOptions.y = savedBounds.y;
|
||||
} else {
|
||||
const minVisibleSize = 100;
|
||||
if (
|
||||
savedBounds.x >= -minVisibleSize &&
|
||||
savedBounds.y >= -minVisibleSize &&
|
||||
savedBounds.x < size.width - minVisibleSize &&
|
||||
savedBounds.y < size.height - minVisibleSize
|
||||
) {
|
||||
windowOptions.x = savedBounds.x;
|
||||
windowOptions.y = savedBounds.y;
|
||||
} else {
|
||||
windowOptions.x = Math.floor(
|
||||
(size.width - (savedBounds.width || defaultWidth)) / 2
|
||||
);
|
||||
windowOptions.y = Math.floor(
|
||||
(size.height - (savedBounds.height || defaultHeight)) / 2
|
||||
);
|
||||
}
|
||||
windowOptions.x = Math.floor(
|
||||
(size.width - (savedBounds.width || defaultWidth)) / 2
|
||||
);
|
||||
windowOptions.y = Math.floor(
|
||||
(size.height - (savedBounds.height || defaultHeight)) / 2
|
||||
);
|
||||
}
|
||||
} else {
|
||||
windowOptions.x = Math.floor((size.width - defaultWidth) / 2);
|
||||
|
|
@ -83,103 +76,38 @@ export function createMainWindow() {
|
|||
mainWindow.maximize();
|
||||
}
|
||||
|
||||
let restoreBounds: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} | null = savedBounds
|
||||
? {
|
||||
x: savedBounds.x || 0,
|
||||
y: savedBounds.y || 0,
|
||||
width: savedBounds.width,
|
||||
height: savedBounds.height,
|
||||
}
|
||||
: null;
|
||||
|
||||
const saveBounds = () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const isMaximized = mainWindow.isMaximized();
|
||||
const currentBounds = mainWindow.getBounds();
|
||||
let bounds: WindowBounds = { isMaximized };
|
||||
|
||||
if (isMaximized) {
|
||||
if (restoreBounds) {
|
||||
setWindowBounds({
|
||||
x: restoreBounds.x,
|
||||
y: restoreBounds.y,
|
||||
width: restoreBounds.width,
|
||||
height: restoreBounds.height,
|
||||
isMaximized: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
restoreBounds = currentBounds;
|
||||
setWindowBounds({
|
||||
if (!isMaximized) {
|
||||
bounds = {
|
||||
x: currentBounds.x,
|
||||
y: currentBounds.y,
|
||||
width: currentBounds.width,
|
||||
height: currentBounds.height,
|
||||
isMaximized: false,
|
||||
});
|
||||
isMaximized,
|
||||
};
|
||||
}
|
||||
|
||||
setWindowBounds(bounds);
|
||||
}
|
||||
};
|
||||
|
||||
let lastSavedBounds: string | null = null;
|
||||
let saveTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const debouncedSave = () => {
|
||||
if (saveTimeout) {
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
|
||||
saveTimeout = setTimeout(() => {
|
||||
saveBounds();
|
||||
saveTimeout = null;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const checkBounds = () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const currentBounds = mainWindow.getBounds();
|
||||
const isMaximized = mainWindow.isMaximized();
|
||||
const boundsString = JSON.stringify({ ...currentBounds, isMaximized });
|
||||
|
||||
if (boundsString !== lastSavedBounds) {
|
||||
lastSavedBounds = boundsString;
|
||||
debouncedSave();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const boundsInterval = setInterval(checkBounds, 500);
|
||||
|
||||
mainWindow.on('moved', () => {
|
||||
debouncedSave();
|
||||
});
|
||||
mainWindow.on('resized', () => {
|
||||
debouncedSave();
|
||||
});
|
||||
mainWindow.on('maximize', () => {
|
||||
saveBounds();
|
||||
sendToRenderer('window-maximized');
|
||||
});
|
||||
mainWindow.on('unmaximize', () => {
|
||||
saveBounds();
|
||||
sendToRenderer('window-unmaximized');
|
||||
});
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
if (saveTimeout) {
|
||||
clearTimeout(saveTimeout);
|
||||
}
|
||||
clearInterval(boundsInterval);
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow?.show();
|
||||
});
|
||||
mainWindow.once('ready-to-show', () => mainWindow?.show());
|
||||
|
||||
if (isDevelopment) {
|
||||
mainWindow.loadURL('http://localhost:5173');
|
||||
|
|
@ -218,6 +146,7 @@ export function createMainWindow() {
|
|||
}));
|
||||
|
||||
mainWindow.on('close', () => {
|
||||
saveBounds();
|
||||
app.quit();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ const koboldAPI: KoboldAPI = {
|
|||
getCurrentInstallDir: () => ipcRenderer.invoke('kobold:getCurrentInstallDir'),
|
||||
selectInstallDirectory: () =>
|
||||
ipcRenderer.invoke('kobold:selectInstallDirectory'),
|
||||
downloadRelease: (asset) =>
|
||||
ipcRenderer.invoke('kobold:downloadRelease', asset),
|
||||
downloadRelease: (asset, options) =>
|
||||
ipcRenderer.invoke('kobold:downloadRelease', asset, options),
|
||||
launchKoboldCpp: (args) => ipcRenderer.invoke('kobold:launchKoboldCpp', args),
|
||||
getConfigFiles: () => ipcRenderer.invoke('kobold:getConfigFiles'),
|
||||
saveConfigFile: (configName, configData) =>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface HandleDownloadParams {
|
|||
item: DownloadItem;
|
||||
isUpdate?: boolean;
|
||||
wasCurrentBinary?: boolean;
|
||||
oldVersionPath?: string;
|
||||
}
|
||||
|
||||
const transformReleaseToDownloadItems = (
|
||||
|
|
@ -96,7 +97,12 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
|||
},
|
||||
|
||||
handleDownload: async (params: HandleDownloadParams) => {
|
||||
const { item, isUpdate = false, wasCurrentBinary = false } = params;
|
||||
const {
|
||||
item,
|
||||
isUpdate = false,
|
||||
wasCurrentBinary = false,
|
||||
oldVersionPath,
|
||||
} = params;
|
||||
const { downloading } = get();
|
||||
|
||||
if (downloading) {
|
||||
|
|
@ -116,11 +122,13 @@ export const useKoboldVersionsStore = create<KoboldVersionsState>(
|
|||
browser_download_url: item.url,
|
||||
size: item.size,
|
||||
version: item.version,
|
||||
isUpdate,
|
||||
wasCurrentBinary,
|
||||
};
|
||||
|
||||
await window.electronAPI.kobold.downloadRelease(asset);
|
||||
await window.electronAPI.kobold.downloadRelease(asset, {
|
||||
isUpdate,
|
||||
wasCurrentBinary,
|
||||
oldVersionPath,
|
||||
});
|
||||
|
||||
progressCleanup();
|
||||
set({ downloading: null, downloadProgress: {} });
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@
|
|||
border-right: 1px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
[data-mantine-color-scheme='light'] .mantine-Select-option:hover {
|
||||
background-color: #e9ecef !important;
|
||||
}
|
||||
|
||||
/* Custom scrollbars */
|
||||
::-webkit-scrollbar {
|
||||
width: 0.5rem;
|
||||
|
|
|
|||
9
src/types/electron.d.ts
vendored
9
src/types/electron.d.ts
vendored
|
|
@ -17,8 +17,12 @@ export interface GitHubAsset {
|
|||
browser_download_url: string;
|
||||
size: number;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface DownloadReleaseOptions {
|
||||
isUpdate?: boolean;
|
||||
wasCurrentBinary?: boolean;
|
||||
oldVersionPath?: string;
|
||||
}
|
||||
|
||||
export interface GitHubRelease {
|
||||
|
|
@ -113,7 +117,10 @@ export interface KoboldAPI {
|
|||
getAvailableBackends: (includeDisabled?: boolean) => Promise<BackendOption[]>;
|
||||
getCurrentInstallDir: () => Promise<string>;
|
||||
selectInstallDirectory: () => Promise<string | null>;
|
||||
downloadRelease: (asset: GitHubAsset) => Promise<void>;
|
||||
downloadRelease: (
|
||||
asset: GitHubAsset,
|
||||
options: DownloadReleaseOptions
|
||||
) => Promise<void>;
|
||||
launchKoboldCpp: (
|
||||
args?: string[]
|
||||
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
||||
|
|
|
|||
|
|
@ -989,7 +989,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mantine/core@npm:^8.3.1":
|
||||
"@mantine/core@npm:8.3.1":
|
||||
version: 8.3.1
|
||||
resolution: "@mantine/core@npm:8.3.1"
|
||||
dependencies:
|
||||
|
|
@ -1007,7 +1007,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mantine/hooks@npm:^8.3.1":
|
||||
"@mantine/hooks@npm:8.3.1":
|
||||
version: 8.3.1
|
||||
resolution: "@mantine/hooks@npm:8.3.1"
|
||||
peerDependencies:
|
||||
|
|
@ -3863,8 +3863,8 @@ __metadata:
|
|||
"@codemirror/view": "npm:^6.38.3"
|
||||
"@eslint/js": "npm:^9.36.0"
|
||||
"@fontsource/inter": "npm:^5.2.8"
|
||||
"@mantine/core": "npm:^8.3.1"
|
||||
"@mantine/hooks": "npm:^8.3.1"
|
||||
"@mantine/core": "npm:8.3.1"
|
||||
"@mantine/hooks": "npm:8.3.1"
|
||||
"@types/node": "npm:^24.5.2"
|
||||
"@types/react": "npm:^19.1.13"
|
||||
"@types/react-dom": "npm:^19.1.9"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue