From 345bec8a05c6643bd73618654e09bf048887dcd7 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Wed, 10 Jun 2026 03:41:31 +0200 Subject: [PATCH] Fix chunked TTS failing on newline-delimited text (e.g. German bullet lists) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit splitTextIntoChunks only split on sentence terminators (.!?), so structured text separated by newlines was never chunked — the full page went as one request, causing a TCP timeout that the browser reported as "Failed to fetch". Fix: process each line individually before applying the sentence regex so newline-separated segments become their own split points. Also move generation.js from deferred batch E into the main feature batch C so generateChunkedTts is always defined before the user can click Generate. Co-Authored-By: Claude Sonnet 4.6 --- static/app.js | 23 ++++++++++++++++------- static/js/generation.js | 23 ++++++++++++++++------- static/loader.js | 5 ++--- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/static/app.js b/static/app.js index b9bc01e..158b760 100644 --- a/static/app.js +++ b/static/app.js @@ -8027,16 +8027,25 @@ async function mergeWavBlobs(blobs) { function splitTextIntoChunks(text, maxLen = 800) { const abbrev = /\b(Mr|Mrs|Ms|Dr|Prof|Sr|Jr|vs|etc|e\.g|i\.e)\.\s/g; const safe = text.replace(abbrev, m => m.replace('.', '\x00')); - const parts = safe.match(/[^.!?]+[.!?]+\s*/g) || []; - const last = safe.replace(/[^.!?]+[.!?]+\s*/g, '').trim(); - if (last) parts.push(last); const restore = s => s.replace(/\x00/g, '.'); - if (!parts.length) return [text]; + + // Gather segments from each line: sentences first, then any remaining line text. + // This ensures newline-delimited text (e.g. German bullet lists) gets split too. + const segments = []; + for (const line of safe.split('\n')) { + const sentences = line.match(/[^.!?]+[.!?]+\s*/g) || []; + const rest = line.replace(/[^.!?]+[.!?]+\s*/g, '').trim(); + segments.push(...sentences); + if (rest) segments.push(rest); + } + if (!segments.length) return [text]; + const chunks = []; let cur = ''; - for (const p of parts) { - if ((cur + p).length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = p; } - else cur += p; + for (const seg of segments) { + const joined = cur ? cur + ' ' + seg.trim() : seg.trim(); + if (joined.length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = seg.trim(); } + else cur = joined; } if (cur.trim()) chunks.push(restore(cur.trim())); return chunks.length ? chunks : [text]; diff --git a/static/js/generation.js b/static/js/generation.js index fb5ba7b..2430af7 100644 --- a/static/js/generation.js +++ b/static/js/generation.js @@ -57,16 +57,25 @@ async function mergeWavBlobs(blobs) { function splitTextIntoChunks(text, maxLen = 800) { const abbrev = /\b(Mr|Mrs|Ms|Dr|Prof|Sr|Jr|vs|etc|e\.g|i\.e)\.\s/g; const safe = text.replace(abbrev, m => m.replace('.', '\x00')); - const parts = safe.match(/[^.!?]+[.!?]+\s*/g) || []; - const last = safe.replace(/[^.!?]+[.!?]+\s*/g, '').trim(); - if (last) parts.push(last); const restore = s => s.replace(/\x00/g, '.'); - if (!parts.length) return [text]; + + // Gather segments from each line: sentences first, then any remaining line text. + // This ensures newline-delimited text (e.g. German bullet lists) gets split too. + const segments = []; + for (const line of safe.split('\n')) { + const sentences = line.match(/[^.!?]+[.!?]+\s*/g) || []; + const rest = line.replace(/[^.!?]+[.!?]+\s*/g, '').trim(); + segments.push(...sentences); + if (rest) segments.push(rest); + } + if (!segments.length) return [text]; + const chunks = []; let cur = ''; - for (const p of parts) { - if ((cur + p).length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = p; } - else cur += p; + for (const seg of segments) { + const joined = cur ? cur + ' ' + seg.trim() : seg.trim(); + if (joined.length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = seg.trim(); } + else cur = joined; } if (cur.trim()) chunks.push(restore(cur.trim())); return chunks.length ? chunks : [text]; diff --git a/static/loader.js b/static/loader.js index af80c29..1120e22 100644 --- a/static/loader.js +++ b/static/loader.js @@ -117,6 +117,7 @@ '/static/js/voice-clone.js', '/static/js/voice-library.js', '/static/js/tts-preview.js', + '/static/js/generation.js', '/static/js/benchmark.js', '/static/js/stt.js', '/static/js/rehearser-parse.js', @@ -152,12 +153,10 @@ if (window.initAccessibilityEnhancements) window.initAccessibilityEnhancements(); // E — post-init modules load in background AFTER the UI is already visible. - // Engines, AI backends, generation, and conversation sections load while - // the user browses Voices / Clone / Design, not before. + // Engines, AI backends, and conversation load while the user browses other tabs. _loadBatch([ '/static/js/engines.js', '/static/js/ai-backends.js', - '/static/js/generation.js', '/static/js/conversation.js', ]); })();