@@ -72,22 +72,64 @@ pub fn filter_usable_blocks(blocks: &[SessionOcrBlock], ui_junk: &UiJunkList) ->
7272///
7373/// @ai-context: 归档图按时间戳命名(full/{ts}.webp,image_store 约定);目录
7474/// 缺失/无图 → 保持 None(前端无缩略图降级,不阻断)。
75+ /// @ai-context: 审查修复(2026-08-20):目录**一次扫描**建时间戳映射(原实现
76+ /// 每屏一次 read_dir——N 屏 = N 次目录遍历,会话多屏时浪费 IO)。
7577pub fn attach_images ( screens : & mut [ SessionScreen ] , images_dir : & Path ) {
78+ if screens. is_empty ( ) {
79+ return ;
80+ }
81+ let ts_list = list_full_image_timestamps ( images_dir) ;
82+ if ts_list. is_empty ( ) {
83+ return ;
84+ }
7685 for s in screens. iter_mut ( ) {
7786 if s. image_ref . is_none ( ) {
78- s. image_ref = match_image ( images_dir , s. first_seen_ms ) ;
87+ s. image_ref = match_timestamp ( & ts_list , s. first_seen_ms ) ;
7988 }
8089 }
8190}
8291
92+ /// 归档 full 图时间戳列表(纯 IO,一次扫描)。
93+ fn list_full_image_timestamps ( images_dir : & Path ) -> Vec < u64 > {
94+ let entries = std:: fs:: read_dir ( images_dir. join ( "full" ) ) . ok ( ) ;
95+ let mut ts: Vec < u64 > = entries
96+ . into_iter ( )
97+ . flatten ( )
98+ . filter_map ( |e| e. ok ( ) )
99+ . filter ( |e| e. path ( ) . extension ( ) . is_some_and ( |x| x == "webp" ) )
100+ . filter_map ( |e| {
101+ e. file_name ( )
102+ . to_string_lossy ( )
103+ . trim_end_matches ( ".webp" )
104+ . parse :: < u64 > ( )
105+ . ok ( )
106+ } )
107+ . collect ( ) ;
108+ ts. sort_unstable ( ) ;
109+ ts
110+ }
111+
112+ /// 时间戳匹配(纯函数):取 ≤ 目标的最近者;无则取最早者(None=空列表)。
113+ fn match_timestamp ( sorted : & [ u64 ] , target : u64 ) -> Option < String > {
114+ let ts = sorted
115+ . iter ( )
116+ . rev ( )
117+ . find ( |& & t| t <= target)
118+ . copied ( )
119+ . or_else ( || sorted. first ( ) . copied ( ) ) ?;
120+ Some ( format ! ( "full/{}.webp" , ts) )
121+ }
122+
83123/// 屏构建核心(纯编排,无 IO):块流 → 屏序列。
84124fn build_screens_inner ( session_id : i64 , blocks : & [ SessionOcrBlock ] ) -> Vec < SessionScreen > {
85125 let full: Vec < & SessionOcrBlock > =
86126 blocks. iter ( ) . filter ( |b| b. region == "full" && !b. text . trim ( ) . is_empty ( ) ) . collect ( ) ;
87127 if full. is_empty ( ) {
88128 return Vec :: new ( ) ;
89129 }
90- // ① 有 screen_id 的新数据按屏号分组(同屏块时间连续落库——连续分组成立);
130+ // ① 有 screen_id 的新数据按屏号分组(同屏块时间连续落库——连续分组成立:
131+ // 在线 ScreenTracker 屏号单调递增,同一屏号绝不跨屏段出现;非连续同号
132+ // 仅可能来自外部写入(防御不变量注释,遇破坏时按独立屏处理不崩溃);
91133 // 无 screen_id 的旧数据单独聚类兜底
92134 let mut clusters: Vec < ( Option < i64 > , ScreenCluster ) > = Vec :: new ( ) ;
93135 let mut grouped: Vec < ( i64 , Vec < & SessionOcrBlock > ) > = Vec :: new ( ) ;
0 commit comments