Commit ·
07e4766
1
Parent(s): 4aeaaaa
Stream the finished-audio download with MB progress
Browse filesAfter 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>
- 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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;
|