@@ -162,6 +162,23 @@ pub async fn ai_refine_start(
162162 tasks. insert ( task_id, AiTaskEntry { state : AiTaskState :: Pending , result : None } ) ;
163163 trim_tasks ( & mut tasks) ;
164164 }
165+ // F2 任务中心(2026-08-21):任务记录落库(pending 起步;终态在
166+ // run_refine_task 回写——写库失败不阻断 AI 调用,H2 设计)
167+ let _ = st. db . insert_ai_task ( & crate :: db_ai_tasks:: AiTaskRecord {
168+ task_id,
169+ op_type : "refine" . to_string ( ) ,
170+ ref_id : session_id,
171+ state : "pending" . to_string ( ) ,
172+ result_json : None ,
173+ cost_yuan : None ,
174+ elapsed_ms : None ,
175+ model : None ,
176+ error : None ,
177+ slices : None ,
178+ created_at : crate :: db_sessions_rows:: unix_seconds ( ) ,
179+ finished_at : None ,
180+ adopted : false ,
181+ } ) ;
165182 let st2 = st. clone ( ) ;
166183 tauri:: async_runtime:: spawn_blocking ( move || run_refine_task ( st2, task_id, session_id, mock) ) ;
167184 Ok ( AiTaskHandle { task_id, state : AiTaskState :: Pending } )
@@ -195,14 +212,33 @@ pub fn ai_refine_result(state: State<'_, AppState>, task_id: u64) -> Result<AiRe
195212 }
196213}
197214
215+ /// 任务历史(F2 任务中心:前端面板数据源——按类型列最近任务)。
216+ #[ tauri:: command]
217+ pub fn ai_task_history (
218+ state : State < ' _ , AppState > ,
219+ op_type : String ,
220+ limit : Option < usize > ,
221+ ) -> Result < Vec < crate :: db_ai_tasks:: AiTaskRecord > , String > {
222+ if op_type != "refine" && op_type != "enrich" {
223+ return Err ( "无效的任务类型(refine|enrich)" . to_string ( ) ) ;
224+ }
225+ state
226+ . db
227+ . list_ai_tasks ( & op_type, limit. unwrap_or ( 50 ) . min ( 200 ) )
228+ . map_err ( |e| e. to_string ( ) )
229+ }
230+
198231/// 采纳落库(REQ-141:diff 预览后用户采纳;v0.8.0 M4 版本化写路径——
199232/// ① 以规则基线建笔记(首快照)→ ② 精修版 = 新版本(ai-refine,含成本
200233/// meta)→ ③ 成本落库 note_ai_usage)。
234+ /// @ai-context: F2(2026-08-21):task_id 可选——传入时标记任务已采纳
235+ /// (防重启后从任务中心重复采纳产生重复笔记)。
201236#[ tauri:: command]
202237pub fn ai_refine_apply (
203238 state : State < ' _ , AppState > ,
204239 session_id : i64 ,
205240 result : AiRefineResult ,
241+ task_id : Option < u64 > ,
206242) -> Result < Note , String > {
207243 if session_id <= 0 {
208244 return Err ( "无效的会话 id" . to_string ( ) ) ;
@@ -259,6 +295,12 @@ pub fn ai_refine_apply(
259295 } ,
260296 )
261297 . map_err ( |e| e. to_string ( ) ) ?;
298+ // F2 任务中心:标记采纳 + 成本回填(task_id 可选——旧前端调用不传则跳过;
299+ // 防重启后从任务中心重复采纳产生重复笔记)
300+ if let Some ( tid) = task_id {
301+ let _ = state. db . mark_ai_task_adopted ( tid) ;
302+ let _ = state. db . update_ai_task_cost ( tid, cost) ;
303+ }
262304 state
263305 . db
264306 . get_note ( note. id )
@@ -279,6 +321,7 @@ pub fn ai_refine_apply(
279321fn run_refine_task ( st : AppState , task_id : u64 , session_id : i64 , mock : bool ) {
280322 // 诊断日志(2026-08-21 真机"排队中"排查):tauri dev 终端可见各阶段进度
281323 eprintln ! ( "[refine-task] task={} start session={} mock={}" , task_id, session_id, mock) ;
324+ let started = std:: time:: Instant :: now ( ) ;
282325 let outcome = std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || {
283326 run_refine_task_inner ( & st, task_id, session_id, mock)
284327 } ) )
@@ -287,6 +330,7 @@ fn run_refine_task(st: AppState, task_id: u64, session_id: i64, mock: bool) {
287330 "精修任务内部错误(panic)——请重试;若复现请反馈" . to_string ( ) ,
288331 ) )
289332 } ) ;
333+ let elapsed_ms = started. elapsed ( ) . as_millis ( ) as i64 ;
290334 match outcome {
291335 Ok ( result) => {
292336 eprintln ! (
@@ -305,6 +349,15 @@ fn run_refine_task(st: AppState, task_id: u64, session_id: i64, mock: bool) {
305349 // F1 修复(2026-08-21):精修调用上审计——REQ-140 轨迹可见化
306350 // (此前只有余额/测试连接/复核有记录,精修补充零审计)
307351 push_refine_audit ( & st, session_id, "ok" , Some ( & result. model ) ) ;
352+ // F2 任务中心:终态落库(写库失败不阻断——H2 设计)
353+ let result_json = serde_json:: to_string ( & result) . ok ( ) ;
354+ let _ = st. db . finish_ai_task (
355+ task_id,
356+ "succeeded" ,
357+ result_json. as_deref ( ) ,
358+ None ,
359+ elapsed_ms,
360+ ) ;
308361 }
309362 Err ( reason) => {
310363 // 打印具体 message——区分"未配置密钥"vs"密钥无效(401/403)"(真机排查)
@@ -314,8 +367,15 @@ fn run_refine_task(st: AppState, task_id: u64, session_id: i64, mock: bool) {
314367 reason. kind( ) ,
315368 reason. message( )
316369 ) ;
317- set_task ( & st, task_id, AiTaskState :: Failed { reason } ) ;
370+ set_task ( & st, task_id, AiTaskState :: Failed { reason : reason . clone ( ) } ) ;
318371 push_refine_audit ( & st, session_id, "error" , None ) ;
372+ let _ = st. db . finish_ai_task (
373+ task_id,
374+ "failed" ,
375+ None ,
376+ Some ( & format ! ( "{}: {}" , reason. kind( ) , reason. message( ) ) ) ,
377+ elapsed_ms,
378+ ) ;
319379 }
320380 }
321381}
0 commit comments