feat: allow user to config Gemma 4 E4B and MDX-NET model download URL
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
export const LOCAL_LLM_PROVIDER_KEY = 'local_browser';
|
||||
export const LOCAL_LLM_MODEL_URL =
|
||||
export const LOCAL_LLM_DEFAULT_MODEL_URL =
|
||||
'https://huggingface.co/notabilia/gemma-4-E4B-it-litert-lm/resolve/main/gemma-4-E4B-it-web.task';
|
||||
export const LOCAL_LLM_MODEL_FILENAME = 'gemma-4-E4B-it-web.task';
|
||||
export const LOCAL_LLM_MODEL_EXPECTED_SIZE_BYTES = 2964324352;
|
||||
export const LOCAL_LLM_DISPLAY_NAME = 'Gemma 4 E4B';
|
||||
export const LOCAL_LLM_LEGACY_FILENAMES = [
|
||||
'gemma-3n-E4B-it-int4-Web.litertlm',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OpfsModelCache, type ModelDownloadProgress } from './opfsModelCache';
|
||||
import { LOCAL_LLM_MODEL_FILENAME } from './localLLMConfig';
|
||||
import { LOCAL_LLM_MODEL_EXPECTED_SIZE_BYTES, LOCAL_LLM_MODEL_FILENAME } from './localLLMConfig';
|
||||
|
||||
const cache = new OpfsModelCache({ directoryName: 'models' });
|
||||
let writingToCachePromise: Promise<void> | null = null;
|
||||
@@ -51,7 +51,9 @@ const createProgressReader = (
|
||||
|
||||
export class LocalLLMModelCache {
|
||||
public static async exists(filename: string = LOCAL_LLM_MODEL_FILENAME): Promise<boolean> {
|
||||
return cache.exists(filename);
|
||||
return cache.exists(filename, {
|
||||
expectedSizeBytes: LOCAL_LLM_MODEL_EXPECTED_SIZE_BYTES,
|
||||
});
|
||||
}
|
||||
|
||||
public static async getFile(filename: string = LOCAL_LLM_MODEL_FILENAME): Promise<File> {
|
||||
@@ -98,6 +100,9 @@ export class LocalLLMModelCache {
|
||||
streamForCache,
|
||||
filename,
|
||||
totalBytes > 0 ? totalBytes : null,
|
||||
{
|
||||
expectedSizeBytes: LOCAL_LLM_MODEL_EXPECTED_SIZE_BYTES,
|
||||
},
|
||||
progress => onProgress?.({ ...progress, fromCache: false }),
|
||||
);
|
||||
writingToCachePromise = writingToCachePromise.finally(() => {
|
||||
@@ -117,6 +122,13 @@ export class LocalLLMModelCache {
|
||||
filename: string = LOCAL_LLM_MODEL_FILENAME,
|
||||
onProgress?: (progress: ModelDownloadProgress) => void,
|
||||
): Promise<void> {
|
||||
await cache.download(sourceUrl, filename, onProgress);
|
||||
await cache.download(
|
||||
sourceUrl,
|
||||
filename,
|
||||
{
|
||||
expectedSizeBytes: LOCAL_LLM_MODEL_EXPECTED_SIZE_BYTES,
|
||||
},
|
||||
onProgress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
detectLocalLLMRuntimeSupport,
|
||||
LOCAL_LLM_LEGACY_FILENAMES,
|
||||
LOCAL_LLM_MODEL_FILENAME,
|
||||
LOCAL_LLM_MODEL_URL,
|
||||
type LocalLLMRuntimeSupport,
|
||||
} from './localLLMConfig';
|
||||
import { LocalLLMModelCache } from './localLLMModelCache';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { LocalSeparatorModelConfig } from './localSeparatorTypes';
|
||||
|
||||
export const LOCAL_SEPARATOR_MODEL_URL =
|
||||
export const LOCAL_SEPARATOR_DEFAULT_MODEL_URL =
|
||||
'https://huggingface.co/notabilia/uvr5-models/resolve/main/UVR-MDX-NET-Inst_HQ_3.onnx';
|
||||
|
||||
export const LOCAL_SEPARATOR_MODEL_FILENAME = 'UVR-MDX-NET-Inst_HQ_3.onnx';
|
||||
export const LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES = 66759214;
|
||||
|
||||
export const LOCAL_SEPARATOR_MODEL_CONFIG: LocalSeparatorModelConfig = {
|
||||
filename: LOCAL_SEPARATOR_MODEL_FILENAME,
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { LOCAL_SEPARATOR_MODEL_FILENAME } from './localSeparatorConfig';
|
||||
import {
|
||||
LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES,
|
||||
LOCAL_SEPARATOR_MODEL_FILENAME,
|
||||
} from './localSeparatorConfig';
|
||||
import { OpfsModelCache, type ModelDownloadProgress } from './opfsModelCache';
|
||||
|
||||
const cache = new OpfsModelCache({ directoryName: 'models' });
|
||||
@@ -7,7 +10,9 @@ export { type ModelDownloadProgress };
|
||||
|
||||
export class LocalSeparatorModelCache {
|
||||
public static async exists(filename: string = LOCAL_SEPARATOR_MODEL_FILENAME): Promise<boolean> {
|
||||
return cache.exists(filename);
|
||||
return cache.exists(filename, {
|
||||
expectedSizeBytes: LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES,
|
||||
});
|
||||
}
|
||||
|
||||
public static async getFile(filename: string = LOCAL_SEPARATOR_MODEL_FILENAME): Promise<File> {
|
||||
@@ -27,6 +32,13 @@ export class LocalSeparatorModelCache {
|
||||
filename: string = LOCAL_SEPARATOR_MODEL_FILENAME,
|
||||
onProgress?: (progress: ModelDownloadProgress) => void,
|
||||
): Promise<void> {
|
||||
await cache.download(sourceUrl, filename, onProgress);
|
||||
await cache.download(
|
||||
sourceUrl,
|
||||
filename,
|
||||
{
|
||||
expectedSizeBytes: LOCAL_SEPARATOR_MODEL_EXPECTED_SIZE_BYTES,
|
||||
},
|
||||
onProgress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ interface OpfsModelCacheOptions {
|
||||
sizeSuffix?: string;
|
||||
}
|
||||
|
||||
interface ModelCacheValidationOptions {
|
||||
expectedSizeBytes?: number | null;
|
||||
}
|
||||
|
||||
export class OpfsModelCache {
|
||||
private readonly directoryName: string;
|
||||
private readonly sizeSuffix: string;
|
||||
@@ -18,7 +22,7 @@ export class OpfsModelCache {
|
||||
this.sizeSuffix = options.sizeSuffix ?? '.size';
|
||||
}
|
||||
|
||||
public async exists(filename: string): Promise<boolean> {
|
||||
public async exists(filename: string, options: ModelCacheValidationOptions = {}): Promise<boolean> {
|
||||
try {
|
||||
const dir = await this.getDir();
|
||||
const fileHandle = await dir.getFileHandle(filename);
|
||||
@@ -29,6 +33,10 @@ export class OpfsModelCache {
|
||||
await this.delete(filename);
|
||||
return false;
|
||||
}
|
||||
if (options.expectedSizeBytes != null && expectedSize !== options.expectedSizeBytes) {
|
||||
await this.delete(filename);
|
||||
return false;
|
||||
}
|
||||
if (file.size !== expectedSize) {
|
||||
await this.delete(filename);
|
||||
return false;
|
||||
@@ -64,6 +72,7 @@ export class OpfsModelCache {
|
||||
public async download(
|
||||
sourceUrl: string,
|
||||
filename: string,
|
||||
options: ModelCacheValidationOptions = {},
|
||||
onProgress?: (progress: ModelDownloadProgress) => void,
|
||||
): Promise<void> {
|
||||
const response = await fetch(sourceUrl);
|
||||
@@ -75,13 +84,14 @@ export class OpfsModelCache {
|
||||
if (!response.body) {
|
||||
throw new Error('Model download response did not include a readable body.');
|
||||
}
|
||||
await this.downloadStream(response.body, filename, totalBytes, onProgress);
|
||||
await this.downloadStream(response.body, filename, totalBytes, options, onProgress);
|
||||
}
|
||||
|
||||
public async downloadStream(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
filename: string,
|
||||
totalBytes: number | null,
|
||||
options: ModelCacheValidationOptions = {},
|
||||
onProgress?: (progress: ModelDownloadProgress) => void,
|
||||
): Promise<void> {
|
||||
const dir = await this.getDir();
|
||||
@@ -111,6 +121,9 @@ export class OpfsModelCache {
|
||||
if (!Number.isFinite(sizeValue) || sizeValue <= 0) {
|
||||
throw new Error('Model download did not provide a valid size.');
|
||||
}
|
||||
if (options.expectedSizeBytes != null && receivedBytes !== options.expectedSizeBytes) {
|
||||
throw new Error(`Model download size mismatch for ${filename}: expected ${options.expectedSizeBytes} bytes, got ${receivedBytes}.`);
|
||||
}
|
||||
|
||||
const sizeHandle = await dir.getFileHandle(this.getSizeFilename(filename), { create: true });
|
||||
const sizeWritable = await sizeHandle.createWritable();
|
||||
|
||||
Reference in New Issue
Block a user