1313
1414//! @ai-context: 目录模块(批 0-C3 Task 4 拆分,2026-09-11,原 751 行 → 外壳 ≤300)。
1515//! 子模块职责(D6,按域划分,逐条列出):
16+ //! · apply.rs —— 采纳落库 + 任务历史
1617//! · dto.rs —— IPC 契约类型(4 个 serde 结构体;字段序 = JSON 键序)
1718//! · gate.rs —— 成本硬拦截(免费档/本地 Provider 跳过;查询失败宽容放行)
1819//! · registry.rs —— 任务注册表 + id 序列 + 容量守卫 + 状态写入口
2324//! #[tauri::command] 定义随域下沉后,注册路径同步改成
2425//! crate::commands_ai_refine::<子模块>::<cmd>(IPC 名 = 路径末段,逐字不变)。
2526
27+ pub ( crate ) mod apply;
2628mod dto;
2729mod gate;
2830mod registry;
@@ -33,123 +35,3 @@ pub use dto::{AiRefineResult, AiTaskHandle, RefineEstimateView, RefineStrategyIn
3335pub use registry:: { claim_task_id, task_registry, task_seq, task_seq_lower_bound, AiTaskEntry } ;
3436pub ( crate ) use gate:: ensure_balance_for;
3537pub ( crate ) use registry:: { set_task, trim_tasks} ;
36-
37- use tauri:: State ;
38-
39- use crate :: commands:: AppState ;
40- use crate :: types:: { NewNote , Note } ;
41-
42- /// 任务历史(F2 任务中心:前端面板数据源——按类型列最近任务)。
43- #[ tauri:: command]
44- pub fn ai_task_history (
45- state : State < ' _ , AppState > ,
46- op_type : String ,
47- limit : Option < usize > ,
48- ) -> Result < Vec < crate :: db_ai_tasks:: AiTaskRecord > , String > {
49- if op_type != "refine" && op_type != "enrich" {
50- return Err ( "无效的任务类型(refine|enrich)" . to_string ( ) ) ;
51- }
52- state
53- . db
54- . list_ai_tasks ( & op_type, limit. unwrap_or ( 50 ) . min ( 200 ) )
55- . map_err ( |e| e. to_string ( ) )
56- }
57-
58- /// 采纳落库(REQ-141:diff 预览后用户采纳;v0.8.0 M4 版本化写路径——
59- /// ① 以规则基线建笔记(首快照)→ ② 精修版 = 新版本(ai-refine,含成本
60- /// meta)→ ③ 成本落库 note_ai_usage)。
61- /// @ai-context: F2(2026-08-21):task_id 可选——传入时标记任务已采纳
62- /// (防重启后从任务中心重复采纳产生重复笔记)。
63- #[ tauri:: command]
64- pub fn ai_refine_apply (
65- state : State < ' _ , AppState > ,
66- session_id : i64 ,
67- result : AiRefineResult ,
68- task_id : Option < u64 > ,
69- ) -> Result < Note , String > {
70- if session_id <= 0 {
71- return Err ( "无效的会话 id" . to_string ( ) ) ;
72- }
73- let session = state
74- . db
75- . get_session ( session_id)
76- . map_err ( |e| e. to_string ( ) ) ?
77- . ok_or_else ( || format ! ( "会话不存在: {}" , session_id) ) ?;
78- let fallback = format ! ( "{}(AI 精修)" , session. title) ;
79- let title = crate :: commands:: normalize_title ( result. title . clone ( ) , & fallback) ;
80- // 审查修复(2026-09-04):采纳守卫**前置**于任何写库——F2 注释自称"服务端
81- // 前置校验"但实为后置(先建基线笔记/写版本/记 usage 再拒,重复采纳会留
82- // 孤儿半态且 Err 早退不播 Notes);双点/陈旧面板/多入口并发兜底依赖此处。
83- if let Some ( tid) = task_id {
84- if state. db . is_ai_task_adopted ( tid) {
85- return Err ( "该任务结果已采纳落库——请勿重复采纳(可到笔记页查看)" . to_string ( ) ) ;
86- }
87- }
88- // ① 规则基线建笔记(版本链首快照原料——可回溯精修前内容)
89- let new = NewNote {
90- title : title. clone ( ) ,
91- content : result. base_markdown . clone ( ) ,
92- source : "classroom" . to_string ( ) ,
93- session_id : Some ( session_id) ,
94- rule_version : Some ( "rule" . to_string ( ) ) ,
95- purify_stats : None ,
96- tags : None ,
97- properties : None ,
98- // v0.11.0:精修基线笔记继承会话组归属(组化接线在会话转笔记链路写入,
99- // 精修新建基线同样归组——同会话同组,防组内资产漏登)
100- group_id : crate :: note_group_assign:: group_of_session ( & state. db , session_id) . ok ( ) . flatten ( ) ,
101- } ;
102- let note = state. db . create_note ( & new) . map_err ( |e| e. to_string ( ) ) ?;
103- // ② 精修版落库(新版本 ai-refine + 成本 meta)
104- // 审查修复(2026-08-21):落库成本用模型感知单价(与预估同口径——
105- // 付费模型预估 ¥X 不再记 ¥0)
106- let cost = crate :: ai_cost:: usage_cost_for_model (
107- result. base_markdown . chars ( ) . count ( ) ,
108- result. refined_markdown . chars ( ) . count ( ) ,
109- & result. model ,
110- ) ;
111- let meta = crate :: note_version:: VersionMeta {
112- cost_yuan : Some ( cost) ,
113- model : Some ( result. model . clone ( ) ) ,
114- slices : Some ( result. slices ) ,
115- merged_from : None ,
116- } ;
117- state
118- . db
119- . versioned_save (
120- note. id ,
121- & result. refined_markdown ,
122- crate :: note_version:: NoteVersionSource :: AiRefine ,
123- & meta,
124- )
125- . map_err ( |e| e. to_string ( ) ) ?;
126- // ③ 成本落库(token 估算与预估同口径——校准单价表数据源)
127- state
128- . db
129- . record_ai_usage (
130- note. id ,
131- & crate :: db_ai_usage:: AiUsageInput {
132- op_type : "refine" ,
133- tokens_in : result. base_markdown . chars ( ) . count ( ) ,
134- tokens_out : result. refined_markdown . chars ( ) . count ( ) ,
135- cost_yuan : cost,
136- model : result. model . clone ( ) ,
137- slices : result. slices ,
138- } ,
139- )
140- . map_err ( |e| e. to_string ( ) ) ?;
141- // F2 任务中心:标记采纳 + 成本回填(task_id 可选——旧前端调用不传则跳过;
142- // 防重启后从任务中心重复采纳产生重复笔记——采纳守卫已前置于写库前)
143- if let Some ( tid) = task_id {
144- let _ = state. db . mark_ai_task_adopted ( tid) ;
145- let _ = state. db . update_ai_task_cost ( tid, cost) ;
146- }
147- let note_row = state
148- . db
149- . get_note ( note. id )
150- . map_err ( |e| e. to_string ( ) ) ?
151- . ok_or_else ( || "笔记不存在" . to_string ( ) ) ?;
152- // REQ-278:采纳落库 = 笔记内容变更 → 广播 notes 域(任务中心/会话页采纳后笔记页即时刷新)
153- crate :: notify:: emit_changed ( & state. app , crate :: notify:: DataDomain :: Notes ) ;
154- Ok ( note_row)
155- }
0 commit comments