from __future__ import annotations import re ZALO_MAX_TEXT_LENGTH = 2000 _SENTENCE_BOUNDARY = re.compile(r"[。!?.!?\n]") def chunk_text(text: str, limit: int = ZALO_MAX_TEXT_LENGTH) -> list[str]: if len(text) <= limit: return [text] chunks: list[str] = [] paragraphs = text.split("\n\n") current = "" for para in paragraphs: if len(current) + len(para) + 2 <= limit: current = f"{current}\n\n{para}" if current else para else: if current: chunks.append(current) if len(para) > limit: sub_chunks = _chunk_long_paragraph(para, limit) if sub_chunks: if sub_chunks[-1]: current = sub_chunks.pop() else: sub_chunks.pop() current = "" chunks.extend(sub_chunks) else: current = "" else: current = para if current: chunks.append(current) return chunks or [text] def _chunk_long_paragraph(text: str, limit: int) -> list[str]: chunks: list[str] = [] remaining = text while len(remaining) > limit: split_at = _find_split_point(remaining, limit) chunks.append(remaining[:split_at].rstrip()) remaining = remaining[split_at:].lstrip() chunks.append(remaining) return chunks def _find_split_point(text: str, limit: int) -> int: candidates = [m.start() for m in _SENTENCE_BOUNDARY.finditer(text, limit // 2, limit)] if candidates: return candidates[-1] + 1 newline = text.rfind("\n", limit // 2, limit) if newline != -1: return newline + 1 space = text.rfind(" ", limit // 2, limit) if space != -1: return space + 1 return limit