基于 blocks 构建嵌套章节大纲。 - 每个 heading 作为一个 section - section.char_end = 下一个 level <= 自己 level 的 heading 的 char_start,或 total_chars - section.preview = section 内第一个 paragraph/blockquote/list 块的清洗文本(200 字内) - sections 按 heading 出现顺序嵌套构建
(blocks: list[Block], total_chars: int)
| 246 | |
| 247 | |
| 248 | def build_outline(blocks: list[Block], total_chars: int) -> dict: |
| 249 | """ |
| 250 | 基于 blocks 构建嵌套章节大纲。 |
| 251 | - 每个 heading 作为一个 section |
| 252 | - section.char_end = 下一个 level <= 自己 level 的 heading 的 char_start,或 total_chars |
| 253 | - section.preview = section 内第一个 paragraph/blockquote/list 块的清洗文本(200 字内) |
| 254 | - sections 按 heading 出现顺序嵌套构建 |
| 255 | """ |
| 256 | headings = [b for b in blocks if b.kind == "heading"] |
| 257 | paragraph_like_kinds = {"paragraph", "list", "blockquote", "table", "code", "figure"} |
| 258 | paragraphs_count = sum(1 for b in blocks if b.kind in paragraph_like_kinds) |
| 259 | |
| 260 | sections: list[Section] = [] |
| 261 | stack: list[Section] = [] |
| 262 | seq_by_level: dict[int, int] = {} |
| 263 | |
| 264 | # preview 候选:仅取 paragraph / list / blockquote 三种(最像描述性内容) |
| 265 | preview_candidate_kinds = {"paragraph", "list", "blockquote"} |
| 266 | |
| 267 | for i, h in enumerate(headings): |
| 268 | # 找下一个 level <= 当前 level 的 heading 作为本节边界 |
| 269 | end_char = total_chars |
| 270 | for j in range(i + 1, len(headings)): |
| 271 | if headings[j].level <= h.level: |
| 272 | end_char = headings[j].char_start |
| 273 | break |
| 274 | |
| 275 | # 在本节范围内找首个 paragraph-ish 块作为 preview |
| 276 | preview_text = "" |
| 277 | for b in blocks: |
| 278 | if b.kind == "heading": |
| 279 | continue |
| 280 | if b.char_start <= h.char_end: |
| 281 | continue |
| 282 | if b.char_start >= end_char: |
| 283 | break |
| 284 | if b.kind in preview_candidate_kinds: |
| 285 | preview_text = _clean_preview(b.text) |
| 286 | if preview_text: |
| 287 | break |
| 288 | |
| 289 | seq_by_level[h.level] = seq_by_level.get(h.level, 0) + 1 |
| 290 | sec = Section( |
| 291 | level=h.level, |
| 292 | seq=seq_by_level[h.level], |
| 293 | anchor=h.anchor or "", |
| 294 | title=h.title or "", |
| 295 | line=h.line_start, |
| 296 | char_start=h.char_start, |
| 297 | char_end=end_char, |
| 298 | preview=preview_text, |
| 299 | agent_summary=None, |
| 300 | children=[], |
| 301 | ) |
| 302 | |
| 303 | while stack and stack[-1].level >= h.level: |
| 304 | stack.pop() |
| 305 | if stack: |