ACloudCenter Claude Fable 5 commited on
Commit
07e4766
·
1 Parent(s): 4aeaaaa

Stream the finished-audio download with MB progress

Browse files

After a long render, fetching the WAV to the browser can take minutes
(hundreds of MB), during which the UI sat on Complete with no player —
indistinguishable from a hang. The fetch is now streamed with a live
'Downloading your take… X / Y MB' status; Complete only shows once the
audio is actually in hand. Stop is hidden during the transfer since the
render is already done.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. static/app.js +27 -2
static/app.js CHANGED
@@ -20,6 +20,7 @@ const PRIMARY_STAGE_MESSAGES = {
20
  preparing_inputs: ["Preparing", "Formatting the conversation for the model."],
21
  generating_audio: ["Generating", "Synthesizing speech — this is the longest step."],
22
  processing_audio: ["Finalizing", "Converting tensors into a playable waveform."],
 
23
  complete: ["Complete", "Press play, or download the WAV."],
24
  error: ["Error", "Check the log for details."],
25
  cancelled: ["Stopped", "Generation cancelled. The next run may need a cold start."],
@@ -1477,7 +1478,8 @@ function setStatus(stage, fallbackText) {
1477
  el.statusCard.classList.toggle("error", stage === "error" || stage === "cancelled");
1478
  el.statusTitle.textContent = title;
1479
  el.statusDesc.textContent = fallbackText || defaultDesc || "";
1480
- el.stopGenBtn.hidden = !running;
 
1481
  }
1482
 
1483
  let generateAbort = null;
@@ -1597,8 +1599,31 @@ el.generateBtn.addEventListener("click", async () => {
1597
  }
1598
 
1599
  if (evt.stage === "complete" && evt.audio_id) {
 
 
 
1600
  const audioRes = await fetch(`/api/audio/${evt.audio_id}`);
1601
- const blob = await audioRes.blob();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1602
  const url = URL.createObjectURL(blob);
1603
  el.resultAudio.src = url;
1604
  el.downloadBtn.href = url;
 
20
  preparing_inputs: ["Preparing", "Formatting the conversation for the model."],
21
  generating_audio: ["Generating", "Synthesizing speech — this is the longest step."],
22
  processing_audio: ["Finalizing", "Converting tensors into a playable waveform."],
23
+ downloading: ["Downloading", "Transferring the finished audio to your browser."],
24
  complete: ["Complete", "Press play, or download the WAV."],
25
  error: ["Error", "Check the log for details."],
26
  cancelled: ["Stopped", "Generation cancelled. The next run may need a cold start."],
 
1478
  el.statusCard.classList.toggle("error", stage === "error" || stage === "cancelled");
1479
  el.statusTitle.textContent = title;
1480
  el.statusDesc.textContent = fallbackText || defaultDesc || "";
1481
+ // No stop while downloading: the render is already done, only the transfer remains.
1482
+ el.stopGenBtn.hidden = !running || stage === "downloading";
1483
  }
1484
 
1485
  let generateAbort = null;
 
1599
  }
1600
 
1601
  if (evt.stage === "complete" && evt.audio_id) {
1602
+ // Long takes are hundreds of MB — stream the download with progress
1603
+ // so "Complete" never looks like a hang while the WAV transfers.
1604
+ setStatus("downloading");
1605
  const audioRes = await fetch(`/api/audio/${evt.audio_id}`);
1606
+ if (!audioRes.ok) throw new Error("The finished audio could not be fetched from the server.");
1607
+ const totalBytes = Number(audioRes.headers.get("Content-Length")) || 0;
1608
+ const audioReader = audioRes.body.getReader();
1609
+ const parts = [];
1610
+ let received = 0;
1611
+ let lastShown = -1;
1612
+ while (true) {
1613
+ const part = await audioReader.read();
1614
+ if (part.done) break;
1615
+ parts.push(part.value);
1616
+ received += part.value.length;
1617
+ const mb = Math.floor(received / 1048576);
1618
+ if (mb !== lastShown) {
1619
+ lastShown = mb;
1620
+ setStatus("downloading", totalBytes
1621
+ ? `Downloading your take… ${mb} / ${Math.ceil(totalBytes / 1048576)} MB`
1622
+ : `Downloading your take… ${mb} MB`);
1623
+ }
1624
+ }
1625
+ const blob = new Blob(parts, { type: audioRes.headers.get("Content-Type") || "audio/wav" });
1626
+ setStatus("complete");
1627
  const url = URL.createObjectURL(blob);
1628
  el.resultAudio.src = url;
1629
  el.downloadBtn.href = url;