Fix chunked TTS failing on newline-delimited text (e.g. German bullet lists)

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 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-06-10 03:41:31 +02:00
parent e6cadd26a3
commit 345bec8a05
3 changed files with 34 additions and 17 deletions

View File

@ -8027,16 +8027,25 @@ async function mergeWavBlobs(blobs) {
function splitTextIntoChunks(text, maxLen = 800) { function splitTextIntoChunks(text, maxLen = 800) {
const abbrev = /\b(Mr|Mrs|Ms|Dr|Prof|Sr|Jr|vs|etc|e\.g|i\.e)\.\s/g; 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 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, '.'); 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 = []; const chunks = [];
let cur = ''; let cur = '';
for (const p of parts) { for (const seg of segments) {
if ((cur + p).length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = p; } const joined = cur ? cur + ' ' + seg.trim() : seg.trim();
else cur += p; if (joined.length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = seg.trim(); }
else cur = joined;
} }
if (cur.trim()) chunks.push(restore(cur.trim())); if (cur.trim()) chunks.push(restore(cur.trim()));
return chunks.length ? chunks : [text]; return chunks.length ? chunks : [text];

View File

@ -57,16 +57,25 @@ async function mergeWavBlobs(blobs) {
function splitTextIntoChunks(text, maxLen = 800) { function splitTextIntoChunks(text, maxLen = 800) {
const abbrev = /\b(Mr|Mrs|Ms|Dr|Prof|Sr|Jr|vs|etc|e\.g|i\.e)\.\s/g; 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 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, '.'); 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 = []; const chunks = [];
let cur = ''; let cur = '';
for (const p of parts) { for (const seg of segments) {
if ((cur + p).length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = p; } const joined = cur ? cur + ' ' + seg.trim() : seg.trim();
else cur += p; if (joined.length > maxLen && cur) { chunks.push(restore(cur.trim())); cur = seg.trim(); }
else cur = joined;
} }
if (cur.trim()) chunks.push(restore(cur.trim())); if (cur.trim()) chunks.push(restore(cur.trim()));
return chunks.length ? chunks : [text]; return chunks.length ? chunks : [text];

View File

@ -117,6 +117,7 @@
'/static/js/voice-clone.js', '/static/js/voice-clone.js',
'/static/js/voice-library.js', '/static/js/voice-library.js',
'/static/js/tts-preview.js', '/static/js/tts-preview.js',
'/static/js/generation.js',
'/static/js/benchmark.js', '/static/js/benchmark.js',
'/static/js/stt.js', '/static/js/stt.js',
'/static/js/rehearser-parse.js', '/static/js/rehearser-parse.js',
@ -152,12 +153,10 @@
if (window.initAccessibilityEnhancements) window.initAccessibilityEnhancements(); if (window.initAccessibilityEnhancements) window.initAccessibilityEnhancements();
// E — post-init modules load in background AFTER the UI is already visible. // E — post-init modules load in background AFTER the UI is already visible.
// Engines, AI backends, generation, and conversation sections load while // Engines, AI backends, and conversation load while the user browses other tabs.
// the user browses Voices / Clone / Design, not before.
_loadBatch([ _loadBatch([
'/static/js/engines.js', '/static/js/engines.js',
'/static/js/ai-backends.js', '/static/js/ai-backends.js',
'/static/js/generation.js',
'/static/js/conversation.js', '/static/js/conversation.js',
]); ]);
})(); })();