// Clipwhit speech-to-text worker: Whisper in the browser with transformers.js (WebGPU when available, else WASM). // Audio arrives as 16 kHz mono Float32Array; nothing is uploaded. © 2026 CyberMax. All rights reserved. import { pipeline, WhisperTextStreamer, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0'; env.allowLocalModels = false; let asr = null, loadedKey = null; async function load(model, device, post) { const key = `${model}|${device}`; if (asr && loadedKey === key) return asr; if (asr) { try { await asr.dispose(); } catch {} asr = null; loadedKey = null; } const dtype = device === 'webgpu' ? { encoder_model: 'fp32', decoder_model_merged: 'q4' } : 'q8'; asr = await pipeline('automatic-speech-recognition', model, { device, dtype, progress_callback: (p) => post({ type: 'load', file: p.file, loaded: p.loaded, total: p.total }), }); loadedKey = key; return asr; } self.onmessage = async ({ data }) => { if (data?.type !== 'run') return; const post = (m) => self.postMessage(m); try { let device = data.device, p; try { p = await load(data.model, device, post); } catch (e) { if (device !== 'webgpu') throw e; post({ type: 'info', message: 'WebGPU could not start; using the CPU instead.' }); device = 'wasm'; p = await load(data.model, device, post); } post({ type: 'ready', device }); const english = /\.en$/.test(data.model); const opts = { chunk_length_s: 30, stride_length_s: 5, return_timestamps: true, ...(english ? {} : { language: data.language || null, task: 'transcribe' }) }; let done = 0, out; try { const time_precision = p.processor.feature_extractor.config.chunk_length / p.model.config.max_source_positions; const streamer = new WhisperTextStreamer(p.tokenizer, { time_precision, skip_prompt: true, callback_function: (text) => post({ type: 'partial', text }), on_finalize: () => post({ type: 'chunk', done: ++done }), }); out = await p(data.audio, { ...opts, streamer }); } catch (e) { if (!/stream/i.test(String(e?.message || e))) throw e; out = await p(data.audio, opts); } post({ type: 'done', chunks: out.chunks || [], text: out.text, device }); } catch (e) { post({ type: 'error', message: String(e?.message || e) }); } };