cp500's picture
Upload js/src/hub.ts with huggingface_hub
5505540 verified
Raw
History Blame Contribute Delete
2.57 kB
/**
* Hugging Face Hub fetcher with browser caching.
*
* We don't pull in @huggingface/hub: it's a heavy dep with a lot of
* upload-side machinery we don't need for a read-only inference
* client. The hub URL pattern is stable enough to inline.
*
* URL pattern:
* https://huggingface.co/<repo>/resolve/<revision>/<filename>
*
* The ``main`` revision is fine for everyone except library authors;
* pin to a commit SHA for reproducibility once the model lands.
*/
const HF_BASE = 'https://huggingface.co';
export interface HubFile {
/** Repo identifier, e.g. ``cp500/infon-coref-pointer``. */
repo: string;
/** Path inside the repo, e.g. ``onnx/backbone_bio_fp16.onnx``. */
path: string;
/** Branch, tag, or commit SHA. */
revision?: string;
}
/** Build a downloadable URL for a file in an HF repo. */
export function hubUrl({ repo, path, revision = 'main' }: HubFile): string {
return `${HF_BASE}/${repo}/resolve/${revision}/${path}`;
}
/**
* Fetch a file from the Hub as an ``ArrayBuffer``.
*
* Browsers automatically cache by URL via the HTTP cache, so repeated
* loads in the same session reuse the disk copy. For longer-term
* caching we use ``caches.open`` (Cache API) when available — that
* survives reloads and works offline.
*/
export async function fetchHubFile(
file: HubFile,
opts?: { cacheName?: string },
): Promise<ArrayBuffer> {
const url = hubUrl(file);
const cacheName = opts?.cacheName ?? 'infon-coref-v1';
// Browser Cache API path.
if (typeof caches !== 'undefined') {
try {
const cache = await caches.open(cacheName);
const cached = await cache.match(url);
if (cached) return await cached.arrayBuffer();
const r = await fetch(url);
if (!r.ok) throw new Error(`hub fetch ${url}: ${r.status}`);
// Clone before consuming so we can stash it in the cache.
cache.put(url, r.clone()).catch(() => {
/* cache failures are non-fatal */
});
return await r.arrayBuffer();
} catch (err) {
// Cache API exists but write failed (e.g. quota). Fall through
// to plain fetch.
if (!(err instanceof TypeError)) throw err;
}
}
// Plain fetch (Node 18+ has it; older Node needs polyfill).
const r = await fetch(url);
if (!r.ok) throw new Error(`hub fetch ${url}: ${r.status}`);
return await r.arrayBuffer();
}
/** Fetch a file as JSON. */
export async function fetchHubJson<T = unknown>(file: HubFile): Promise<T> {
const buf = await fetchHubFile(file);
return JSON.parse(new TextDecoder().decode(buf)) as T;
}