|
31 | 31 | use crate::note_body_source::{detect_body_source, BodySource}; |
32 | 32 | use crate::purify_config::PurifyConfig; |
33 | 33 | use crate::symbol_normalize::SymbolNormalizeConfig; |
34 | | -use crate::types::{SessionOcrBlock, SessionScreen, SessionSegment, TranscriptSegment}; |
| 34 | +use crate::types::{SessionOcrBlock, SessionScreen, SessionSegment}; |
35 | 35 | use crate::ui_junk::UiJunkList; |
36 | 36 | use crate::verbal_normalize::{NormalizeConfig, NormalizeStrength}; |
37 | 37 |
|
| 38 | +// ──────────────────────────────────────────────────────────── |
| 39 | +// 拆分子模块(AGENTS.md §3:单文件 ≤300 行)——父模块**自己**用 #[path] 声明, |
| 40 | +// 不动 lib.rs 的 mod 块;三个文件与 note_filter.rs 平铺于 src/(#[path] 相对 |
| 41 | +// 「源文件所在目录」解析)。模块私有 ⇒ 外部经下方再导出访问,调用点零改动。 |
| 42 | +// ──────────────────────────────────────────────────────────── |
| 43 | + |
| 44 | +/// 渲染层(屏卡 → 画面要点行 / markdown 组装 / 异常警示行)。 |
| 45 | +#[path = "note_filter_render.rs"] |
| 46 | +mod render; |
| 47 | + |
| 48 | +/// 再导出:可见性与项 **1:1**(`pub` 项用 `pub use`、`pub(crate)` 项用 |
| 49 | +/// `pub(crate) use`——后者写成 `pub use` 会触发 E0365,项本身不允许外泄)。 |
| 50 | +pub use self::render::{refresh_screen_points, render_screen_points}; |
| 51 | +pub(crate) use self::render::{apply_session_warning, rebuild_markdown}; |
| 52 | + |
38 | 53 | /// 净化环境(依赖注入聚合——净化配置 + 符号映射 + OCR 纠错表)。 |
39 | 54 | /// |
40 | 55 | /// @ai-context: filter_note/convert_to_note 参数收敛(clippy too_many_arguments |
@@ -490,135 +505,6 @@ fn is_fragment(seg: &SessionSegment, config: &PurifyConfig) -> bool { |
490 | 505 | || text.chars().all(|c| !c.is_alphanumeric() && !is_cjk(c)) |
491 | 506 | } |
492 | 507 |
|
493 | | -/// 画面要点屏段落渲染(纯函数):屏卡 → 笔记画面要点行(Markdown 列表项)。 |
494 | | -/// |
495 | | -/// @ai-context: 每屏一段:`- **[MM:SS–MM:SS] 标题**` + 正文行(二级列表)+ |
496 | | -/// 标签行 + 配图行(image_ref 有值时;src 为相对 data_dir 路径 |
497 | | -/// `session-images/{sid}/full/{ts}.webp`——前端渲染拼 baseUrl)。 |
498 | | -/// @ai-context: 无标题屏(旧数据无 bbox 降级)用"画面 N"占位——不丢内容。 |
499 | | -pub fn render_screen_points(screens: &[SessionScreen]) -> Vec<String> { |
500 | | - let mut lines = Vec::new(); |
501 | | - for (i, s) in screens.iter().enumerate() { |
502 | | - let range = format!( |
503 | | - "[{}–{}]", |
504 | | - crate::concat::format_timestamp(s.first_seen_ms), |
505 | | - crate::concat::format_timestamp(s.last_seen_ms) |
506 | | - ); |
507 | | - let title = s.title.clone().unwrap_or_else(|| format!("画面 {}", i + 1)); |
508 | | - lines.push(format!("- **{} {}**", range, title)); |
509 | | - for b in &s.body { |
510 | | - lines.push(format!(" - {}", b)); |
511 | | - } |
512 | | - if !s.labels.is_empty() { |
513 | | - lines.push(format!(" - 标签:{}", s.labels.join(" · "))); |
514 | | - } |
515 | | - if let Some(rel) = &s.image_ref { |
516 | | - // 修复(2026-08-21 审查):缺闭合 `)`——前端渲染正则 |
517 | | - // `^\s*-\s*!\[([^\]]*)\]\(([^)]*)\)$` 要求 `)$` 结尾,缺括号时 |
518 | | - // 配图行永远匹配不上 → 渲染为纯文本而非图片(丢图真因之一) |
519 | | - lines.push(format!(" - ", i + 1, s.session_id, rel)); |
520 | | - } |
521 | | - } |
522 | | - lines |
523 | | -} |
524 | | - |
525 | | -/// 会话异常警示行(REQ-170,纯函数):status != finished → 追加警示。 |
526 | | -/// |
527 | | -/// @ai-context: 会话31 实证:status=failed 但内容完整(停止链路异常翻案)—— |
528 | | -/// 照常转笔记但诚实标注"内容可能不完整",用户自行判断; |
529 | | -/// 警示行是普通 Markdown 引用行(用户可手动删除)。 |
530 | | -/// @ai-context: 预览/落库/AI 复核三出口共用(REQ-081 单一管线)——命令层在 |
531 | | -/// refresh_screen_points 前调用,markdown 重建口径一致。 |
532 | | -pub(crate) fn apply_session_warning(result: &mut NoteFilterResult, status: &str) { |
533 | | - if status == crate::db_sessions::SESSION_STATUS_FINISHED { |
534 | | - result.warning = None; |
535 | | - } else { |
536 | | - result.warning = Some(format!("> ⚠️ 会话异常({}),内容可能不完整", status)); |
537 | | - } |
538 | | -} |
539 | | - |
540 | | -/// 刷新画面要点数据(纯函数):ocr_screens 重新渲染 + 重建 markdown。 |
541 | | -/// |
542 | | -/// @ai-context: 命令层 attach_images 填充 image_ref 后调用——原料视图 |
543 | | -/// 屏卡配图随 image_ref 出现/消失;markdown 重建不含画面要点 |
544 | | -/// (v0.11.5 移出笔记,配图行仅存于 AI 精修 image 块); |
545 | | -/// 净化配置随 result 透传(段落阈值/锚点与预览口径一致)。 |
546 | | -pub fn refresh_screen_points(result: &mut NoteFilterResult) { |
547 | | - result.ocr_points = render_screen_points(&result.ocr_screens); |
548 | | - // v0.12.0(ADR-021):按正文源分派重建——OcrDirect 走 OCR 正文重建 |
549 | | - // (kept 为空,走 rebuild_markdown 会把 OCR 正文覆盖成标题仅) |
550 | | - result.markdown = match result.body_source { |
551 | | - BodySource::OcrDirect => crate::note_filter_ocr::rebuild_ocr_markdown( |
552 | | - &result.title, |
553 | | - &result.ocr_body, |
554 | | - &result.purify, |
555 | | - result.warning.as_deref(), |
556 | | - ), |
557 | | - _ => rebuild_markdown( |
558 | | - &result.title, |
559 | | - &result.kept, |
560 | | - &[], |
561 | | - &result.purify, |
562 | | - result.warning.as_deref(), |
563 | | - ), |
564 | | - }; |
565 | | -} |
566 | | - |
567 | | -/// 组装 Markdown(标题 + 讲述内容;段落切分复用 concat 口径; |
568 | | -/// v0.7.5 REQ-165/170/173:段首 [MM:SS] 时间戳锚点(可回跳原视频位置,可开关) |
569 | | -/// + 段落阈值走净化配置 + 会话异常警示行(None=无警示)。 |
570 | | -/// v0.11.5:画面要点段移出笔记(_ocr_points 签名保留兼容调用方,忽略)。 |
571 | | -pub(crate) fn rebuild_markdown( |
572 | | - title: &str, |
573 | | - kept: &[SessionSegment], |
574 | | - _ocr_points: &[String], |
575 | | - config: &PurifyConfig, |
576 | | - warning: Option<&str>, |
577 | | -) -> String { |
578 | | - let mut md = assemble_purified_markdown(title, kept, _ocr_points, config); |
579 | | - if let Some(w) = warning { |
580 | | - md = format!("{}\n\n{}", w, md); |
581 | | - } |
582 | | - md |
583 | | -} |
584 | | - |
585 | | -/// 净化组装(无警示行版——供 rebuild_markdown 内部与警示拼接)。 |
586 | | -fn assemble_purified_markdown( |
587 | | - title: &str, |
588 | | - kept: &[SessionSegment], |
589 | | - _ocr_points: &[String], |
590 | | - config: &PurifyConfig, |
591 | | -) -> String { |
592 | | - let transcript: Vec<TranscriptSegment> = kept |
593 | | - .iter() |
594 | | - .map(|s| TranscriptSegment { |
595 | | - start_ms: s.start_ms, |
596 | | - end_ms: s.end_ms, |
597 | | - text: s.text.clone(), |
598 | | - word_timestamps: None, |
599 | | - // 段落切分不消费置信度/音量(None 占位) |
600 | | - confidence: None, |
601 | | - volume: None, |
602 | | - }) |
603 | | - .collect(); |
604 | | - let paragraphs = crate::concat::split_transcript_paragraphs_with( |
605 | | - &transcript, |
606 | | - config.paragraph_max_chars, |
607 | | - config.paragraph_max_span_ms, |
608 | | - ); |
609 | | - let anchored: Vec<String> = paragraphs |
610 | | - .into_iter() |
611 | | - .map(|(start_ms, text)| { |
612 | | - if config.anchor_timestamps { |
613 | | - format!("{} {}", crate::concat::format_timestamp(start_ms), text) |
614 | | - } else { |
615 | | - text |
616 | | - } |
617 | | - }) |
618 | | - .collect(); |
619 | | - crate::concat::assemble_markdown(title, &anchored, &[]) |
620 | | -} |
621 | | - |
622 | 508 | /// CJK 统一表意文字区段(含扩展 A)。 |
623 | 509 | fn is_cjk(c: char) -> bool { |
624 | 510 | let u = c as u32; |
|
0 commit comments