/** * "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//resolve/main/onnx/.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, modelIds: readonly string[], ): Set { const downloaded = new Set(); 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> { 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(); } }