"""Small, dependency-free text helpers for the brain bridge. Kept separate from ``bridge.server`` (which imports Flask and the heavy brain) so the pure logic here can be unit-tested in isolation. """ from __future__ import annotations import re from typing import List, Optional # Sentence terminators across scripts: ASCII . ! ? plus the CJK fullwidth forms # and the ellipsis. Runs of terminators ("?!", "...") collapse into one boundary. # A run of newlines is also a boundary. This is punctuation-only and therefore # language-agnostic (no hardcoded words), per the project's multilingual rule. _BOUNDARY = re.compile(r"([.!?。!?…]+|\n+)") def split_sentences(text: Optional[str], min_len: int = 5) -> List[str]: """Split ``text`` into sentence-sized chunks for streaming TTS. Each chunk ends at a sentence boundary so it can be synthesised and played while later chunks are still being spoken. Fragments shorter than ``min_len`` characters (interjections like "네.", "응") are merged into an adjacent chunk so we don't emit choppy micro-clips. Returns an empty list for blank input and never loses visible content. """ text = (text or "").strip() if not text: return [] parts = _BOUNDARY.split(text) chunks: List[str] = [] buf = "" for i in range(0, len(parts), 2): seg = parts[i] delim = parts[i + 1] if i + 1 < len(parts) else "" buf += seg + delim # Flush at a real boundary once the buffer is a worthwhile clip. if delim and len(buf.strip()) >= min_len: chunks.append(buf.strip()) buf = "" tail = buf.strip() if tail: if chunks and len(tail) < min_len: chunks[-1] = chunks[-1] + " " + tail else: chunks.append(tail) return chunks