- M3: hand-rolled service worker (precache + runtime /ort caching), precache manifest injection, SW registration/update toast, manifest + generated icons, production build script, offline indicator in ModelPicker (D-005/D-007) - M4 backend: bun:sqlite users/sessions (D-009), Passport local strategy behind connect-compat adapter (D-008), /api/auth routes wired into dev+prod serving, Bun.password Argon2id, 38 auth tests - M4 frontend: AuthForms + useAuth + framework-free authClient, session-aware header, anonymous-first fail-silent offline handling, 18 client tests - D-013: server engine (M5) will use faster-whisper via Python sidecar; browser side stays Transformers.js (CTranslate2 has no WASM build) - bun test: 83 pass / tsc --noEmit clean - Note: M3 doc closeout (WorkBreakdown/Architecture/tests/offline.md) still in flight from a background agent; follow-up commit will finish docs Co-Authored-By: Claude Fable 5 <[email protected]>
61 lines
2.1 KiB
TypeScript
61 lines
2.1 KiB
TypeScript
/**
|
|
* "Is this model already downloaded?" helper (Decisions.md D-005).
|
|
*
|
|
* Transformers.js v3 persists Hub downloads in the browser Cache API (Cache
|
|
* Storage, cache name "transformers-cache"). We inspect the cached request
|
|
* URLs and match them against catalog model ids. Used by `ModelPicker` for the
|
|
* downloaded badge (M2) and reused by the offline indicator (M3).
|
|
*
|
|
* `matchCachedModels` is pure so it can be unit-tested; only
|
|
* `getDownloadedModels` touches the `caches` global.
|
|
*/
|
|
|
|
/** Cache name used by Transformers.js v3 (`env.cacheName` default). */
|
|
export const TRANSFORMERS_CACHE_NAME = "transformers-cache";
|
|
|
|
/**
|
|
* Pure matcher: which of `modelIds` have at least one cached ONNX weight file?
|
|
*
|
|
* Cached URLs look like
|
|
* `https://huggingface.co/<modelId>/resolve/main/onnx/<file>.onnx`. We require
|
|
* an `.onnx` hit (not just tokenizer/config JSON) so a model whose weights
|
|
* never finished downloading does not show as available.
|
|
*/
|
|
export function matchCachedModels(
|
|
urls: Iterable<string>,
|
|
modelIds: readonly string[],
|
|
): Set<string> {
|
|
const downloaded = new Set<string>();
|
|
for (const url of urls) {
|
|
if (!url.split("?")[0]?.endsWith(".onnx")) continue;
|
|
for (const modelId of modelIds) {
|
|
if (!downloaded.has(modelId) && url.includes(`/${modelId}/`)) {
|
|
downloaded.add(modelId);
|
|
}
|
|
}
|
|
}
|
|
return downloaded;
|
|
}
|
|
|
|
/**
|
|
* Query the browser Cache API for models with cached weights. Returns an
|
|
* empty set where Cache Storage is unavailable (non-secure contexts, tests).
|
|
*/
|
|
export async function getDownloadedModels(
|
|
modelIds: readonly string[],
|
|
): Promise<Set<string>> {
|
|
if (typeof caches === "undefined") return new Set();
|
|
try {
|
|
const urls: string[] = [];
|
|
// Match any transformers-owned cache, in case the name is ever versioned.
|
|
for (const name of await caches.keys()) {
|
|
if (!name.includes("transformers")) continue;
|
|
const cache = await caches.open(name);
|
|
for (const request of await cache.keys()) urls.push(request.url);
|
|
}
|
|
return matchCachedModels(urls, modelIds);
|
|
} catch {
|
|
return new Set();
|
|
}
|
|
}
|