Transcript export (.txt/.srt) and model storage manager (M6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0084e0435a
commit
8f2da530e2
9 files changed
+743
-12
No files matched your search
+129
-2
@@ -1,8 +1,16 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { matchCachedModels } from "../src/lib/modelCache";
|
||||
import {
|
||||
deleteModelFromCache,
|
||||
getModelStorageInfo,
|
||||
matchCachedModels,
|
||||
modelIdForUrl,
|
||||
type StorageCache,
|
||||
type StorageCacheStorage,
|
||||
} from "../src/lib/modelCache";
|
||||
|
||||
// Pure URL matching (D-005 downloaded badge); the `caches` wrapper is
|
||||
// browser-only and exercised manually.
|
||||
// browser-only and exercised manually. The M6 storage helpers take an
|
||||
// injectable CacheStorage-shaped object, mocked below.
|
||||
|
||||
const TINY = "onnx-community/whisper-tiny.en";
|
||||
const BASE = "onnx-community/whisper-base";
|
||||
@@ -47,3 +55,122 @@ describe("matchCachedModels", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("modelIdForUrl", () => {
|
||||
test("matches on the /modelId/ segment, any file type", () => {
|
||||
expect(modelIdForUrl(hubUrl(TINY, "tokenizer.json"), [TINY, BASE])).toBe(TINY);
|
||||
expect(modelIdForUrl(hubUrl(BASE, "onnx/encoder_model_q4.onnx"), [TINY, BASE])).toBe(
|
||||
BASE,
|
||||
);
|
||||
});
|
||||
|
||||
test("'.en' variant URLs never match the multilingual sibling", () => {
|
||||
const url = hubUrl(TINY, "onnx/encoder_model.onnx");
|
||||
expect(modelIdForUrl(url, ["onnx-community/whisper-tiny"])).toBeUndefined();
|
||||
});
|
||||
|
||||
test("unmatched URLs yield undefined", () => {
|
||||
expect(modelIdForUrl("https://example.com/other.bin", [TINY])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// M6 storage helpers, against a mocked Cache Storage
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Mutable mock: cache name → (url → body size in bytes). */
|
||||
function mockCacheStorage(
|
||||
data: Record<string, Record<string, number>>,
|
||||
): StorageCacheStorage {
|
||||
const stores = new Map<string, Map<string, number>>(
|
||||
Object.entries(data).map(([name, entries]) => [
|
||||
name,
|
||||
new Map(Object.entries(entries)),
|
||||
]),
|
||||
);
|
||||
function openCache(name: string): StorageCache {
|
||||
const entries = stores.get(name) ?? new Map<string, number>();
|
||||
return {
|
||||
keys: async () => [...entries.keys()].map((url) => ({ url })),
|
||||
match: async (request) => {
|
||||
const size = entries.get(request.url);
|
||||
if (size === undefined) return undefined;
|
||||
return { blob: async () => new Blob([new Uint8Array(size)]) };
|
||||
},
|
||||
delete: async (request) => entries.delete(request.url),
|
||||
};
|
||||
}
|
||||
return {
|
||||
keys: async () => [...stores.keys()],
|
||||
open: async (name) => openCache(name),
|
||||
};
|
||||
}
|
||||
|
||||
describe("getModelStorageInfo", () => {
|
||||
test("sums bytes and counts files per model, in catalog order", async () => {
|
||||
const storage = mockCacheStorage({
|
||||
"transformers-cache": {
|
||||
[hubUrl(BASE, "onnx/encoder_model_q8.onnx")]: 2_000,
|
||||
[hubUrl(TINY, "onnx/encoder_model_quantized.onnx")]: 1_000,
|
||||
[hubUrl(TINY, "onnx/decoder_model_merged_quantized.onnx")]: 3_000,
|
||||
[hubUrl(TINY, "tokenizer.json")]: 50,
|
||||
},
|
||||
});
|
||||
expect(await getModelStorageInfo([TINY, BASE], storage)).toEqual([
|
||||
{ modelId: TINY, fileCount: 3, bytes: 4_050 },
|
||||
{ modelId: BASE, fileCount: 1, bytes: 2_000 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("ignores non-transformers caches and unmatched URLs", async () => {
|
||||
const storage = mockCacheStorage({
|
||||
"whisper-runtime-v1": { "https://example.com/ort/ort-wasm.wasm": 9_999_999 },
|
||||
"transformers-cache": {
|
||||
[hubUrl(TINY, "config.json")]: 10,
|
||||
"https://example.com/unrelated.bin": 123,
|
||||
},
|
||||
});
|
||||
expect(await getModelStorageInfo([TINY, BASE], storage)).toEqual([
|
||||
{ modelId: TINY, fileCount: 1, bytes: 10 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("returns [] with no cache storage (non-browser contexts)", async () => {
|
||||
expect(await getModelStorageInfo([TINY], undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteModelFromCache", () => {
|
||||
test("removes only the target model's entries and reports deletion", async () => {
|
||||
const storage = mockCacheStorage({
|
||||
"transformers-cache": {
|
||||
[hubUrl(TINY, "onnx/encoder_model_quantized.onnx")]: 1_000,
|
||||
[hubUrl(TINY, "tokenizer.json")]: 50,
|
||||
[hubUrl(BASE, "onnx/encoder_model_q8.onnx")]: 2_000,
|
||||
},
|
||||
});
|
||||
expect(await deleteModelFromCache(TINY, storage)).toBe(true);
|
||||
expect(await getModelStorageInfo([TINY, BASE], storage)).toEqual([
|
||||
{ modelId: BASE, fileCount: 1, bytes: 2_000 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("does not delete the '.en' sibling's files", async () => {
|
||||
const multilingualTiny = "onnx-community/whisper-tiny";
|
||||
const storage = mockCacheStorage({
|
||||
"transformers-cache": {
|
||||
[hubUrl(TINY, "onnx/encoder_model_quantized.onnx")]: 1_000,
|
||||
},
|
||||
});
|
||||
expect(await deleteModelFromCache(multilingualTiny, storage)).toBe(false);
|
||||
expect(await getModelStorageInfo([TINY], storage)).toEqual([
|
||||
{ modelId: TINY, fileCount: 1, bytes: 1_000 },
|
||||
]);
|
||||
});
|
||||
|
||||
test("returns false when nothing matched or storage is unavailable", async () => {
|
||||
const storage = mockCacheStorage({ "transformers-cache": {} });
|
||||
expect(await deleteModelFromCache(TINY, storage)).toBe(false);
|
||||
expect(await deleteModelFromCache(TINY, undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user