| 294 | if (close == std::string::npos || close + 1 >= text.size()) { |
| 295 | return std::nullopt; |
| 296 | } |
| 297 | const std::string prefix = engine::io::trim_ascii_whitespace(text.substr(0, close + 1)); |
| 298 | const std::string body = engine::io::trim_ascii_whitespace(text.substr(close + 1)); |
| 299 | if (prefix.empty() || body.empty()) { |
| 300 | return std::nullopt; |
| 301 | } |
| 302 | return std::make_pair(prefix, body); |
| 303 | } |
| 304 | |
| 305 | std::vector<std::string> split_text_chunks_default( |
| 306 | std::string_view text, |
| 307 | int64_t codepoint_budget) { |
| 308 | if (codepoint_budget <= 0) { |
| 309 | throw std::runtime_error("text chunk budget must be positive"); |
| 310 | } |
| 311 | const std::string trimmed = engine::io::trim_ascii_whitespace(std::string(text)); |
| 312 | if (trimmed.empty()) { |
| 313 | return {}; |
| 314 | } |
| 315 | const auto spans = split_utf8_spans(trimmed, "text chunk"); |
| 316 | if (static_cast<int64_t>(spans.size()) <= codepoint_budget) { |
| 317 | return {trimmed}; |
| 318 | } |
| 319 | |
| 320 | const auto words = split_word_ranges(spans); |
| 321 | if (words.empty()) { |
| 322 | return {}; |
| 323 | } |
| 324 | |
| 325 | std::vector<std::string> chunks; |
| 326 | size_t word_start = 0; |
| 327 | while (word_start < words.size()) { |
| 328 | size_t hard_end = word_start; |
| 329 | while (hard_end < words.size()) { |
| 330 | const size_t chunk_span_start = words[word_start].span_start; |
| 331 | const size_t candidate_span_end = words[hard_end].span_end; |
| 332 | const auto chunk_codepoints = static_cast<int64_t>(candidate_span_end - chunk_span_start); |
| 333 | if (chunk_codepoints > codepoint_budget) { |
| 334 | break; |
| 335 | } |
| 336 | ++hard_end; |
| 337 | } |
| 338 | |
| 339 | if (hard_end == word_start) { |
| 340 | hard_end = word_start + 1; |
| 341 | } |
| 342 | |
| 343 | size_t chunk_end = hard_end; |
| 344 | if (hard_end < words.size() && hard_end > word_start + 1) { |
| 345 | for (size_t i = hard_end; i > word_start + 1; --i) { |
| 346 | if (words[i - 1].sentence_break) { |
| 347 | chunk_end = i; |
| 348 | break; |
| 349 | } |
| 350 | } |
| 351 | if (chunk_end == hard_end) { |
| 352 | for (size_t i = hard_end; i > word_start + 1; --i) { |
| 353 | if (words[i - 1].clause_break) { |
no test coverage detected