Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/tools/media/stt.engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,18 @@ export async function createTranscriber(
): Promise<Transcriber> {
if (cached && cached.model === model) return cached.transcriber;

const { pipeline } = await import('@huggingface/transformers');
const { pipeline, env } = await import('@huggingface/transformers');

// Fetch model weights through our same-origin /hf proxy so they cache reliably
// (Hugging Face redirects large files to a CDN the browser won't cache). Skip on
// localhost dev, where the Cloudflare Worker proxy isn't running.
try {
const origin = self.location?.origin ?? '';
if (origin && !/localhost|127\.0\.0\.1|\[::1\]/.test(origin)) {
env.remoteHost = `${origin}/hf`;
}
} catch { /* non-worker context — leave the default */ }

const backend: SttBackend = (await webgpuAvailable()) ? 'webgpu' : 'wasm';

const pipe = await pipeline('automatic-speech-recognition', model, {
Expand Down
25 changes: 25 additions & 0 deletions worker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ export default {
return env.SIGNAL.getByName(roomId).fetch(request);
}

// Same-origin proxy for Hugging Face model files. transformers.js fetches
// Whisper weights from huggingface.co, which 302-redirects large files to a
// CDN — the browser/Workbox can't reliably cache those redirected responses,
// so the model re-downloaded on every refresh. Proxying resolves the redirect
// server-side and returns the bytes same-origin with an immutable cache header,
// so the browser HTTP-caches them and they survive refreshes.
if (url.pathname.startsWith('/hf/')) {
if (request.method !== 'GET' && request.method !== 'HEAD') {
return new Response('Method not allowed', { status: 405 });
}
const upstream = 'https://huggingface.co/' + url.pathname.slice('/hf/'.length) + url.search;
const res = await fetch(upstream, {
headers: { 'user-agent': 'goodwebtools-proxy' },
cf: { cacheEverything: true, cacheTtl: 31536000 },
});
if (!res.ok && res.status !== 206) {
return new Response('Upstream model fetch failed', { status: res.status || 502 });
}
const headers = new Headers(res.headers);
headers.set('cache-control', 'public, max-age=31536000, immutable');
headers.set('access-control-allow-origin', '*');
headers.delete('set-cookie');
return new Response(res.body, { status: res.status, headers });
}

if (url.pathname.startsWith('/models/')) {
if (request.method !== 'GET' && request.method !== 'HEAD') {
return new Response('Method not allowed', { status: 405 });
Expand Down
Loading