mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
WIP: download and store web-based models instead of relying on kcpp
This commit is contained in:
parent
f2d15591a7
commit
26d0b2158d
14 changed files with 543 additions and 144 deletions
15
.gitignore
vendored
15
.gitignore
vendored
|
|
@ -11,18 +11,3 @@ Thumbs.db
|
||||||
# Yarn
|
# Yarn
|
||||||
.yarn/
|
.yarn/
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
t5xxl_fp8_e4m3fn.safetensors.aria2
|
|
||||||
ae.safetensors
|
|
||||||
clip_l.safetensors
|
|
||||||
t5xxl_fp8_e4m3fn.safetensors
|
|
||||||
flux1-kontext-dev-Q3_K_S.gguf
|
|
||||||
gemma-3-4b-it.Q8_0.gguf
|
|
||||||
.webui_secret_key
|
|
||||||
chroma-unlocked-v29-Q3_K_L.gguf
|
|
||||||
Qwen-Image-Edit-2509-Q4_K_S.gguf
|
|
||||||
Qwen2.5-VL-7B-Instruct.mmproj-Q8_0.gguf
|
|
||||||
Qwen2.5-VL-7B-Instruct.Q4_K_S.gguf
|
|
||||||
qwen_image_vae.safetensors
|
|
||||||
flux1-kontext-dev-Q4_K_S.gguf
|
|
||||||
chroma-unlocked-v45-Q4_0.gguf
|
|
||||||
|
|
|
||||||
12
package.json
12
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "gerbil",
|
"name": "gerbil",
|
||||||
"productName": "Gerbil",
|
"productName": "Gerbil",
|
||||||
"version": "1.9.0",
|
"version": "2.0.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": "./",
|
||||||
|
|
@ -40,11 +40,11 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.6",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/yauzl": "^2.10.3",
|
"@types/yauzl": "^2.10.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||||
"@typescript-eslint/parser": "^8.46.4",
|
"@typescript-eslint/parser": "^8.47.0",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"electron": "^38.7.0",
|
"electron": "^38.7.0",
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/search": "^6.5.11",
|
"@codemirror/search": "^6.5.11",
|
||||||
"@codemirror/theme-one-dark": "^6.1.3",
|
"@codemirror/theme-one-dark": "^6.1.3",
|
||||||
"@codemirror/view": "^6.38.7",
|
"@codemirror/view": "^6.38.8",
|
||||||
"@fontsource/inter": "^5.2.8",
|
"@fontsource/inter": "^5.2.8",
|
||||||
"@huggingface/gguf": "^0.3.2",
|
"@huggingface/gguf": "^0.3.2",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.8",
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
"@uiw/react-codemirror": "^4.25.3",
|
"@uiw/react-codemirror": "^4.25.3",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
"lucide-react": "^0.553.0",
|
"lucide-react": "^0.554.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",
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => {
|
||||||
onSelectFile={handleSelectModelFile}
|
onSelectFile={handleSelectModelFile}
|
||||||
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
|
showAnalyze
|
||||||
|
paramType="model"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -77,15 +77,17 @@ export const ImageGenerationTab = () => {
|
||||||
onSelectFile={handleSelectSdmodelFile}
|
onSelectFile={handleSelectSdmodelFile}
|
||||||
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
|
showAnalyze
|
||||||
|
paramType="sdmodel"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
label="T5-XXL File"
|
label="T5XXL File"
|
||||||
value={sdt5xxl}
|
value={sdt5xxl}
|
||||||
placeholder="Select a T5-XXL file or enter a direct URL"
|
placeholder="Select a T5-XXL encoder file or enter a direct URL"
|
||||||
tooltip="T5-XXL text encoder model for enhanced text understanding."
|
tooltip="T5-XXL text encoder model for advanced text understanding."
|
||||||
onChange={handleSdt5xxlChange}
|
onChange={handleSdt5xxlChange}
|
||||||
onSelectFile={handleSelectSdt5xxlFile}
|
onSelectFile={handleSelectSdt5xxlFile}
|
||||||
|
paramType="sdt5xxl"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
|
|
@ -95,6 +97,7 @@ export const ImageGenerationTab = () => {
|
||||||
tooltip="CLIP-L text encoder model for text-image understanding."
|
tooltip="CLIP-L text encoder model for text-image understanding."
|
||||||
onChange={handleSdcliplChange}
|
onChange={handleSdcliplChange}
|
||||||
onSelectFile={handleSelectSdcliplFile}
|
onSelectFile={handleSelectSdcliplFile}
|
||||||
|
paramType="sdclipl"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
|
|
@ -104,6 +107,7 @@ export const ImageGenerationTab = () => {
|
||||||
tooltip="CLIP-G text encoder model for enhanced text-image understanding."
|
tooltip="CLIP-G text encoder model for enhanced text-image understanding."
|
||||||
onChange={handleSdclipgChange}
|
onChange={handleSdclipgChange}
|
||||||
onSelectFile={handleSelectSdclipgFile}
|
onSelectFile={handleSelectSdclipgFile}
|
||||||
|
paramType="sdclipg"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
|
|
@ -113,6 +117,7 @@ export const ImageGenerationTab = () => {
|
||||||
tooltip="PhotoMaker is a model that allows face cloning. Select a .safetensors PhotoMaker file to be loaded (SDXL only)."
|
tooltip="PhotoMaker is a model that allows face cloning. Select a .safetensors PhotoMaker file to be loaded (SDXL only)."
|
||||||
onChange={handleSdphotomakerChange}
|
onChange={handleSdphotomakerChange}
|
||||||
onSelectFile={handleSelectSdphotomakerFile}
|
onSelectFile={handleSelectSdphotomakerFile}
|
||||||
|
paramType="sdphotomaker"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
|
|
@ -122,6 +127,7 @@ export const ImageGenerationTab = () => {
|
||||||
tooltip="Variational Autoencoder model for improved image quality."
|
tooltip="Variational Autoencoder model for improved image quality."
|
||||||
onChange={handleSdvaeChange}
|
onChange={handleSdvaeChange}
|
||||||
onSelectFile={handleSelectSdvaeFile}
|
onSelectFile={handleSelectSdvaeFile}
|
||||||
|
paramType="sdvae"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModelFileField
|
<ModelFileField
|
||||||
|
|
@ -131,6 +137,7 @@ export const ImageGenerationTab = () => {
|
||||||
tooltip="LoRa (Low-Rank Adaptation) file for customizing image generation. Select a .safetensors or .gguf LoRa file to be loaded. Should be unquantized."
|
tooltip="LoRa (Low-Rank Adaptation) file for customizing image generation. Select a .safetensors or .gguf LoRa file to be loaded. Should be unquantized."
|
||||||
onChange={handleSdloraChange}
|
onChange={handleSdloraChange}
|
||||||
onSelectFile={handleSelectSdloraFile}
|
onSelectFile={handleSelectSdloraFile}
|
||||||
|
paramType="sdlora"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectWithTooltip
|
<SelectWithTooltip
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
import { Group, TextInput, ActionIcon, Tooltip, Button } from '@mantine/core';
|
import { useEffect, useState } from 'react';
|
||||||
import { useState } from 'react';
|
import {
|
||||||
|
Group,
|
||||||
|
ActionIcon,
|
||||||
|
Tooltip,
|
||||||
|
Button,
|
||||||
|
Combobox,
|
||||||
|
useCombobox,
|
||||||
|
TextInput,
|
||||||
|
} from '@mantine/core';
|
||||||
import { File, Search, Info } from 'lucide-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 { ModelAnalysisModal } from '@/components/screens/Launch/ModelAnalysisModal';
|
||||||
import { getInputValidationState } from '@/utils/validation';
|
import { getInputValidationState } from '@/utils/validation';
|
||||||
import { logError } from '@/utils/logger';
|
import { logError } from '@/utils/logger';
|
||||||
import type { ModelAnalysis } from '@/types';
|
import type { ModelAnalysis, ModelParamType, CachedModel } from '@/types';
|
||||||
|
|
||||||
interface ModelFileFieldProps {
|
interface ModelFileFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -16,6 +24,7 @@ interface ModelFileFieldProps {
|
||||||
onSelectFile: () => void;
|
onSelectFile: () => void;
|
||||||
searchUrl?: string;
|
searchUrl?: string;
|
||||||
showAnalyze?: boolean;
|
showAnalyze?: boolean;
|
||||||
|
paramType: ModelParamType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelFileField = ({
|
export const ModelFileField = ({
|
||||||
|
|
@ -27,6 +36,7 @@ export const ModelFileField = ({
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
searchUrl,
|
searchUrl,
|
||||||
showAnalyze = false,
|
showAnalyze = false,
|
||||||
|
paramType,
|
||||||
}: ModelFileFieldProps) => {
|
}: ModelFileFieldProps) => {
|
||||||
const validationState = getInputValidationState(value);
|
const validationState = getInputValidationState(value);
|
||||||
const [analysisModalOpened, setAnalysisModalOpened] = useState(false);
|
const [analysisModalOpened, setAnalysisModalOpened] = useState(false);
|
||||||
|
|
@ -35,6 +45,26 @@ export const ModelFileField = ({
|
||||||
);
|
);
|
||||||
const [analysisLoading, setAnalysisLoading] = useState(false);
|
const [analysisLoading, setAnalysisLoading] = useState(false);
|
||||||
const [analysisError, setAnalysisError] = useState<string>();
|
const [analysisError, setAnalysisError] = useState<string>();
|
||||||
|
const [cachedModels, setCachedModels] = useState<CachedModel[]>([]);
|
||||||
|
const combobox = useCombobox();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const models =
|
||||||
|
await window.electronAPI.kobold.getLocalModels(paramType);
|
||||||
|
setCachedModels(models);
|
||||||
|
} catch (error) {
|
||||||
|
logError('Failed to load cached models:', error as Error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [paramType]);
|
||||||
|
|
||||||
|
const options = cachedModels.map((model) => (
|
||||||
|
<Combobox.Option value={model.path} key={model.path}>
|
||||||
|
{model.author}/{model.model}
|
||||||
|
</Combobox.Option>
|
||||||
|
));
|
||||||
|
|
||||||
const getHelperText = () => {
|
const getHelperText = () => {
|
||||||
if (validationState === 'neutral') return undefined;
|
if (validationState === 'neutral') return undefined;
|
||||||
|
|
@ -72,12 +102,37 @@ export const ModelFileField = ({
|
||||||
<LabelWithTooltip label={label} tooltip={tooltip} />
|
<LabelWithTooltip label={label} tooltip={tooltip} />
|
||||||
<Group gap="xs" align="flex-start">
|
<Group gap="xs" align="flex-start">
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
<TextInput
|
<Combobox
|
||||||
placeholder={placeholder}
|
store={combobox}
|
||||||
value={value}
|
onOptionSubmit={(val) => {
|
||||||
onChange={(event) => onChange(event.currentTarget.value)}
|
onChange(val);
|
||||||
error={validationState === 'invalid' ? getHelperText() : undefined}
|
combobox.closeDropdown();
|
||||||
/>
|
}}
|
||||||
|
>
|
||||||
|
<Combobox.Target>
|
||||||
|
<TextInput
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={value}
|
||||||
|
onChange={(event) => {
|
||||||
|
onChange(event.currentTarget.value);
|
||||||
|
combobox.openDropdown();
|
||||||
|
}}
|
||||||
|
onFocus={() => combobox.openDropdown()}
|
||||||
|
onBlur={() => combobox.closeDropdown()}
|
||||||
|
error={
|
||||||
|
validationState === 'invalid' ? getHelperText() : undefined
|
||||||
|
}
|
||||||
|
rightSection={<Combobox.Chevron />}
|
||||||
|
rightSectionPointerEvents="none"
|
||||||
|
/>
|
||||||
|
</Combobox.Target>
|
||||||
|
|
||||||
|
{options.length > 0 && (
|
||||||
|
<Combobox.Dropdown>
|
||||||
|
<Combobox.Options>{options}</Combobox.Options>
|
||||||
|
</Combobox.Dropdown>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={onSelectFile}
|
onClick={onSelectFile}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export const SERVER_READY_SIGNALS = {
|
||||||
export const DEFAULT_CONTEXT_SIZE = 4096;
|
export const DEFAULT_CONTEXT_SIZE = 4096;
|
||||||
|
|
||||||
export const DEFAULT_MODEL_URL =
|
export const DEFAULT_MODEL_URL =
|
||||||
'https://huggingface.co/MaziyarPanahi/gemma-3-4b-it-GGUF/resolve/main/gemma-3-4b-it.Q8_0.gguf?download=true';
|
'https://huggingface.co/MaziyarPanahi/gemma-3-4b-it-GGUF/resolve/main/gemma-3-4b-it.Q8_0.gguf';
|
||||||
|
|
||||||
export const DEFAULT_AUTO_GPU_LAYERS = true;
|
export const DEFAULT_AUTO_GPU_LAYERS = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
selectModelFile,
|
selectModelFile,
|
||||||
selectInstallDirectory,
|
selectInstallDirectory,
|
||||||
} from '@/main/modules/koboldcpp/config';
|
} from '@/main/modules/koboldcpp/config';
|
||||||
|
import { getLocalModelsForType } from '@/main/modules/koboldcpp/modelDownload';
|
||||||
import { analyzeGGUFModel } from '@/main/modules/koboldcpp/analyze';
|
import { analyzeGGUFModel } from '@/main/modules/koboldcpp/analyze';
|
||||||
import {
|
import {
|
||||||
get as getConfig,
|
get as getConfig,
|
||||||
|
|
@ -156,6 +157,12 @@ export function setupIPCHandlers() {
|
||||||
selectModelFile(title)
|
selectModelFile(title)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ipcMain.handle('kobold:getLocalModels', (_, paramType: string) =>
|
||||||
|
getLocalModelsForType(
|
||||||
|
paramType as Parameters<typeof getLocalModelsForType>[0]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle('kobold:analyzeModel', async (_, filePath: string) =>
|
ipcMain.handle('kobold:analyzeModel', async (_, filePath: string) =>
|
||||||
analyzeGGUFModel(filePath)
|
analyzeGGUFModel(filePath)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,58 @@ import { startFrontend as startSillyTavernFrontend } from '@/main/modules/sillyt
|
||||||
import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui';
|
import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui';
|
||||||
import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches';
|
import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches';
|
||||||
import { startProxy, stopProxy } from '../proxy';
|
import { startProxy, stopProxy } from '../proxy';
|
||||||
|
import { resolveModelPath } from '../modelDownload';
|
||||||
import type {
|
import type {
|
||||||
FrontendPreference,
|
FrontendPreference,
|
||||||
ImageGenerationFrontendPreference,
|
ImageGenerationFrontendPreference,
|
||||||
|
ModelParamType,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
|
|
||||||
let koboldProcess: ChildProcess | null = null;
|
let koboldProcess: ChildProcess | null = null;
|
||||||
|
|
||||||
|
async function resolveModelPaths(args: string[]) {
|
||||||
|
const resolvedArgs: string[] = [];
|
||||||
|
const modelParams = [
|
||||||
|
'--model',
|
||||||
|
'--sdmodel',
|
||||||
|
'--sdt5xxl',
|
||||||
|
'--sdclipl',
|
||||||
|
'--sdclipg',
|
||||||
|
'--sdphotomaker',
|
||||||
|
'--sdvae',
|
||||||
|
'--sdlora',
|
||||||
|
'--mmproj',
|
||||||
|
'--whispermodel',
|
||||||
|
'--draftmodel',
|
||||||
|
'--ttsmodel',
|
||||||
|
'--ttswavtokenizer',
|
||||||
|
'--embeddingsmodel',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
|
||||||
|
if (modelParams.includes(arg) && i + 1 < args.length) {
|
||||||
|
resolvedArgs.push(arg);
|
||||||
|
const urlOrPath = args[i + 1];
|
||||||
|
try {
|
||||||
|
const paramType = arg.slice(2) as ModelParamType;
|
||||||
|
const resolvedPath = await resolveModelPath(urlOrPath, paramType);
|
||||||
|
resolvedArgs.push(resolvedPath);
|
||||||
|
i++;
|
||||||
|
} catch (error) {
|
||||||
|
logError(`Failed to resolve model path for ${arg}:`, error as Error);
|
||||||
|
resolvedArgs.push(urlOrPath);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolvedArgs.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedArgs;
|
||||||
|
}
|
||||||
|
|
||||||
export async function launchKoboldCpp(
|
export async function launchKoboldCpp(
|
||||||
args: string[] = [],
|
args: string[] = [],
|
||||||
frontendPreference: FrontendPreference = 'koboldcpp',
|
frontendPreference: FrontendPreference = 'koboldcpp',
|
||||||
|
|
@ -64,7 +109,8 @@ export async function launchKoboldCpp(
|
||||||
await patchKcppSduiEmbd(binaryDir);
|
await patchKcppSduiEmbd(binaryDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalArgs = [...args];
|
const resolvedArgs = await resolveModelPaths(args);
|
||||||
|
const finalArgs = [...resolvedArgs];
|
||||||
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
|
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
|
||||||
|
|
||||||
await startProxy(koboldHost, koboldPort);
|
await startProxy(koboldHost, koboldPort);
|
||||||
|
|
|
||||||
291
src/main/modules/koboldcpp/modelDownload.ts
Normal file
291
src/main/modules/koboldcpp/modelDownload.ts
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
import { join, basename, dirname } from 'path';
|
||||||
|
import { mkdir, readdir, stat } from 'fs/promises';
|
||||||
|
import { createWriteStream } from 'fs';
|
||||||
|
import { get as httpGet } from 'http';
|
||||||
|
import { get as httpsGet } from 'https';
|
||||||
|
import { getInstallDir } from '@/main/modules/config';
|
||||||
|
import { pathExists } from '@/utils/node/fs';
|
||||||
|
import { logError } from '@/utils/node/logging';
|
||||||
|
import { sendToRenderer } from '@/main/modules/window';
|
||||||
|
import type { ModelParamType, CachedModel } from '@/types';
|
||||||
|
|
||||||
|
interface DownloadProgress {
|
||||||
|
type: 'progress' | 'complete' | 'error';
|
||||||
|
percent?: number;
|
||||||
|
downloaded?: string;
|
||||||
|
total?: string;
|
||||||
|
speed?: string;
|
||||||
|
eta?: string;
|
||||||
|
error?: string;
|
||||||
|
localPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHuggingFaceUrl(url: string) {
|
||||||
|
const hfMatch = url.match(
|
||||||
|
/huggingface\.co\/([^/]+)\/([^/]+)\/(?:resolve|blob)\/[^/]+\/(.+)/
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hfMatch) {
|
||||||
|
return {
|
||||||
|
author: hfMatch[1],
|
||||||
|
model: hfMatch[2],
|
||||||
|
filename: basename(hfMatch[3]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
author: 'external',
|
||||||
|
model: 'models',
|
||||||
|
filename: basename(url.split('?')[0]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelLocalPath(url: string, paramType: ModelParamType) {
|
||||||
|
const installDir = getInstallDir();
|
||||||
|
const { author, model, filename } = parseHuggingFaceUrl(url);
|
||||||
|
|
||||||
|
return join(installDir, 'models', paramType, author, model, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUrl(url: string) {
|
||||||
|
if (url.includes('huggingface.co') && url.includes('/blob/')) {
|
||||||
|
return url.replace('/blob/', '/resolve/');
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadFile(
|
||||||
|
url: string,
|
||||||
|
outputPath: string,
|
||||||
|
onProgress: (progress: DownloadProgress) => void
|
||||||
|
) {
|
||||||
|
const normalizedUrl = normalizeUrl(url);
|
||||||
|
const outputDir = dirname(outputPath);
|
||||||
|
|
||||||
|
await mkdir(outputDir, { recursive: true });
|
||||||
|
|
||||||
|
return new Promise<boolean>((resolve, reject) => {
|
||||||
|
const httpModule = normalizedUrl.startsWith('https') ? httpsGet : httpGet;
|
||||||
|
|
||||||
|
const handleRedirect = (requestUrl: string, redirectCount = 0): void => {
|
||||||
|
if (redirectCount > 10) {
|
||||||
|
reject(new Error('Too many redirects'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpModule(requestUrl, (response) => {
|
||||||
|
if (
|
||||||
|
response.statusCode === 301 ||
|
||||||
|
response.statusCode === 302 ||
|
||||||
|
response.statusCode === 307 ||
|
||||||
|
response.statusCode === 308
|
||||||
|
) {
|
||||||
|
const redirectUrl = response.headers.location;
|
||||||
|
if (redirectUrl) {
|
||||||
|
handleRedirect(redirectUrl, redirectCount + 1);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Redirect without location header'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
reject(
|
||||||
|
new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalBytes = parseInt(response.headers['content-length'] || '0');
|
||||||
|
let downloadedBytes = 0;
|
||||||
|
let lastReportTime = Date.now();
|
||||||
|
let lastReportedBytes = 0;
|
||||||
|
|
||||||
|
const fileStream = createWriteStream(outputPath);
|
||||||
|
|
||||||
|
response.on('data', (chunk: Buffer) => {
|
||||||
|
downloadedBytes += chunk.length;
|
||||||
|
fileStream.write(chunk);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const timeDiff = (now - lastReportTime) / 1000;
|
||||||
|
|
||||||
|
if (timeDiff >= 0.5) {
|
||||||
|
const bytesDiff = downloadedBytes - lastReportedBytes;
|
||||||
|
const speedBytesPerSec = bytesDiff / timeDiff;
|
||||||
|
const percent = totalBytes
|
||||||
|
? Math.round((downloadedBytes / totalBytes) * 100)
|
||||||
|
: 0;
|
||||||
|
const downloadedMB = (downloadedBytes / 1024 / 1024).toFixed(2);
|
||||||
|
const totalMB = (totalBytes / 1024 / 1024).toFixed(2);
|
||||||
|
const speedMBPerSec = (speedBytesPerSec / 1024 / 1024).toFixed(2);
|
||||||
|
|
||||||
|
const remainingBytes = totalBytes - downloadedBytes;
|
||||||
|
const etaSeconds = speedBytesPerSec
|
||||||
|
? Math.round(remainingBytes / speedBytesPerSec)
|
||||||
|
: 0;
|
||||||
|
const etaMinutes = Math.floor(etaSeconds / 60);
|
||||||
|
const etaSecondsRemainder = etaSeconds % 60;
|
||||||
|
const etaStr =
|
||||||
|
etaMinutes > 0
|
||||||
|
? `${etaMinutes}m${etaSecondsRemainder}s`
|
||||||
|
: `${etaSeconds}s`;
|
||||||
|
|
||||||
|
const progressMsg = totalBytes
|
||||||
|
? `Downloaded ${downloadedMB}MB / ${totalMB}MB (${percent}%) - ${speedMBPerSec}MB/s - ETA: ${etaStr}`
|
||||||
|
: `Downloaded ${downloadedMB}MB - ${speedMBPerSec}MB/s`;
|
||||||
|
|
||||||
|
sendToRenderer('kobold-output', `\r${progressMsg}`);
|
||||||
|
|
||||||
|
onProgress({
|
||||||
|
type: 'progress',
|
||||||
|
percent,
|
||||||
|
downloaded: `${downloadedMB}MB`,
|
||||||
|
total: totalBytes ? `${totalMB}MB` : undefined,
|
||||||
|
speed: `${speedMBPerSec}MB/s`,
|
||||||
|
eta: totalBytes ? etaStr : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
lastReportTime = now;
|
||||||
|
lastReportedBytes = downloadedBytes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on('end', () => {
|
||||||
|
fileStream.end();
|
||||||
|
fileStream.on('finish', () => {
|
||||||
|
sendToRenderer('kobold-output', '\n');
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on('error', (err) => {
|
||||||
|
fileStream.close();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileStream.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}).on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRedirect(normalizedUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidModelUrl(url: string) {
|
||||||
|
if (!url || url.trim() === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validExtensions = ['.gguf', '.safetensors', '.bin', '.ggml'];
|
||||||
|
const urlPath = url.split('?')[0];
|
||||||
|
return validExtensions.some((ext) => urlPath.toLowerCase().endsWith(ext));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveModelPath(
|
||||||
|
urlOrPath: string,
|
||||||
|
paramType: ModelParamType,
|
||||||
|
onProgress?: (progress: DownloadProgress) => void
|
||||||
|
) {
|
||||||
|
if (!isValidModelUrl(urlOrPath)) {
|
||||||
|
return urlOrPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localPath = getModelLocalPath(urlOrPath, paramType);
|
||||||
|
|
||||||
|
if (await pathExists(localPath)) {
|
||||||
|
sendToRenderer('kobold-output', `Using cached model at: ${localPath}\n`);
|
||||||
|
onProgress?.({
|
||||||
|
type: 'complete',
|
||||||
|
localPath,
|
||||||
|
});
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToRenderer(
|
||||||
|
'kobold-output',
|
||||||
|
`Downloading model from ${urlOrPath} to ${localPath}...\n`
|
||||||
|
);
|
||||||
|
|
||||||
|
const progressCallback = onProgress || ((p: DownloadProgress) => p);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await downloadFile(urlOrPath, localPath, progressCallback);
|
||||||
|
|
||||||
|
sendToRenderer(
|
||||||
|
'kobold-output',
|
||||||
|
`Model downloaded successfully to: ${localPath}\n\n`
|
||||||
|
);
|
||||||
|
progressCallback({
|
||||||
|
type: 'complete',
|
||||||
|
localPath,
|
||||||
|
});
|
||||||
|
return localPath;
|
||||||
|
} catch (error) {
|
||||||
|
progressCallback({
|
||||||
|
type: 'error',
|
||||||
|
error: `Download failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||||
|
});
|
||||||
|
throw new Error(
|
||||||
|
`Failed to download model from ${urlOrPath}: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLocalModelsForType(paramType: ModelParamType) {
|
||||||
|
const installDir = getInstallDir();
|
||||||
|
const modelsDir = join(installDir, 'models', paramType);
|
||||||
|
|
||||||
|
if (!(await pathExists(modelsDir))) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const models: CachedModel[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authors = await readdir(modelsDir);
|
||||||
|
|
||||||
|
for (const author of authors) {
|
||||||
|
const authorPath = join(modelsDir, author);
|
||||||
|
const authorStat = await stat(authorPath);
|
||||||
|
|
||||||
|
if (authorStat.isDirectory()) {
|
||||||
|
const modelDirs = await readdir(authorPath);
|
||||||
|
|
||||||
|
for (const modelDir of modelDirs) {
|
||||||
|
const modelPath = join(authorPath, modelDir);
|
||||||
|
const modelStat = await stat(modelPath);
|
||||||
|
|
||||||
|
if (modelStat.isDirectory()) {
|
||||||
|
const files = await readdir(modelPath);
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const filePath = join(modelPath, file);
|
||||||
|
const fileStat = await stat(filePath);
|
||||||
|
|
||||||
|
if (fileStat.isFile()) {
|
||||||
|
models.push({
|
||||||
|
path: filePath,
|
||||||
|
author,
|
||||||
|
model: modelDir,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logError('Error scanning local models:', error as Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
getLocalModels: (paramType) =>
|
||||||
|
ipcRenderer.invoke('kobold:getLocalModels', paramType),
|
||||||
analyzeModel: (filePath) =>
|
analyzeModel: (filePath) =>
|
||||||
ipcRenderer.invoke('kobold:analyzeModel', filePath),
|
ipcRenderer.invoke('kobold:analyzeModel', filePath),
|
||||||
calculateOptimalLayers: (
|
calculateOptimalLayers: (
|
||||||
|
|
|
||||||
2
src/types/electron.d.ts
vendored
2
src/types/electron.d.ts
vendored
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
BackendSupport,
|
BackendSupport,
|
||||||
Screen,
|
Screen,
|
||||||
ModelAnalysis,
|
ModelAnalysis,
|
||||||
|
CachedModel,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
import type { MantineColorScheme } from '@mantine/core';
|
import type { MantineColorScheme } from '@mantine/core';
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -156,6 +157,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>;
|
||||||
|
getLocalModels: (paramType: string) => Promise<CachedModel[]>;
|
||||||
analyzeModel: (filePath: string) => Promise<ModelAnalysis>;
|
analyzeModel: (filePath: string) => Promise<ModelAnalysis>;
|
||||||
calculateOptimalLayers: (
|
calculateOptimalLayers: (
|
||||||
modelPath: string,
|
modelPath: string,
|
||||||
|
|
|
||||||
22
src/types/index.d.ts
vendored
22
src/types/index.d.ts
vendored
|
|
@ -18,6 +18,28 @@ export type ImageGenerationFrontendPreference = 'match' | 'builtin';
|
||||||
|
|
||||||
export type Screen = 'welcome' | 'download' | 'launch' | 'interface';
|
export type Screen = 'welcome' | 'download' | 'launch' | 'interface';
|
||||||
|
|
||||||
|
export type ModelParamType =
|
||||||
|
| 'model'
|
||||||
|
| 'sdmodel'
|
||||||
|
| 'sdt5xxl'
|
||||||
|
| 'sdclipl'
|
||||||
|
| 'sdclipg'
|
||||||
|
| 'sdphotomaker'
|
||||||
|
| 'sdvae'
|
||||||
|
| 'sdlora'
|
||||||
|
| 'mmproj'
|
||||||
|
| 'whispermodel'
|
||||||
|
| 'draftmodel'
|
||||||
|
| 'ttsmodel'
|
||||||
|
| 'ttswavtokenizer'
|
||||||
|
| 'embeddingsmodel';
|
||||||
|
|
||||||
|
export interface CachedModel {
|
||||||
|
path: string;
|
||||||
|
author: string;
|
||||||
|
model: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GitHubAsset {
|
export interface GitHubAsset {
|
||||||
name: string;
|
name: string;
|
||||||
browser_download_url: string;
|
browser_download_url: string;
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,8 @@
|
||||||
export const handleTerminalOutput = (prevContent: string, newData: string) => {
|
export const handleTerminalOutput = (prevContent: string, newData: string) => {
|
||||||
if (newData.includes('\r')) {
|
if (newData.startsWith('\r') && !newData.startsWith('\r\n')) {
|
||||||
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
|
const lines = prevContent.split('\n');
|
||||||
|
lines[lines.length - 1] = newData.slice(1);
|
||||||
if (hasStandaloneCarriageReturns) {
|
return lines.join('\n');
|
||||||
const combined = prevContent + newData;
|
|
||||||
|
|
||||||
const lines = combined.split(/(\r?\n)/);
|
|
||||||
const processedLines: string[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i += 2) {
|
|
||||||
const line = lines[i] || '';
|
|
||||||
const lineBreak = lines[i + 1] || '';
|
|
||||||
|
|
||||||
if (line.includes('\r')) {
|
|
||||||
const parts = line.split('\r');
|
|
||||||
processedLines.push(parts[parts.length - 1] + lineBreak);
|
|
||||||
} else {
|
|
||||||
processedLines.push(line + lineBreak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedLines.join('').replace(/\r?\n$/, '');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevContent + newData;
|
return prevContent + newData;
|
||||||
|
|
|
||||||
172
yarn.lock
172
yarn.lock
|
|
@ -315,15 +315,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0, @codemirror/view@npm:^6.38.7":
|
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0, @codemirror/view@npm:^6.38.8":
|
||||||
version: 6.38.7
|
version: 6.38.8
|
||||||
resolution: "@codemirror/view@npm:6.38.7"
|
resolution: "@codemirror/view@npm:6.38.8"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@codemirror/state": "npm:^6.5.0"
|
"@codemirror/state": "npm:^6.5.0"
|
||||||
crelt: "npm:^1.0.6"
|
crelt: "npm:^1.0.6"
|
||||||
style-mod: "npm:^4.1.0"
|
style-mod: "npm:^4.1.0"
|
||||||
w3c-keyname: "npm:^2.2.4"
|
w3c-keyname: "npm:^2.2.4"
|
||||||
checksum: 10c0/48e237f790d4f9e173e2428f73809c4f147dff401e616e8155397ce17ab296a8c69b0267ce7a565b93a3e1c531f9573e3666069b8f113ac0b98da4997c7e7f9b
|
checksum: 10c0/42d5d40a00651adcf6cd90b45790556a339b034943a27daabb60f961591fb4a622f270917017f0ca304f62d9dd91f681992d8e2617738c39978b424f9a538aa1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -1431,12 +1431,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:^19.2.5":
|
"@types/react@npm:^19.2.6":
|
||||||
version: 19.2.5
|
version: 19.2.6
|
||||||
resolution: "@types/react@npm:19.2.5"
|
resolution: "@types/react@npm:19.2.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
csstype: "npm:^3.0.2"
|
csstype: "npm:^3.2.2"
|
||||||
checksum: 10c0/1f9a92c73a5ea5b167f59cd0b5b9460fde65bd22b63b6d23bfaace8ad38537df127c97657418b4912a7a03a66e6451e82a41b84718d638ec1c8e4f0515d94793
|
checksum: 10c0/23b1100f88662ce9f9e4fcca3a2b4ef9fff1ecde24ede2b2dcbd07731e48d6946fd7fd156cd133f5b25321694b0569cd9b8dd30b22c4e076d1cf4c8cdd9a75cb
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -1472,106 +1472,106 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@npm:^8.46.4":
|
"@typescript-eslint/eslint-plugin@npm:^8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.46.4"
|
resolution: "@typescript-eslint/eslint-plugin@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/regexpp": "npm:^4.10.0"
|
"@eslint-community/regexpp": "npm:^4.10.0"
|
||||||
"@typescript-eslint/scope-manager": "npm:8.46.4"
|
"@typescript-eslint/scope-manager": "npm:8.47.0"
|
||||||
"@typescript-eslint/type-utils": "npm:8.46.4"
|
"@typescript-eslint/type-utils": "npm:8.47.0"
|
||||||
"@typescript-eslint/utils": "npm:8.46.4"
|
"@typescript-eslint/utils": "npm:8.47.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:8.46.4"
|
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||||
graphemer: "npm:^1.4.0"
|
graphemer: "npm:^1.4.0"
|
||||||
ignore: "npm:^7.0.0"
|
ignore: "npm:^7.0.0"
|
||||||
natural-compare: "npm:^1.4.0"
|
natural-compare: "npm:^1.4.0"
|
||||||
ts-api-utils: "npm:^2.1.0"
|
ts-api-utils: "npm:^2.1.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@typescript-eslint/parser": ^8.46.4
|
"@typescript-eslint/parser": ^8.47.0
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/c487e55c2f35e89126a13a6997f06494c26a3c96b9a7685421e2d92929f3ab302c1c234f0add9113705fbad693b05b3b87cebe5219bc71b2af9ee7aa8e7dc12c
|
checksum: 10c0/abd35affd21bc199e5e274b8e91e4225a127edf9cbe5047c465f859d7e393d07556ea42b40004e769ed59b18cfe25ab30942c854e23026d4f78d350eb71de03e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/parser@npm:^8.46.4":
|
"@typescript-eslint/parser@npm:^8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/parser@npm:8.46.4"
|
resolution: "@typescript-eslint/parser@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager": "npm:8.46.4"
|
"@typescript-eslint/scope-manager": "npm:8.47.0"
|
||||||
"@typescript-eslint/types": "npm:8.46.4"
|
"@typescript-eslint/types": "npm:8.47.0"
|
||||||
"@typescript-eslint/typescript-estree": "npm:8.46.4"
|
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:8.46.4"
|
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/bef98fa9250d5720479c10f803ca66a2a0b382158a8b462fd1c710351f7b423570c273556fb828e64d8a87041d54d51fa5a5e1e88ebdc1c88da0ee1098f9405e
|
checksum: 10c0/8f8c9514ffe8c2fca9e2d1d3e9f9f8dd4cb55c14f0ef2f4f265a9180615ec98dc455d373893f76f86760f37e449fd0f4afda46c1211291b9736a05ba010912f2
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/project-service@npm:8.46.4":
|
"@typescript-eslint/project-service@npm:8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/project-service@npm:8.46.4"
|
resolution: "@typescript-eslint/project-service@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/tsconfig-utils": "npm:^8.46.4"
|
"@typescript-eslint/tsconfig-utils": "npm:^8.47.0"
|
||||||
"@typescript-eslint/types": "npm:^8.46.4"
|
"@typescript-eslint/types": "npm:^8.47.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/81c5de7b85a2b1bff51ef27d25f11be992b7e550bfe34d4cbc4eb71f0fd03bcc1619644ac8efd594c515c894317f98db9176ef333004718d997c666791ca8b95
|
checksum: 10c0/6d7ec78c63d672178727b2d79856b470bd99e90d387335decec026931caa94c6907afc4690b884ce1eaca65f2d8b8f070a5c6e70e47971dfeec34dfd022933b8
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@npm:8.46.4":
|
"@typescript-eslint/scope-manager@npm:8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/scope-manager@npm:8.46.4"
|
resolution: "@typescript-eslint/scope-manager@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types": "npm:8.46.4"
|
"@typescript-eslint/types": "npm:8.47.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:8.46.4"
|
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||||
checksum: 10c0/f614b5a95f1803a4298a5192c48f39327fa6085c0753cd67b03728767b8dee79020ebc8896974cba530fe039a5723e157eed74675683f1a4ed87959cd695c997
|
checksum: 10c0/2faa11e30724ca3a0648cdf83e0fc0fbdfcd89168fa0598d235a89604ee20c1f51ca2b70716f2bc0f1ea843de85976c0852de4549ba4649406d6b4acaf63f9c7
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils@npm:8.46.4, @typescript-eslint/tsconfig-utils@npm:^8.46.4":
|
"@typescript-eslint/tsconfig-utils@npm:8.47.0, @typescript-eslint/tsconfig-utils@npm:^8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.4"
|
resolution: "@typescript-eslint/tsconfig-utils@npm:8.47.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/d8ed135c56a15be10822053490b22a4f32ca912deca2c6d3c93a8fec32572842af84d762f0d2ed142b99f1e8251d97402aed9ce9950ef3dc0a8c90e4e1e459fc
|
checksum: 10c0/d62b1840344912f916e590dad0cc5aa8816ce281ea9cac7485a28c4427ecbb88c52fa64b3d8cc520c7cab401ede8631e1b3176306cd3d496f756046e5d0c345f
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@npm:8.46.4":
|
"@typescript-eslint/type-utils@npm:8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/type-utils@npm:8.46.4"
|
resolution: "@typescript-eslint/type-utils@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types": "npm:8.46.4"
|
"@typescript-eslint/types": "npm:8.47.0"
|
||||||
"@typescript-eslint/typescript-estree": "npm:8.46.4"
|
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||||
"@typescript-eslint/utils": "npm:8.46.4"
|
"@typescript-eslint/utils": "npm:8.47.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
ts-api-utils: "npm:^2.1.0"
|
ts-api-utils: "npm:^2.1.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/d4e08a2d2d66b92a93a45c6efd1df272612982ac27204df9a989371f3a7d6eb5a069fc9898ca5b3a5ad70e2df1bc97e77b1f548e229608605b1a1cb33abc2c95
|
checksum: 10c0/68311ad455ed7e6c86e5a561b1a54383b35bc6fec37a642afca1d72ddd74a944f3f5bea5aa493e161c0422f8042da442596455e451ef9204b1fce13a84b256e6
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/types@npm:8.46.4, @typescript-eslint/types@npm:^8.46.4":
|
"@typescript-eslint/types@npm:8.47.0, @typescript-eslint/types@npm:^8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/types@npm:8.46.4"
|
resolution: "@typescript-eslint/types@npm:8.47.0"
|
||||||
checksum: 10c0/b92166dd9b6d8e4cf0a6a90354b6e94af8542d8ab341aed3955990e6599db7a583af638e22909a1417e41fd8a0ef5861c5ba12ad84b307c27d26f3e0c5e2020f
|
checksum: 10c0/0d7f139b29f2581e905463c904b9aef37d8bc62f7b647cd3950d8b139a9fa6821faa5370f4975ccbbd2b2046a50629bd78729be390fb2663e6d103ecda22d794
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@npm:8.46.4":
|
"@typescript-eslint/typescript-estree@npm:8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/typescript-estree@npm:8.46.4"
|
resolution: "@typescript-eslint/typescript-estree@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/project-service": "npm:8.46.4"
|
"@typescript-eslint/project-service": "npm:8.47.0"
|
||||||
"@typescript-eslint/tsconfig-utils": "npm:8.46.4"
|
"@typescript-eslint/tsconfig-utils": "npm:8.47.0"
|
||||||
"@typescript-eslint/types": "npm:8.46.4"
|
"@typescript-eslint/types": "npm:8.47.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:8.46.4"
|
"@typescript-eslint/visitor-keys": "npm:8.47.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
fast-glob: "npm:^3.3.2"
|
fast-glob: "npm:^3.3.2"
|
||||||
is-glob: "npm:^4.0.3"
|
is-glob: "npm:^4.0.3"
|
||||||
|
|
@ -1580,32 +1580,32 @@ __metadata:
|
||||||
ts-api-utils: "npm:^2.1.0"
|
ts-api-utils: "npm:^2.1.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/e115dbd8580801e9b8892a19056ccb91e7c912b587b22ee5a9b7ec03547eff89ad18ea18a31210ea779cf9f4ccec9428f98b62151c26709e19e7adbdd5ca990b
|
checksum: 10c0/b63e72f85382f9022a52c606738400d599a3d27318ec48bad21039758aa6d74050fb2462aa61bac1de8bd5951bc24f775d1dde74140433c60e2943e045c21649
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/utils@npm:8.46.4":
|
"@typescript-eslint/utils@npm:8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/utils@npm:8.46.4"
|
resolution: "@typescript-eslint/utils@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
"@eslint-community/eslint-utils": "npm:^4.7.0"
|
||||||
"@typescript-eslint/scope-manager": "npm:8.46.4"
|
"@typescript-eslint/scope-manager": "npm:8.47.0"
|
||||||
"@typescript-eslint/types": "npm:8.46.4"
|
"@typescript-eslint/types": "npm:8.47.0"
|
||||||
"@typescript-eslint/typescript-estree": "npm:8.46.4"
|
"@typescript-eslint/typescript-estree": "npm:8.47.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: ">=4.8.4 <6.0.0"
|
typescript: ">=4.8.4 <6.0.0"
|
||||||
checksum: 10c0/6e4f4d51113f74edcfc83b135c73edf7c46919895659c2e7d5945ab084bc051ed5f980918d23a941d1a9f96a38c8ddc22c12b5aafa8e35ef3bb9d9c6b00b6c79
|
checksum: 10c0/8774f4e5748bdcefad32b4d06aee589208f4e78500c6c39bd6819b9602fc4212ed69fd774ccd2ad847f87a6bc0092d4db51e440668e7512d366969ab038a74f5
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@npm:8.46.4":
|
"@typescript-eslint/visitor-keys@npm:8.47.0":
|
||||||
version: 8.46.4
|
version: 8.47.0
|
||||||
resolution: "@typescript-eslint/visitor-keys@npm:8.46.4"
|
resolution: "@typescript-eslint/visitor-keys@npm:8.47.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types": "npm:8.46.4"
|
"@typescript-eslint/types": "npm:8.47.0"
|
||||||
eslint-visitor-keys: "npm:^4.2.1"
|
eslint-visitor-keys: "npm:^4.2.1"
|
||||||
checksum: 10c0/35dd6aa2b53fc3f4f214e9edf730cc69d0eb9f77ffd978354d092feda7358e60052e15d891fa8577e9ebee5fdea8083e02fe286dd3a96bbafcb1305dce15b80c
|
checksum: 10c0/14aedfdb5bf9b4c310b4a64cb62af94f35515af44911bae266205138165b3a8dc2cd57db3255ec27531dfa3552ba79a700ec8d745b0d18bca220a7f9f437ad06
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -2491,10 +2491,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"csstype@npm:^3.0.2":
|
"csstype@npm:^3.2.2":
|
||||||
version: 3.1.3
|
version: 3.2.3
|
||||||
resolution: "csstype@npm:3.1.3"
|
resolution: "csstype@npm:3.2.3"
|
||||||
checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248
|
checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
@ -3765,18 +3765,18 @@ __metadata:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@codemirror/search": "npm:^6.5.11"
|
"@codemirror/search": "npm:^6.5.11"
|
||||||
"@codemirror/theme-one-dark": "npm:^6.1.3"
|
"@codemirror/theme-one-dark": "npm:^6.1.3"
|
||||||
"@codemirror/view": "npm:^6.38.7"
|
"@codemirror/view": "npm:^6.38.8"
|
||||||
"@eslint/js": "npm:^9.39.1"
|
"@eslint/js": "npm:^9.39.1"
|
||||||
"@fontsource/inter": "npm:^5.2.8"
|
"@fontsource/inter": "npm:^5.2.8"
|
||||||
"@huggingface/gguf": "npm:^0.3.2"
|
"@huggingface/gguf": "npm:^0.3.2"
|
||||||
"@mantine/core": "npm:^8.3.8"
|
"@mantine/core": "npm:^8.3.8"
|
||||||
"@mantine/hooks": "npm:^8.3.8"
|
"@mantine/hooks": "npm:^8.3.8"
|
||||||
"@types/node": "npm:^24.10.1"
|
"@types/node": "npm:^24.10.1"
|
||||||
"@types/react": "npm:^19.2.5"
|
"@types/react": "npm:^19.2.6"
|
||||||
"@types/react-dom": "npm:^19.2.3"
|
"@types/react-dom": "npm:^19.2.3"
|
||||||
"@types/yauzl": "npm:^2.10.3"
|
"@types/yauzl": "npm:^2.10.3"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.46.4"
|
"@typescript-eslint/eslint-plugin": "npm:^8.47.0"
|
||||||
"@typescript-eslint/parser": "npm:^8.46.4"
|
"@typescript-eslint/parser": "npm:^8.47.0"
|
||||||
"@uiw/react-codemirror": "npm:^4.25.3"
|
"@uiw/react-codemirror": "npm:^4.25.3"
|
||||||
"@vitejs/plugin-react": "npm:^5.1.1"
|
"@vitejs/plugin-react": "npm:^5.1.1"
|
||||||
cross-env: "npm:^10.1.0"
|
cross-env: "npm:^10.1.0"
|
||||||
|
|
@ -3794,7 +3794,7 @@ __metadata:
|
||||||
execa: "npm:^9.6.0"
|
execa: "npm:^9.6.0"
|
||||||
globals: "npm:^16.5.0"
|
globals: "npm:^16.5.0"
|
||||||
jiti: "npm:^2.6.1"
|
jiti: "npm:^2.6.1"
|
||||||
lucide-react: "npm:^0.553.0"
|
lucide-react: "npm:^0.554.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"
|
||||||
|
|
@ -4869,12 +4869,12 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"lucide-react@npm:^0.553.0":
|
"lucide-react@npm:^0.554.0":
|
||||||
version: 0.553.0
|
version: 0.554.0
|
||||||
resolution: "lucide-react@npm:0.553.0"
|
resolution: "lucide-react@npm:0.554.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/b1283f1a2302a50d6a16bd67b9f7417ae08a74cba59ae5ca9953527cb9724247df12ba8c72ca2fc6a70b745f8315cc709ac7573643c4088728766da151999ef6
|
checksum: 10c0/35b11d6f11e4b00047175fcc52f531156be66e5ae7c1f689d0d18bc7bbf06d9e0bf33c767f449e25d99f105acf420399346787491aafe0b55dc68b8d45937e87
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue