WIP: download and store web-based models instead of relying on kcpp

This commit is contained in:
Egor 2025-11-18 23:23:48 -08:00
parent f2d15591a7
commit 26d0b2158d
14 changed files with 543 additions and 144 deletions

15
.gitignore vendored
View file

@ -11,18 +11,3 @@ Thumbs.db
# Yarn
.yarn/
.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

View file

@ -1,7 +1,7 @@
{
"name": "gerbil",
"productName": "Gerbil",
"version": "1.9.0",
"version": "2.0.0",
"description": "Run Large Language Models locally",
"main": "out/main/index.js",
"homepage": "./",
@ -40,11 +40,11 @@
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3",
"@types/yauzl": "^2.10.3",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0",
"@vitejs/plugin-react": "^5.1.1",
"cross-env": "^10.1.0",
"electron": "^38.7.0",
@ -67,7 +67,7 @@
"dependencies": {
"@codemirror/search": "^6.5.11",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.7",
"@codemirror/view": "^6.38.8",
"@fontsource/inter": "^5.2.8",
"@huggingface/gguf": "^0.3.2",
"@mantine/core": "^8.3.8",
@ -75,7 +75,7 @@
"@uiw/react-codemirror": "^4.25.3",
"electron-updater": "^6.6.2",
"execa": "^9.6.0",
"lucide-react": "^0.553.0",
"lucide-react": "^0.554.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-error-boundary": "^6.0.0",

View file

@ -44,6 +44,7 @@ export const GeneralTab = ({ configLoaded = true }: GeneralTabProps) => {
onSelectFile={handleSelectModelFile}
searchUrl="https://huggingface.co/models?pipeline_tag=text-generation&library=gguf&sort=trending"
showAnalyze
paramType="model"
/>
<div>

View file

@ -77,15 +77,17 @@ export const ImageGenerationTab = () => {
onSelectFile={handleSelectSdmodelFile}
searchUrl="https://huggingface.co/models?pipeline_tag=text-to-image&library=gguf&sort=trending"
showAnalyze
paramType="sdmodel"
/>
<ModelFileField
label="T5-XXL File"
label="T5XXL File"
value={sdt5xxl}
placeholder="Select a T5-XXL file or enter a direct URL"
tooltip="T5-XXL text encoder model for enhanced text understanding."
placeholder="Select a T5-XXL encoder file or enter a direct URL"
tooltip="T5-XXL text encoder model for advanced text understanding."
onChange={handleSdt5xxlChange}
onSelectFile={handleSelectSdt5xxlFile}
paramType="sdt5xxl"
/>
<ModelFileField
@ -95,6 +97,7 @@ export const ImageGenerationTab = () => {
tooltip="CLIP-L text encoder model for text-image understanding."
onChange={handleSdcliplChange}
onSelectFile={handleSelectSdcliplFile}
paramType="sdclipl"
/>
<ModelFileField
@ -104,6 +107,7 @@ export const ImageGenerationTab = () => {
tooltip="CLIP-G text encoder model for enhanced text-image understanding."
onChange={handleSdclipgChange}
onSelectFile={handleSelectSdclipgFile}
paramType="sdclipg"
/>
<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)."
onChange={handleSdphotomakerChange}
onSelectFile={handleSelectSdphotomakerFile}
paramType="sdphotomaker"
/>
<ModelFileField
@ -122,6 +127,7 @@ export const ImageGenerationTab = () => {
tooltip="Variational Autoencoder model for improved image quality."
onChange={handleSdvaeChange}
onSelectFile={handleSelectSdvaeFile}
paramType="sdvae"
/>
<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."
onChange={handleSdloraChange}
onSelectFile={handleSelectSdloraFile}
paramType="sdlora"
/>
<SelectWithTooltip

View file

@ -1,11 +1,19 @@
import { Group, TextInput, ActionIcon, Tooltip, Button } from '@mantine/core';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import {
Group,
ActionIcon,
Tooltip,
Button,
Combobox,
useCombobox,
TextInput,
} from '@mantine/core';
import { File, Search, Info } from 'lucide-react';
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
import { ModelAnalysisModal } from '@/components/screens/Launch/ModelAnalysisModal';
import { getInputValidationState } from '@/utils/validation';
import { logError } from '@/utils/logger';
import type { ModelAnalysis } from '@/types';
import type { ModelAnalysis, ModelParamType, CachedModel } from '@/types';
interface ModelFileFieldProps {
label: string;
@ -16,6 +24,7 @@ interface ModelFileFieldProps {
onSelectFile: () => void;
searchUrl?: string;
showAnalyze?: boolean;
paramType: ModelParamType;
}
export const ModelFileField = ({
@ -27,6 +36,7 @@ export const ModelFileField = ({
onSelectFile,
searchUrl,
showAnalyze = false,
paramType,
}: ModelFileFieldProps) => {
const validationState = getInputValidationState(value);
const [analysisModalOpened, setAnalysisModalOpened] = useState(false);
@ -35,6 +45,26 @@ export const ModelFileField = ({
);
const [analysisLoading, setAnalysisLoading] = useState(false);
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 = () => {
if (validationState === 'neutral') return undefined;
@ -72,12 +102,37 @@ export const ModelFileField = ({
<LabelWithTooltip label={label} tooltip={tooltip} />
<Group gap="xs" align="flex-start">
<div style={{ flex: 1 }}>
<Combobox
store={combobox}
onOptionSubmit={(val) => {
onChange(val);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<TextInput
placeholder={placeholder}
value={value}
onChange={(event) => onChange(event.currentTarget.value)}
error={validationState === 'invalid' ? getHelperText() : undefined}
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>
<Button
onClick={onSelectFile}

View file

@ -15,7 +15,7 @@ export const SERVER_READY_SIGNALS = {
export const DEFAULT_CONTEXT_SIZE = 4096;
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;

View file

@ -21,6 +21,7 @@ import {
selectModelFile,
selectInstallDirectory,
} from '@/main/modules/koboldcpp/config';
import { getLocalModelsForType } from '@/main/modules/koboldcpp/modelDownload';
import { analyzeGGUFModel } from '@/main/modules/koboldcpp/analyze';
import {
get as getConfig,
@ -156,6 +157,12 @@ export function setupIPCHandlers() {
selectModelFile(title)
);
ipcMain.handle('kobold:getLocalModels', (_, paramType: string) =>
getLocalModelsForType(
paramType as Parameters<typeof getLocalModelsForType>[0]
)
);
ipcMain.handle('kobold:analyzeModel', async (_, filePath: string) =>
analyzeGGUFModel(filePath)
);

View file

@ -15,13 +15,58 @@ import { startFrontend as startSillyTavernFrontend } from '@/main/modules/sillyt
import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui';
import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches';
import { startProxy, stopProxy } from '../proxy';
import { resolveModelPath } from '../modelDownload';
import type {
FrontendPreference,
ImageGenerationFrontendPreference,
ModelParamType,
} from '@/types';
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(
args: string[] = [],
frontendPreference: FrontendPreference = 'koboldcpp',
@ -64,7 +109,8 @@ export async function launchKoboldCpp(
await patchKcppSduiEmbd(binaryDir);
}
const finalArgs = [...args];
const resolvedArgs = await resolveModelPaths(args);
const finalArgs = [...resolvedArgs];
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
await startProxy(koboldHost, koboldPort);

View 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;
}

View file

@ -51,6 +51,8 @@ const koboldAPI: KoboldAPI = {
ipcRenderer.invoke('kobold:parseConfigFile', filePath),
selectModelFile: (title) =>
ipcRenderer.invoke('kobold:selectModelFile', title),
getLocalModels: (paramType) =>
ipcRenderer.invoke('kobold:getLocalModels', paramType),
analyzeModel: (filePath) =>
ipcRenderer.invoke('kobold:analyzeModel', filePath),
calculateOptimalLayers: (

View file

@ -10,6 +10,7 @@ import type {
BackendSupport,
Screen,
ModelAnalysis,
CachedModel,
} from '@/types';
import type { MantineColorScheme } from '@mantine/core';
import type {
@ -156,6 +157,7 @@ export interface KoboldAPI {
setSelectedConfig: (configName: string) => Promise<boolean>;
parseConfigFile: (filePath: string) => Promise<KoboldConfig | null>;
selectModelFile: (title?: string) => Promise<string | null>;
getLocalModels: (paramType: string) => Promise<CachedModel[]>;
analyzeModel: (filePath: string) => Promise<ModelAnalysis>;
calculateOptimalLayers: (
modelPath: string,

22
src/types/index.d.ts vendored
View file

@ -18,6 +18,28 @@ export type ImageGenerationFrontendPreference = 'match' | 'builtin';
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 {
name: string;
browser_download_url: string;

View file

@ -1,27 +1,8 @@
export const handleTerminalOutput = (prevContent: string, newData: string) => {
if (newData.includes('\r')) {
const hasStandaloneCarriageReturns = /\r(?!\n)/g.test(newData);
if (hasStandaloneCarriageReturns) {
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$/, '');
}
if (newData.startsWith('\r') && !newData.startsWith('\r\n')) {
const lines = prevContent.split('\n');
lines[lines.length - 1] = newData.slice(1);
return lines.join('\n');
}
return prevContent + newData;

172
yarn.lock
View file

@ -315,15 +315,15 @@ __metadata:
languageName: node
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":
version: 6.38.7
resolution: "@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.8
resolution: "@codemirror/view@npm:6.38.8"
dependencies:
"@codemirror/state": "npm:^6.5.0"
crelt: "npm:^1.0.6"
style-mod: "npm:^4.1.0"
w3c-keyname: "npm:^2.2.4"
checksum: 10c0/48e237f790d4f9e173e2428f73809c4f147dff401e616e8155397ce17ab296a8c69b0267ce7a565b93a3e1c531f9573e3666069b8f113ac0b98da4997c7e7f9b
checksum: 10c0/42d5d40a00651adcf6cd90b45790556a339b034943a27daabb60f961591fb4a622f270917017f0ca304f62d9dd91f681992d8e2617738c39978b424f9a538aa1
languageName: node
linkType: hard
@ -1431,12 +1431,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:^19.2.5":
version: 19.2.5
resolution: "@types/react@npm:19.2.5"
"@types/react@npm:^19.2.6":
version: 19.2.6
resolution: "@types/react@npm:19.2.6"
dependencies:
csstype: "npm:^3.0.2"
checksum: 10c0/1f9a92c73a5ea5b167f59cd0b5b9460fde65bd22b63b6d23bfaace8ad38537df127c97657418b4912a7a03a66e6451e82a41b84718d638ec1c8e4f0515d94793
csstype: "npm:^3.2.2"
checksum: 10c0/23b1100f88662ce9f9e4fcca3a2b4ef9fff1ecde24ede2b2dcbd07731e48d6946fd7fd156cd133f5b25321694b0569cd9b8dd30b22c4e076d1cf4c8cdd9a75cb
languageName: node
linkType: hard
@ -1472,106 +1472,106 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/eslint-plugin@npm:8.46.4"
"@typescript-eslint/eslint-plugin@npm:^8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.47.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.46.4"
"@typescript-eslint/type-utils": "npm:8.46.4"
"@typescript-eslint/utils": "npm:8.46.4"
"@typescript-eslint/visitor-keys": "npm:8.46.4"
"@typescript-eslint/scope-manager": "npm:8.47.0"
"@typescript-eslint/type-utils": "npm:8.47.0"
"@typescript-eslint/utils": "npm:8.47.0"
"@typescript-eslint/visitor-keys": "npm:8.47.0"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.46.4
"@typescript-eslint/parser": ^8.47.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/c487e55c2f35e89126a13a6997f06494c26a3c96b9a7685421e2d92929f3ab302c1c234f0add9113705fbad693b05b3b87cebe5219bc71b2af9ee7aa8e7dc12c
checksum: 10c0/abd35affd21bc199e5e274b8e91e4225a127edf9cbe5047c465f859d7e393d07556ea42b40004e769ed59b18cfe25ab30942c854e23026d4f78d350eb71de03e
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/parser@npm:8.46.4"
"@typescript-eslint/parser@npm:^8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/parser@npm:8.47.0"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.46.4"
"@typescript-eslint/types": "npm:8.46.4"
"@typescript-eslint/typescript-estree": "npm:8.46.4"
"@typescript-eslint/visitor-keys": "npm:8.46.4"
"@typescript-eslint/scope-manager": "npm:8.47.0"
"@typescript-eslint/types": "npm:8.47.0"
"@typescript-eslint/typescript-estree": "npm:8.47.0"
"@typescript-eslint/visitor-keys": "npm:8.47.0"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/bef98fa9250d5720479c10f803ca66a2a0b382158a8b462fd1c710351f7b423570c273556fb828e64d8a87041d54d51fa5a5e1e88ebdc1c88da0ee1098f9405e
checksum: 10c0/8f8c9514ffe8c2fca9e2d1d3e9f9f8dd4cb55c14f0ef2f4f265a9180615ec98dc455d373893f76f86760f37e449fd0f4afda46c1211291b9736a05ba010912f2
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/project-service@npm:8.46.4"
"@typescript-eslint/project-service@npm:8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/project-service@npm:8.47.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.46.4"
"@typescript-eslint/types": "npm:^8.46.4"
"@typescript-eslint/tsconfig-utils": "npm:^8.47.0"
"@typescript-eslint/types": "npm:^8.47.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/81c5de7b85a2b1bff51ef27d25f11be992b7e550bfe34d4cbc4eb71f0fd03bcc1619644ac8efd594c515c894317f98db9176ef333004718d997c666791ca8b95
checksum: 10c0/6d7ec78c63d672178727b2d79856b470bd99e90d387335decec026931caa94c6907afc4690b884ce1eaca65f2d8b8f070a5c6e70e47971dfeec34dfd022933b8
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/scope-manager@npm:8.46.4"
"@typescript-eslint/scope-manager@npm:8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/scope-manager@npm:8.47.0"
dependencies:
"@typescript-eslint/types": "npm:8.46.4"
"@typescript-eslint/visitor-keys": "npm:8.46.4"
checksum: 10c0/f614b5a95f1803a4298a5192c48f39327fa6085c0753cd67b03728767b8dee79020ebc8896974cba530fe039a5723e157eed74675683f1a4ed87959cd695c997
"@typescript-eslint/types": "npm:8.47.0"
"@typescript-eslint/visitor-keys": "npm:8.47.0"
checksum: 10c0/2faa11e30724ca3a0648cdf83e0fc0fbdfcd89168fa0598d235a89604ee20c1f51ca2b70716f2bc0f1ea843de85976c0852de4549ba4649406d6b4acaf63f9c7
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.46.4, @typescript-eslint/tsconfig-utils@npm:^8.46.4":
version: 8.46.4
resolution: "@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.47.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.47.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/d8ed135c56a15be10822053490b22a4f32ca912deca2c6d3c93a8fec32572842af84d762f0d2ed142b99f1e8251d97402aed9ce9950ef3dc0a8c90e4e1e459fc
checksum: 10c0/d62b1840344912f916e590dad0cc5aa8816ce281ea9cac7485a28c4427ecbb88c52fa64b3d8cc520c7cab401ede8631e1b3176306cd3d496f756046e5d0c345f
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/type-utils@npm:8.46.4"
"@typescript-eslint/type-utils@npm:8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/type-utils@npm:8.47.0"
dependencies:
"@typescript-eslint/types": "npm:8.46.4"
"@typescript-eslint/typescript-estree": "npm:8.46.4"
"@typescript-eslint/utils": "npm:8.46.4"
"@typescript-eslint/types": "npm:8.47.0"
"@typescript-eslint/typescript-estree": "npm:8.47.0"
"@typescript-eslint/utils": "npm:8.47.0"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/d4e08a2d2d66b92a93a45c6efd1df272612982ac27204df9a989371f3a7d6eb5a069fc9898ca5b3a5ad70e2df1bc97e77b1f548e229608605b1a1cb33abc2c95
checksum: 10c0/68311ad455ed7e6c86e5a561b1a54383b35bc6fec37a642afca1d72ddd74a944f3f5bea5aa493e161c0422f8042da442596455e451ef9204b1fce13a84b256e6
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.46.4, @typescript-eslint/types@npm:^8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/types@npm:8.46.4"
checksum: 10c0/b92166dd9b6d8e4cf0a6a90354b6e94af8542d8ab341aed3955990e6599db7a583af638e22909a1417e41fd8a0ef5861c5ba12ad84b307c27d26f3e0c5e2020f
"@typescript-eslint/types@npm:8.47.0, @typescript-eslint/types@npm:^8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/types@npm:8.47.0"
checksum: 10c0/0d7f139b29f2581e905463c904b9aef37d8bc62f7b647cd3950d8b139a9fa6821faa5370f4975ccbbd2b2046a50629bd78729be390fb2663e6d103ecda22d794
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/typescript-estree@npm:8.46.4"
"@typescript-eslint/typescript-estree@npm:8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/typescript-estree@npm:8.47.0"
dependencies:
"@typescript-eslint/project-service": "npm:8.46.4"
"@typescript-eslint/tsconfig-utils": "npm:8.46.4"
"@typescript-eslint/types": "npm:8.46.4"
"@typescript-eslint/visitor-keys": "npm:8.46.4"
"@typescript-eslint/project-service": "npm:8.47.0"
"@typescript-eslint/tsconfig-utils": "npm:8.47.0"
"@typescript-eslint/types": "npm:8.47.0"
"@typescript-eslint/visitor-keys": "npm:8.47.0"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@ -1580,32 +1580,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/e115dbd8580801e9b8892a19056ccb91e7c912b587b22ee5a9b7ec03547eff89ad18ea18a31210ea779cf9f4ccec9428f98b62151c26709e19e7adbdd5ca990b
checksum: 10c0/b63e72f85382f9022a52c606738400d599a3d27318ec48bad21039758aa6d74050fb2462aa61bac1de8bd5951bc24f775d1dde74140433c60e2943e045c21649
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/utils@npm:8.46.4"
"@typescript-eslint/utils@npm:8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/utils@npm:8.47.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.46.4"
"@typescript-eslint/types": "npm:8.46.4"
"@typescript-eslint/typescript-estree": "npm:8.46.4"
"@typescript-eslint/scope-manager": "npm:8.47.0"
"@typescript-eslint/types": "npm:8.47.0"
"@typescript-eslint/typescript-estree": "npm:8.47.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/6e4f4d51113f74edcfc83b135c73edf7c46919895659c2e7d5945ab084bc051ed5f980918d23a941d1a9f96a38c8ddc22c12b5aafa8e35ef3bb9d9c6b00b6c79
checksum: 10c0/8774f4e5748bdcefad32b4d06aee589208f4e78500c6c39bd6819b9602fc4212ed69fd774ccd2ad847f87a6bc0092d4db51e440668e7512d366969ab038a74f5
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.46.4":
version: 8.46.4
resolution: "@typescript-eslint/visitor-keys@npm:8.46.4"
"@typescript-eslint/visitor-keys@npm:8.47.0":
version: 8.47.0
resolution: "@typescript-eslint/visitor-keys@npm:8.47.0"
dependencies:
"@typescript-eslint/types": "npm:8.46.4"
"@typescript-eslint/types": "npm:8.47.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10c0/35dd6aa2b53fc3f4f214e9edf730cc69d0eb9f77ffd978354d092feda7358e60052e15d891fa8577e9ebee5fdea8083e02fe286dd3a96bbafcb1305dce15b80c
checksum: 10c0/14aedfdb5bf9b4c310b4a64cb62af94f35515af44911bae266205138165b3a8dc2cd57db3255ec27531dfa3552ba79a700ec8d745b0d18bca220a7f9f437ad06
languageName: node
linkType: hard
@ -2491,10 +2491,10 @@ __metadata:
languageName: node
linkType: hard
"csstype@npm:^3.0.2":
version: 3.1.3
resolution: "csstype@npm:3.1.3"
checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248
"csstype@npm:^3.2.2":
version: 3.2.3
resolution: "csstype@npm:3.2.3"
checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce
languageName: node
linkType: hard
@ -3765,18 +3765,18 @@ __metadata:
dependencies:
"@codemirror/search": "npm:^6.5.11"
"@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"
"@fontsource/inter": "npm:^5.2.8"
"@huggingface/gguf": "npm:^0.3.2"
"@mantine/core": "npm:^8.3.8"
"@mantine/hooks": "npm:^8.3.8"
"@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/yauzl": "npm:^2.10.3"
"@typescript-eslint/eslint-plugin": "npm:^8.46.4"
"@typescript-eslint/parser": "npm:^8.46.4"
"@typescript-eslint/eslint-plugin": "npm:^8.47.0"
"@typescript-eslint/parser": "npm:^8.47.0"
"@uiw/react-codemirror": "npm:^4.25.3"
"@vitejs/plugin-react": "npm:^5.1.1"
cross-env: "npm:^10.1.0"
@ -3794,7 +3794,7 @@ __metadata:
execa: "npm:^9.6.0"
globals: "npm:^16.5.0"
jiti: "npm:^2.6.1"
lucide-react: "npm:^0.553.0"
lucide-react: "npm:^0.554.0"
prettier: "npm:^3.6.2"
react: "npm:^19.2.0"
react-dom: "npm:^19.2.0"
@ -4869,12 +4869,12 @@ __metadata:
languageName: node
linkType: hard
"lucide-react@npm:^0.553.0":
version: 0.553.0
resolution: "lucide-react@npm:0.553.0"
"lucide-react@npm:^0.554.0":
version: 0.554.0
resolution: "lucide-react@npm:0.554.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
checksum: 10c0/b1283f1a2302a50d6a16bd67b9f7417ae08a74cba59ae5ca9953527cb9724247df12ba8c72ca2fc6a70b745f8315cc709ac7573643c4088728766da151999ef6
checksum: 10c0/35b11d6f11e4b00047175fcc52f531156be66e5ae7c1f689d0d18bc7bbf06d9e0bf33c767f449e25d99f105acf420399346787491aafe0b55dc68b8d45937e87
languageName: node
linkType: hard