mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
allow analyzing local .gguf model files
This commit is contained in:
parent
eb921c93b3
commit
273d1b690f
12 changed files with 408 additions and 52 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.7.3",
|
"version": "1.8.0",
|
||||||
"description": "Run Large Language Models locally",
|
"description": "Run Large Language Models locally",
|
||||||
"main": "out/main/index.js",
|
"main": "out/main/index.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
"@types/yauzl": "^2.10.3",
|
"@types/yauzl": "^2.10.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
||||||
"@typescript-eslint/parser": "^8.46.2",
|
"@typescript-eslint/parser": "^8.46.2",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.1.0",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"electron": "^38.4.0",
|
"electron": "^38.4.0",
|
||||||
"electron-builder": "^26.0.12",
|
"electron-builder": "^26.0.12",
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
"eslint-plugin-no-comments": "^1.1.10",
|
"eslint-plugin-no-comments": "^1.1.10",
|
||||||
"eslint-plugin-promise": "^7.2.1",
|
"eslint-plugin-promise": "^7.2.1",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.0",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-sonarjs": "^3.0.5",
|
"eslint-plugin-sonarjs": "^3.0.5",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"jiti": "^2.6.1",
|
"jiti": "^2.6.1",
|
||||||
|
|
@ -69,12 +69,13 @@
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@codemirror/view": "^6.38.6",
|
"@codemirror/view": "^6.38.6",
|
||||||
"@fontsource/inter": "^5.2.8",
|
"@fontsource/inter": "^5.2.8",
|
||||||
|
"@huggingface/gguf": "^0.3.2",
|
||||||
"@mantine/core": "^8.3.5",
|
"@mantine/core": "^8.3.5",
|
||||||
"@mantine/hooks": "^8.3.5",
|
"@mantine/hooks": "^8.3.5",
|
||||||
"@uiw/react-codemirror": "^4.25.2",
|
"@uiw/react-codemirror": "^4.25.2",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"lucide-react": "^0.546.0",
|
"lucide-react": "^0.548.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => {
|
||||||
tooltip="Select a GGUF text generation model file for chat and completion tasks."
|
tooltip="Select a GGUF text generation model file for chat and completion tasks."
|
||||||
onChange={handleModelChange}
|
onChange={handleModelChange}
|
||||||
onSelectFile={handleSelectModelFile}
|
onSelectFile={handleSelectModelFile}
|
||||||
showSearchHF
|
|
||||||
searchUrl="https://huggingface.co/models?pipeline_tag=text-generation&library=gguf&sort=trending"
|
searchUrl="https://huggingface.co/models?pipeline_tag=text-generation&library=gguf&sort=trending"
|
||||||
|
showAnalyze
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ export const ImageGenerationTab = () => {
|
||||||
tooltip="The primary image generation model. This is the main model that will generate images."
|
tooltip="The primary image generation model. This is the main model that will generate images."
|
||||||
onChange={handleSdmodelChange}
|
onChange={handleSdmodelChange}
|
||||||
onSelectFile={handleSelectSdmodelFile}
|
onSelectFile={handleSelectSdmodelFile}
|
||||||
showSearchHF
|
|
||||||
searchUrl="https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending"
|
searchUrl="https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending"
|
||||||
|
showAnalyze
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
|
|
|
||||||
135
src/components/screens/Launch/ModelAnalysisModal.tsx
Normal file
135
src/components/screens/Launch/ModelAnalysisModal.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { Modal } from '@/components/Modal';
|
||||||
|
import { Stack, Group, Text, Divider, Alert, rem } from '@mantine/core';
|
||||||
|
import { Info } from 'lucide-react';
|
||||||
|
import type { ModelAnalysis } from '@/types';
|
||||||
|
|
||||||
|
interface ModelAnalysisModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
analysis: ModelAnalysis | null;
|
||||||
|
loading?: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InfoRowProps {
|
||||||
|
label: string;
|
||||||
|
value?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InfoRow = ({ label, value }: InfoRowProps) => {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group gap="md" wrap="nowrap">
|
||||||
|
<Text size="sm" c="dimmed" style={{ minWidth: rem(160) }}>
|
||||||
|
{label}:
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModelAnalysisModal = ({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
analysis,
|
||||||
|
loading = false,
|
||||||
|
error,
|
||||||
|
}: ModelAnalysisModalProps) => (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title={
|
||||||
|
<Group gap="xs">
|
||||||
|
<Info size={20} />
|
||||||
|
<Text>Model Analysis</Text>
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
size="lg"
|
||||||
|
showCloseButton
|
||||||
|
>
|
||||||
|
{loading && (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Analyzing model...
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert color="red" title="Analysis Failed">
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{analysis && (
|
||||||
|
<Stack gap="md">
|
||||||
|
<div>
|
||||||
|
<Text size="sm" fw={600} mb="xs">
|
||||||
|
General Information
|
||||||
|
</Text>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<InfoRow
|
||||||
|
label="Architecture"
|
||||||
|
value={analysis.general.architecture}
|
||||||
|
/>
|
||||||
|
{analysis.general.name && (
|
||||||
|
<InfoRow label="Model Name" value={analysis.general.name} />
|
||||||
|
)}
|
||||||
|
{analysis.general.parameterCount && (
|
||||||
|
<InfoRow
|
||||||
|
label="Parameters"
|
||||||
|
value={analysis.general.parameterCount}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<InfoRow label="File Size" value={analysis.general.fileSize} />
|
||||||
|
{analysis.architecture.layers && (
|
||||||
|
<InfoRow label="Layers" value={analysis.architecture.layers} />
|
||||||
|
)}
|
||||||
|
{analysis.architecture.expertCount && (
|
||||||
|
<InfoRow
|
||||||
|
label="Expert Count (MoE)"
|
||||||
|
value={analysis.architecture.expertCount}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{analysis.context.maxContextLength && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<div>
|
||||||
|
<Text size="sm" fw={600} mb="xs">
|
||||||
|
Context
|
||||||
|
</Text>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<InfoRow
|
||||||
|
label="Max Context Length"
|
||||||
|
value={analysis.context.maxContextLength}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text size="sm" fw={600} mb="xs">
|
||||||
|
Memory Estimates
|
||||||
|
</Text>
|
||||||
|
<Stack gap="xs">
|
||||||
|
<InfoRow label="Full VRAM" value={analysis.estimates.fullGpuVram} />
|
||||||
|
<InfoRow label="Full RAM" value={analysis.estimates.systemRam} />
|
||||||
|
{analysis.estimates.vramPerLayer && (
|
||||||
|
<InfoRow
|
||||||
|
label="VRAM per Layer"
|
||||||
|
value={analysis.estimates.vramPerLayer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import { Group, TextInput, Button } from '@mantine/core';
|
import { Group, TextInput, ActionIcon, Tooltip, Button } from '@mantine/core';
|
||||||
import { File, Search } from 'lucide-react';
|
import { useState } from 'react';
|
||||||
|
import { File, Search, Info } from 'lucide-react';
|
||||||
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
|
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
|
||||||
|
import { ModelAnalysisModal } from '@/components/screens/Launch/ModelAnalysisModal';
|
||||||
import { getInputValidationState } from '@/utils/validation';
|
import { getInputValidationState } from '@/utils/validation';
|
||||||
|
import { logError } from '@/utils/logger';
|
||||||
|
import type { ModelAnalysis } from '@/types';
|
||||||
|
|
||||||
interface ModelFileFieldProps {
|
interface ModelFileFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -10,8 +14,8 @@ interface ModelFileFieldProps {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
onSelectFile: () => void;
|
onSelectFile: () => void;
|
||||||
showSearchHF?: boolean;
|
|
||||||
searchUrl?: string;
|
searchUrl?: string;
|
||||||
|
showAnalyze?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelFileField = ({
|
export const ModelFileField = ({
|
||||||
|
|
@ -21,10 +25,16 @@ export const ModelFileField = ({
|
||||||
tooltip,
|
tooltip,
|
||||||
onChange,
|
onChange,
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
showSearchHF = false,
|
searchUrl,
|
||||||
searchUrl = 'https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending',
|
showAnalyze = false,
|
||||||
}: ModelFileFieldProps) => {
|
}: ModelFileFieldProps) => {
|
||||||
const validationState = getInputValidationState(value);
|
const validationState = getInputValidationState(value);
|
||||||
|
const [analysisModalOpened, setAnalysisModalOpened] = useState(false);
|
||||||
|
const [modelAnalysis, setModelAnalysis] = useState<ModelAnalysis | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [analysisLoading, setAnalysisLoading] = useState(false);
|
||||||
|
const [analysisError, setAnalysisError] = useState<string>();
|
||||||
|
|
||||||
const getHelperText = () => {
|
const getHelperText = () => {
|
||||||
if (!value.trim()) return undefined;
|
if (!value.trim()) return undefined;
|
||||||
|
|
@ -36,6 +46,29 @@ export const ModelFileField = ({
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isLocalFile = value.trim() && validationState === 'local';
|
||||||
|
|
||||||
|
const handleAnalyzeModel = async () => {
|
||||||
|
if (!value.trim()) return;
|
||||||
|
|
||||||
|
setAnalysisModalOpened(true);
|
||||||
|
setAnalysisLoading(true);
|
||||||
|
setAnalysisError(undefined);
|
||||||
|
setModelAnalysis(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const analysis = await window.electronAPI.kobold.analyzeModel(value);
|
||||||
|
setModelAnalysis(analysis);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : 'Failed to analyze model';
|
||||||
|
setAnalysisError(errorMessage);
|
||||||
|
logError('Failed to analyze model:', error as Error);
|
||||||
|
} finally {
|
||||||
|
setAnalysisLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LabelWithTooltip label={label} tooltip={tooltip} />
|
<LabelWithTooltip label={label} tooltip={tooltip} />
|
||||||
|
|
@ -55,16 +88,38 @@ export const ModelFileField = ({
|
||||||
>
|
>
|
||||||
Browse
|
Browse
|
||||||
</Button>
|
</Button>
|
||||||
{showSearchHF && (
|
{searchUrl && (
|
||||||
<Button
|
<Tooltip label="Search Hugging Face">
|
||||||
onClick={() => window.electronAPI.app.openExternal(searchUrl!)}
|
<ActionIcon
|
||||||
variant="outline"
|
onClick={() => window.electronAPI.app.openExternal(searchUrl)}
|
||||||
leftSection={<Search size={16} />}
|
variant="outline"
|
||||||
>
|
size="lg"
|
||||||
Search HF
|
>
|
||||||
</Button>
|
<Search size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{showAnalyze && isLocalFile && (
|
||||||
|
<Tooltip label="Analyze model">
|
||||||
|
<ActionIcon
|
||||||
|
onClick={handleAnalyzeModel}
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
<Info size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
<ModelAnalysisModal
|
||||||
|
opened={analysisModalOpened}
|
||||||
|
onClose={() => setAnalysisModalOpened(false)}
|
||||||
|
analysis={modelAnalysis}
|
||||||
|
loading={analysisLoading}
|
||||||
|
error={analysisError}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
selectModelFile,
|
selectModelFile,
|
||||||
selectInstallDirectory,
|
selectInstallDirectory,
|
||||||
} from '@/main/modules/koboldcpp/config';
|
} from '@/main/modules/koboldcpp/config';
|
||||||
|
import { analyzeGGUFModel } from '@/main/modules/koboldcpp/analyze';
|
||||||
import {
|
import {
|
||||||
get as getConfig,
|
get as getConfig,
|
||||||
set as setConfig,
|
set as setConfig,
|
||||||
|
|
@ -156,6 +157,10 @@ export function setupIPCHandlers() {
|
||||||
selectModelFile(title)
|
selectModelFile(title)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle('kobold:analyzeModel', async (_, filePath: string) =>
|
||||||
|
analyzeGGUFModel(filePath)
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle('config:get', (_, key) => getConfig(key));
|
ipcMain.handle('config:get', (_, key) => getConfig(key));
|
||||||
|
|
||||||
ipcMain.on('config:set', (_, key, value) => setConfig(key, value));
|
ipcMain.on('config:set', (_, key, value) => setConfig(key, value));
|
||||||
|
|
|
||||||
110
src/main/modules/koboldcpp/analyze.ts
Normal file
110
src/main/modules/koboldcpp/analyze.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { gguf } from '@huggingface/gguf';
|
||||||
|
import { stat } from 'fs/promises';
|
||||||
|
import { logError } from '@/utils/node/logging';
|
||||||
|
import { formatBytes } from '@/utils/format';
|
||||||
|
|
||||||
|
function estimateMemoryRequirements(fileSize: number) {
|
||||||
|
const vramOverhead = 1.1;
|
||||||
|
const fullGpuVram = fileSize * vramOverhead;
|
||||||
|
|
||||||
|
const ramOverhead = 1.2;
|
||||||
|
const systemRam = fileSize * ramOverhead;
|
||||||
|
|
||||||
|
return {
|
||||||
|
fullGpuVram: formatBytes(fullGpuVram),
|
||||||
|
systemRam: formatBytes(systemRam),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function estimateVramPerLayer(fileSize: number, layers?: number) {
|
||||||
|
if (!layers || layers === 0) return undefined;
|
||||||
|
const perLayer = fileSize / layers;
|
||||||
|
return formatBytes(perLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatParameterCount(params?: number) {
|
||||||
|
if (!params) return undefined;
|
||||||
|
if (params >= 1e9) return `${(params / 1e9).toFixed(1)}B`;
|
||||||
|
if (params >= 1e6) return `${(params / 1e6).toFixed(1)}M`;
|
||||||
|
if (params >= 1e3) return `${(params / 1e3).toFixed(1)}K`;
|
||||||
|
return params.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatContextLength(length?: number) {
|
||||||
|
if (!length) return undefined;
|
||||||
|
if (length >= 1e6) return `${(length / 1e6).toFixed(1)}M`;
|
||||||
|
if (length >= 1e3) return `${(length / 1e3).toFixed(0)}K`;
|
||||||
|
return length.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMetadataValue(metadata: Record<string, unknown>, key: string) {
|
||||||
|
return metadata[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function analyzeGGUFModel(filePath: string) {
|
||||||
|
try {
|
||||||
|
const stats = await stat(filePath);
|
||||||
|
const fileSize = stats.size;
|
||||||
|
|
||||||
|
const { metadata } = await gguf(filePath, {
|
||||||
|
allowLocalFile: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const metadataRecord = metadata as Record<string, unknown>;
|
||||||
|
|
||||||
|
const architecture = getMetadataValue(
|
||||||
|
metadataRecord,
|
||||||
|
'general.architecture'
|
||||||
|
) as string;
|
||||||
|
const name = getMetadataValue(metadataRecord, 'general.name') as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
const paramCount = getMetadataValue(
|
||||||
|
metadataRecord,
|
||||||
|
'general.parameter_count'
|
||||||
|
) as number | undefined;
|
||||||
|
|
||||||
|
const contextLength = getMetadataValue(
|
||||||
|
metadataRecord,
|
||||||
|
`${architecture}.context_length`
|
||||||
|
) as number | undefined;
|
||||||
|
|
||||||
|
const blockCount = getMetadataValue(
|
||||||
|
metadataRecord,
|
||||||
|
`${architecture}.block_count`
|
||||||
|
) as number | undefined;
|
||||||
|
const expertCount = getMetadataValue(
|
||||||
|
metadataRecord,
|
||||||
|
`${architecture}.expert_count`
|
||||||
|
) as number | undefined;
|
||||||
|
|
||||||
|
const memoryEstimates = estimateMemoryRequirements(fileSize);
|
||||||
|
const vramPerLayer = estimateVramPerLayer(fileSize, blockCount);
|
||||||
|
|
||||||
|
return {
|
||||||
|
general: {
|
||||||
|
architecture,
|
||||||
|
name,
|
||||||
|
fileSize: formatBytes(fileSize),
|
||||||
|
parameterCount: formatParameterCount(paramCount),
|
||||||
|
},
|
||||||
|
context: {
|
||||||
|
maxContextLength: formatContextLength(contextLength),
|
||||||
|
},
|
||||||
|
architecture: {
|
||||||
|
layers: blockCount,
|
||||||
|
expertCount,
|
||||||
|
},
|
||||||
|
estimates: {
|
||||||
|
fullGpuVram: memoryEstimates.fullGpuVram,
|
||||||
|
systemRam: memoryEstimates.systemRam,
|
||||||
|
vramPerLayer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
logError('Error analyzing GGUF model:', error as Error);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to analyze model: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,8 @@ const koboldAPI: KoboldAPI = {
|
||||||
ipcRenderer.invoke('kobold:parseConfigFile', filePath),
|
ipcRenderer.invoke('kobold:parseConfigFile', filePath),
|
||||||
selectModelFile: (title) =>
|
selectModelFile: (title) =>
|
||||||
ipcRenderer.invoke('kobold:selectModelFile', title),
|
ipcRenderer.invoke('kobold:selectModelFile', title),
|
||||||
|
analyzeModel: (filePath) =>
|
||||||
|
ipcRenderer.invoke('kobold:analyzeModel', filePath),
|
||||||
stopKoboldCpp: () => ipcRenderer.invoke('kobold:stopKoboldCpp'),
|
stopKoboldCpp: () => ipcRenderer.invoke('kobold:stopKoboldCpp'),
|
||||||
onDownloadProgress: (callback) => {
|
onDownloadProgress: (callback) => {
|
||||||
const handler = (_: IpcRendererEvent, progress: number) =>
|
const handler = (_: IpcRendererEvent, progress: number) =>
|
||||||
|
|
|
||||||
8
src/types/electron.d.ts
vendored
8
src/types/electron.d.ts
vendored
|
|
@ -5,7 +5,12 @@ import type {
|
||||||
GPUMemoryInfo,
|
GPUMemoryInfo,
|
||||||
SystemMemoryInfo,
|
SystemMemoryInfo,
|
||||||
} from '@/types/hardware';
|
} from '@/types/hardware';
|
||||||
import type { BackendOption, BackendSupport, Screen } from '@/types';
|
import type {
|
||||||
|
BackendOption,
|
||||||
|
BackendSupport,
|
||||||
|
Screen,
|
||||||
|
ModelAnalysis,
|
||||||
|
} from '@/types';
|
||||||
import type { MantineColorScheme } from '@mantine/core';
|
import type { MantineColorScheme } from '@mantine/core';
|
||||||
import type {
|
import type {
|
||||||
CpuMetrics,
|
CpuMetrics,
|
||||||
|
|
@ -142,6 +147,7 @@ export interface KoboldAPI {
|
||||||
setSelectedConfig: (configName: string) => Promise<boolean>;
|
setSelectedConfig: (configName: string) => Promise<boolean>;
|
||||||
parseConfigFile: (filePath: string) => Promise<KoboldConfig | null>;
|
parseConfigFile: (filePath: string) => Promise<KoboldConfig | null>;
|
||||||
selectModelFile: (title?: string) => Promise<string | null>;
|
selectModelFile: (title?: string) => Promise<string | null>;
|
||||||
|
analyzeModel: (filePath: string) => Promise<ModelAnalysis>;
|
||||||
stopKoboldCpp: () => void;
|
stopKoboldCpp: () => void;
|
||||||
onDownloadProgress: (callback: (progress: number) => void) => () => void;
|
onDownloadProgress: (callback: (progress: number) => void) => () => void;
|
||||||
onInstallDirChanged: (callback: (newPath: string) => void) => () => void;
|
onInstallDirChanged: (callback: (newPath: string) => void) => () => void;
|
||||||
|
|
|
||||||
21
src/types/index.d.ts
vendored
21
src/types/index.d.ts
vendored
|
|
@ -86,3 +86,24 @@ export interface BackendSupport {
|
||||||
failsafe: boolean;
|
failsafe: boolean;
|
||||||
cuda: boolean;
|
cuda: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelAnalysis {
|
||||||
|
general: {
|
||||||
|
architecture: string;
|
||||||
|
name?: string;
|
||||||
|
fileSize: string;
|
||||||
|
parameterCount?: string;
|
||||||
|
};
|
||||||
|
context: {
|
||||||
|
maxContextLength?: string;
|
||||||
|
};
|
||||||
|
architecture: {
|
||||||
|
layers?: number;
|
||||||
|
expertCount?: number;
|
||||||
|
};
|
||||||
|
estimates: {
|
||||||
|
fullGpuVram: string;
|
||||||
|
systemRam: string;
|
||||||
|
vramPerLayer?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,11 @@ const isValidFilePath = (path: string) => {
|
||||||
export const getInputValidationState = (path: string) => {
|
export const getInputValidationState = (path: string) => {
|
||||||
if (!path.trim()) return 'neutral';
|
if (!path.trim()) return 'neutral';
|
||||||
|
|
||||||
if (isValidUrl(path) || isValidFilePath(path)) {
|
const isUrl = isValidUrl(path);
|
||||||
return 'valid';
|
const isFile = isValidFilePath(path);
|
||||||
}
|
|
||||||
|
if (isUrl) return 'valid';
|
||||||
|
if (isFile) return 'local';
|
||||||
|
|
||||||
return 'invalid';
|
return 'invalid';
|
||||||
};
|
};
|
||||||
|
|
|
||||||
77
yarn.lock
77
yarn.lock
|
|
@ -803,6 +803,24 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@huggingface/gguf@npm:^0.3.2":
|
||||||
|
version: 0.3.2
|
||||||
|
resolution: "@huggingface/gguf@npm:0.3.2"
|
||||||
|
dependencies:
|
||||||
|
"@huggingface/tasks": "npm:^0.19.47"
|
||||||
|
bin:
|
||||||
|
gguf-view: dist/cli.js
|
||||||
|
checksum: 10c0/f39bfc6b468bdd4f9ee1da548dde504dd1cb6ceee75c6c8c2c5473ed48bed789cbf75604f3e04a1b91cf9b3714e4fa081a3fb8f2189196b659bfce4824e558f9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@huggingface/tasks@npm:^0.19.47":
|
||||||
|
version: 0.19.58
|
||||||
|
resolution: "@huggingface/tasks@npm:0.19.58"
|
||||||
|
checksum: 10c0/3349a674bba49dfd729285ae6bb5698291748ba1cc80c8eb1e390b6627d7e97acad52114dbf196b1215a4e962d50c4cad6ec1cf91887301fd7c1115fa8ac4217
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@humanfs/core@npm:^0.19.1":
|
"@humanfs/core@npm:^0.19.1":
|
||||||
version: 0.19.1
|
version: 0.19.1
|
||||||
resolution: "@humanfs/core@npm:0.19.1"
|
resolution: "@humanfs/core@npm:0.19.1"
|
||||||
|
|
@ -1053,10 +1071,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@rolldown/pluginutils@npm:1.0.0-beta.38":
|
"@rolldown/pluginutils@npm:1.0.0-beta.43":
|
||||||
version: 1.0.0-beta.38
|
version: 1.0.0-beta.43
|
||||||
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.38"
|
resolution: "@rolldown/pluginutils@npm:1.0.0-beta.43"
|
||||||
checksum: 10c0/8353ec2528349f79e27d1a3193806725b85830da334e935cbb606d88c1177c58ea6519c578e4e93e5f677f5b22aecb8738894dbed14603e14b6bffe3facf1002
|
checksum: 10c0/1c17a0b16c277a0fdbab080fd22ef91e37c1f0d710ecfdacb6a080068062eb14ff030d0e9d2ec2325a1d4246dba0c49625755c82c0090f6cbf98d16e80183e02
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -1636,19 +1654,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@vitejs/plugin-react@npm:^5.0.4":
|
"@vitejs/plugin-react@npm:^5.1.0":
|
||||||
version: 5.0.4
|
version: 5.1.0
|
||||||
resolution: "@vitejs/plugin-react@npm:5.0.4"
|
resolution: "@vitejs/plugin-react@npm:5.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core": "npm:^7.28.4"
|
"@babel/core": "npm:^7.28.4"
|
||||||
"@babel/plugin-transform-react-jsx-self": "npm:^7.27.1"
|
"@babel/plugin-transform-react-jsx-self": "npm:^7.27.1"
|
||||||
"@babel/plugin-transform-react-jsx-source": "npm:^7.27.1"
|
"@babel/plugin-transform-react-jsx-source": "npm:^7.27.1"
|
||||||
"@rolldown/pluginutils": "npm:1.0.0-beta.38"
|
"@rolldown/pluginutils": "npm:1.0.0-beta.43"
|
||||||
"@types/babel__core": "npm:^7.20.5"
|
"@types/babel__core": "npm:^7.20.5"
|
||||||
react-refresh: "npm:^0.17.0"
|
react-refresh: "npm:^0.18.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||||
checksum: 10c0/bb9360a4b4c0abf064d22211756b999faf23889ac150de490590ca7bd029b0ef7f4cd8ba3a32b86682a62d46fb7bebd75b3fa9835c57c78123f4a646de2e0136
|
checksum: 10c0/e192a12e2b854df109eafb1d06c0bc848e8e2b162c686aa6b999b1048658983e72674b2068ccc37562fcce44d32ad92b65f3a4e1897a0cb7859c2ee69cc63eac
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3191,18 +3209,18 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"eslint-plugin-react-hooks@npm:^7.0.0":
|
"eslint-plugin-react-hooks@npm:^7.0.1":
|
||||||
version: 7.0.0
|
version: 7.0.1
|
||||||
resolution: "eslint-plugin-react-hooks@npm:7.0.0"
|
resolution: "eslint-plugin-react-hooks@npm:7.0.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core": "npm:^7.24.4"
|
"@babel/core": "npm:^7.24.4"
|
||||||
"@babel/parser": "npm:^7.24.4"
|
"@babel/parser": "npm:^7.24.4"
|
||||||
hermes-parser: "npm:^0.25.1"
|
hermes-parser: "npm:^0.25.1"
|
||||||
zod: "npm:^3.22.4 || ^4.0.0"
|
zod: "npm:^3.25.0 || ^4.0.0"
|
||||||
zod-validation-error: "npm:^3.0.3 || ^4.0.0"
|
zod-validation-error: "npm:^3.5.0 || ^4.0.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
|
||||||
checksum: 10c0/911c9efdd9b102ce2eabac247dff8c217ecb8d6972aaf3b7eecfb1cfc293d4d902766355993ff7a37a33c0abde3e76971f43bc1c8ff36d6c123310e5680d0423
|
checksum: 10c0/1e711d1a9d1fa9cfc51fa1572500656577201199c70c795c6a27adfc1df39e5c598f69aab6aa91117753d23cc1f11388579a2bed14921cf9a4efe60ae8618496
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3750,6 +3768,7 @@ __metadata:
|
||||||
"@codemirror/view": "npm:^6.38.6"
|
"@codemirror/view": "npm:^6.38.6"
|
||||||
"@eslint/js": "npm:^9.38.0"
|
"@eslint/js": "npm:^9.38.0"
|
||||||
"@fontsource/inter": "npm:^5.2.8"
|
"@fontsource/inter": "npm:^5.2.8"
|
||||||
|
"@huggingface/gguf": "npm:^0.3.2"
|
||||||
"@mantine/core": "npm:^8.3.5"
|
"@mantine/core": "npm:^8.3.5"
|
||||||
"@mantine/hooks": "npm:^8.3.5"
|
"@mantine/hooks": "npm:^8.3.5"
|
||||||
"@types/node": "npm:^24.9.1"
|
"@types/node": "npm:^24.9.1"
|
||||||
|
|
@ -3759,7 +3778,7 @@ __metadata:
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.46.2"
|
"@typescript-eslint/eslint-plugin": "npm:^8.46.2"
|
||||||
"@typescript-eslint/parser": "npm:^8.46.2"
|
"@typescript-eslint/parser": "npm:^8.46.2"
|
||||||
"@uiw/react-codemirror": "npm:^4.25.2"
|
"@uiw/react-codemirror": "npm:^4.25.2"
|
||||||
"@vitejs/plugin-react": "npm:^5.0.4"
|
"@vitejs/plugin-react": "npm:^5.1.0"
|
||||||
cross-env: "npm:^10.1.0"
|
cross-env: "npm:^10.1.0"
|
||||||
electron: "npm:^38.4.0"
|
electron: "npm:^38.4.0"
|
||||||
electron-builder: "npm:^26.0.12"
|
electron-builder: "npm:^26.0.12"
|
||||||
|
|
@ -3770,12 +3789,12 @@ __metadata:
|
||||||
eslint-plugin-no-comments: "npm:^1.1.10"
|
eslint-plugin-no-comments: "npm:^1.1.10"
|
||||||
eslint-plugin-promise: "npm:^7.2.1"
|
eslint-plugin-promise: "npm:^7.2.1"
|
||||||
eslint-plugin-react: "npm:^7.37.5"
|
eslint-plugin-react: "npm:^7.37.5"
|
||||||
eslint-plugin-react-hooks: "npm:^7.0.0"
|
eslint-plugin-react-hooks: "npm:^7.0.1"
|
||||||
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.4.0"
|
globals: "npm:^16.4.0"
|
||||||
jiti: "npm:^2.6.1"
|
jiti: "npm:^2.6.1"
|
||||||
lucide-react: "npm:^0.546.0"
|
lucide-react: "npm:^0.548.0"
|
||||||
prettier: "npm:^3.6.2"
|
prettier: "npm:^3.6.2"
|
||||||
react: "npm:^19.2.0"
|
react: "npm:^19.2.0"
|
||||||
react-dom: "npm:^19.2.0"
|
react-dom: "npm:^19.2.0"
|
||||||
|
|
@ -4850,12 +4869,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lucide-react@npm:^0.546.0":
|
"lucide-react@npm:^0.548.0":
|
||||||
version: 0.546.0
|
version: 0.548.0
|
||||||
resolution: "lucide-react@npm:0.546.0"
|
resolution: "lucide-react@npm:0.548.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
checksum: 10c0/42ee0bd358517f012297aefb69b54da0b3c62f1ac8485ffa24141b63f05c9f4a682eacc2637c4b13f597aed11f9a8c79627af0c17717400bebb25581daeaad80
|
checksum: 10c0/4b3416982927622a8aad49edd3ed53c7a7c202e7357188f56b8dd582e8f22d33b30fda736440a2e640e0faf9baa724a06d0d3c1de5ecf42d2844eb6b24ddefdd
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -5693,10 +5712,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-refresh@npm:^0.17.0":
|
"react-refresh@npm:^0.18.0":
|
||||||
version: 0.17.0
|
version: 0.18.0
|
||||||
resolution: "react-refresh@npm:0.17.0"
|
resolution: "react-refresh@npm:0.18.0"
|
||||||
checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c
|
checksum: 10c0/34a262f7fd803433a534f50deb27a148112a81adcae440c7d1cbae7ef14d21ea8f2b3d783e858cb7698968183b77755a38b4d4b5b1d79b4f4689c2f6d358fff2
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -7388,7 +7407,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"zod-validation-error@npm:^3.0.3 || ^4.0.0":
|
"zod-validation-error@npm:^3.5.0 || ^4.0.0":
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
resolution: "zod-validation-error@npm:4.0.2"
|
resolution: "zod-validation-error@npm:4.0.2"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -7397,7 +7416,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"zod@npm:^3.22.4 || ^4.0.0":
|
"zod@npm:^3.25.0 || ^4.0.0":
|
||||||
version: 4.1.12
|
version: 4.1.12
|
||||||
resolution: "zod@npm:4.1.12"
|
resolution: "zod@npm:4.1.12"
|
||||||
checksum: 10c0/b64c1feb19e99d77075261eaf613e0b2be4dfcd3551eff65ad8b4f2a079b61e379854d066f7d447491fcf193f45babd8095551a9d47973d30b46b6d8e2c46774
|
checksum: 10c0/b64c1feb19e99d77075261eaf613e0b2be4dfcd3551eff65ad8b4f2a079b61e379854d066f7d447491fcf193f45babd8095551a9d47973d30b46b6d8e2c46774
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue