mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
WIP: refactor to make all code async, forbit sync node utils, simplifying rocm downloads
This commit is contained in:
parent
94662613d5
commit
dacdd24dde
24 changed files with 354 additions and 464 deletions
|
|
@ -24,12 +24,20 @@ const config = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.{js,mjs,cjs,ts,tsx}'],
|
files: ['**/*.{js,mjs,cjs,ts,tsx}'],
|
||||||
|
ignores: [
|
||||||
|
'*.config.*',
|
||||||
|
'vite.config.*',
|
||||||
|
'eslint.config.*',
|
||||||
|
'postcss.config.*',
|
||||||
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
parser: tsParser,
|
parser: tsParser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
projectService: true,
|
||||||
|
allowDefaultProject: true,
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
jsx: true,
|
jsx: true,
|
||||||
},
|
},
|
||||||
|
|
@ -100,10 +108,25 @@ const config = [
|
||||||
message:
|
message:
|
||||||
'Import specific React hooks/utilities instead of default React import. Use automatic JSX transform.',
|
'Import specific React hooks/utilities instead of default React import. Use automatic JSX transform.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
group: ['fs'],
|
||||||
|
importNames: ['readFileSync', 'writeFileSync', 'existsSync'],
|
||||||
|
message:
|
||||||
|
'Use async file operations instead: readFile, writeFile, access from fs/promises',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'CallExpression[callee.name=/.*Sync$/]',
|
||||||
|
message:
|
||||||
|
'Synchronous file operations are forbidden. Use async alternatives.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
'import/no-default-export': 'error',
|
'import/no-default-export': 'error',
|
||||||
'import/prefer-default-export': 'off',
|
'import/prefer-default-export': 'off',
|
||||||
'import/no-unresolved': 'off',
|
'import/no-unresolved': 'off',
|
||||||
|
|
@ -120,6 +143,7 @@ const config = [
|
||||||
'@typescript-eslint/no-inferrable-types': 'warn',
|
'@typescript-eslint/no-inferrable-types': 'warn',
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/return-await': ['error', 'never'],
|
||||||
|
|
||||||
'sonarjs/cognitive-complexity': ['warn', 25],
|
'sonarjs/cognitive-complexity': ['warn', 25],
|
||||||
|
|
||||||
|
|
@ -141,8 +165,20 @@ const config = [
|
||||||
'eslint.config.*',
|
'eslint.config.*',
|
||||||
'postcss.config.*',
|
'postcss.config.*',
|
||||||
],
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-default-export': 'off',
|
'import/no-default-export': 'off',
|
||||||
|
'@typescript-eslint/return-await': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "friendly-kobold",
|
"name": "friendly-kobold",
|
||||||
"productName": "Friendly Kobold",
|
"productName": "Friendly Kobold",
|
||||||
"version": "0.9.0",
|
"version": "1.0.0",
|
||||||
"description": "A desktop app for running Large Language Models locally",
|
"description": "A desktop app for running Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
@ -82,8 +82,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^8.2.7",
|
"@mantine/core": "^8.2.7",
|
||||||
"@mantine/hooks": "^8.2.7",
|
"@mantine/hooks": "^8.2.7",
|
||||||
|
"axios": "^1.11.0",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"got": "^14.4.7",
|
|
||||||
"lucide-react": "^0.542.0",
|
"lucide-react": "^0.542.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ export const App = () => {
|
||||||
const handleBinaryUpdate = async (download: DownloadItem) => {
|
const handleBinaryUpdate = async (download: DownloadItem) => {
|
||||||
await Logger.safeExecute(async () => {
|
await Logger.safeExecute(async () => {
|
||||||
const success = await sharedHandleDownload({
|
const success = await sharedHandleDownload({
|
||||||
type: 'asset',
|
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: true,
|
wasCurrentBinary: true,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ interface DownloadScreenProps {
|
||||||
|
|
||||||
export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
const {
|
const {
|
||||||
platformInfo,
|
platform,
|
||||||
availableDownloads,
|
availableDownloads,
|
||||||
loadingPlatform,
|
loadingPlatform,
|
||||||
loadingRemote,
|
loadingRemote,
|
||||||
|
|
@ -36,7 +36,6 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
|
|
||||||
await Logger.safeExecute(async () => {
|
await Logger.safeExecute(async () => {
|
||||||
const success = await sharedHandleDownload({
|
const success = await sharedHandleDownload({
|
||||||
type: 'asset',
|
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: false,
|
isUpdate: false,
|
||||||
wasCurrentBinary: false,
|
wasCurrentBinary: false,
|
||||||
|
|
@ -126,7 +125,7 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
|
||||||
<Text fw={500}>No downloads available</Text>
|
<Text fw={500}>No downloads available</Text>
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
No downloads available for your platform (
|
No downloads available for your platform (
|
||||||
{getPlatformDisplayName(platformInfo.platform)}).
|
{getPlatformDisplayName(platform)}).
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,6 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await sharedHandleDownload({
|
const success = await sharedHandleDownload({
|
||||||
type: 'asset',
|
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: false,
|
isUpdate: false,
|
||||||
wasCurrentBinary: false,
|
wasCurrentBinary: false,
|
||||||
|
|
@ -226,7 +225,6 @@ export const VersionsTab = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await sharedHandleDownload({
|
const success = await sharedHandleDownload({
|
||||||
type: 'asset',
|
|
||||||
item: download,
|
item: download,
|
||||||
isUpdate: true,
|
isUpdate: true,
|
||||||
wasCurrentBinary: version.isCurrent,
|
wasCurrentBinary: version.isCurrent,
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,13 @@ import type {
|
||||||
InstalledVersion,
|
InstalledVersion,
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
|
||||||
interface PlatformInfo {
|
|
||||||
platform: string;
|
|
||||||
hasAMDGPU: boolean;
|
|
||||||
hasROCm: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CachedReleaseData {
|
interface CachedReleaseData {
|
||||||
releases: DownloadItem[];
|
releases: DownloadItem[];
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HandleDownloadParams {
|
interface HandleDownloadParams {
|
||||||
type: 'asset' | 'rocm';
|
item: DownloadItem;
|
||||||
item?: DownloadItem;
|
|
||||||
isUpdate?: boolean;
|
isUpdate?: boolean;
|
||||||
wasCurrentBinary?: boolean;
|
wasCurrentBinary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +65,6 @@ const transformReleaseToDownloadItems = (
|
||||||
url: asset.browser_download_url,
|
url: asset.browser_download_url,
|
||||||
size: asset.size,
|
size: asset.size,
|
||||||
version,
|
version,
|
||||||
type: 'asset' as const,
|
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -144,7 +136,7 @@ const getLatestReleaseWithDownloadStatus =
|
||||||
};
|
};
|
||||||
|
|
||||||
interface UseKoboldVersionsReturn {
|
interface UseKoboldVersionsReturn {
|
||||||
platformInfo: PlatformInfo;
|
platform: string;
|
||||||
availableDownloads: DownloadItem[];
|
availableDownloads: DownloadItem[];
|
||||||
loadingPlatform: boolean;
|
loadingPlatform: boolean;
|
||||||
loadingRemote: boolean;
|
loadingRemote: boolean;
|
||||||
|
|
@ -159,16 +151,11 @@ 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>;
|
getLatestReleaseWithDownloadStatus: () => Promise<ReleaseWithStatus | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
const [platformInfo, setPlatformInfo] = useState<PlatformInfo>({
|
const [platform, setPlatform] = useState('');
|
||||||
platform: '',
|
|
||||||
hasAMDGPU: false,
|
|
||||||
hasROCm: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [availableDownloads, setAvailableDownloads] = useState<DownloadItem[]>(
|
const [availableDownloads, setAvailableDownloads] = useState<DownloadItem[]>(
|
||||||
[]
|
[]
|
||||||
|
|
@ -182,54 +169,24 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
Record<string, number>
|
Record<string, number>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
const loadPlatformInfo = useCallback(async () => {
|
const loadPlatform = useCallback(async () => {
|
||||||
setLoadingPlatform(true);
|
setLoadingPlatform(true);
|
||||||
|
|
||||||
try {
|
|
||||||
const platform = await window.electronAPI.kobold.getPlatform();
|
const platform = await window.electronAPI.kobold.getPlatform();
|
||||||
|
|
||||||
let hasAMDGPU = false;
|
setPlatform(platform);
|
||||||
let hasROCm = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const gpuInfo = await window.electronAPI.kobold.detectGPU();
|
|
||||||
hasAMDGPU = gpuInfo.hasAMD;
|
|
||||||
|
|
||||||
if (gpuInfo.hasAMD) {
|
|
||||||
const rocmInfo = await window.electronAPI.kobold.detectROCm();
|
|
||||||
hasROCm = rocmInfo.supported;
|
|
||||||
}
|
|
||||||
} catch (gpuError) {
|
|
||||||
window.electronAPI.logs.logError(
|
|
||||||
'GPU detection failed:',
|
|
||||||
gpuError as Error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlatformInfo({
|
|
||||||
platform: platform.platform,
|
|
||||||
hasAMDGPU,
|
|
||||||
hasROCm,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
window.electronAPI.logs.logError(
|
|
||||||
'Failed to load platform info:',
|
|
||||||
error as Error
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setLoadingPlatform(false);
|
setLoadingPlatform(false);
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadRemoteVersions = useCallback(async () => {
|
const loadRemoteVersions = useCallback(async () => {
|
||||||
if (!platformInfo.platform) return;
|
if (!platform) return;
|
||||||
|
|
||||||
setLoadingRemote(true);
|
setLoadingRemote(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cached = loadFromCache();
|
const cached = loadFromCache();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
const rocm = await getROCmDownload(platformInfo.platform);
|
const rocm = await getROCmDownload();
|
||||||
const allDownloads: DownloadItem[] = [...cached.releases];
|
const allDownloads: DownloadItem[] = [...cached.releases];
|
||||||
if (rocm) {
|
if (rocm) {
|
||||||
allDownloads.push(rocm);
|
allDownloads.push(rocm);
|
||||||
|
|
@ -240,8 +197,8 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [releases, rocm] = await Promise.all([
|
const [releases, rocm] = await Promise.all([
|
||||||
fetchLatestReleaseFromAPI(platformInfo.platform),
|
fetchLatestReleaseFromAPI(platform),
|
||||||
getROCmDownload(platformInfo.platform),
|
getROCmDownload(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
saveToCache(releases);
|
saveToCache(releases);
|
||||||
|
|
@ -260,9 +217,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
|
|
||||||
const cached = loadFromCache();
|
const cached = loadFromCache();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
const rocm = await getROCmDownload(platformInfo.platform).catch(
|
const rocm = await getROCmDownload().catch(() => null);
|
||||||
() => null
|
|
||||||
);
|
|
||||||
const allDownloads: DownloadItem[] = [...cached.releases];
|
const allDownloads: DownloadItem[] = [...cached.releases];
|
||||||
if (rocm) {
|
if (rocm) {
|
||||||
allDownloads.push(rocm);
|
allDownloads.push(rocm);
|
||||||
|
|
@ -272,31 +227,25 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingRemote(false);
|
setLoadingRemote(false);
|
||||||
}
|
}
|
||||||
}, [platformInfo.platform]);
|
}, [platform]);
|
||||||
|
|
||||||
const handleDownload = useCallback(
|
const handleDownload = useCallback(
|
||||||
async ({
|
async ({
|
||||||
type,
|
|
||||||
item,
|
item,
|
||||||
isUpdate = false,
|
isUpdate = false,
|
||||||
wasCurrentBinary = false,
|
wasCurrentBinary = false,
|
||||||
}: HandleDownloadParams): Promise<boolean> => {
|
}: HandleDownloadParams): Promise<boolean> => {
|
||||||
if (type === 'asset' && !item) return false;
|
const downloadName = item.name;
|
||||||
|
|
||||||
const downloadName = item?.name || 'download';
|
|
||||||
|
|
||||||
setDownloading(downloadName);
|
setDownloading(downloadName);
|
||||||
setDownloadProgress((prev) => ({ ...prev, [downloadName]: 0 }));
|
setDownloadProgress((prev) => ({ ...prev, [downloadName]: 0 }));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result =
|
const result = await window.electronAPI.kobold.downloadRelease({
|
||||||
type === 'rocm'
|
name: item.name,
|
||||||
? await window.electronAPI.kobold.downloadROCm()
|
browser_download_url: item.url,
|
||||||
: await window.electronAPI.kobold.downloadRelease({
|
size: item.size,
|
||||||
name: item!.name,
|
version: item.version,
|
||||||
browser_download_url: item!.url,
|
|
||||||
size: item!.size,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
isUpdate,
|
isUpdate,
|
||||||
wasCurrentBinary,
|
wasCurrentBinary,
|
||||||
});
|
});
|
||||||
|
|
@ -304,7 +253,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
return result.success !== false;
|
return result.success !== false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.electronAPI.logs.logError(
|
window.electronAPI.logs.logError(
|
||||||
`Failed to download ${type}:`,
|
`Failed to download ${item.name}:`,
|
||||||
error as Error
|
error as Error
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -326,14 +275,14 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
}, [loadRemoteVersions]);
|
}, [loadRemoteVersions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPlatformInfo();
|
loadPlatform();
|
||||||
}, [loadPlatformInfo]);
|
}, [loadPlatform]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (platformInfo.platform) {
|
if (platform) {
|
||||||
loadRemoteVersions();
|
loadRemoteVersions();
|
||||||
}
|
}
|
||||||
}, [platformInfo.platform, loadRemoteVersions]);
|
}, [platform, loadRemoteVersions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleProgress = (progress: number) => {
|
const handleProgress = (progress: number) => {
|
||||||
|
|
@ -353,7 +302,7 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
}, [downloading]);
|
}, [downloading]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
platformInfo,
|
platform,
|
||||||
availableDownloads,
|
availableDownloads,
|
||||||
loadingPlatform,
|
loadingPlatform,
|
||||||
loadingRemote,
|
loadingRemote,
|
||||||
|
|
@ -364,8 +313,6 @@ export const useKoboldVersions = (): UseKoboldVersionsReturn => {
|
||||||
handleDownload,
|
handleDownload,
|
||||||
setDownloading,
|
setDownloading,
|
||||||
setDownloadProgress,
|
setDownloadProgress,
|
||||||
getROCmDownload: (platform?: string) =>
|
|
||||||
getROCmDownload(platform || platformInfo.platform),
|
|
||||||
getLatestReleaseWithDownloadStatus,
|
getLatestReleaseWithDownloadStatus,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
import { getDisplayNameFromPath, compareVersions } from '@/utils/version';
|
import { getDisplayNameFromPath, compareVersions } from '@/utils/version';
|
||||||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||||
|
import { getROCmDownload } from '@/utils/rocm';
|
||||||
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
import type { InstalledVersion, DownloadItem } from '@/types/electron';
|
||||||
|
|
||||||
interface UpdateInfo {
|
interface UpdateInfo {
|
||||||
|
|
@ -17,7 +18,7 @@ export const useUpdateChecker = () => {
|
||||||
);
|
);
|
||||||
const [dismissedUpdatesLoaded, setDismissedUpdatesLoaded] = useState(false);
|
const [dismissedUpdatesLoaded, setDismissedUpdatesLoaded] = useState(false);
|
||||||
|
|
||||||
const { availableDownloads: releases, getROCmDownload } = useKoboldVersions();
|
const { availableDownloads: releases } = useKoboldVersions();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDismissedUpdates = async () => {
|
const loadDismissedUpdates = async () => {
|
||||||
|
|
@ -71,6 +72,7 @@ export const useUpdateChecker = () => {
|
||||||
window.electronAPI.kobold.getInstalledVersions(),
|
window.electronAPI.kobold.getInstalledVersions(),
|
||||||
getROCmDownload(),
|
getROCmDownload(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!currentBinaryPath || installedVersionsResult.length === 0) {
|
if (!currentBinaryPath || installedVersionsResult.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +124,7 @@ export const useUpdateChecker = () => {
|
||||||
} finally {
|
} finally {
|
||||||
setIsChecking(false);
|
setIsChecking(false);
|
||||||
}
|
}
|
||||||
}, [dismissedUpdates, dismissedUpdatesLoaded, releases, getROCmDownload]);
|
}, [dismissedUpdates, dismissedUpdatesLoaded, releases]);
|
||||||
|
|
||||||
const dismissUpdate = useCallback(async () => {
|
const dismissUpdate = useCallback(async () => {
|
||||||
if (updateInfo) {
|
if (updateInfo) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { existsSync, readFileSync } from 'fs';
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
|
|
||||||
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
||||||
import { terminateProcess } from '@/utils/process';
|
import { terminateProcess } from '@/utils/process';
|
||||||
|
import { pathExists, readJsonFile } from '@/utils/fs';
|
||||||
|
|
||||||
export class LightweightCliHandler {
|
export class LightweightCliHandler {
|
||||||
private getConfigDir(appName: string): string {
|
private getConfigDir(appName: string): string {
|
||||||
|
|
@ -26,22 +26,24 @@ export class LightweightCliHandler {
|
||||||
return join(this.getConfigDir(PRODUCT_NAME), CONFIG_FILE_NAME);
|
return join(this.getConfigDir(PRODUCT_NAME), CONFIG_FILE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentKoboldBinary(): string | null {
|
private async getCurrentKoboldBinary(): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const configPath = this.getConfigPath();
|
const configPath = this.getConfigPath();
|
||||||
if (!existsSync(configPath)) {
|
if (!(await pathExists(configPath))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
const config = await readJsonFile<{ currentKoboldBinary?: string }>(
|
||||||
return config.currentKoboldBinary || null;
|
configPath
|
||||||
|
);
|
||||||
|
return config?.currentKoboldBinary || null;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCliMode(args: string[]): Promise<void> {
|
async handleCliMode(args: string[]): Promise<void> {
|
||||||
const currentBinary = this.getCurrentKoboldBinary();
|
const currentBinary = await this.getCurrentKoboldBinary();
|
||||||
|
|
||||||
if (!currentBinary) {
|
if (!currentBinary) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|
@ -50,7 +52,7 @@ export class LightweightCliHandler {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsSync(currentBinary)) {
|
if (!(await pathExists(currentBinary))) {
|
||||||
console.error(`Error: KoboldCpp binary not found at: ${currentBinary}`);
|
console.error(`Error: KoboldCpp binary not found at: ${currentBinary}`);
|
||||||
console.error('Please run the GUI to download or reconfigure KoboldCpp.');
|
console.error('Please run the GUI to download or reconfigure KoboldCpp.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { app } from 'electron';
|
import { app } from 'electron';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { existsSync, mkdirSync } from 'fs';
|
|
||||||
|
|
||||||
import { WindowManager } from '@/main/managers/WindowManager';
|
import { WindowManager } from '@/main/managers/WindowManager';
|
||||||
import { ConfigManager } from '@/main/managers/ConfigManager';
|
import { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
|
|
@ -12,6 +11,7 @@ import { BinaryService } from '@/main/services/BinaryService';
|
||||||
import { IPCHandlers } from '@/main/ipc';
|
import { IPCHandlers } from '@/main/ipc';
|
||||||
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
import { PRODUCT_NAME, CONFIG_FILE_NAME } from '@/constants';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
|
import { ensureDir } from '@/utils/fs';
|
||||||
|
|
||||||
export class FriendlyKoboldApp {
|
export class FriendlyKoboldApp {
|
||||||
private windowManager: WindowManager;
|
private windowManager: WindowManager;
|
||||||
|
|
@ -31,7 +31,6 @@ export class FriendlyKoboldApp {
|
||||||
this.getConfigPath(),
|
this.getConfigPath(),
|
||||||
this.logManager
|
this.logManager
|
||||||
);
|
);
|
||||||
this.ensureInstallDirectory();
|
|
||||||
this.windowManager = new WindowManager();
|
this.windowManager = new WindowManager();
|
||||||
this.hardwareService = new HardwareService(this.logManager);
|
this.hardwareService = new HardwareService(this.logManager);
|
||||||
|
|
||||||
|
|
@ -80,22 +79,22 @@ export class FriendlyKoboldApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureInstallDirectory() {
|
private async ensureInstallDirectory(): Promise<void> {
|
||||||
const installDir =
|
const installDir =
|
||||||
this.configManager.getInstallDir() ||
|
this.configManager.getInstallDir() ||
|
||||||
this.getDefaultInstallDir(PRODUCT_NAME);
|
this.getDefaultInstallDir(PRODUCT_NAME);
|
||||||
|
|
||||||
if (!this.configManager.getInstallDir()) {
|
if (!this.configManager.getInstallDir()) {
|
||||||
this.configManager.setInstallDir(installDir);
|
await this.configManager.setInstallDir(installDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsSync(installDir)) {
|
await ensureDir(installDir);
|
||||||
mkdirSync(installDir, { recursive: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
await app.whenReady();
|
await app.whenReady();
|
||||||
|
await this.configManager.initialize();
|
||||||
|
await this.ensureInstallDirectory();
|
||||||
|
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
app.setAppUserModelId('com.friendly-kobold.app');
|
app.setAppUserModelId('com.friendly-kobold.app');
|
||||||
|
|
|
||||||
|
|
@ -52,26 +52,10 @@ export class IPCHandlers {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHandlers() {
|
setupHandlers() {
|
||||||
ipcMain.handle('kobold:downloadRelease', async (_, asset) => {
|
ipcMain.handle('kobold:downloadRelease', async (_, asset) => ({
|
||||||
try {
|
success: true,
|
||||||
const mainWindow = this.koboldManager
|
path: await this.koboldManager.downloadRelease(asset),
|
||||||
.getWindowManager()
|
}));
|
||||||
.getMainWindow();
|
|
||||||
|
|
||||||
const filePath = await this.koboldManager.downloadRelease(
|
|
||||||
asset,
|
|
||||||
(progress: number) => {
|
|
||||||
if (mainWindow) {
|
|
||||||
mainWindow.webContents.send('download-progress', progress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { success: true, path: filePath };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:getInstalledVersions', () =>
|
ipcMain.handle('kobold:getInstalledVersions', () =>
|
||||||
this.koboldManager.getInstalledVersions()
|
this.koboldManager.getInstalledVersions()
|
||||||
|
|
@ -131,26 +115,7 @@ export class IPCHandlers {
|
||||||
this.binaryService.getAvailableBackends(includeDisabled)
|
this.binaryService.getAvailableBackends(includeDisabled)
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:getPlatform', () => ({
|
ipcMain.handle('kobold:getPlatform', () => process.platform);
|
||||||
platform: process.platform,
|
|
||||||
arch: process.arch,
|
|
||||||
}));
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:downloadROCm', async () => {
|
|
||||||
try {
|
|
||||||
const mainWindow = this.koboldManager
|
|
||||||
.getWindowManager()
|
|
||||||
.getMainWindow();
|
|
||||||
|
|
||||||
return await this.koboldManager.downloadROCm((progress: number) => {
|
|
||||||
if (mainWindow) {
|
|
||||||
mainWindow.webContents.send('download-progress', progress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('kobold:launchKoboldCpp', (_, args) =>
|
ipcMain.handle('kobold:launchKoboldCpp', (_, args) =>
|
||||||
this.launchKoboldCppWithCustomFrontends(args)
|
this.launchKoboldCppWithCustomFrontends(args)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
import { LogManager } from '@/main/managers/LogManager';
|
||||||
|
import { readJsonFile, writeJsonFile } from '@/utils/fs';
|
||||||
import type { FrontendPreference } from '@/types';
|
import type { FrontendPreference } from '@/types';
|
||||||
|
|
||||||
type ConfigValue = string | number | boolean | unknown[] | undefined;
|
type ConfigValue = string | number | boolean | unknown[] | undefined;
|
||||||
|
|
@ -20,23 +20,25 @@ export class ConfigManager {
|
||||||
constructor(configPath: string, logManager: LogManager) {
|
constructor(configPath: string, logManager: LogManager) {
|
||||||
this.configPath = configPath;
|
this.configPath = configPath;
|
||||||
this.logManager = logManager;
|
this.logManager = logManager;
|
||||||
this.config = this.loadConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadConfig(): AppConfig {
|
async initialize(): Promise<void> {
|
||||||
try {
|
this.config = await this.loadConfig();
|
||||||
if (existsSync(this.configPath)) {
|
|
||||||
return JSON.parse(readFileSync(this.configPath, 'utf8'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async loadConfig(): Promise<AppConfig> {
|
||||||
|
try {
|
||||||
|
const config = await readJsonFile<AppConfig>(this.configPath);
|
||||||
|
return config || {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logManager.logError('Error loading config:', error as Error);
|
this.logManager.logError('Error loading config:', error as Error);
|
||||||
}
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private saveConfig() {
|
private async saveConfig(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
await writeJsonFile(this.configPath, this.config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logManager.logError('Error saving config:', error as Error);
|
this.logManager.logError('Error saving config:', error as Error);
|
||||||
}
|
}
|
||||||
|
|
@ -46,18 +48,18 @@ export class ConfigManager {
|
||||||
return this.config[key];
|
return this.config[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, value: ConfigValue): void {
|
async set(key: string, value: ConfigValue): Promise<void> {
|
||||||
this.config[key] = value;
|
this.config[key] = value;
|
||||||
this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstallDir(): string | undefined {
|
getInstallDir(): string | undefined {
|
||||||
return this.config.installDir;
|
return this.config.installDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
setInstallDir(dir: string) {
|
async setInstallDir(dir: string): Promise<void> {
|
||||||
this.config.installDir = dir;
|
this.config.installDir = dir;
|
||||||
this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentKoboldBinary(): string | undefined {
|
getCurrentKoboldBinary(): string | undefined {
|
||||||
|
|
@ -65,17 +67,17 @@ export class ConfigManager {
|
||||||
return path ? path.trim() : path;
|
return path ? path.trim() : path;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentKoboldBinary(binaryPath: string) {
|
async setCurrentKoboldBinary(binaryPath: string): Promise<void> {
|
||||||
this.config.currentKoboldBinary = binaryPath;
|
this.config.currentKoboldBinary = binaryPath;
|
||||||
this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedConfig(): string | undefined {
|
getSelectedConfig(): string | undefined {
|
||||||
return this.config.selectedConfig;
|
return this.config.selectedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedConfig(configName: string) {
|
async setSelectedConfig(configName: string): Promise<void> {
|
||||||
this.config.selectedConfig = configName;
|
this.config.selectedConfig = configName;
|
||||||
this.saveConfig();
|
await this.saveConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,16 @@
|
||||||
import { spawn, ChildProcess } from 'child_process';
|
import { spawn, ChildProcess } from 'child_process';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import {
|
import { rm, readdir, stat } from 'fs/promises';
|
||||||
existsSync,
|
|
||||||
readdirSync,
|
|
||||||
statSync,
|
|
||||||
readFileSync,
|
|
||||||
writeFileSync,
|
|
||||||
} from 'fs';
|
|
||||||
import { rm } from 'fs/promises';
|
|
||||||
import { dialog } from 'electron';
|
import { dialog } from 'electron';
|
||||||
|
|
||||||
import { execa } from 'execa';
|
import { execa } from 'execa';
|
||||||
import { terminateProcess } from '@/utils/process';
|
import { terminateProcess } from '@/utils/process';
|
||||||
import { getROCmDownload } from '@/utils/rocm';
|
|
||||||
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 { PRODUCT_NAME, SERVER_READY_SIGNALS } from '@/constants';
|
import { PRODUCT_NAME, SERVER_READY_SIGNALS } from '@/constants';
|
||||||
import { downloadAndUnpackBinary } from '@/utils/server/download';
|
import { downloadAndUnpackBinary } from '@/utils/server/download';
|
||||||
|
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/fs';
|
||||||
import type {
|
import type {
|
||||||
GitHubAsset,
|
GitHubAsset,
|
||||||
InstalledVersion,
|
InstalledVersion,
|
||||||
|
|
@ -25,7 +18,6 @@ import type {
|
||||||
} from '@/types/electron';
|
} from '@/types/electron';
|
||||||
|
|
||||||
export class KoboldCppManager {
|
export class KoboldCppManager {
|
||||||
private installDir: string;
|
|
||||||
private koboldProcess: ChildProcess | null = null;
|
private koboldProcess: ChildProcess | null = null;
|
||||||
private configManager: ConfigManager;
|
private configManager: ConfigManager;
|
||||||
private logManager: LogManager;
|
private logManager: LogManager;
|
||||||
|
|
@ -39,7 +31,10 @@ export class KoboldCppManager {
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
this.logManager = logManager;
|
this.logManager = logManager;
|
||||||
this.windowManager = windowManager;
|
this.windowManager = windowManager;
|
||||||
this.installDir = this.configManager.getInstallDir() || '';
|
}
|
||||||
|
|
||||||
|
private get installDir(): string {
|
||||||
|
return this.configManager.getInstallDir() || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private async removeDirectoryWithRetry(
|
private async removeDirectoryWithRetry(
|
||||||
|
|
@ -73,20 +68,22 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadRelease(
|
async downloadRelease(asset: GitHubAsset): Promise<string> {
|
||||||
asset: GitHubAsset,
|
|
||||||
onProgress?: (progress: number) => void
|
|
||||||
): Promise<string> {
|
|
||||||
const result = await downloadAndUnpackBinary(
|
const result = await downloadAndUnpackBinary(
|
||||||
{
|
{
|
||||||
name: asset.name,
|
name: asset.name,
|
||||||
url: asset.browser_download_url,
|
url: asset.browser_download_url,
|
||||||
size: asset.size,
|
size: asset.size,
|
||||||
type: 'asset' as const,
|
version: asset.version,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
installDir: this.installDir,
|
installDir: this.installDir,
|
||||||
onProgress,
|
onProgress: (progress: number) => {
|
||||||
|
const mainWindow = this.windowManager.getMainWindow();
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('download-progress', progress);
|
||||||
|
}
|
||||||
|
},
|
||||||
isUpdate: asset.isUpdate,
|
isUpdate: asset.isUpdate,
|
||||||
wasCurrentBinary: asset.wasCurrentBinary,
|
wasCurrentBinary: asset.wasCurrentBinary,
|
||||||
logManager: this.logManager,
|
logManager: this.logManager,
|
||||||
|
|
@ -128,13 +125,13 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLauncherPath(unpackedDir: string): string | null {
|
private async getLauncherPath(unpackedDir: string): Promise<string | null> {
|
||||||
const extensions =
|
const extensions =
|
||||||
process.platform === 'win32' ? ['.exe', ''] : ['', '.exe'];
|
process.platform === 'win32' ? ['.exe', ''] : ['', '.exe'];
|
||||||
|
|
||||||
for (const ext of extensions) {
|
for (const ext of extensions) {
|
||||||
const launcherPath = join(unpackedDir, `koboldcpp-launcher${ext}`);
|
const launcherPath = join(unpackedDir, `koboldcpp-launcher${ext}`);
|
||||||
if (existsSync(launcherPath)) {
|
if (await pathExists(launcherPath)) {
|
||||||
return launcherPath;
|
return launcherPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,21 +141,21 @@ export class KoboldCppManager {
|
||||||
|
|
||||||
async getInstalledVersions(): Promise<InstalledVersion[]> {
|
async getInstalledVersions(): Promise<InstalledVersion[]> {
|
||||||
try {
|
try {
|
||||||
if (!existsSync(this.installDir)) {
|
if (!(await pathExists(this.installDir))) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = readdirSync(this.installDir);
|
const items = await readdir(this.installDir);
|
||||||
const launchers: { path: string; filename: string; size: number }[] = [];
|
const launchers: { path: string; filename: string; size: number }[] = [];
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const itemPath = join(this.installDir, item);
|
const itemPath = join(this.installDir, item);
|
||||||
const stats = statSync(itemPath);
|
const stats = await stat(itemPath);
|
||||||
|
|
||||||
if (stats.isDirectory()) {
|
if (stats.isDirectory()) {
|
||||||
const launcherPath = this.getLauncherPath(itemPath);
|
const launcherPath = await this.getLauncherPath(itemPath);
|
||||||
if (launcherPath && existsSync(launcherPath)) {
|
if (launcherPath && (await pathExists(launcherPath))) {
|
||||||
const launcherStats = statSync(launcherPath);
|
const launcherStats = await stat(launcherPath);
|
||||||
const launcherFilename = launcherPath.split(/[/\\]/).pop() || '';
|
const launcherFilename = launcherPath.split(/[/\\]/).pop() || '';
|
||||||
launchers.push({
|
launchers.push({
|
||||||
path: launcherPath,
|
path: launcherPath,
|
||||||
|
|
@ -210,19 +207,19 @@ export class KoboldCppManager {
|
||||||
const configFiles: { name: string; path: string; size: number }[] = [];
|
const configFiles: { name: string; path: string; size: number }[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (existsSync(this.installDir)) {
|
if (await pathExists(this.installDir)) {
|
||||||
const files = readdirSync(this.installDir);
|
const files = await readdir(this.installDir);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const filePath = join(this.installDir, file);
|
const filePath = join(this.installDir, file);
|
||||||
|
|
||||||
|
const stats = await stat(filePath);
|
||||||
if (
|
if (
|
||||||
statSync(filePath).isFile() &&
|
stats.isFile() &&
|
||||||
(file.endsWith('.kcpps') ||
|
(file.endsWith('.kcpps') ||
|
||||||
file.endsWith('.kcppt') ||
|
file.endsWith('.kcppt') ||
|
||||||
file.endsWith('.json'))
|
file.endsWith('.json'))
|
||||||
) {
|
) {
|
||||||
const stats = statSync(filePath);
|
|
||||||
configFiles.push({
|
configFiles.push({
|
||||||
name: file,
|
name: file,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
|
|
@ -243,14 +240,12 @@ export class KoboldCppManager {
|
||||||
|
|
||||||
async parseConfigFile(filePath: string): Promise<KoboldConfig | null> {
|
async parseConfigFile(filePath: string): Promise<KoboldConfig | null> {
|
||||||
try {
|
try {
|
||||||
if (!existsSync(filePath)) {
|
if (!(await pathExists(filePath))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = readFileSync(filePath, 'utf-8');
|
const config = await readJsonFile(filePath);
|
||||||
const config = JSON.parse(content);
|
return config as KoboldConfig;
|
||||||
|
|
||||||
return config;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logManager.logError('Error parsing config file:', error as Error);
|
this.logManager.logError('Error parsing config file:', error as Error);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -268,7 +263,7 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
const configPath = join(this.installDir, configFileName);
|
const configPath = join(this.installDir, configFileName);
|
||||||
writeFileSync(configPath, JSON.stringify(configData, null, 2), 'utf-8');
|
await writeJsonFile(configPath, configData);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logManager.logError('Error saving config file:', error as Error);
|
this.logManager.logError('Error saving config file:', error as Error);
|
||||||
|
|
@ -307,7 +302,7 @@ export class KoboldCppManager {
|
||||||
const currentBinaryPath = this.configManager.getCurrentKoboldBinary();
|
const currentBinaryPath = this.configManager.getCurrentKoboldBinary();
|
||||||
const versions = await this.getInstalledVersions();
|
const versions = await this.getInstalledVersions();
|
||||||
|
|
||||||
if (currentBinaryPath && existsSync(currentBinaryPath)) {
|
if (currentBinaryPath && (await pathExists(currentBinaryPath))) {
|
||||||
const currentVersion = versions.find((v) => v.path === currentBinaryPath);
|
const currentVersion = versions.find((v) => v.path === currentBinaryPath);
|
||||||
if (currentVersion) {
|
if (currentVersion) {
|
||||||
return currentVersion;
|
return currentVersion;
|
||||||
|
|
@ -316,12 +311,12 @@ export class KoboldCppManager {
|
||||||
|
|
||||||
const firstVersion = versions[0];
|
const firstVersion = versions[0];
|
||||||
if (firstVersion) {
|
if (firstVersion) {
|
||||||
this.configManager.setCurrentKoboldBinary(firstVersion.path);
|
await this.configManager.setCurrentKoboldBinary(firstVersion.path);
|
||||||
return firstVersion;
|
return firstVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBinaryPath) {
|
if (currentBinaryPath) {
|
||||||
this.configManager.setCurrentKoboldBinary('');
|
await this.configManager.setCurrentKoboldBinary('');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -348,8 +343,8 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCurrentVersion(binaryPath: string): Promise<boolean> {
|
async setCurrentVersion(binaryPath: string): Promise<boolean> {
|
||||||
if (existsSync(binaryPath)) {
|
if (await pathExists(binaryPath)) {
|
||||||
this.configManager.setCurrentKoboldBinary(binaryPath);
|
await this.configManager.setCurrentKoboldBinary(binaryPath);
|
||||||
|
|
||||||
this.windowManager.sendToRenderer('versions-updated');
|
this.windowManager.sendToRenderer('versions-updated');
|
||||||
|
|
||||||
|
|
@ -359,13 +354,21 @@ export class KoboldCppManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVersionFromBinary(binaryPath: string): Promise<string | null> {
|
async getVersionFromBinary(launcherPath: string): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
if (!existsSync(binaryPath)) {
|
if (!(await pathExists(launcherPath))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await execa(binaryPath, ['--version'], {
|
const folderName = launcherPath.split(/[/\\]/).slice(-2, -1)[0];
|
||||||
|
if (folderName) {
|
||||||
|
const versionMatch = folderName.match(/-(\d+\.\d+(?:\.\d+)?)$/);
|
||||||
|
if (versionMatch) {
|
||||||
|
return versionMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await execa(launcherPath, ['--version'], {
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
|
|
@ -413,8 +416,7 @@ export class KoboldCppManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.canceled && result.filePaths.length > 0) {
|
if (!result.canceled && result.filePaths.length > 0) {
|
||||||
this.installDir = result.filePaths[0];
|
await this.configManager.setInstallDir(result.filePaths[0]);
|
||||||
this.configManager.setInstallDir(result.filePaths[0]);
|
|
||||||
|
|
||||||
this.windowManager.sendToRenderer(
|
this.windowManager.sendToRenderer(
|
||||||
'install-dir-changed',
|
'install-dir-changed',
|
||||||
|
|
@ -438,7 +440,7 @@ export class KoboldCppManager {
|
||||||
this.koboldProcess = null;
|
this.koboldProcess = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsSync(versionPath)) {
|
if (!(await pathExists(versionPath))) {
|
||||||
throw new Error('Selected version file does not exist');
|
throw new Error('Selected version file does not exist');
|
||||||
}
|
}
|
||||||
this.koboldProcess = spawn(versionPath, args, {
|
this.koboldProcess = spawn(versionPath, args, {
|
||||||
|
|
@ -482,45 +484,6 @@ export class KoboldCppManager {
|
||||||
return this.koboldProcess !== null && !this.koboldProcess.killed;
|
return this.koboldProcess !== null && !this.koboldProcess.killed;
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadROCm(onProgress?: (progress: number) => void): Promise<{
|
|
||||||
success: boolean;
|
|
||||||
path?: string;
|
|
||||||
error?: string;
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
const rocmInfo = await getROCmDownload(process.platform);
|
|
||||||
if (!rocmInfo) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'ROCm version not available for this platform',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await downloadAndUnpackBinary(
|
|
||||||
{
|
|
||||||
name: rocmInfo.name,
|
|
||||||
url: rocmInfo.url,
|
|
||||||
size: rocmInfo.size,
|
|
||||||
type: 'rocm' as const,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
installDir: this.installDir,
|
|
||||||
onProgress,
|
|
||||||
logManager: this.logManager,
|
|
||||||
configManager: this.configManager,
|
|
||||||
windowManager: this.windowManager,
|
|
||||||
unpackFunction: this.unpackKoboldCpp.bind(this),
|
|
||||||
getLauncherPath: this.getLauncherPath.bind(this),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: (error as Error).message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getInstalledVersion(): Promise<string | undefined> {
|
async getInstalledVersion(): Promise<string | undefined> {
|
||||||
const currentVersion = await this.getCurrentVersion();
|
const currentVersion = await this.getCurrentVersion();
|
||||||
return currentVersion?.version;
|
return currentVersion?.version;
|
||||||
|
|
@ -535,7 +498,7 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentVersion = await this.getCurrentVersion();
|
const currentVersion = await this.getCurrentVersion();
|
||||||
if (!currentVersion || !existsSync(currentVersion.path)) {
|
if (!currentVersion || !(await pathExists(currentVersion.path))) {
|
||||||
const rawPath = this.configManager.getCurrentKoboldBinary();
|
const rawPath = this.configManager.getCurrentKoboldBinary();
|
||||||
const error = currentVersion
|
const error = currentVersion
|
||||||
? `KoboldCpp binary file does not exist at path: ${currentVersion.path}`
|
? `KoboldCpp binary file does not exist at path: ${currentVersion.path}`
|
||||||
|
|
@ -645,7 +608,7 @@ export class KoboldCppManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return await readyPromise;
|
return readyPromise;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = (error as Error).message;
|
const errorMessage = (error as Error).message;
|
||||||
this.logManager.logError(
|
this.logManager.logError(
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ import { spawn } from 'child_process';
|
||||||
import { createServer, request, type Server } from 'http';
|
import { createServer, request, type Server } from 'http';
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
||||||
import type { ChildProcess } from 'child_process';
|
import type { ChildProcess } from 'child_process';
|
||||||
|
|
||||||
import { LogManager } from './LogManager';
|
import { LogManager } from './LogManager';
|
||||||
import { WindowManager } from './WindowManager';
|
import { WindowManager } from './WindowManager';
|
||||||
import { SILLYTAVERN } from '@/constants';
|
import { SILLYTAVERN } from '@/constants';
|
||||||
import { terminateProcess } from '@/utils/process';
|
import { terminateProcess } from '@/utils/process';
|
||||||
|
import { pathExists, readJsonFile, writeJsonFile } from '@/utils/fs';
|
||||||
|
|
||||||
export interface SillyTavernConfig {
|
export interface SillyTavernConfig {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -70,14 +70,14 @@ export class SillyTavernManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSillyTavernDataRoot(): string {
|
private async getSillyTavernDataRoot(): Promise<string> {
|
||||||
if (this.detectedDataRoot) {
|
if (this.detectedDataRoot) {
|
||||||
return this.detectedDataRoot;
|
return this.detectedDataRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallback = this.getFallbackDataRoot();
|
const fallback = this.getFallbackDataRoot();
|
||||||
|
|
||||||
if (existsSync(fallback)) {
|
if (await pathExists(fallback)) {
|
||||||
this.detectedDataRoot = fallback;
|
this.detectedDataRoot = fallback;
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ export class SillyTavernManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const path of alternatePaths) {
|
for (const path of alternatePaths) {
|
||||||
if (existsSync(path)) {
|
if (await pathExists(path)) {
|
||||||
this.detectedDataRoot = path;
|
this.detectedDataRoot = path;
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
@ -113,8 +113,9 @@ export class SillyTavernManager {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSillyTavernSettingsPath(): string {
|
private async getSillyTavernSettingsPath(): Promise<string> {
|
||||||
return join(this.getSillyTavernDataRoot(), 'default-user', 'settings.json');
|
const dataRoot = await this.getSillyTavernDataRoot();
|
||||||
|
return join(dataRoot, 'default-user', 'settings.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
async isNpxAvailable(): Promise<boolean> {
|
async isNpxAvailable(): Promise<boolean> {
|
||||||
|
|
@ -150,9 +151,9 @@ export class SillyTavernManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ensureSillyTavernSettings(): Promise<void> {
|
private async ensureSillyTavernSettings(): Promise<void> {
|
||||||
const settingsPath = this.getSillyTavernSettingsPath();
|
const settingsPath = await this.getSillyTavernSettingsPath();
|
||||||
|
|
||||||
if (existsSync(settingsPath)) {
|
if (await pathExists(settingsPath)) {
|
||||||
this.windowManager.sendKoboldOutput(
|
this.windowManager.sendKoboldOutput(
|
||||||
`SillyTavern settings found at ${settingsPath}`
|
`SillyTavern settings found at ${settingsPath}`
|
||||||
);
|
);
|
||||||
|
|
@ -281,16 +282,19 @@ export class SillyTavernManager {
|
||||||
isImageMode: boolean
|
isImageMode: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const configPath = this.getSillyTavernSettingsPath();
|
const configPath = await this.getSillyTavernSettingsPath();
|
||||||
let settings: Record<string, unknown> = {};
|
let settings: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (existsSync(configPath)) {
|
if (await pathExists(configPath)) {
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(configPath, 'utf-8');
|
const existingSettings =
|
||||||
settings = JSON.parse(content) as Record<string, unknown>;
|
await readJsonFile<Record<string, unknown>>(configPath);
|
||||||
|
if (existingSettings) {
|
||||||
|
settings = existingSettings;
|
||||||
this.windowManager.sendKoboldOutput(
|
this.windowManager.sendKoboldOutput(
|
||||||
`Loaded existing SillyTavern settings`
|
`Loaded existing SillyTavern settings`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this.windowManager.sendKoboldOutput(
|
this.windowManager.sendKoboldOutput(
|
||||||
`Could not read existing settings, creating new ones`
|
`Could not read existing settings, creating new ones`
|
||||||
|
|
@ -333,7 +337,7 @@ export class SillyTavernManager {
|
||||||
`Configured SillyTavern for text generation with KoboldCpp at ${koboldUrl}`
|
`Configured SillyTavern for text generation with KoboldCpp at ${koboldUrl}`
|
||||||
);
|
);
|
||||||
|
|
||||||
writeFileSync(configPath, JSON.stringify(settings, null, 2), 'utf-8');
|
await writeJsonFile(configPath, settings);
|
||||||
|
|
||||||
this.windowManager.sendKoboldOutput(
|
this.windowManager.sendKoboldOutput(
|
||||||
`SillyTavern configuration updated successfully!`
|
`SillyTavern configuration updated successfully!`
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { BrowserWindow, app, Menu, shell, nativeImage } from 'electron';
|
import { BrowserWindow, app, Menu, shell, nativeImage } from 'electron';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import { stripVTControlCharacters } from 'util';
|
import { stripVTControlCharacters } from 'util';
|
||||||
import { GITHUB_API, PRODUCT_NAME } from '../../constants';
|
import { GITHUB_API, PRODUCT_NAME } from '../../constants';
|
||||||
import type { IPCChannel, IPCChannelPayloads } from '@/types/ipc';
|
import type { IPCChannel, IPCChannelPayloads } from '@/types/ipc';
|
||||||
|
import { readTextFile } from '@/utils/fs';
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
private mainWindow: BrowserWindow | null = null;
|
private mainWindow: BrowserWindow | null = null;
|
||||||
|
|
@ -301,7 +301,8 @@ export class WindowManager {
|
||||||
|
|
||||||
private async showAboutDialog() {
|
private async showAboutDialog() {
|
||||||
const packagePath = join(app.getAppPath(), 'package.json');
|
const packagePath = join(app.getAppPath(), 'package.json');
|
||||||
const packageInfo = JSON.parse(readFileSync(packagePath, 'utf8'));
|
const packageContent = await readTextFile(packagePath);
|
||||||
|
const packageInfo = packageContent ? JSON.parse(packageContent) : {};
|
||||||
const electronVersion = process.versions.electron;
|
const electronVersion = process.versions.electron;
|
||||||
const chromeVersion = process.versions.chrome;
|
const chromeVersion = process.versions.chrome;
|
||||||
const nodeVersion = process.versions.node;
|
const nodeVersion = process.versions.node;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
import { LogManager } from '@/main/managers/LogManager';
|
import { LogManager } from '@/main/managers/LogManager';
|
||||||
import type { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
import type { KoboldCppManager } from '@/main/managers/KoboldCppManager';
|
||||||
import type { HardwareService } from '@/main/services/HardwareService';
|
import type { HardwareService } from '@/main/services/HardwareService';
|
||||||
import type { BackendOption } from '@/types';
|
import type { BackendOption } from '@/types';
|
||||||
|
import { pathExists } from '@/utils/fs';
|
||||||
|
|
||||||
export interface BackendSupport {
|
export interface BackendSupport {
|
||||||
rocm: boolean;
|
rocm: boolean;
|
||||||
|
|
@ -31,9 +31,9 @@ export class BinaryService {
|
||||||
this.hardwareService = hardwareService;
|
this.hardwareService = hardwareService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private detectBackendSupportFromPath(
|
private async detectBackendSupportFromPath(
|
||||||
koboldBinaryPath: string
|
koboldBinaryPath: string
|
||||||
): BackendSupport {
|
): Promise<BackendSupport> {
|
||||||
if (this.backendSupportCache.has(koboldBinaryPath)) {
|
if (this.backendSupportCache.has(koboldBinaryPath)) {
|
||||||
return this.backendSupportCache.get(koboldBinaryPath)!;
|
return this.backendSupportCache.get(koboldBinaryPath)!;
|
||||||
}
|
}
|
||||||
|
|
@ -54,28 +54,28 @@ export class BinaryService {
|
||||||
const platform = process.platform;
|
const platform = process.platform;
|
||||||
const libExtension = platform === 'win32' ? '.dll' : '.so';
|
const libExtension = platform === 'win32' ? '.dll' : '.so';
|
||||||
|
|
||||||
const hasKoboldCppLib = (name: string) => {
|
const hasKoboldCppLib = async (name: string): Promise<boolean> => {
|
||||||
const filename = `${name}${libExtension}`;
|
const filename = `${name}${libExtension}`;
|
||||||
|
|
||||||
if (platform === 'win32') {
|
if (platform === 'win32') {
|
||||||
return (
|
return (
|
||||||
existsSync(join(binaryDir, filename)) ||
|
(await pathExists(join(binaryDir, filename))) ||
|
||||||
existsSync(join(internalDir, filename))
|
(await pathExists(join(internalDir, filename)))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
existsSync(join(internalDir, filename)) ||
|
(await pathExists(join(internalDir, filename))) ||
|
||||||
existsSync(join(binaryDir, filename))
|
(await pathExists(join(binaryDir, filename)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
support.rocm = hasKoboldCppLib('koboldcpp_hipblas');
|
support.rocm = await hasKoboldCppLib('koboldcpp_hipblas');
|
||||||
support.vulkan = hasKoboldCppLib('koboldcpp_vulkan');
|
support.vulkan = await hasKoboldCppLib('koboldcpp_vulkan');
|
||||||
support.clblast = hasKoboldCppLib('koboldcpp_clblast');
|
support.clblast = await hasKoboldCppLib('koboldcpp_clblast');
|
||||||
support.noavx2 = hasKoboldCppLib('koboldcpp_noavx2');
|
support.noavx2 = await hasKoboldCppLib('koboldcpp_noavx2');
|
||||||
support.failsafe = hasKoboldCppLib('koboldcpp_failsafe');
|
support.failsafe = await hasKoboldCppLib('koboldcpp_failsafe');
|
||||||
support.cuda = hasKoboldCppLib('koboldcpp_cublas');
|
support.cuda = await hasKoboldCppLib('koboldcpp_cublas');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logManager.logError(
|
this.logManager.logError(
|
||||||
'Error detecting backend support:',
|
'Error detecting backend support:',
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ const koboldAPI: KoboldAPI = {
|
||||||
getInstalledVersions: () => ipcRenderer.invoke('kobold:getInstalledVersions'),
|
getInstalledVersions: () => ipcRenderer.invoke('kobold:getInstalledVersions'),
|
||||||
setCurrentVersion: (version: string) =>
|
setCurrentVersion: (version: string) =>
|
||||||
ipcRenderer.invoke('kobold:setCurrentVersion', version),
|
ipcRenderer.invoke('kobold:setCurrentVersion', version),
|
||||||
downloadROCm: () => ipcRenderer.invoke('kobold:downloadROCm'),
|
|
||||||
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'),
|
||||||
|
|
|
||||||
11
src/types/electron.d.ts
vendored
11
src/types/electron.d.ts
vendored
|
|
@ -2,7 +2,6 @@ import type {
|
||||||
CPUCapabilities,
|
CPUCapabilities,
|
||||||
GPUCapabilities,
|
GPUCapabilities,
|
||||||
BasicGPUInfo,
|
BasicGPUInfo,
|
||||||
PlatformInfo,
|
|
||||||
GPUMemoryInfo,
|
GPUMemoryInfo,
|
||||||
} from '@/types/hardware';
|
} from '@/types/hardware';
|
||||||
import type { BackendOption } from '@/types';
|
import type { BackendOption } from '@/types';
|
||||||
|
|
@ -11,7 +10,7 @@ export interface GitHubAsset {
|
||||||
name: string;
|
name: string;
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
size: number;
|
size: number;
|
||||||
created_at: string;
|
version?: string;
|
||||||
isUpdate?: boolean;
|
isUpdate?: boolean;
|
||||||
wasCurrentBinary?: boolean;
|
wasCurrentBinary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +53,6 @@ export interface DownloadItem {
|
||||||
url: string;
|
url: string;
|
||||||
size: number;
|
size: number;
|
||||||
version?: string;
|
version?: string;
|
||||||
type: 'asset' | 'rocm';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KoboldConfig {
|
export interface KoboldConfig {
|
||||||
|
|
@ -98,7 +96,7 @@ export interface KoboldConfig {
|
||||||
export interface KoboldAPI {
|
export interface KoboldAPI {
|
||||||
getInstalledVersions: () => Promise<InstalledVersion[]>;
|
getInstalledVersions: () => Promise<InstalledVersion[]>;
|
||||||
setCurrentVersion: (version: string) => Promise<boolean>;
|
setCurrentVersion: (version: string) => Promise<boolean>;
|
||||||
getPlatform: () => Promise<PlatformInfo>;
|
getPlatform: () => Promise<string>;
|
||||||
detectGPU: () => Promise<BasicGPUInfo>;
|
detectGPU: () => Promise<BasicGPUInfo>;
|
||||||
detectCPU: () => Promise<CPUCapabilities>;
|
detectCPU: () => Promise<CPUCapabilities>;
|
||||||
detectGPUCapabilities: () => Promise<GPUCapabilities>;
|
detectGPUCapabilities: () => Promise<GPUCapabilities>;
|
||||||
|
|
@ -118,11 +116,6 @@ export interface KoboldAPI {
|
||||||
downloadRelease: (
|
downloadRelease: (
|
||||||
asset: GitHubAsset
|
asset: GitHubAsset
|
||||||
) => Promise<{ success: boolean; path?: string; error?: string }>;
|
) => Promise<{ success: boolean; path?: string; error?: string }>;
|
||||||
downloadROCm: () => Promise<{
|
|
||||||
success: boolean;
|
|
||||||
path?: string;
|
|
||||||
error?: string;
|
|
||||||
}>;
|
|
||||||
launchKoboldCpp: (
|
launchKoboldCpp: (
|
||||||
args?: string[]
|
args?: string[]
|
||||||
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
) => Promise<{ success: boolean; pid?: number; error?: string }>;
|
||||||
|
|
|
||||||
7
src/types/hardware.d.ts
vendored
7
src/types/hardware.d.ts
vendored
|
|
@ -40,12 +40,7 @@ export interface HardwareInfo {
|
||||||
gpuCapabilities?: GPUCapabilities;
|
gpuCapabilities?: GPUCapabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformInfo {
|
|
||||||
platform: string;
|
|
||||||
arch: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SystemCapabilities {
|
export interface SystemCapabilities {
|
||||||
hardware: HardwareInfo;
|
hardware: HardwareInfo;
|
||||||
platform: PlatformInfo;
|
platform: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
src/types/index.d.ts
vendored
2
src/types/index.d.ts
vendored
|
|
@ -18,7 +18,7 @@ export interface GitHubAsset {
|
||||||
name: string;
|
name: string;
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
size: number;
|
size: number;
|
||||||
created_at: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubRelease {
|
export interface GitHubRelease {
|
||||||
|
|
|
||||||
55
src/utils/fs.ts
Normal file
55
src/utils/fs.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { readFile, writeFile, access, mkdir } from 'fs/promises';
|
||||||
|
import { constants } from 'fs';
|
||||||
|
|
||||||
|
export const pathExists = async (path: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await access(path, constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readJsonFile = async <T = unknown>(
|
||||||
|
path: string
|
||||||
|
): Promise<T | null> => {
|
||||||
|
try {
|
||||||
|
const content = await readFile(path, 'utf-8');
|
||||||
|
return JSON.parse(content) as T;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeJsonFile = async (
|
||||||
|
path: string,
|
||||||
|
data: unknown
|
||||||
|
): Promise<void> => {
|
||||||
|
const content = JSON.stringify(data, null, 2);
|
||||||
|
await writeFile(path, content, 'utf-8');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ensureDir = async (path: string): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await mkdir(path, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as { code?: string }).code !== 'EEXIST') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readTextFile = async (path: string): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
return readFile(path, 'utf-8');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeTextFile = async (
|
||||||
|
path: string,
|
||||||
|
content: string
|
||||||
|
): Promise<void> => {
|
||||||
|
await writeFile(path, content, 'utf-8');
|
||||||
|
};
|
||||||
|
|
@ -10,7 +10,7 @@ export class Logger {
|
||||||
errorMessage: string
|
errorMessage: string
|
||||||
): Promise<T | null> {
|
): Promise<T | null> {
|
||||||
try {
|
try {
|
||||||
return await operation();
|
return operation();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logError(errorMessage, error as Error);
|
this.logError(errorMessage, error as Error);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import { GITHUB_API, ROCM } from '@/constants';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import type { DownloadItem } from '@/types/electron';
|
||||||
import type { GitHubAsset } from '@/types';
|
import type { GitHubAsset } from '@/types';
|
||||||
|
|
||||||
export async function getROCmDownload(
|
export async function getROCmDownload(): Promise<DownloadItem | null> {
|
||||||
platform: string
|
const platform = await window.electronAPI.kobold.getPlatform();
|
||||||
): Promise<DownloadItem | null> {
|
|
||||||
if (platform === 'linux') {
|
if (platform === 'linux') {
|
||||||
return getLinuxROCmDownload();
|
return getLinuxROCmDownload();
|
||||||
} else if (platform === 'win32') {
|
} else if (platform === 'win32') {
|
||||||
|
|
@ -29,7 +29,6 @@ async function getLinuxROCmDownload(): Promise<DownloadItem | null> {
|
||||||
url: ROCM.LINUX.DOWNLOAD_URL,
|
url: ROCM.LINUX.DOWNLOAD_URL,
|
||||||
size: ROCM.LINUX.SIZE_BYTES_APPROX,
|
size: ROCM.LINUX.SIZE_BYTES_APPROX,
|
||||||
version,
|
version,
|
||||||
type: 'rocm',
|
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return {
|
return {
|
||||||
|
|
@ -37,7 +36,6 @@ async function getLinuxROCmDownload(): Promise<DownloadItem | null> {
|
||||||
url: ROCM.LINUX.DOWNLOAD_URL,
|
url: ROCM.LINUX.DOWNLOAD_URL,
|
||||||
size: ROCM.LINUX.SIZE_BYTES_APPROX,
|
size: ROCM.LINUX.SIZE_BYTES_APPROX,
|
||||||
version: 'unknown',
|
version: 'unknown',
|
||||||
type: 'rocm',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,11 +64,10 @@ async function getWindowsROCmDownload(): Promise<DownloadItem | null> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: windowsAsset.name.replace('.exe', ''),
|
name: windowsAsset.name,
|
||||||
url: windowsAsset.browser_download_url,
|
url: windowsAsset.browser_download_url,
|
||||||
size: windowsAsset.size,
|
size: windowsAsset.size,
|
||||||
version,
|
version,
|
||||||
type: 'rocm',
|
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
import { createWriteStream, chmodSync, existsSync } from 'fs';
|
import { createWriteStream } from 'fs';
|
||||||
import { unlink, rename, mkdir } from 'fs/promises';
|
import { unlink, rename, mkdir, chmod } from 'fs/promises';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { pipeline } from 'stream/promises';
|
import axios from 'axios';
|
||||||
import got from 'got';
|
|
||||||
import type { LogManager } from '@/main/managers/LogManager';
|
import type { LogManager } from '@/main/managers/LogManager';
|
||||||
import type { ConfigManager } from '@/main/managers/ConfigManager';
|
import type { ConfigManager } from '@/main/managers/ConfigManager';
|
||||||
import type { WindowManager } from '@/main/managers/WindowManager';
|
import type { WindowManager } from '@/main/managers/WindowManager';
|
||||||
import type { DownloadItem } from '@/types/electron';
|
import type { DownloadItem } from '@/types/electron';
|
||||||
import { stripAssetExtensions } from '@/utils/version';
|
import { stripAssetExtensions } from '@/utils/version';
|
||||||
import type { ChildProcess } from 'child_process';
|
import type { ChildProcess } from 'child_process';
|
||||||
|
import { pathExists } from '@/utils/fs';
|
||||||
|
|
||||||
export interface DownloadOptions {
|
export interface DownloadOptions {
|
||||||
installDir: string;
|
installDir: string;
|
||||||
onProgress?: (progress: number) => void;
|
onProgress?: (progress: number) => void;
|
||||||
isUpdate?: boolean;
|
isUpdate?: boolean;
|
||||||
wasCurrentBinary?: boolean;
|
wasCurrentBinary?: boolean;
|
||||||
|
version?: string;
|
||||||
logManager: LogManager;
|
logManager: LogManager;
|
||||||
configManager: ConfigManager;
|
configManager: ConfigManager;
|
||||||
windowManager: WindowManager;
|
windowManager: WindowManager;
|
||||||
unpackFunction: (packedPath: string, unpackDir: string) => Promise<void>;
|
unpackFunction: (packedPath: string, unpackDir: string) => Promise<void>;
|
||||||
getLauncherPath: (unpackedDir: string) => string | null;
|
getLauncherPath: (unpackedDir: string) => Promise<string | null>;
|
||||||
removeDirectoryWithRetry?: (dirPath: string) => Promise<void>;
|
removeDirectoryWithRetry?: (dirPath: string) => Promise<void>;
|
||||||
cleanup?: () => Promise<void>;
|
cleanup?: () => Promise<void>;
|
||||||
koboldProcess?: ChildProcess;
|
koboldProcess?: ChildProcess;
|
||||||
|
|
@ -40,7 +41,7 @@ async function handleExistingDirectory(
|
||||||
removeDirectoryWithRetry: ((dirPath: string) => Promise<void>) | undefined,
|
removeDirectoryWithRetry: ((dirPath: string) => Promise<void>) | undefined,
|
||||||
logManager: LogManager
|
logManager: LogManager
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
if (!isUpdate || !existsSync(unpackedDirPath)) {
|
if (!isUpdate || !(await pathExists(unpackedDirPath))) {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,24 +82,34 @@ async function downloadFile(
|
||||||
onProgress: ((progress: number) => void) | undefined,
|
onProgress: ((progress: number) => void) | undefined,
|
||||||
logManager: LogManager
|
logManager: LogManager
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<{ success: boolean; error?: string }> {
|
||||||
|
try {
|
||||||
const writer = createWriteStream(tempPackedFilePath);
|
const writer = createWriteStream(tempPackedFilePath);
|
||||||
let downloadedBytes = 0;
|
let downloadedBytes = 0;
|
||||||
|
|
||||||
|
const response = await axios({
|
||||||
|
method: 'GET',
|
||||||
|
url: item.url,
|
||||||
|
responseType: 'stream',
|
||||||
|
timeout: 30000,
|
||||||
|
maxRedirects: 5,
|
||||||
|
});
|
||||||
|
|
||||||
const totalBytes = item.size;
|
const totalBytes = item.size;
|
||||||
|
|
||||||
try {
|
response.data.on('data', (chunk: Buffer) => {
|
||||||
await pipeline(
|
downloadedBytes += chunk.length;
|
||||||
got.stream(item.url).on('downloadProgress', (progress) => {
|
|
||||||
downloadedBytes = progress.transferred;
|
|
||||||
if (onProgress && totalBytes > 0) {
|
if (onProgress && totalBytes > 0) {
|
||||||
onProgress((downloadedBytes / totalBytes) * 100);
|
onProgress((downloadedBytes / totalBytes) * 100);
|
||||||
}
|
}
|
||||||
}),
|
});
|
||||||
writer
|
|
||||||
);
|
|
||||||
|
|
||||||
|
response.data.pipe(writer);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
writer.on('finish', async () => {
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
try {
|
try {
|
||||||
chmodSync(tempPackedFilePath, 0o755);
|
await chmod(tempPackedFilePath, 0o755);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logManager.logError(
|
logManager.logError(
|
||||||
'Failed to make binary executable:',
|
'Failed to make binary executable:',
|
||||||
|
|
@ -106,16 +117,20 @@ async function downloadFile(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resolve({ success: true });
|
||||||
|
});
|
||||||
|
writer.on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
return { success: true };
|
response.data.on('error', (error: Error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return { success: false, error: (error as Error).message };
|
||||||
success: false,
|
|
||||||
error: `Download failed: ${(error as Error).message}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadAndUnpackBinary(
|
export async function downloadAndUnpackBinary(
|
||||||
item: DownloadItem,
|
item: DownloadItem,
|
||||||
options: DownloadOptions
|
options: DownloadOptions
|
||||||
|
|
@ -125,6 +140,7 @@ export async function downloadAndUnpackBinary(
|
||||||
onProgress,
|
onProgress,
|
||||||
isUpdate,
|
isUpdate,
|
||||||
wasCurrentBinary,
|
wasCurrentBinary,
|
||||||
|
version,
|
||||||
logManager,
|
logManager,
|
||||||
configManager,
|
configManager,
|
||||||
windowManager,
|
windowManager,
|
||||||
|
|
@ -137,7 +153,8 @@ export async function downloadAndUnpackBinary(
|
||||||
|
|
||||||
const tempPackedFilePath = join(installDir, `${item.name}.packed`);
|
const tempPackedFilePath = join(installDir, `${item.name}.packed`);
|
||||||
const baseFilename = stripAssetExtensions(item.name);
|
const baseFilename = stripAssetExtensions(item.name);
|
||||||
const unpackedDirPath = join(installDir, baseFilename);
|
const folderName = version ? `${baseFilename}-${version}` : baseFilename;
|
||||||
|
const unpackedDirPath = join(installDir, folderName);
|
||||||
|
|
||||||
const dirResult = await handleExistingDirectory(
|
const dirResult = await handleExistingDirectory(
|
||||||
unpackedDirPath,
|
unpackedDirPath,
|
||||||
|
|
@ -167,16 +184,16 @@ export async function downloadAndUnpackBinary(
|
||||||
await mkdir(unpackedDirPath, { recursive: true });
|
await mkdir(unpackedDirPath, { recursive: true });
|
||||||
await unpackFunction(tempPackedFilePath, unpackedDirPath);
|
await unpackFunction(tempPackedFilePath, unpackedDirPath);
|
||||||
|
|
||||||
let launcherPath = getLauncherPath(unpackedDirPath);
|
let launcherPath = await getLauncherPath(unpackedDirPath);
|
||||||
|
|
||||||
if (!launcherPath || !existsSync(launcherPath)) {
|
if (!launcherPath || !(await pathExists(launcherPath))) {
|
||||||
const expectedLauncherName =
|
const expectedLauncherName =
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
? 'koboldcpp-launcher.exe'
|
? 'koboldcpp-launcher.exe'
|
||||||
: 'koboldcpp-launcher';
|
: 'koboldcpp-launcher';
|
||||||
const newLauncherPath = join(unpackedDirPath, expectedLauncherName);
|
const newLauncherPath = join(unpackedDirPath, expectedLauncherName);
|
||||||
|
|
||||||
if (existsSync(tempPackedFilePath)) {
|
if (await pathExists(tempPackedFilePath)) {
|
||||||
try {
|
try {
|
||||||
await rename(tempPackedFilePath, newLauncherPath);
|
await rename(tempPackedFilePath, newLauncherPath);
|
||||||
launcherPath = newLauncherPath;
|
launcherPath = newLauncherPath;
|
||||||
|
|
@ -195,10 +212,10 @@ export async function downloadAndUnpackBinary(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (launcherPath && existsSync(launcherPath)) {
|
if (launcherPath && (await pathExists(launcherPath))) {
|
||||||
const currentBinary = configManager.getCurrentKoboldBinary();
|
const currentBinary = configManager.getCurrentKoboldBinary();
|
||||||
if (!currentBinary || (isUpdate && wasCurrentBinary)) {
|
if (!currentBinary || (isUpdate && wasCurrentBinary)) {
|
||||||
configManager.setCurrentKoboldBinary(launcherPath);
|
await configManager.setCurrentKoboldBinary(launcherPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
windowManager.sendToRenderer('versions-updated');
|
windowManager.sendToRenderer('versions-updated');
|
||||||
|
|
|
||||||
153
yarn.lock
153
yarn.lock
|
|
@ -1142,13 +1142,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@sindresorhus/is@npm:^7.0.1":
|
|
||||||
version: 7.0.2
|
|
||||||
resolution: "@sindresorhus/is@npm:7.0.2"
|
|
||||||
checksum: 10c0/50881c9b651e189972087de9104e0d259a2a0dc93c604e863b3be1847e31c3dce685e76a41c0ae92198ae02b36d30d07b723a2d72015ce3cf910afc6dc337ff5
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@sindresorhus/merge-streams@npm:^4.0.0":
|
"@sindresorhus/merge-streams@npm:^4.0.0":
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
resolution: "@sindresorhus/merge-streams@npm:4.0.0"
|
resolution: "@sindresorhus/merge-streams@npm:4.0.0"
|
||||||
|
|
@ -1165,15 +1158,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@szmarczak/http-timer@npm:^5.0.1":
|
|
||||||
version: 5.0.1
|
|
||||||
resolution: "@szmarczak/http-timer@npm:5.0.1"
|
|
||||||
dependencies:
|
|
||||||
defer-to-connect: "npm:^2.0.1"
|
|
||||||
checksum: 10c0/4629d2fbb2ea67c2e9dc03af235c0991c79ebdddcbc19aed5d5732fb29ce01c13331e9b1a491584b9069bd6ecde6581dcbf871f11b7eefdebbab34de6cf2197e
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@tootallnate/once@npm:2":
|
"@tootallnate/once@npm:2":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "@tootallnate/once@npm:2.0.0"
|
resolution: "@tootallnate/once@npm:2.0.0"
|
||||||
|
|
@ -1259,7 +1243,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.4":
|
"@types/http-cache-semantics@npm:*":
|
||||||
version: 4.0.4
|
version: 4.0.4
|
||||||
resolution: "@types/http-cache-semantics@npm:4.0.4"
|
resolution: "@types/http-cache-semantics@npm:4.0.4"
|
||||||
checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6
|
checksum: 10c0/51b72568b4b2863e0fe8d6ce8aad72a784b7510d72dc866215642da51d84945a9459fa89f49ec48f1e9a1752e6a78e85a4cda0ded06b1c73e727610c925f9ce6
|
||||||
|
|
@ -1885,6 +1869,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"axios@npm:^1.11.0":
|
||||||
|
version: 1.11.0
|
||||||
|
resolution: "axios@npm:1.11.0"
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: "npm:^1.15.6"
|
||||||
|
form-data: "npm:^4.0.4"
|
||||||
|
proxy-from-env: "npm:^1.1.0"
|
||||||
|
checksum: 10c0/5de273d33d43058610e4d252f0963cc4f10714da0bfe872e8ef2cbc23c2c999acc300fd357b6bce0fc84a2ca9bd45740fa6bb28199ce2c1266c8b1a393f2b36e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"balanced-match@npm:^1.0.0":
|
"balanced-match@npm:^1.0.0":
|
||||||
version: 1.0.2
|
version: 1.0.2
|
||||||
resolution: "balanced-match@npm:1.0.2"
|
resolution: "balanced-match@npm:1.0.2"
|
||||||
|
|
@ -2092,28 +2087,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"cacheable-lookup@npm:^7.0.0":
|
|
||||||
version: 7.0.0
|
|
||||||
resolution: "cacheable-lookup@npm:7.0.0"
|
|
||||||
checksum: 10c0/63a9c144c5b45cb5549251e3ea774c04d63063b29e469f7584171d059d3a88f650f47869a974e2d07de62116463d742c287a81a625e791539d987115cb081635
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"cacheable-request@npm:^12.0.1":
|
|
||||||
version: 12.0.1
|
|
||||||
resolution: "cacheable-request@npm:12.0.1"
|
|
||||||
dependencies:
|
|
||||||
"@types/http-cache-semantics": "npm:^4.0.4"
|
|
||||||
get-stream: "npm:^9.0.1"
|
|
||||||
http-cache-semantics: "npm:^4.1.1"
|
|
||||||
keyv: "npm:^4.5.4"
|
|
||||||
mimic-response: "npm:^4.0.0"
|
|
||||||
normalize-url: "npm:^8.0.1"
|
|
||||||
responselike: "npm:^3.0.0"
|
|
||||||
checksum: 10c0/3ccc26519c8dd0821fcb21fa00781e55f05ab6e1da1487fbbee9c8c03435a3cf72c29a710a991cebe398fb9a5274e2a772fc488546d402db8dc21310764ed83a
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"cacheable-request@npm:^7.0.2":
|
"cacheable-request@npm:^7.0.2":
|
||||||
version: 7.0.4
|
version: 7.0.4
|
||||||
resolution: "cacheable-request@npm:7.0.4"
|
resolution: "cacheable-request@npm:7.0.4"
|
||||||
|
|
@ -2545,7 +2518,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1":
|
"defer-to-connect@npm:^2.0.0":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "defer-to-connect@npm:2.0.1"
|
resolution: "defer-to-connect@npm:2.0.1"
|
||||||
checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782
|
checksum: 10c0/625ce28e1b5ad10cf77057b9a6a727bf84780c17660f6644dab61dd34c23de3001f03cedc401f7d30a4ed9965c2e8a7336e220a329146f2cf85d4eddea429782
|
||||||
|
|
@ -3555,6 +3528,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"follow-redirects@npm:^1.15.6":
|
||||||
|
version: 1.15.11
|
||||||
|
resolution: "follow-redirects@npm:1.15.11"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"for-each@npm:^0.3.3, for-each@npm:^0.3.5":
|
"for-each@npm:^0.3.3, for-each@npm:^0.3.5":
|
||||||
version: 0.3.5
|
version: 0.3.5
|
||||||
resolution: "for-each@npm:0.3.5"
|
resolution: "for-each@npm:0.3.5"
|
||||||
|
|
@ -3574,14 +3557,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"form-data-encoder@npm:^4.0.2":
|
"form-data@npm:^4.0.0, form-data@npm:^4.0.4":
|
||||||
version: 4.1.0
|
|
||||||
resolution: "form-data-encoder@npm:4.1.0"
|
|
||||||
checksum: 10c0/cbd655aa8ffff6f7c2733b1d8e95fa9a2fe8a88a90bde29fb54b8e02c9406e51f32a014bfe8297d67fbac9f77614d14a8b4bbc4fd0352838e67e97a881d06332
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"form-data@npm:^4.0.0":
|
|
||||||
version: 4.0.4
|
version: 4.0.4
|
||||||
resolution: "form-data@npm:4.0.4"
|
resolution: "form-data@npm:4.0.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -3607,6 +3583,7 @@ __metadata:
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.41.0"
|
"@typescript-eslint/eslint-plugin": "npm:^8.41.0"
|
||||||
"@typescript-eslint/parser": "npm:^8.41.0"
|
"@typescript-eslint/parser": "npm:^8.41.0"
|
||||||
"@vitejs/plugin-react": "npm:^5.0.2"
|
"@vitejs/plugin-react": "npm:^5.0.2"
|
||||||
|
axios: "npm:^1.11.0"
|
||||||
cross-env: "npm:^10.0.0"
|
cross-env: "npm:^10.0.0"
|
||||||
electron: "npm:^37.4.0"
|
electron: "npm:^37.4.0"
|
||||||
electron-builder: "npm:^26.0.12"
|
electron-builder: "npm:^26.0.12"
|
||||||
|
|
@ -3620,7 +3597,6 @@ __metadata:
|
||||||
eslint-plugin-sonarjs: "npm:^3.0.5"
|
eslint-plugin-sonarjs: "npm:^3.0.5"
|
||||||
execa: "npm:^9.6.0"
|
execa: "npm:^9.6.0"
|
||||||
globals: "npm:^16.3.0"
|
globals: "npm:^16.3.0"
|
||||||
got: "npm:^14.4.7"
|
|
||||||
husky: "npm:^9.1.7"
|
husky: "npm:^9.1.7"
|
||||||
jiti: "npm:^2.5.1"
|
jiti: "npm:^2.5.1"
|
||||||
lint-staged: "npm:^16.1.5"
|
lint-staged: "npm:^16.1.5"
|
||||||
|
|
@ -3827,7 +3803,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"get-stream@npm:^9.0.0, get-stream@npm:^9.0.1":
|
"get-stream@npm:^9.0.0":
|
||||||
version: 9.0.1
|
version: 9.0.1
|
||||||
resolution: "get-stream@npm:9.0.1"
|
resolution: "get-stream@npm:9.0.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -3973,25 +3949,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"got@npm:^14.4.7":
|
|
||||||
version: 14.4.7
|
|
||||||
resolution: "got@npm:14.4.7"
|
|
||||||
dependencies:
|
|
||||||
"@sindresorhus/is": "npm:^7.0.1"
|
|
||||||
"@szmarczak/http-timer": "npm:^5.0.1"
|
|
||||||
cacheable-lookup: "npm:^7.0.0"
|
|
||||||
cacheable-request: "npm:^12.0.1"
|
|
||||||
decompress-response: "npm:^6.0.0"
|
|
||||||
form-data-encoder: "npm:^4.0.2"
|
|
||||||
http2-wrapper: "npm:^2.2.1"
|
|
||||||
lowercase-keys: "npm:^3.0.0"
|
|
||||||
p-cancelable: "npm:^4.0.1"
|
|
||||||
responselike: "npm:^3.0.0"
|
|
||||||
type-fest: "npm:^4.26.1"
|
|
||||||
checksum: 10c0/9b5b8dbc0642c78dbc64ab5ff6f12f6edab3e0cb80e89a3a69623a79ba3986f0ff0066a116fba47c0aacce4b0ba1eccf72f923f7fac13a31ce852bf9e2cb8f81
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6":
|
"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6":
|
||||||
version: 4.2.11
|
version: 4.2.11
|
||||||
resolution: "graceful-fs@npm:4.2.11"
|
resolution: "graceful-fs@npm:4.2.11"
|
||||||
|
|
@ -4110,16 +4067,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"http2-wrapper@npm:^2.2.1":
|
|
||||||
version: 2.2.1
|
|
||||||
resolution: "http2-wrapper@npm:2.2.1"
|
|
||||||
dependencies:
|
|
||||||
quick-lru: "npm:^5.1.1"
|
|
||||||
resolve-alpn: "npm:^1.2.0"
|
|
||||||
checksum: 10c0/7207201d3c6e53e72e510c9b8912e4f3e468d3ecc0cf3bf52682f2aac9cd99358b896d1da4467380adc151cf97c412bedc59dc13dae90c523f42053a7449eedb
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"https-proxy-agent@npm:^5.0.0":
|
"https-proxy-agent@npm:^5.0.0":
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
resolution: "https-proxy-agent@npm:5.0.1"
|
resolution: "https-proxy-agent@npm:5.0.1"
|
||||||
|
|
@ -4955,13 +4902,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lowercase-keys@npm:^3.0.0":
|
|
||||||
version: 3.0.0
|
|
||||||
resolution: "lowercase-keys@npm:3.0.0"
|
|
||||||
checksum: 10c0/ef62b9fa5690ab0a6e4ef40c94efce68e3ed124f583cc3be38b26ff871da0178a28b9a84ce0c209653bb25ca135520ab87fea7cd411a54ac4899cb2f30501430
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
|
"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0":
|
||||||
version: 10.4.3
|
version: 10.4.3
|
||||||
resolution: "lru-cache@npm:10.4.3"
|
resolution: "lru-cache@npm:10.4.3"
|
||||||
|
|
@ -5141,13 +5081,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"mimic-response@npm:^4.0.0":
|
|
||||||
version: 4.0.0
|
|
||||||
resolution: "mimic-response@npm:4.0.0"
|
|
||||||
checksum: 10c0/761d788d2668ae9292c489605ffd4fad220f442fbae6832adce5ebad086d691e906a6d5240c290293c7a11e99fbdbbef04abbbed498bf8699a4ee0f31315e3fb
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"minimatch@npm:9.0.5, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4":
|
"minimatch@npm:9.0.5, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4":
|
||||||
version: 9.0.5
|
version: 9.0.5
|
||||||
resolution: "minimatch@npm:9.0.5"
|
resolution: "minimatch@npm:9.0.5"
|
||||||
|
|
@ -5460,13 +5393,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"normalize-url@npm:^8.0.1":
|
|
||||||
version: 8.0.2
|
|
||||||
resolution: "normalize-url@npm:8.0.2"
|
|
||||||
checksum: 10c0/1c62eee6ce184ad4a463ff2984ce5e440a5058c9dd7c5ef80c0a7696bbb1d3638534e266afb14ef9678dfa07fb6c980ef4cde990c80eeee55900c378b7970584
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"npm-run-path@npm:^6.0.0":
|
"npm-run-path@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "npm-run-path@npm:6.0.0"
|
resolution: "npm-run-path@npm:6.0.0"
|
||||||
|
|
@ -5662,13 +5588,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"p-cancelable@npm:^4.0.1":
|
|
||||||
version: 4.0.1
|
|
||||||
resolution: "p-cancelable@npm:4.0.1"
|
|
||||||
checksum: 10c0/12636623f46784ba962b6fe7a1f34d021f1d9a2cc12c43e270baa715ea872d5c8c7d9f086ed420b8b9817e91d9bbe92c14c90e5dddd4a9968c81a2a7aef7089d
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0 ":
|
"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0 ":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "p-limit@npm:3.1.0"
|
resolution: "p-limit@npm:3.1.0"
|
||||||
|
|
@ -5918,6 +5837,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"proxy-from-env@npm:^1.1.0":
|
||||||
|
version: 1.1.0
|
||||||
|
resolution: "proxy-from-env@npm:1.1.0"
|
||||||
|
checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"pump@npm:^3.0.0":
|
"pump@npm:^3.0.0":
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
resolution: "pump@npm:3.0.3"
|
resolution: "pump@npm:3.0.3"
|
||||||
|
|
@ -6142,7 +6068,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0":
|
"resolve-alpn@npm:^1.0.0":
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
resolution: "resolve-alpn@npm:1.2.1"
|
resolution: "resolve-alpn@npm:1.2.1"
|
||||||
checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4
|
checksum: 10c0/b70b29c1843bc39781ef946c8cd4482e6d425976599c0f9c138cec8209e4e0736161bf39319b01676a847000085dfdaf63583c6fb4427bf751a10635bd2aa0c4
|
||||||
|
|
@ -6217,15 +6143,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"responselike@npm:^3.0.0":
|
|
||||||
version: 3.0.0
|
|
||||||
resolution: "responselike@npm:3.0.0"
|
|
||||||
dependencies:
|
|
||||||
lowercase-keys: "npm:^3.0.0"
|
|
||||||
checksum: 10c0/8af27153f7e47aa2c07a5f2d538cb1e5872995f0e9ff77def858ecce5c3fe677d42b824a62cde502e56d275ab832b0a8bd350d5cd6b467ac0425214ac12ae658
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"restore-cursor@npm:^3.1.0":
|
"restore-cursor@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "restore-cursor@npm:3.1.0"
|
resolution: "restore-cursor@npm:3.1.0"
|
||||||
|
|
@ -7161,7 +7078,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"type-fest@npm:^4.26.1, type-fest@npm:^4.27.0":
|
"type-fest@npm:^4.27.0":
|
||||||
version: 4.41.0
|
version: 4.41.0
|
||||||
resolution: "type-fest@npm:4.41.0"
|
resolution: "type-fest@npm:4.41.0"
|
||||||
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
|
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue