Fix progress bar display and correct model size to 2.5GB
Browse files- Convert parakeet.js progress format to UI-expected format
- Send initiate/progress/done messages for progress bars
- Track seen files to avoid duplicate initiate messages
- Update model size from 2.1GB to 2.5GB (encoder is 2.3GB alone)
- Progress bars now show download status correctly
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- assets/index-D32DZD7n.js +0 -0
- assets/index-PHZx3Gsx.js +0 -0
- assets/index-sJbWHcwg.js +0 -0
- assets/worker-CY2tAIOW.js +1 -0
- assets/worker-D2CeqmVH.js +1 -0
- assets/worker-y2BU5_Lc.js +1 -0
- index.html +1 -1
- source/src/App.jsx +4 -4
- source/src/worker.js +34 -2
assets/index-D32DZD7n.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
assets/index-PHZx3Gsx.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
assets/index-sJbWHcwg.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
assets/worker-CY2tAIOW.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
async function f(t,a={}){const{getParakeetModel:e}=await import("./hub-BlMT648A.js"),{ParakeetModel:s}=await import("./parakeet-xcg-VHSn.js"),{MODELS:r}=await import("./models-Dq2DCePq.js"),o=r[t]?.repoId||t,n=await e(o,a);return s.fromUrls({...n.urls,filenames:n.filenames,preprocessorBackend:n.preprocessorBackend,...a})}let i=null,m=!1;async function w(t="parakeet-tdt-0.6b-v3",a={}){if(m)return{status:"loading",message:"Model is already loading..."};if(i)return{status:"ready",message:"Model already loaded"};try{m=!0;const e=a.device==="webgpu"?"webgpu-hybrid":"wasm";self.postMessage({status:"loading",message:`Downloading Parakeet ${t}... (~2.1GB, this may take 1-2 minutes)`}),console.log(`[Worker] Loading model with backend: ${e}`);const s=e==="wasm"?{encoderQuant:"int8",decoderQuant:"int8",preprocessor:"nemo128"}:{encoderQuant:"fp32",decoderQuant:"int8",preprocessor:"nemo128"},r=new Set;i=await f(t,{backend:e,...s,progress:u=>{const{loaded:g,total:c,file:l}=u,p=c>0?Math.round(g/c*100):0;r.has(l)||(r.add(l),self.postMessage({status:"initiate",file:l,progress:0,total:c})),self.postMessage({status:"progress",file:l,progress:p,total:c,loaded:g}),g>=c&&self.postMessage({status:"done",file:l})}});const n=i.session?.executionProviders?.[0]||e;console.log(`[Worker] Model loaded. Requested: ${e}, Actual provider: ${n}`),self.postMessage({status:"loading",message:"Model downloaded, warming up..."});const d=new Float32Array(16e3);return await i.transcribe(d,16e3),self.postMessage({status:"ready",message:`Parakeet ${t} loaded successfully!`,device:e,modelVersion:t}),{status:"ready",device:e}}catch(e){return console.error("Failed to load model:",e),self.postMessage({status:"error",message:`Failed to load model: ${e.message}`,error:e.toString()}),{status:"error",error:e.toString()}}finally{m=!1}}async function h(t,a=null){if(!i)throw new Error("Model not loaded. Call load() first.");try{const e=performance.now(),s=await i.transcribe(t,16e3,{returnTimestamps:!0,returnConfidences:!0,temperature:1}),o=(performance.now()-e)/1e3,n=t.length/16e3,d=n/o,u=k(s.words||[]);return{text:s.utterance_text||"",sentences:u,words:s.words||[],chunks:s.words||[],metadata:{latency:o,audioDuration:n,rtf:d,language:a,confidence:s.confidence_scores,metrics:s.metrics}}}catch(e){throw console.error("Transcription error:",e),e}}function k(t){if(!t||t.length===0)return[];const a=[];let e=[],s=t[0].start_time||0;for(let r=0;r<t.length;r++){const o=t[r];e.push(o.text),(/[.!?]$/.test(o.text)||r===t.length-1)&&(a.push({text:e.join(" ").trim(),start:s,end:o.end_time||o.start_time||0}),r<t.length-1&&(e=[],s=t[r+1].start_time||o.end_time||0))}return a}self.onmessage=async t=>{const{type:a,data:e}=t.data;try{switch(a){case"load":await w(e?.modelVersion,e?.options||{});break;case"transcribe":const s=await h(e.audio,e.language);self.postMessage({status:"transcription",result:s});break;case"ping":self.postMessage({status:"pong"});break;default:self.postMessage({status:"error",message:`Unknown message type: ${a}`})}}catch(s){self.postMessage({status:"error",message:s.message,error:s.toString()})}};
|
assets/worker-D2CeqmVH.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
async function f(t,a={}){const{getParakeetModel:e}=await import("./hub-BlMT648A.js"),{ParakeetModel:s}=await import("./parakeet-xcg-VHSn.js"),{MODELS:r}=await import("./models-Dq2DCePq.js"),o=r[t]?.repoId||t,n=await e(o,a);return s.fromUrls({...n.urls,filenames:n.filenames,preprocessorBackend:n.preprocessorBackend,...a})}let i=null,m=!1;async function w(t="parakeet-tdt-0.6b-v3",a={}){if(m)return{status:"loading",message:"Model is already loading..."};if(i)return{status:"ready",message:"Model already loaded"};try{m=!0;const e=a.device==="webgpu"?"webgpu-hybrid":"wasm";self.postMessage({status:"loading",message:`Downloading Parakeet ${t}... (~2.5GB, this may take 1-2 minutes)`}),console.log(`[Worker] Loading model with backend: ${e}`);const s=e==="wasm"?{encoderQuant:"int8",decoderQuant:"int8",preprocessor:"nemo128"}:{encoderQuant:"fp32",decoderQuant:"int8",preprocessor:"nemo128"},r=new Set;i=await f(t,{backend:e,...s,progress:u=>{const{loaded:g,total:c,file:l}=u,p=c>0?Math.round(g/c*100):0;r.has(l)||(r.add(l),self.postMessage({status:"initiate",file:l,progress:0,total:c})),self.postMessage({status:"progress",file:l,progress:p,total:c,loaded:g}),g>=c&&self.postMessage({status:"done",file:l})}});const n=i.session?.executionProviders?.[0]||e;console.log(`[Worker] Model loaded. Requested: ${e}, Actual provider: ${n}`),self.postMessage({status:"loading",message:"Model downloaded, warming up..."});const d=new Float32Array(16e3);return await i.transcribe(d,16e3),self.postMessage({status:"ready",message:`Parakeet ${t} loaded successfully!`,device:e,modelVersion:t}),{status:"ready",device:e}}catch(e){return console.error("Failed to load model:",e),self.postMessage({status:"error",message:`Failed to load model: ${e.message}`,error:e.toString()}),{status:"error",error:e.toString()}}finally{m=!1}}async function h(t,a=null){if(!i)throw new Error("Model not loaded. Call load() first.");try{const e=performance.now(),s=await i.transcribe(t,16e3,{returnTimestamps:!0,returnConfidences:!0,temperature:1}),o=(performance.now()-e)/1e3,n=t.length/16e3,d=n/o,u=k(s.words||[]);return{text:s.utterance_text||"",sentences:u,words:s.words||[],chunks:s.words||[],metadata:{latency:o,audioDuration:n,rtf:d,language:a,confidence:s.confidence_scores,metrics:s.metrics}}}catch(e){throw console.error("Transcription error:",e),e}}function k(t){if(!t||t.length===0)return[];const a=[];let e=[],s=t[0].start_time||0;for(let r=0;r<t.length;r++){const o=t[r];e.push(o.text),(/[.!?]$/.test(o.text)||r===t.length-1)&&(a.push({text:e.join(" ").trim(),start:s,end:o.end_time||o.start_time||0}),r<t.length-1&&(e=[],s=t[r+1].start_time||o.end_time||0))}return a}self.onmessage=async t=>{const{type:a,data:e}=t.data;try{switch(a){case"load":await w(e?.modelVersion,e?.options||{});break;case"transcribe":const s=await h(e.audio,e.language);self.postMessage({status:"transcription",result:s});break;case"ping":self.postMessage({status:"pong"});break;default:self.postMessage({status:"error",message:`Unknown message type: ${a}`})}}catch(s){self.postMessage({status:"error",message:s.message,error:s.toString()})}};
|
assets/worker-y2BU5_Lc.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
async function u(t,r={}){const{getParakeetModel:e}=await import("./hub-BlMT648A.js"),{ParakeetModel:s}=await import("./parakeet-xcg-VHSn.js"),{MODELS:o}=await import("./models-Dq2DCePq.js"),a=o[t]?.repoId||t,n=await e(a,r);return s.fromUrls({...n.urls,filenames:n.filenames,preprocessorBackend:n.preprocessorBackend,...r})}let c=null,l=!1;async function g(t="parakeet-tdt-0.6b-v3",r={}){if(l)return{status:"loading",message:"Model is already loading..."};if(c)return{status:"ready",message:"Model already loaded"};try{l=!0;const e=r.device==="webgpu"?"webgpu-hybrid":"wasm";self.postMessage({status:"loading",message:`Downloading Parakeet ${t}... (~2.1GB, this may take 1-2 minutes)`}),console.log(`[Worker] Loading model with backend: ${e}`),c=await u(t,{backend:e,...e==="wasm"?{encoderQuant:"int8",decoderQuant:"int8",preprocessor:"nemo128"}:{encoderQuant:"fp32",decoderQuant:"int8",preprocessor:"nemo128"},progress:i=>{console.log("[Worker] Progress callback received:",i),self.postMessage(i)}});const a=c.session?.executionProviders?.[0]||e;console.log(`[Worker] Model loaded. Requested: ${e}, Actual provider: ${a}`),self.postMessage({status:"loading",message:"Model downloaded, warming up..."});const n=new Float32Array(16e3);return await c.transcribe(n,16e3),self.postMessage({status:"ready",message:`Parakeet ${t} loaded successfully!`,device:e,modelVersion:t}),{status:"ready",device:e}}catch(e){return console.error("Failed to load model:",e),self.postMessage({status:"error",message:`Failed to load model: ${e.message}`,error:e.toString()}),{status:"error",error:e.toString()}}finally{l=!1}}async function m(t,r=null){if(!c)throw new Error("Model not loaded. Call load() first.");try{const e=performance.now(),s=await c.transcribe(t,16e3,{returnTimestamps:!0,returnConfidences:!0,temperature:1}),a=(performance.now()-e)/1e3,n=t.length/16e3,i=n/a,d=p(s.words||[]);return{text:s.utterance_text||"",sentences:d,words:s.words||[],chunks:s.words||[],metadata:{latency:a,audioDuration:n,rtf:i,language:r,confidence:s.confidence_scores,metrics:s.metrics}}}catch(e){throw console.error("Transcription error:",e),e}}function p(t){if(!t||t.length===0)return[];const r=[];let e=[],s=t[0].start_time||0;for(let o=0;o<t.length;o++){const a=t[o];e.push(a.text),(/[.!?]$/.test(a.text)||o===t.length-1)&&(r.push({text:e.join(" ").trim(),start:s,end:a.end_time||a.start_time||0}),o<t.length-1&&(e=[],s=t[o+1].start_time||a.end_time||0))}return r}self.onmessage=async t=>{const{type:r,data:e}=t.data;try{switch(r){case"load":await g(e?.modelVersion,e?.options||{});break;case"transcribe":const s=await m(e.audio,e.language);self.postMessage({status:"transcription",result:s});break;case"ping":self.postMessage({status:"pong"});break;default:self.postMessage({status:"error",message:`Unknown message type: ${r}`})}}catch(s){self.postMessage({status:"error",message:s.message,error:s.toString()})}};
|
index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
<meta name="description" content="Real-time speech recognition with Parakeet STT and WebGPU acceleration. Progressive transcription demo." />
|
| 8 |
<title>Parakeet STT Progressive Transcription | WebGPU Demo</title>
|
| 9 |
-
<script type="module" crossorigin src="/assets/index-
|
| 10 |
<link rel="stylesheet" crossorigin href="/assets/index-4ud1a0so.css">
|
| 11 |
</head>
|
| 12 |
<body>
|
|
|
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
<meta name="description" content="Real-time speech recognition with Parakeet STT and WebGPU acceleration. Progressive transcription demo." />
|
| 8 |
<title>Parakeet STT Progressive Transcription | WebGPU Demo</title>
|
| 9 |
+
<script type="module" crossorigin src="/assets/index-D32DZD7n.js"></script>
|
| 10 |
<link rel="stylesheet" crossorigin href="/assets/index-4ud1a0so.css">
|
| 11 |
</head>
|
| 12 |
<body>
|
source/src/App.jsx
CHANGED
|
@@ -140,7 +140,7 @@ function App() {
|
|
| 140 |
};
|
| 141 |
|
| 142 |
const clearCache = async () => {
|
| 143 |
-
if (!confirm('Clear cached model files (~2.
|
| 144 |
return;
|
| 145 |
}
|
| 146 |
|
|
@@ -386,7 +386,7 @@ function App() {
|
|
| 386 |
Real-time speech recognition with smart progressive streaming • WebGPU accelerated
|
| 387 |
</p>
|
| 388 |
<p className="text-gray-500 text-xs mt-2">
|
| 389 |
-
💾 Model files (~2.
|
| 390 |
</p>
|
| 391 |
</div>
|
| 392 |
</header>
|
|
@@ -441,7 +441,7 @@ function App() {
|
|
| 441 |
onClick={loadModel}
|
| 442 |
className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 rounded-lg font-semibold transition-all duration-200 shadow-lg hover:shadow-xl"
|
| 443 |
>
|
| 444 |
-
Load Model (~2.
|
| 445 |
</button>
|
| 446 |
<button
|
| 447 |
onClick={clearCache}
|
|
@@ -560,7 +560,7 @@ function App() {
|
|
| 560 |
browser using WebGPU acceleration. No data is sent to servers.
|
| 561 |
</p>
|
| 562 |
<p className="text-xs">
|
| 563 |
-
Model: Parakeet TDT 0.6B v3 (ONNX via parakeet.js) • 25 languages supported • ~2.
|
| 564 |
</p>
|
| 565 |
</div>
|
| 566 |
</div>
|
|
|
|
| 140 |
};
|
| 141 |
|
| 142 |
const clearCache = async () => {
|
| 143 |
+
if (!confirm('Clear cached model files (~2.5GB)? You will need to re-download the model.')) {
|
| 144 |
return;
|
| 145 |
}
|
| 146 |
|
|
|
|
| 386 |
Real-time speech recognition with smart progressive streaming • WebGPU accelerated
|
| 387 |
</p>
|
| 388 |
<p className="text-gray-500 text-xs mt-2">
|
| 389 |
+
💾 Model files (~2.5GB) are cached locally for faster loading on future visits
|
| 390 |
</p>
|
| 391 |
</div>
|
| 392 |
</header>
|
|
|
|
| 441 |
onClick={loadModel}
|
| 442 |
className="px-6 py-3 bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 rounded-lg font-semibold transition-all duration-200 shadow-lg hover:shadow-xl"
|
| 443 |
>
|
| 444 |
+
Load Model (~2.5GB)
|
| 445 |
</button>
|
| 446 |
<button
|
| 447 |
onClick={clearCache}
|
|
|
|
| 560 |
browser using WebGPU acceleration. No data is sent to servers.
|
| 561 |
</p>
|
| 562 |
<p className="text-xs">
|
| 563 |
+
Model: Parakeet TDT 0.6B v3 (ONNX via parakeet.js) • 25 languages supported • ~2.5GB download (cached locally)
|
| 564 |
</p>
|
| 565 |
</div>
|
| 566 |
</div>
|
source/src/worker.js
CHANGED
|
@@ -31,7 +31,7 @@ async function loadModel(modelVersion = 'parakeet-tdt-0.6b-v3', options = {}) {
|
|
| 31 |
|
| 32 |
self.postMessage({
|
| 33 |
status: 'loading',
|
| 34 |
-
message: `Downloading Parakeet ${modelVersion}... (~2.
|
| 35 |
});
|
| 36 |
|
| 37 |
// Load model using parakeet.js fromHub helper
|
|
@@ -44,8 +44,40 @@ async function loadModel(modelVersion = 'parakeet-tdt-0.6b-v3', options = {}) {
|
|
| 44 |
: { encoderQuant: 'fp32', decoderQuant: 'int8', preprocessor: 'nemo128' }; // WebGPU-hybrid: FP32 encoder + INT8 decoder
|
| 45 |
|
| 46 |
// Progress callback to forward download progress
|
|
|
|
|
|
|
|
|
|
| 47 |
const progressCallback = (progressData) => {
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
};
|
| 50 |
|
| 51 |
model = await fromHub(modelVersion, {
|
|
|
|
| 31 |
|
| 32 |
self.postMessage({
|
| 33 |
status: 'loading',
|
| 34 |
+
message: `Downloading Parakeet ${modelVersion}... (~2.5GB, this may take 1-2 minutes)`,
|
| 35 |
});
|
| 36 |
|
| 37 |
// Load model using parakeet.js fromHub helper
|
|
|
|
| 44 |
: { encoderQuant: 'fp32', decoderQuant: 'int8', preprocessor: 'nemo128' }; // WebGPU-hybrid: FP32 encoder + INT8 decoder
|
| 45 |
|
| 46 |
// Progress callback to forward download progress
|
| 47 |
+
// parakeet.js sends: {loaded, total, file}
|
| 48 |
+
// Convert to format expected by UI: {status, file, progress, total}
|
| 49 |
+
const seenFiles = new Set();
|
| 50 |
const progressCallback = (progressData) => {
|
| 51 |
+
const { loaded, total, file } = progressData;
|
| 52 |
+
const progress = total > 0 ? Math.round((loaded / total) * 100) : 0;
|
| 53 |
+
|
| 54 |
+
// Send initiate message for new files
|
| 55 |
+
if (!seenFiles.has(file)) {
|
| 56 |
+
seenFiles.add(file);
|
| 57 |
+
self.postMessage({
|
| 58 |
+
status: 'initiate',
|
| 59 |
+
file,
|
| 60 |
+
progress: 0,
|
| 61 |
+
total
|
| 62 |
+
});
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// Send progress update
|
| 66 |
+
self.postMessage({
|
| 67 |
+
status: 'progress',
|
| 68 |
+
file,
|
| 69 |
+
progress,
|
| 70 |
+
total,
|
| 71 |
+
loaded
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
// Send done message when complete
|
| 75 |
+
if (loaded >= total) {
|
| 76 |
+
self.postMessage({
|
| 77 |
+
status: 'done',
|
| 78 |
+
file
|
| 79 |
+
});
|
| 80 |
+
}
|
| 81 |
};
|
| 82 |
|
| 83 |
model = await fromHub(modelVersion, {
|