mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
use autoGpuLayers by default and persist its value to config, show un update app icon in appheader, more code cleanup
This commit is contained in:
parent
c00b97a3f6
commit
aa554a1b58
16 changed files with 421 additions and 379 deletions
|
|
@ -13,7 +13,7 @@ A desktop app for running Large Language Models locally. <!-- markdownlint-disab
|
||||||
- **Smart process management** - Prevents runaway background processes and system resource waste
|
- **Smart process management** - Prevents runaway background processes and system resource waste
|
||||||
- **Optimized performance** - Automatically unpacks binaries for faster operation and reduced memory usage
|
- **Optimized performance** - Automatically unpacks binaries for faster operation and reduced memory usage
|
||||||
- **Image generation support** - Built-in presets for Flux and Chroma image generation workflows
|
- **Image generation support** - Built-in presets for Flux and Chroma image generation workflows
|
||||||
- **Adaptive theming** - Light, dark, and system theme modes that automatically follow your OS preferences
|
- **SillyTavern integration** - Seamlessly launch SillyTavern for advanced character interactions (requires [Node.js](https://nodejs.org/))
|
||||||
- **Privacy-focused** - Everything runs locally on your machine, no data sent to external servers
|
- **Privacy-focused** - Everything runs locally on your machine, no data sent to external servers
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ import {
|
||||||
Image,
|
Image,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { Settings, ArrowLeft } from 'lucide-react';
|
import { Settings, ArrowLeft, CircleFadingArrowUp } from 'lucide-react';
|
||||||
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
import { soundAssets, playSound, initializeAudio } from '@/utils/sounds';
|
||||||
|
import { useAppUpdateChecker } from '@/hooks/useAppUpdateChecker';
|
||||||
import iconUrl from '/icon.png';
|
import iconUrl from '/icon.png';
|
||||||
import { FRONTENDS } from '@/constants';
|
import { FRONTENDS } from '@/constants';
|
||||||
import type { InterfaceTab, FrontendPreference, Screen } from '@/types';
|
import type { InterfaceTab, FrontendPreference, Screen } from '@/types';
|
||||||
|
|
@ -38,6 +39,7 @@ export const AppHeader = ({
|
||||||
const [logoClickCount, setLogoClickCount] = useState(0);
|
const [logoClickCount, setLogoClickCount] = useState(0);
|
||||||
const [isElephantMode, setIsElephantMode] = useState(false);
|
const [isElephantMode, setIsElephantMode] = useState(false);
|
||||||
const [isMouseSqueaking, setIsMouseSqueaking] = useState(false);
|
const [isMouseSqueaking, setIsMouseSqueaking] = useState(false);
|
||||||
|
const { hasUpdate, openReleasePage } = useAppUpdateChecker();
|
||||||
|
|
||||||
const handleLogoClick = async () => {
|
const handleLogoClick = async () => {
|
||||||
await initializeAudio();
|
await initializeAudio();
|
||||||
|
|
@ -148,8 +150,25 @@ export const AppHeader = ({
|
||||||
minWidth: '6.25rem',
|
minWidth: '6.25rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
|
gap: '0.5rem',
|
||||||
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{hasUpdate && (
|
||||||
|
<Tooltip label="New release available" position="bottom">
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
color="orange"
|
||||||
|
size="xl"
|
||||||
|
onClick={openReleasePage}
|
||||||
|
aria-label="New release available"
|
||||||
|
>
|
||||||
|
<CircleFadingArrowUp
|
||||||
|
style={{ width: rem(20), height: rem(20) }}
|
||||||
|
/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<Tooltip label="Settings" position="bottom">
|
<Tooltip label="Settings" position="bottom">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ export const LaunchScreen = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildConfigData = () => ({
|
const buildConfigData = () => ({
|
||||||
|
autoGpuLayers: autoGpuLayers,
|
||||||
gpulayers: gpuLayers,
|
gpulayers: gpuLayers,
|
||||||
contextsize: contextSize,
|
contextsize: contextSize,
|
||||||
model: modelPath,
|
model: modelPath,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export const VersionsTab = () => {
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
loadRemoteVersions,
|
loadRemoteVersions,
|
||||||
handleDownload: sharedHandleDownload,
|
handleDownload: sharedHandleDownload,
|
||||||
|
getLatestReleaseWithDownloadStatus,
|
||||||
} = useKoboldVersions();
|
} = useKoboldVersions();
|
||||||
|
|
||||||
const [installedVersions, setInstalledVersions] = useState<
|
const [installedVersions, setInstalledVersions] = useState<
|
||||||
|
|
@ -104,8 +105,7 @@ export const VersionsTab = () => {
|
||||||
|
|
||||||
const loadLatestRelease = useCallback(async () => {
|
const loadLatestRelease = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const release =
|
const release = await getLatestReleaseWithDownloadStatus();
|
||||||
await window.electronAPI.kobold.getLatestReleaseWithStatus();
|
|
||||||
setLatestRelease(release);
|
setLatestRelease(release);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.electronAPI.logs.logError(
|
window.electronAPI.logs.logError(
|
||||||
|
|
@ -113,7 +113,7 @@ export const VersionsTab = () => {
|
||||||
error as Error
|
error as Error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [getLatestReleaseWithDownloadStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadInstalledVersions();
|
loadInstalledVersions();
|
||||||
|
|
|
||||||
80
src/hooks/useAppUpdateChecker.ts
Normal file
80
src/hooks/useAppUpdateChecker.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { compareVersions } from '@/utils/downloadUtils';
|
||||||
|
import { GITHUB_API } from '@/constants';
|
||||||
|
|
||||||
|
interface AppUpdateInfo {
|
||||||
|
currentVersion: string;
|
||||||
|
latestVersion: string;
|
||||||
|
releaseUrl: string;
|
||||||
|
hasUpdate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAppUpdateChecker = () => {
|
||||||
|
const [updateInfo, setUpdateInfo] = useState<AppUpdateInfo | null>(null);
|
||||||
|
const [isChecking, setIsChecking] = useState(false);
|
||||||
|
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
||||||
|
|
||||||
|
const checkForAppUpdates = useCallback(async () => {
|
||||||
|
setIsChecking(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentVersion = await window.electronAPI.app.getVersion();
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`${GITHUB_API.BASE_URL}/repos/${GITHUB_API.FRIENDLY_KOBOLD_REPO}/releases/latest`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch latest release');
|
||||||
|
}
|
||||||
|
|
||||||
|
const release = await response.json();
|
||||||
|
const latestVersion = release.tag_name?.replace(/^v/, '') || '';
|
||||||
|
|
||||||
|
if (!latestVersion) {
|
||||||
|
throw new Error('Invalid release data');
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
|
||||||
|
|
||||||
|
const updateInfo: AppUpdateInfo = {
|
||||||
|
currentVersion,
|
||||||
|
latestVersion,
|
||||||
|
releaseUrl: release.html_url,
|
||||||
|
hasUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
setUpdateInfo(updateInfo);
|
||||||
|
setLastChecked(new Date());
|
||||||
|
|
||||||
|
return updateInfo;
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError(
|
||||||
|
'Failed to check for app updates:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setIsChecking(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const openReleasePage = useCallback(() => {
|
||||||
|
if (updateInfo?.releaseUrl) {
|
||||||
|
window.electronAPI.app.openExternal(updateInfo.releaseUrl);
|
||||||
|
}
|
||||||
|
}, [updateInfo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkForAppUpdates();
|
||||||
|
}, [checkForAppUpdates]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateInfo,
|
||||||
|
isChecking,
|
||||||
|
lastChecked,
|
||||||
|
checkForAppUpdates,
|
||||||
|
openReleasePage,
|
||||||
|
hasUpdate: updateInfo?.hasUpdate || false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import { GITHUB_API, ROCM } from '@/constants';
|
||||||
|
import { filterAssetsByPlatform } from '@/utils/platform';
|
||||||
|
import type {
|
||||||
|
DownloadItem,
|
||||||
|
GitHubRelease,
|
||||||
|
ReleaseWithStatus,
|
||||||
|
GitHubAsset,
|
||||||
|
InstalledVersion,
|
||||||
|
} from '@/types/electron';
|
||||||
|
|
||||||
interface PlatformInfo {
|
interface PlatformInfo {
|
||||||
platform: string;
|
platform: string;
|
||||||
|
|
@ -7,6 +15,164 @@ interface PlatformInfo {
|
||||||
hasROCm: boolean;
|
hasROCm: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CachedReleaseData {
|
||||||
|
releases: DownloadItem[];
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CACHE_KEY = 'kobold-releases-cache';
|
||||||
|
const CACHE_DURATION = 60000;
|
||||||
|
|
||||||
|
const loadFromCache = (): CachedReleaseData | null => {
|
||||||
|
try {
|
||||||
|
const cached = localStorage.getItem(CACHE_KEY);
|
||||||
|
if (!cached) return null;
|
||||||
|
|
||||||
|
const data: CachedReleaseData = JSON.parse(cached);
|
||||||
|
const isExpired = Date.now() - data.timestamp > CACHE_DURATION;
|
||||||
|
|
||||||
|
return isExpired ? null : data;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveToCache = (releases: DownloadItem[]) => {
|
||||||
|
try {
|
||||||
|
const data: CachedReleaseData = {
|
||||||
|
releases,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError(
|
||||||
|
'Failed to save releases to cache',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const transformReleaseToDownloadItems = (
|
||||||
|
release: GitHubRelease,
|
||||||
|
platform: string
|
||||||
|
): DownloadItem[] => {
|
||||||
|
const version = release.tag_name?.replace(/^v/, '') || 'unknown';
|
||||||
|
const platformAssets = filterAssetsByPlatform(release.assets, platform);
|
||||||
|
|
||||||
|
return platformAssets.map((asset) => ({
|
||||||
|
name: asset.name,
|
||||||
|
url: asset.browser_download_url,
|
||||||
|
size: asset.size,
|
||||||
|
version,
|
||||||
|
type: 'asset' as const,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchLatestReleaseFromAPI = async (
|
||||||
|
platform: string
|
||||||
|
): Promise<DownloadItem[]> => {
|
||||||
|
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 403) {
|
||||||
|
throw new Error('GitHub API rate limit reached');
|
||||||
|
}
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const release: GitHubRelease = await response.json();
|
||||||
|
return transformReleaseToDownloadItems(release, platform);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getROCmDownload = async (
|
||||||
|
platform: string
|
||||||
|
): Promise<DownloadItem | null> => {
|
||||||
|
if (platform !== 'linux') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestRelease = await response.json();
|
||||||
|
const version = latestRelease?.tag_name?.replace(/^v/, '') || 'unknown';
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: ROCM.BINARY_NAME,
|
||||||
|
url: ROCM.DOWNLOAD_URL,
|
||||||
|
size: ROCM.SIZE_BYTES_APPROX,
|
||||||
|
version,
|
||||||
|
type: 'rocm',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError(
|
||||||
|
'Failed to fetch ROCm version info:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
name: ROCM.BINARY_NAME,
|
||||||
|
url: ROCM.DOWNLOAD_URL,
|
||||||
|
size: ROCM.SIZE_BYTES_APPROX,
|
||||||
|
version: 'unknown',
|
||||||
|
type: 'rocm',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLatestReleaseWithDownloadStatus =
|
||||||
|
async (): Promise<ReleaseWithStatus | null> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
||||||
|
if (!response.ok) return null;
|
||||||
|
|
||||||
|
const latestRelease = await response.json();
|
||||||
|
if (!latestRelease) return null;
|
||||||
|
|
||||||
|
const installedVersions =
|
||||||
|
await window.electronAPI.kobold.getInstalledVersions();
|
||||||
|
|
||||||
|
const availableAssets = latestRelease.assets.map((asset: GitHubAsset) => {
|
||||||
|
const installedVersion = installedVersions.find(
|
||||||
|
(v: InstalledVersion) => {
|
||||||
|
const pathParts = v.path.split(/[/\\]/);
|
||||||
|
const launcherIndex = pathParts.findIndex(
|
||||||
|
(part) =>
|
||||||
|
part === 'koboldcpp-launcher' ||
|
||||||
|
part === 'koboldcpp-launcher.exe'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (launcherIndex > 0) {
|
||||||
|
const directoryName = pathParts[launcherIndex - 1];
|
||||||
|
return directoryName === asset.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
asset,
|
||||||
|
isDownloaded: !!installedVersion,
|
||||||
|
installedVersion: installedVersion?.version,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
release: latestRelease,
|
||||||
|
availableAssets,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
window.electronAPI.logs.logError(
|
||||||
|
'Failed to fetch latest release with status:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
interface UseKoboldVersionsReturn {
|
interface UseKoboldVersionsReturn {
|
||||||
platformInfo: PlatformInfo;
|
platformInfo: PlatformInfo;
|
||||||
availableDownloads: DownloadItem[];
|
availableDownloads: DownloadItem[];
|
||||||
|
|
@ -15,6 +181,7 @@ interface UseKoboldVersionsReturn {
|
||||||
downloading: string | null;
|
downloading: string | null;
|
||||||
downloadProgress: Record<string, number>;
|
downloadProgress: Record<string, number>;
|
||||||
loadRemoteVersions: () => Promise<void>;
|
loadRemoteVersions: () => Promise<void>;
|
||||||
|
refresh: () => Promise<void>;
|
||||||
handleDownload: (
|
handleDownload: (
|
||||||
type: 'asset' | 'rocm',
|
type: 'asset' | 'rocm',
|
||||||
item?: DownloadItem,
|
item?: DownloadItem,
|
||||||
|
|
@ -27,6 +194,8 @@ interface UseKoboldVersionsReturn {
|
||||||
| Record<string, number>
|
| Record<string, number>
|
||||||
| ((prev: Record<string, number>) => Record<string, number>)
|
| ((prev: Record<string, number>) => Record<string, number>)
|
||||||
) => void;
|
) => void;
|
||||||
|
getROCmDownload: (platform?: string) => Promise<DownloadItem | null>;
|
||||||
|
getLatestReleaseWithDownloadStatus: () => Promise<ReleaseWithStatus | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
|
|
@ -93,11 +262,25 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
setLoadingRemote(true);
|
setLoadingRemote(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const cached = loadFromCache();
|
||||||
|
if (cached) {
|
||||||
|
const rocm = await getROCmDownload(platformInfo.platform);
|
||||||
|
const allDownloads: DownloadItem[] = [...cached.releases];
|
||||||
|
if (rocm) {
|
||||||
|
allDownloads.push(rocm);
|
||||||
|
}
|
||||||
|
setAvailableDownloads(allDownloads);
|
||||||
|
setLoadingRemote(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const [releases, rocm] = await Promise.all([
|
const [releases, rocm] = await Promise.all([
|
||||||
window.electronAPI.kobold.getLatestRelease(),
|
fetchLatestReleaseFromAPI(platformInfo.platform),
|
||||||
window.electronAPI.kobold.getROCmDownload(),
|
getROCmDownload(platformInfo.platform),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
saveToCache(releases);
|
||||||
|
|
||||||
const allDownloads: DownloadItem[] = [...releases];
|
const allDownloads: DownloadItem[] = [...releases];
|
||||||
if (rocm) {
|
if (rocm) {
|
||||||
allDownloads.push(rocm);
|
allDownloads.push(rocm);
|
||||||
|
|
@ -109,6 +292,18 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
'Failed to load remote versions:',
|
'Failed to load remote versions:',
|
||||||
error as Error
|
error as Error
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cached = loadFromCache();
|
||||||
|
if (cached) {
|
||||||
|
const rocm = await getROCmDownload(platformInfo.platform).catch(
|
||||||
|
() => null
|
||||||
|
);
|
||||||
|
const allDownloads: DownloadItem[] = [...cached.releases];
|
||||||
|
if (rocm) {
|
||||||
|
allDownloads.push(rocm);
|
||||||
|
}
|
||||||
|
setAvailableDownloads(allDownloads);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingRemote(false);
|
setLoadingRemote(false);
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +355,11 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const refresh = useCallback(async () => {
|
||||||
|
localStorage.removeItem(CACHE_KEY);
|
||||||
|
await loadRemoteVersions();
|
||||||
|
}, [loadRemoteVersions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPlatformInfo();
|
loadPlatformInfo();
|
||||||
}, [loadPlatformInfo]);
|
}, [loadPlatformInfo]);
|
||||||
|
|
@ -195,8 +395,12 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
downloading,
|
downloading,
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
loadRemoteVersions,
|
loadRemoteVersions,
|
||||||
|
refresh,
|
||||||
handleDownload,
|
handleDownload,
|
||||||
setDownloading,
|
setDownloading,
|
||||||
setDownloadProgress,
|
setDownloadProgress,
|
||||||
|
getROCmDownload: (platform?: string) =>
|
||||||
|
getROCmDownload(platform || platformInfo.platform),
|
||||||
|
getLatestReleaseWithDownloadStatus,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { getDisplayNameFromPath } from '@/utils/versionUtils';
|
import { getDisplayNameFromPath } from '@/utils/versionUtils';
|
||||||
import { compareVersions } from '@/utils/downloadUtils';
|
import { compareVersions } from '@/utils/downloadUtils';
|
||||||
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
||||||
|
|
||||||
interface UpdateInfo {
|
interface UpdateInfo {
|
||||||
|
|
@ -17,6 +18,8 @@ export const useUpdateChecker = () => {
|
||||||
);
|
);
|
||||||
const [dismissedUpdatesLoaded, setDismissedUpdatesLoaded] = useState(false);
|
const [dismissedUpdatesLoaded, setDismissedUpdatesLoaded] = useState(false);
|
||||||
|
|
||||||
|
const { availableDownloads: releases, getROCmDownload } = useKoboldVersions();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDismissedUpdates = async () => {
|
const loadDismissedUpdates = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -54,28 +57,26 @@ export const useUpdateChecker = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const checkForUpdates = useCallback(async () => {
|
const checkForUpdates = useCallback(async () => {
|
||||||
if (!dismissedUpdatesLoaded) {
|
if (!dismissedUpdatesLoaded || releases.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsChecking(true);
|
setIsChecking(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [currentBinaryPath, installedVersions, releases, rocm] =
|
const [currentBinaryPath, installedVersionsResult, rocmDownload] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.electronAPI.config.get(
|
window.electronAPI.config.get(
|
||||||
'currentKoboldBinary'
|
'currentKoboldBinary'
|
||||||
) as Promise<string>,
|
) as Promise<string>,
|
||||||
window.electronAPI.kobold.getInstalledVersions(),
|
window.electronAPI.kobold.getInstalledVersions(),
|
||||||
window.electronAPI.kobold.getLatestRelease(),
|
getROCmDownload(),
|
||||||
window.electronAPI.kobold.getROCmDownload(),
|
|
||||||
]);
|
]);
|
||||||
|
if (!currentBinaryPath || installedVersionsResult.length === 0) {
|
||||||
if (!currentBinaryPath || installedVersions.length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentVersion = installedVersions.find(
|
const currentVersion = installedVersionsResult.find(
|
||||||
(v: InstalledVersion) => v.path === currentBinaryPath
|
(v: InstalledVersion) => v.path === currentBinaryPath
|
||||||
);
|
);
|
||||||
if (!currentVersion) {
|
if (!currentVersion) {
|
||||||
|
|
@ -83,8 +84,8 @@ export const useUpdateChecker = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableDownloads: DownloadItem[] = [...releases];
|
const availableDownloads: DownloadItem[] = [...releases];
|
||||||
if (rocm) {
|
if (rocmDownload) {
|
||||||
availableDownloads.push(rocm);
|
availableDownloads.push(rocmDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDisplayName = getDisplayNameFromPath(currentVersion);
|
const currentDisplayName = getDisplayNameFromPath(currentVersion);
|
||||||
|
|
@ -122,7 +123,7 @@ export const useUpdateChecker = () => {
|
||||||
} finally {
|
} finally {
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
}
|
}
|
||||||
}, [dismissedUpdates, dismissedUpdatesLoaded]);
|
}, [dismissedUpdates, dismissedUpdatesLoaded, releases, getROCmDownload]);
|
||||||
|
|
||||||
const dismissUpdate = useCallback(async () => {
|
const dismissUpdate = useCallback(async () => {
|
||||||
if (updateInfo) {
|
if (updateInfo) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
import { LogManager } from '@/main/managers/LogManager';
|
||||||
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||||
import { SillyTavernManager } from '@/main/managers/SillyTavernManager';
|
import { SillyTavernManager } from '@/main/managers/SillyTavernManager';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
|
||||||
import { HardwareService } from '@/main/services/HardwareService';
|
import { HardwareService } from '@/main/services/HardwareService';
|
||||||
import { BinaryService } from '@/main/services/BinaryService';
|
import { BinaryService } from '@/main/services/BinaryService';
|
||||||
import { IPCHandlers } from '@/main/ipc';
|
import { IPCHandlers } from '@/main/ipc';
|
||||||
|
|
@ -16,11 +15,10 @@ import { homedir } from 'os';
|
||||||
|
|
||||||
export class FriendlyKoboldApp {
|
export class FriendlyKoboldApp {
|
||||||
private windowManager: WindowManager;
|
private windowManager: WindowManager;
|
||||||
|
private koboldManager: KoboldCppManager;
|
||||||
private configManager: ConfigManager;
|
private configManager: ConfigManager;
|
||||||
private logManager: LogManager;
|
private logManager: LogManager;
|
||||||
private koboldManager: KoboldCppManager;
|
|
||||||
private sillyTavernManager: SillyTavernManager;
|
private sillyTavernManager: SillyTavernManager;
|
||||||
private githubService: GitHubService;
|
|
||||||
private hardwareService: HardwareService;
|
private hardwareService: HardwareService;
|
||||||
private binaryService: BinaryService;
|
private binaryService: BinaryService;
|
||||||
private ipcHandlers: IPCHandlers;
|
private ipcHandlers: IPCHandlers;
|
||||||
|
|
@ -35,12 +33,10 @@ export class FriendlyKoboldApp {
|
||||||
);
|
);
|
||||||
this.ensureInstallDirectory();
|
this.ensureInstallDirectory();
|
||||||
this.windowManager = new WindowManager();
|
this.windowManager = new WindowManager();
|
||||||
this.githubService = new GitHubService(this.logManager);
|
|
||||||
this.hardwareService = new HardwareService(this.logManager);
|
this.hardwareService = new HardwareService(this.logManager);
|
||||||
|
|
||||||
this.koboldManager = new KoboldCppManager(
|
this.koboldManager = new KoboldCppManager(
|
||||||
this.configManager,
|
this.configManager,
|
||||||
this.githubService,
|
|
||||||
this.windowManager,
|
this.windowManager,
|
||||||
this.logManager
|
this.logManager
|
||||||
);
|
);
|
||||||
|
|
@ -59,7 +55,6 @@ export class FriendlyKoboldApp {
|
||||||
this.ipcHandlers = new IPCHandlers(
|
this.ipcHandlers = new IPCHandlers(
|
||||||
this.koboldManager,
|
this.koboldManager,
|
||||||
this.configManager,
|
this.configManager,
|
||||||
this.githubService,
|
|
||||||
this.hardwareService,
|
this.hardwareService,
|
||||||
this.binaryService,
|
this.binaryService,
|
||||||
this.logManager,
|
this.logManager,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { ipcMain, shell, app } from 'electron';
|
import { ipcMain, shell, app } from 'electron';
|
||||||
import { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
import type { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import type { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
import type { LogManager } from '@/main/managers/LogManager';
|
||||||
import { SillyTavernManager } from '@/main/managers/SillyTavernManager';
|
import type { SillyTavernManager } from '@/main/managers/SillyTavernManager';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
|
||||||
import { HardwareService } from '@/main/services/HardwareService';
|
import { HardwareService } from '@/main/services/HardwareService';
|
||||||
import { BinaryService } from '@/main/services/BinaryService';
|
import { BinaryService } from '@/main/services/BinaryService';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
@ -13,14 +12,12 @@ export class IPCHandlers {
|
||||||
private configManager: ConfigManager;
|
private configManager: ConfigManager;
|
||||||
private logManager: LogManager;
|
private logManager: LogManager;
|
||||||
private sillyTavernManager: SillyTavernManager;
|
private sillyTavernManager: SillyTavernManager;
|
||||||
private githubService: GitHubService;
|
|
||||||
private hardwareService: HardwareService;
|
private hardwareService: HardwareService;
|
||||||
private binaryService: BinaryService;
|
private binaryService: BinaryService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
koboldManager: KoboldCppManager,
|
koboldManager: KoboldCppManager,
|
||||||
configManager: ConfigManager,
|
configManager: ConfigManager,
|
||||||
githubService: GitHubService,
|
|
||||||
hardwareService: HardwareService,
|
hardwareService: HardwareService,
|
||||||
binaryService: BinaryService,
|
binaryService: BinaryService,
|
||||||
logManager: LogManager,
|
logManager: LogManager,
|
||||||
|
|
@ -30,7 +27,6 @@ export class IPCHandlers {
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
this.logManager = logManager;
|
this.logManager = logManager;
|
||||||
this.sillyTavernManager = sillyTavernManager;
|
this.sillyTavernManager = sillyTavernManager;
|
||||||
this.githubService = githubService;
|
|
||||||
this.hardwareService = hardwareService;
|
this.hardwareService = hardwareService;
|
||||||
this.binaryService = binaryService;
|
this.binaryService = binaryService;
|
||||||
}
|
}
|
||||||
|
|
@ -74,15 +70,6 @@ export class IPCHandlers {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHandlers() {
|
setupHandlers() {
|
||||||
ipcMain.handle('kobold:getLatestRelease', () =>
|
|
||||||
this.githubService.getLatestRelease()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:checkForUpdates', async () => {
|
|
||||||
const latest = await this.githubService.getRawLatestRelease();
|
|
||||||
return latest;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:downloadRelease', async (_event, asset) => {
|
ipcMain.handle('kobold:downloadRelease', async (_event, asset) => {
|
||||||
try {
|
try {
|
||||||
const mainWindow = this.koboldManager
|
const mainWindow = this.koboldManager
|
||||||
|
|
@ -126,14 +113,6 @@ export class IPCHandlers {
|
||||||
this.configManager.setSelectedConfig(configName)
|
this.configManager.setSelectedConfig(configName)
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:getCurrentVersion', () =>
|
|
||||||
this.koboldManager.getCurrentVersion()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:getCurrentBinaryInfo', () =>
|
|
||||||
this.koboldManager.getCurrentBinaryInfo()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:setCurrentVersion', (_event, version) =>
|
ipcMain.handle('kobold:setCurrentVersion', (_event, version) =>
|
||||||
this.koboldManager.setCurrentVersion(version)
|
this.koboldManager.setCurrentVersion(version)
|
||||||
);
|
);
|
||||||
|
|
@ -162,10 +141,6 @@ export class IPCHandlers {
|
||||||
this.hardwareService.detectROCm()
|
this.hardwareService.detectROCm()
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:detectAllCapabilities', () =>
|
|
||||||
this.hardwareService.detectAllWithCapabilities()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:detectBackendSupport', () =>
|
ipcMain.handle('kobold:detectBackendSupport', () =>
|
||||||
this.binaryService.detectBackendSupport()
|
this.binaryService.detectBackendSupport()
|
||||||
);
|
);
|
||||||
|
|
@ -181,10 +156,6 @@ export class IPCHandlers {
|
||||||
arch: process.arch,
|
arch: process.arch,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ipcMain.handle('kobold:getROCmDownload', () =>
|
|
||||||
this.koboldManager.getROCmDownload()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:downloadROCm', async () => {
|
ipcMain.handle('kobold:downloadROCm', async () => {
|
||||||
try {
|
try {
|
||||||
const mainWindow = this.koboldManager
|
const mainWindow = this.koboldManager
|
||||||
|
|
@ -201,18 +172,6 @@ export class IPCHandlers {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('kobold:getInstalledVersion', () =>
|
|
||||||
this.koboldManager.getInstalledVersion()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:getVersionFromBinary', (_event, binaryPath) =>
|
|
||||||
this.koboldManager.getVersionFromBinary(binaryPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:getLatestReleaseWithStatus', () =>
|
|
||||||
this.koboldManager.getLatestReleaseWithDownloadStatus()
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:launchKoboldCpp', (_event, args) =>
|
ipcMain.handle('kobold:launchKoboldCpp', (_event, args) =>
|
||||||
this.launchKoboldCppWithCustomFrontends(args)
|
this.launchKoboldCppWithCustomFrontends(args)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,16 @@ import { dialog } from 'electron';
|
||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
import { got } from 'got';
|
import { got } from 'got';
|
||||||
import { pipeline } from 'stream/promises';
|
import { pipeline } from 'stream/promises';
|
||||||
import { GitHubService } from '@/main/services/GitHubService';
|
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
import { LogManager } from '@/main/managers/LogManager';
|
||||||
import { WindowManager } from '@/main/managers/WindowManager';
|
import { WindowManager } from '@/main/managers/WindowManager';
|
||||||
import { ROCM, PRODUCT_NAME } from '@/constants';
|
import { ROCM, PRODUCT_NAME, GITHUB_API } from '@/constants';
|
||||||
import { stripAssetExtensions } from '@/utils/versionUtils';
|
import { stripAssetExtensions } from '@/utils/versionUtils';
|
||||||
import { compareVersions } from '@/utils/downloadUtils';
|
|
||||||
import type {
|
import type {
|
||||||
DownloadItem,
|
DownloadItem,
|
||||||
GitHubAsset,
|
GitHubAsset,
|
||||||
UpdateInfo,
|
|
||||||
ReleaseWithStatus,
|
|
||||||
InstalledVersion,
|
InstalledVersion,
|
||||||
|
KoboldConfig,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
|
||||||
export class KoboldCppManager {
|
export class KoboldCppManager {
|
||||||
|
|
@ -36,18 +33,15 @@ export class KoboldCppManager {
|
||||||
private koboldProcess: ChildProcess | null = null;
|
private koboldProcess: ChildProcess | null = null;
|
||||||
private configManager: ConfigManager;
|
private configManager: ConfigManager;
|
||||||
private logManager: LogManager;
|
private logManager: LogManager;
|
||||||
private githubService: GitHubService;
|
|
||||||
private windowManager: WindowManager;
|
private windowManager: WindowManager;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
configManager: ConfigManager,
|
configManager: ConfigManager,
|
||||||
githubService: GitHubService,
|
|
||||||
windowManager: WindowManager,
|
windowManager: WindowManager,
|
||||||
logManager: LogManager
|
logManager: LogManager
|
||||||
) {
|
) {
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
this.logManager = logManager;
|
this.logManager = logManager;
|
||||||
this.githubService = githubService;
|
|
||||||
this.windowManager = windowManager;
|
this.windowManager = windowManager;
|
||||||
this.installDir = this.configManager.getInstallDir() || '';
|
this.installDir = this.configManager.getInstallDir() || '';
|
||||||
}
|
}
|
||||||
|
|
@ -265,12 +259,7 @@ export class KoboldCppManager {
|
||||||
return configFiles.sort((a, b) => a.name.localeCompare(b.name));
|
return configFiles.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseConfigFile(filePath: string): Promise<{
|
async parseConfigFile(filePath: string): Promise<KoboldConfig | null> {
|
||||||
gpulayers?: number;
|
|
||||||
contextsize?: number;
|
|
||||||
model?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
} | null> {
|
|
||||||
try {
|
try {
|
||||||
if (!existsSync(filePath)) {
|
if (!existsSync(filePath)) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -288,33 +277,7 @@ export class KoboldCppManager {
|
||||||
|
|
||||||
async saveConfigFile(
|
async saveConfigFile(
|
||||||
configName: string,
|
configName: string,
|
||||||
configData: {
|
configData: KoboldConfig
|
||||||
gpulayers?: number;
|
|
||||||
contextsize?: number;
|
|
||||||
model?: string;
|
|
||||||
port?: number;
|
|
||||||
host?: string;
|
|
||||||
multiuser?: number;
|
|
||||||
multiplayer?: boolean;
|
|
||||||
remotetunnel?: boolean;
|
|
||||||
nocertify?: boolean;
|
|
||||||
websearch?: boolean;
|
|
||||||
noshift?: boolean;
|
|
||||||
flashattention?: boolean;
|
|
||||||
noavx2?: boolean;
|
|
||||||
failsafe?: boolean;
|
|
||||||
usemmap?: boolean;
|
|
||||||
usecuda?: boolean;
|
|
||||||
usevulkan?: boolean;
|
|
||||||
useclblast?: [number, number] | boolean;
|
|
||||||
sdmodel?: string;
|
|
||||||
sdt5xxl?: string;
|
|
||||||
sdclipl?: string;
|
|
||||||
sdclipg?: string;
|
|
||||||
sdphotomaker?: string;
|
|
||||||
sdvae?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
if (!this.installDir) {
|
if (!this.installDir) {
|
||||||
|
|
@ -549,16 +512,35 @@ export class KoboldCppManager {
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
|
|
||||||
if (platform === 'linux') {
|
if (platform === 'linux') {
|
||||||
const latestRelease = await this.githubService.getRawLatestRelease();
|
try {
|
||||||
const version = latestRelease?.tag_name?.replace(/^v/, '') || 'unknown';
|
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
const latestRelease = await response.json();
|
||||||
name: ROCM.BINARY_NAME,
|
const version = latestRelease?.tag_name?.replace(/^v/, '') || 'unknown';
|
||||||
url: ROCM.DOWNLOAD_URL,
|
|
||||||
size: ROCM.SIZE_BYTES_APPROX,
|
return {
|
||||||
version,
|
name: ROCM.BINARY_NAME,
|
||||||
type: 'rocm',
|
url: ROCM.DOWNLOAD_URL,
|
||||||
};
|
size: ROCM.SIZE_BYTES_APPROX,
|
||||||
|
version,
|
||||||
|
type: 'rocm',
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logManager.logError(
|
||||||
|
'Failed to fetch ROCm version info:',
|
||||||
|
error as Error
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
name: ROCM.BINARY_NAME,
|
||||||
|
url: ROCM.DOWNLOAD_URL,
|
||||||
|
size: ROCM.SIZE_BYTES_APPROX,
|
||||||
|
version: 'unknown',
|
||||||
|
type: 'rocm',
|
||||||
|
};
|
||||||
|
}
|
||||||
} else if (platform === 'win32') {
|
} else if (platform === 'win32') {
|
||||||
return null;
|
return null;
|
||||||
// The launcher doesn't exist in unpacked state yet.
|
// The launcher doesn't exist in unpacked state yet.
|
||||||
|
|
@ -706,73 +688,6 @@ export class KoboldCppManager {
|
||||||
return currentVersion?.version;
|
return currentVersion?.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkForUpdates(): Promise<UpdateInfo | null> {
|
|
||||||
try {
|
|
||||||
const currentVersion = await this.getCurrentVersion();
|
|
||||||
if (!currentVersion) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestRelease = await this.githubService.getRawLatestRelease();
|
|
||||||
if (!latestRelease) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestVersion = latestRelease.tag_name.replace(/^v/, '');
|
|
||||||
const current = currentVersion.version.replace(/^v/, '');
|
|
||||||
|
|
||||||
const hasUpdate = compareVersions(current, latestVersion) < 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentVersion: current,
|
|
||||||
latestVersion,
|
|
||||||
releaseInfo: latestRelease,
|
|
||||||
hasUpdate,
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLatestReleaseWithDownloadStatus(): Promise<ReleaseWithStatus | null> {
|
|
||||||
try {
|
|
||||||
const latestRelease = await this.githubService.getRawLatestRelease();
|
|
||||||
if (!latestRelease) return null;
|
|
||||||
|
|
||||||
const installedVersions = await this.getInstalledVersions();
|
|
||||||
|
|
||||||
const availableAssets = latestRelease.assets.map((asset: GitHubAsset) => {
|
|
||||||
const installedVersion = installedVersions.find((v) => {
|
|
||||||
const pathParts = v.path.split(/[/\\]/);
|
|
||||||
const launcherIndex = pathParts.findIndex(
|
|
||||||
(part) =>
|
|
||||||
part === 'koboldcpp-launcher' || part === 'koboldcpp-launcher.exe'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (launcherIndex > 0) {
|
|
||||||
const directoryName = pathParts[launcherIndex - 1];
|
|
||||||
return directoryName === asset.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
asset,
|
|
||||||
isDownloaded: !!installedVersion,
|
|
||||||
installedVersion: installedVersion?.version,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
release: latestRelease,
|
|
||||||
availableAssets,
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchKoboldCpp(
|
async launchKoboldCpp(
|
||||||
args: string[] = []
|
args: string[] = []
|
||||||
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,13 @@ export class SillyTavernManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createNpxProcess(args: string[]): ChildProcess {
|
||||||
|
return spawn('npx', args, {
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
detached: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async ensureSillyTavernSettings(): Promise<void> {
|
private async ensureSillyTavernSettings(): Promise<void> {
|
||||||
const settingsPath = this.getSillyTavernSettingsPath();
|
const settingsPath = this.getSillyTavernSettingsPath();
|
||||||
|
|
||||||
|
|
@ -132,10 +139,7 @@ export class SillyTavernManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const initProcess = spawn('npx', spawnArgs, {
|
const initProcess = this.createNpxProcess(spawnArgs);
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
|
||||||
detached: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
let hasResolved = false;
|
let hasResolved = false;
|
||||||
|
|
||||||
|
|
@ -364,10 +368,7 @@ export class SillyTavernManager {
|
||||||
config.port.toString(),
|
config.port.toString(),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.sillyTavernProcess = spawn('npx', sillyTavernArgs, {
|
this.sillyTavernProcess = this.createNpxProcess(sillyTavernArgs);
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
|
||||||
detached: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.sillyTavernProcess.stdout) {
|
if (this.sillyTavernProcess.stdout) {
|
||||||
this.sillyTavernProcess.stdout.on('data', (data: Buffer) => {
|
this.sillyTavernProcess.stdout.on('data', (data: Buffer) => {
|
||||||
|
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
import type { GitHubRelease, DownloadItem } from '@/types/electron';
|
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
|
||||||
import { GITHUB_API } from '@/constants';
|
|
||||||
import { filterAssetsByPlatform } from '@/utils/platform';
|
|
||||||
|
|
||||||
export class GitHubService {
|
|
||||||
private lastApiCall = 0;
|
|
||||||
private apiCooldown = 60000;
|
|
||||||
private cachedRelease: GitHubRelease | null = null;
|
|
||||||
private logManager: LogManager;
|
|
||||||
|
|
||||||
constructor(logManager: LogManager) {
|
|
||||||
this.logManager = logManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLatestRelease(): Promise<DownloadItem[]> {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.lastApiCall < this.apiCooldown && this.cachedRelease) {
|
|
||||||
return this.transformReleaseToDownloadItems(this.cachedRelease);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 403) {
|
|
||||||
this.logManager.logError(
|
|
||||||
'GitHub API rate limit reached, using cached data if available'
|
|
||||||
);
|
|
||||||
return this.cachedRelease
|
|
||||||
? this.transformReleaseToDownloadItems(this.cachedRelease)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastApiCall = now;
|
|
||||||
this.cachedRelease = (await response.json()) as GitHubRelease;
|
|
||||||
return this.transformReleaseToDownloadItems(this.cachedRelease);
|
|
||||||
} catch (error) {
|
|
||||||
this.logManager.logError(
|
|
||||||
'Error fetching latest release:',
|
|
||||||
error as Error
|
|
||||||
);
|
|
||||||
return this.cachedRelease
|
|
||||||
? this.transformReleaseToDownloadItems(this.cachedRelease)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private transformReleaseToDownloadItems(
|
|
||||||
release: GitHubRelease
|
|
||||||
): DownloadItem[] {
|
|
||||||
const version = release.tag_name?.replace(/^v/, '') || 'unknown';
|
|
||||||
const platformAssets = filterAssetsByPlatform(
|
|
||||||
release.assets,
|
|
||||||
process.platform
|
|
||||||
);
|
|
||||||
|
|
||||||
return platformAssets.map((asset) => ({
|
|
||||||
name: asset.name,
|
|
||||||
url: asset.browser_download_url,
|
|
||||||
size: asset.size,
|
|
||||||
version,
|
|
||||||
type: 'asset' as const,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async getRawLatestRelease(): Promise<GitHubRelease | null> {
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.lastApiCall < this.apiCooldown && this.cachedRelease) {
|
|
||||||
return this.cachedRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(GITHUB_API.LATEST_RELEASE_URL);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 403) {
|
|
||||||
this.logManager.logError(
|
|
||||||
'GitHub API rate limit reached, using cached data if available'
|
|
||||||
);
|
|
||||||
return this.cachedRelease;
|
|
||||||
}
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastApiCall = now;
|
|
||||||
this.cachedRelease = (await response.json()) as GitHubRelease;
|
|
||||||
return this.cachedRelease;
|
|
||||||
} catch (error) {
|
|
||||||
this.logManager.logError(
|
|
||||||
'Error fetching latest release:',
|
|
||||||
error as Error
|
|
||||||
);
|
|
||||||
return this.cachedRelease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -141,16 +141,6 @@ export class HardwareService {
|
||||||
return { cpu, gpu };
|
return { cpu, gpu };
|
||||||
}
|
}
|
||||||
|
|
||||||
async detectAllWithCapabilities(): Promise<HardwareInfo> {
|
|
||||||
const [cpu, gpu, gpuCapabilities] = await Promise.all([
|
|
||||||
this.detectCPU(),
|
|
||||||
this.detectGPU(),
|
|
||||||
this.detectGPUCapabilities(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { cpu, gpu, gpuCapabilities };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async detectCUDA(): Promise<{
|
private async detectCUDA(): Promise<{
|
||||||
supported: boolean;
|
supported: boolean;
|
||||||
devices: string[];
|
devices: string[];
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,14 @@ import type {
|
||||||
ConfigAPI,
|
ConfigAPI,
|
||||||
LogsAPI,
|
LogsAPI,
|
||||||
SillyTavernAPI,
|
SillyTavernAPI,
|
||||||
|
KoboldConfig,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
|
||||||
const koboldAPI: KoboldAPI = {
|
const koboldAPI: KoboldAPI = {
|
||||||
getInstalledVersions: () => ipcRenderer.invoke('kobold:getInstalledVersions'),
|
getInstalledVersions: () => ipcRenderer.invoke('kobold:getInstalledVersions'),
|
||||||
getCurrentBinaryInfo: () => ipcRenderer.invoke('kobold:getCurrentBinaryInfo'),
|
|
||||||
setCurrentVersion: (version: string) =>
|
setCurrentVersion: (version: string) =>
|
||||||
ipcRenderer.invoke('kobold:setCurrentVersion', version),
|
ipcRenderer.invoke('kobold:setCurrentVersion', version),
|
||||||
getLatestReleaseWithStatus: () =>
|
|
||||||
ipcRenderer.invoke('kobold:getLatestReleaseWithStatus'),
|
|
||||||
getROCmDownload: () => ipcRenderer.invoke('kobold:getROCmDownload'),
|
|
||||||
downloadROCm: () => ipcRenderer.invoke('kobold:downloadROCm'),
|
downloadROCm: () => ipcRenderer.invoke('kobold:downloadROCm'),
|
||||||
getLatestRelease: () => ipcRenderer.invoke('kobold:getLatestRelease'),
|
|
||||||
getPlatform: () => ipcRenderer.invoke('kobold:getPlatform'),
|
getPlatform: () => ipcRenderer.invoke('kobold:getPlatform'),
|
||||||
detectGPU: () => ipcRenderer.invoke('kobold:detectGPU'),
|
detectGPU: () => ipcRenderer.invoke('kobold:detectGPU'),
|
||||||
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
|
detectCPU: () => ipcRenderer.invoke('kobold:detectCPU'),
|
||||||
|
|
@ -35,36 +31,8 @@ const koboldAPI: KoboldAPI = {
|
||||||
launchKoboldCpp: (args?: string[]) =>
|
launchKoboldCpp: (args?: string[]) =>
|
||||||
ipcRenderer.invoke('kobold:launchKoboldCpp', args),
|
ipcRenderer.invoke('kobold:launchKoboldCpp', args),
|
||||||
getConfigFiles: () => ipcRenderer.invoke('kobold:getConfigFiles'),
|
getConfigFiles: () => ipcRenderer.invoke('kobold:getConfigFiles'),
|
||||||
saveConfigFile: (
|
saveConfigFile: (configName: string, configData: KoboldConfig) =>
|
||||||
configName: string,
|
ipcRenderer.invoke('kobold:saveConfigFile', configName, configData),
|
||||||
configData: {
|
|
||||||
gpulayers?: number;
|
|
||||||
contextsize?: number;
|
|
||||||
model_param?: string;
|
|
||||||
port?: number;
|
|
||||||
host?: string;
|
|
||||||
multiuser?: number;
|
|
||||||
multiplayer?: boolean;
|
|
||||||
remotetunnel?: boolean;
|
|
||||||
nocertify?: boolean;
|
|
||||||
websearch?: boolean;
|
|
||||||
noshift?: boolean;
|
|
||||||
flashattention?: boolean;
|
|
||||||
noavx2?: boolean;
|
|
||||||
failsafe?: boolean;
|
|
||||||
usemmap?: boolean;
|
|
||||||
usecuda?: boolean;
|
|
||||||
usevulkan?: boolean;
|
|
||||||
useclblast?: boolean;
|
|
||||||
sdmodel?: string;
|
|
||||||
sdt5xxl?: string;
|
|
||||||
sdclipl?: string;
|
|
||||||
sdclipg?: string;
|
|
||||||
sdphotomaker?: string;
|
|
||||||
sdvae?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
) => ipcRenderer.invoke('kobold:saveConfigFile', configName, configData),
|
|
||||||
getSelectedConfig: () => ipcRenderer.invoke('kobold:getSelectedConfig'),
|
getSelectedConfig: () => ipcRenderer.invoke('kobold:getSelectedConfig'),
|
||||||
setSelectedConfig: (configName: string) =>
|
setSelectedConfig: (configName: string) =>
|
||||||
ipcRenderer.invoke('kobold:setSelectedConfig', configName),
|
ipcRenderer.invoke('kobold:setSelectedConfig', configName),
|
||||||
|
|
@ -109,6 +77,7 @@ const koboldAPI: KoboldAPI = {
|
||||||
|
|
||||||
const appAPI: AppAPI = {
|
const appAPI: AppAPI = {
|
||||||
openExternal: (url) => ipcRenderer.invoke('app:openExternal', url),
|
openExternal: (url) => ipcRenderer.invoke('app:openExternal', url),
|
||||||
|
getVersion: () => ipcRenderer.invoke('app:getVersion'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const configAPI: ConfigAPI = {
|
const configAPI: ConfigAPI = {
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ interface LaunchConfigState {
|
||||||
|
|
||||||
export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
||||||
gpuLayers: 0,
|
gpuLayers: 0,
|
||||||
autoGpuLayers: false,
|
autoGpuLayers: true,
|
||||||
contextSize: DEFAULT_CONTEXT_SIZE,
|
contextSize: DEFAULT_CONTEXT_SIZE,
|
||||||
modelPath: '',
|
modelPath: '',
|
||||||
additionalArguments: '',
|
additionalArguments: '',
|
||||||
|
|
@ -163,6 +163,12 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
||||||
if (configData) {
|
if (configData) {
|
||||||
const updates: Partial<LaunchConfigState> = {};
|
const updates: Partial<LaunchConfigState> = {};
|
||||||
|
|
||||||
|
if (typeof configData.autoGpuLayers === 'boolean') {
|
||||||
|
updates.autoGpuLayers = configData.autoGpuLayers;
|
||||||
|
} else {
|
||||||
|
updates.autoGpuLayers = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof configData.gpulayers === 'number') {
|
if (typeof configData.gpulayers === 'number') {
|
||||||
updates.gpuLayers = configData.gpulayers;
|
updates.gpuLayers = configData.gpulayers;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
81
src/types/electron.d.ts
vendored
81
src/types/electron.d.ts
vendored
|
|
@ -57,14 +57,47 @@ export interface DownloadItem {
|
||||||
type: 'asset' | 'rocm';
|
type: 'asset' | 'rocm';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface KoboldConfig {
|
||||||
|
gpulayers?: number;
|
||||||
|
contextsize?: number;
|
||||||
|
model_param?: string;
|
||||||
|
port?: number;
|
||||||
|
host?: string;
|
||||||
|
multiuser?: number;
|
||||||
|
multiplayer?: boolean;
|
||||||
|
remotetunnel?: boolean;
|
||||||
|
nocertify?: boolean;
|
||||||
|
websearch?: boolean;
|
||||||
|
noshift?: boolean;
|
||||||
|
flashattention?: boolean;
|
||||||
|
noavx2?: boolean;
|
||||||
|
failsafe?: boolean;
|
||||||
|
lowvram?: boolean;
|
||||||
|
quantmatmul?: boolean;
|
||||||
|
usemmap?: boolean;
|
||||||
|
usecuda?: boolean;
|
||||||
|
usevulkan?: boolean;
|
||||||
|
useclblast?: boolean | [number, number];
|
||||||
|
gpuDeviceSelection?: string;
|
||||||
|
tensorSplit?: string;
|
||||||
|
gpuPlatform?: number;
|
||||||
|
sdmodel?: string;
|
||||||
|
sdt5xxl?: string;
|
||||||
|
sdclipl?: string;
|
||||||
|
sdclipg?: string;
|
||||||
|
sdphotomaker?: string;
|
||||||
|
sdvae?: string;
|
||||||
|
sdlora?: string;
|
||||||
|
sdconvdirect?: string;
|
||||||
|
additionalArguments?: string;
|
||||||
|
autoGpuLayers?: boolean;
|
||||||
|
model?: string;
|
||||||
|
backend?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface KoboldAPI {
|
export interface KoboldAPI {
|
||||||
getInstalledVersions: () => Promise<InstalledVersion[]>;
|
getInstalledVersions: () => Promise<InstalledVersion[]>;
|
||||||
getCurrentBinaryInfo: () => Promise<{
|
|
||||||
path: string;
|
|
||||||
filename: string;
|
|
||||||
} | null>;
|
|
||||||
setCurrentVersion: (version: string) => Promise<boolean>;
|
setCurrentVersion: (version: string) => Promise<boolean>;
|
||||||
getLatestRelease: () => Promise<DownloadItem[]>;
|
|
||||||
getPlatform: () => Promise<PlatformInfo>;
|
getPlatform: () => Promise<PlatformInfo>;
|
||||||
detectGPU: () => Promise<BasicGPUInfo>;
|
detectGPU: () => Promise<BasicGPUInfo>;
|
||||||
detectCPU: () => Promise<CPUCapabilities>;
|
detectCPU: () => Promise<CPUCapabilities>;
|
||||||
|
|
@ -90,50 +123,17 @@ export interface KoboldAPI {
|
||||||
path?: string;
|
path?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}>;
|
}>;
|
||||||
getROCmDownload: () => Promise<DownloadItem | null>;
|
|
||||||
getLatestReleaseWithStatus: () => Promise<ReleaseWithStatus | null>;
|
|
||||||
launchKoboldCpp: (
|
launchKoboldCpp: (
|
||||||
args?: string[]
|
args?: string[]
|
||||||
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
||||||
getConfigFiles: () => Promise<{ name: string; path: string; size: number }[]>;
|
getConfigFiles: () => Promise<{ name: string; path: string; size: number }[]>;
|
||||||
saveConfigFile: (
|
saveConfigFile: (
|
||||||
configName: string,
|
configName: string,
|
||||||
configData: {
|
configData: KoboldConfig
|
||||||
gpulayers?: number;
|
|
||||||
contextsize?: number;
|
|
||||||
model_param?: string;
|
|
||||||
port?: number;
|
|
||||||
host?: string;
|
|
||||||
multiuser?: number;
|
|
||||||
multiplayer?: boolean;
|
|
||||||
remotetunnel?: boolean;
|
|
||||||
nocertify?: boolean;
|
|
||||||
websearch?: boolean;
|
|
||||||
noshift?: boolean;
|
|
||||||
flashattention?: boolean;
|
|
||||||
noavx2?: boolean;
|
|
||||||
failsafe?: boolean;
|
|
||||||
usemmap?: boolean;
|
|
||||||
usecuda?: boolean;
|
|
||||||
usevulkan?: boolean;
|
|
||||||
useclblast?: boolean;
|
|
||||||
sdmodel?: string;
|
|
||||||
sdt5xxl?: string;
|
|
||||||
sdclipl?: string;
|
|
||||||
sdclipg?: string;
|
|
||||||
sdphotomaker?: string;
|
|
||||||
sdvae?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
getSelectedConfig: () => Promise<string | null>;
|
getSelectedConfig: () => Promise<string | null>;
|
||||||
setSelectedConfig: (configName: string) => Promise<boolean>;
|
setSelectedConfig: (configName: string) => Promise<boolean>;
|
||||||
parseConfigFile: (filePath: string) => Promise<{
|
parseConfigFile: (filePath: string) => Promise<KoboldConfig | null>;
|
||||||
gpulayers?: number;
|
|
||||||
contextsize?: number;
|
|
||||||
model_param?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
} | null>;
|
|
||||||
selectModelFile: () => Promise<string | null>;
|
selectModelFile: () => Promise<string | null>;
|
||||||
stopKoboldCpp: () => void;
|
stopKoboldCpp: () => void;
|
||||||
onDownloadProgress: (callback: (progress: number) => void) => void;
|
onDownloadProgress: (callback: (progress: number) => void) => void;
|
||||||
|
|
@ -145,6 +145,7 @@ export interface KoboldAPI {
|
||||||
|
|
||||||
export interface AppAPI {
|
export interface AppAPI {
|
||||||
openExternal: (url: string) => Promise<void>;
|
openExternal: (url: string) => Promise<void>;
|
||||||
|
getVersion: () => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigAPI {
|
export interface ConfigAPI {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue