diff --git a/nginx.conf b/nginx.conf index 1e0b9dc..b9f469b 100644 --- a/nginx.conf +++ b/nginx.conf @@ -52,6 +52,10 @@ proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; + + # Streaming optimization + proxy_buffering off; + proxy_read_timeout 300s; } # Health check diff --git a/ui/client-app/src/services/apiService.js b/ui/client-app/src/services/apiService.js index 8524cb5..d995fb4 100644 --- a/ui/client-app/src/services/apiService.js +++ b/ui/client-app/src/services/apiService.js @@ -349,10 +349,18 @@ method: "POST", headers: { "Content-Type": "application/json", "X-User-ID": userId }, body: JSON.stringify({ text }), + }).catch(err => { + console.error("Fetch transport error:", err); + throw new Error(`Network transport failed: ${err.message}`); }); if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); + let detail = `HTTP error! Status: ${response.status}`; + try { + const errBody = await response.json(); + detail = errBody.detail || detail; + } catch { } + throw new Error(detail); } const totalChunks = parseInt(response.headers.get("X-TTS-Chunk-Count") || "0"); @@ -360,37 +368,36 @@ let leftover = new Uint8Array(0); let chunkIndex = 0; - while (true) { - const { done, value: chunk } = await reader.read(); - if (done) { - if (leftover.length > 0) { - console.warn("Leftover bytes discarded:", leftover.length); - } - break; + try { + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) break; + + let combined = new Uint8Array(leftover.length + chunk.length); + combined.set(leftover); + combined.set(chunk, leftover.length); + + let length = combined.length; + if (length % 2 !== 0) length -= 1; + + const toConvert = combined.slice(0, length); + leftover = combined.slice(length); + const float32Raw = convertPcmToFloat32(toConvert); + + onData(float32Raw, totalChunks, ++chunkIndex); } - - let combined = new Uint8Array(leftover.length + chunk.length); - combined.set(leftover); - combined.set(chunk, leftover.length); - - let length = combined.length; - if (length % 2 !== 0) { - length -= 1; - } - - const toConvert = combined.slice(0, length); - leftover = combined.slice(length); - const float32Raw = convertPcmToFloat32(toConvert); - - // Pass the raw float32 data plus chunk indices for progress tracking - onData(float32Raw, totalChunks, ++chunkIndex); + } catch (readError) { + console.error("Error reading response body stream:", readError); + throw new Error(`Stream interrupted: ${readError.message}`); } } catch (error) { console.error("Failed to stream speech:", error); throw error; } finally { - // We call the onDone callback to let the hook know the stream has ended. - onDone(); + if (onDone) { + // await onDone to ensure persistent storage is finished before resolving + await Promise.resolve(onDone()); + } } };