@@ -371,12 +371,18 @@ pub struct WorkbenchData {
371371
372372/// 工作台数据接口:规则草稿 + 精修结果 + 章节分组 diff 一次取全。
373373///
374- /// refined_markdown 为 None → 尚未精修;否则包含最新笔记精修版内容
375- /// 与章节 diff。
374+ /// refined_markdown 为 None → 尚未精修;否则包含最新精修版内容与章节 diff。
375+ ///
376+ /// @ai-context: 精修版三级数据源(修复:原实现只查已落库笔记——采纳前打开
377+ /// 工作台右侧恒空;且任务成功事件先于 DB 落库,存在竞态):
378+ /// ① refine_result 参数(调用方内存结果——采纳前刚完成的任务,
379+ /// 消除竞态)② 最新未采纳成功任务(DB 持久化——重启后恢复)
380+ /// ③ 已落库笔记最新版本(采纳后)。
376381#[ tauri:: command]
377382pub async fn refine_workbench (
378383 state : State < ' _ , AppState > ,
379384 session_id : i64 ,
385+ refine_result : Option < AiRefineResult > ,
380386) -> Result < WorkbenchData , String > {
381387 if session_id <= 0 {
382388 return Err ( "无效的会话 id" . to_string ( ) ) ;
@@ -398,25 +404,42 @@ pub async fn refine_workbench(
398404 . map_err ( |e| e. to_string ( ) ) ?;
399405 let rule_md = strip_anchors ( & draft. markdown ) ;
400406
401- // ② 查已落库精修笔记(有无精修结果)
407+ // ② 精修版数据源:内存结果(优先)> 未采纳成功任务(DB 兜底)> 已落库笔记
408+ let unadopted = state
409+ . db
410+ . find_latest_unadopted_refine ( session_id)
411+ . map_err ( |e| e. to_string ( ) ) ?;
412+ let pending_result: Option < AiRefineResult > = match ( refine_result, unadopted) {
413+ ( Some ( r) , _) => Some ( r) ,
414+ // 解析失败降级(日志可观测——不影响工作台打开,可经任务中心重取)
415+ ( None , Some ( task) ) => task. result_json . as_deref ( ) . and_then ( |j| {
416+ serde_json:: from_str :: < AiRefineResult > ( j)
417+ . map_err ( |e| eprintln ! ( "[refine-workbench] 未采纳任务 {} 结果解析失败: {}" , task. task_id, e) )
418+ . ok ( )
419+ } ) ,
420+ ( None , None ) => None ,
421+ } ;
422+
402423 let note = state. db . find_note_by_session ( session_id) . map_err ( |e| e. to_string ( ) ) ?;
403- let ( refined_md, sections, stats, meta) = if let Some ( ref note) = note {
424+ let ( refined_md, sections, stats, meta) = if let Some ( result) = pending_result {
425+ let secs = diff_sections ( & rule_md, & result. refined_markdown ) ;
426+ let st = stats_from ( & secs) ;
427+ // 未落库:成本按模型单价在 apply 时核算——此处不做虚假回填
428+ let m = VersionMeta {
429+ cost_yuan : None ,
430+ model : Some ( result. model . clone ( ) ) ,
431+ slices : Some ( result. slices ) ,
432+ merged_from : None ,
433+ } ;
434+ ( Some ( result. refined_markdown ) , secs, st, Some ( m) )
435+ } else if let Some ( ref note) = note {
404436 // 取最新版本内容作为精修版
405437 let versions = state. db . list_versions ( note. id ) . map_err ( |e| e. to_string ( ) ) ?;
406438 let latest = versions. last ( )
407439 . map ( |v| v. content . clone ( ) )
408440 . unwrap_or_else ( || note. content . clone ( ) ) ;
409441 let secs = diff_sections ( & rule_md, & latest) ;
410- let total_added: usize = secs. iter ( ) . map ( |s| s. added_lines . len ( ) ) . sum ( ) ;
411- let total_removed: usize = secs. iter ( ) . map ( |s| s. removed_lines . len ( ) ) . sum ( ) ;
412- let total_unchanged: usize = secs. iter ( )
413- . filter ( |s| s. status == crate :: note_diff:: DiffStatus :: Unchanged )
414- . count ( ) ;
415- let st = DiffStats {
416- added : total_added,
417- removed : total_removed,
418- unchanged : total_unchanged,
419- } ;
442+ let st = stats_from ( & secs) ;
420443 // 取最新版本 meta
421444 let m = versions. last ( ) . map ( |v| VersionMeta {
422445 cost_yuan : v. meta . cost_yuan ,
@@ -438,6 +461,17 @@ pub async fn refine_workbench(
438461 } )
439462}
440463
464+ /// 章节分组 diff 统计(新增/删除/未变行数——与 diff_sections 同口径)。
465+ fn stats_from ( secs : & [ SectionDiff ] ) -> DiffStats {
466+ DiffStats {
467+ added : secs. iter ( ) . map ( |s| s. added_lines . len ( ) ) . sum ( ) ,
468+ removed : secs. iter ( ) . map ( |s| s. removed_lines . len ( ) ) . sum ( ) ,
469+ unchanged : secs. iter ( )
470+ . filter ( |s| s. status == crate :: note_diff:: DiffStatus :: Unchanged )
471+ . count ( ) ,
472+ }
473+ }
474+
441475/// 更新任务状态并推送事件(短锁内完成即释放)。M3 补充任务复用(pub(crate))。
442476pub ( crate ) fn set_task ( st : & AppState , task_id : u64 , new_state : AiTaskState ) {
443477 if let Ok ( mut tasks) = st. ai_tasks . lock ( ) {
@@ -570,4 +604,20 @@ mod tests {
570604 assert ! ( sec. get( "added_lines" ) . is_some( ) ) ;
571605 assert_eq ! ( sec[ "status" ] , "modified" ) ;
572606 }
607+
608+ /// stats_from:按 diff_sections 分组行数统计(工作台头部 新增/删除/未变 徽标)。
609+ #[ test]
610+ fn stats_from_aggregates_section_lines ( ) {
611+ use crate :: note_diff:: { DiffStatus , SectionDiff } ;
612+ let secs = vec ! [
613+ SectionDiff { heading: "A" . into( ) , status: DiffStatus :: Modified , removed_lines: vec![ "a" . into( ) ] , added_lines: vec![ "A" . into( ) , "B" . into( ) ] } ,
614+ SectionDiff { heading: "B" . into( ) , status: DiffStatus :: Unchanged , removed_lines: vec![ ] , added_lines: vec![ ] } ,
615+ SectionDiff { heading: "C" . into( ) , status: DiffStatus :: Added , removed_lines: vec![ ] , added_lines: vec![ "c" . into( ) ] } ,
616+ SectionDiff { heading: "D" . into( ) , status: DiffStatus :: Removed , removed_lines: vec![ "d" . into( ) ] , added_lines: vec![ ] } ,
617+ ] ;
618+ let s = stats_from ( & secs) ;
619+ assert_eq ! ( s. added, 3 ) ;
620+ assert_eq ! ( s. removed, 2 ) ;
621+ assert_eq ! ( s. unchanged, 1 ) ;
622+ }
573623}
0 commit comments