@@ -164,20 +164,33 @@ pub async fn proofread_run(
164164 } ) ;
165165
166166 let started = std:: time:: Instant :: now ( ) ;
167+ // 预估成本(成功/失败记账共用——内联不再重复估算)
168+ let est_tokens = crate :: ai_cost:: estimate_tokens ( chars) ;
169+ let ( est_price, _) = crate :: ai_cost:: price_for_model ( & model) ;
170+ let est_cost = crate :: ai_cost:: estimate_cost ( est_tokens, est_price) ;
167171 let client = if mock {
168172 None
169173 } else {
170- let key = crate :: commands_ai_providers:: resolve_default_provider_key ( & st) ?;
171- let providers = st
172- . ai_providers
173- . lock ( )
174- . map_err ( |e| format ! ( "Provider 存储锁中毒: {}" , e) ) ?
175- . clone ( ) ;
176- Some ( crate :: ai_client:: AiClient :: from_settings_with_store (
177- & settings,
178- key,
179- & providers,
180- ) )
174+ match crate :: commands_ai_providers:: resolve_default_provider_key ( & st) {
175+ Ok ( key) => {
176+ let providers = match st. ai_providers . lock ( ) {
177+ Ok ( g) => g. clone ( ) ,
178+ Err ( e) => {
179+ record_proofread_failure ( & db, task_id, session_id, & model, est_cost, created_at, 0 , & format ! ( "Provider 存储锁中毒: {}" , e) ) ;
180+ return Err ( format ! ( "Provider 存储锁中毒: {}" , e) ) ;
181+ }
182+ } ;
183+ Some ( crate :: ai_client:: AiClient :: from_settings_with_store (
184+ & settings,
185+ key,
186+ & providers,
187+ ) )
188+ }
189+ Err ( e) => {
190+ record_proofread_failure ( & db, task_id, session_id, & model, est_cost, created_at, 0 , & e) ;
191+ return Err ( e) ;
192+ }
193+ }
181194 } ;
182195
183196 let chunks = ai_proofread:: chunk_sentences ( & expected. iter ( ) . map ( |s| s. to_string ( ) ) . collect :: < Vec < _ > > ( ) ) ;
@@ -187,9 +200,14 @@ pub async fn proofread_run(
187200 let user = build_user_prompt (
188201 & chunk. iter ( ) . map ( |& i| expected[ i] . to_string ( ) ) . collect :: < Vec < _ > > ( ) ,
189202 ) ;
190- let raw = client
191- . chat_text ( & build_system_prompt ( ) , & user)
192- . map_err ( |e| format ! ( "校对请求 {}/{} 失败(原文未改动): {}" , ci + 1 , chunks. len( ) , e) ) ?;
203+ let raw = match client. chat_text ( & build_system_prompt ( ) , & user) {
204+ Ok ( raw) => raw,
205+ Err ( e) => {
206+ let msg = format ! ( "校对请求 {}/{} 失败(原文未改动): {}" , ci + 1 , chunks. len( ) , e) ;
207+ record_proofread_failure ( & db, task_id, session_id, & model, est_cost, created_at, started. elapsed ( ) . as_millis ( ) as i64 , & msg) ;
208+ return Err ( msg) ;
209+ }
210+ } ;
193211 let batch_expected: Vec < String > =
194212 chunk. iter ( ) . map ( |& i| expected[ i] . to_string ( ) ) . collect ( ) ;
195213 suggestions. extend ( parse_suggestions ( & raw , & batch_expected) ) ;
@@ -242,12 +260,17 @@ pub async fn proofread_run(
242260 similarity : Some ( similarity) ,
243261 } ) ;
244262 }
245- let draft_count = db. add_refine_drafts ( & drafts) . map_err ( |e| format ! ( "落校对草稿失败: {e}" ) ) ?;
263+ let draft_count = match db. add_refine_drafts ( & drafts) {
264+ Ok ( n) => n,
265+ Err ( e) => {
266+ let msg = format ! ( "落校对草稿失败: {}" , e) ;
267+ record_proofread_failure ( & db, task_id, session_id, & model, est_cost, created_at, started. elapsed ( ) . as_millis ( ) as i64 , & msg) ;
268+ return Err ( msg) ;
269+ }
270+ } ;
246271
247272 let elapsed = started. elapsed ( ) . as_millis ( ) as i64 ;
248- let tokens = crate :: ai_cost:: estimate_tokens ( chars) ;
249- let ( price, _) = crate :: ai_cost:: price_for_model ( & model) ;
250- let cost_yuan = crate :: ai_cost:: estimate_cost ( tokens, price) ;
273+ let cost_yuan = est_cost;
251274 let result_json = serde_json:: json!( {
252275 "draftCount" : draft_count,
253276 "suggestionsReceived" : suggestions_received,
@@ -277,6 +300,38 @@ pub async fn proofread_run(
277300 Ok ( ProofreadRunView { draft_count, suggestions_received, chars, cost_yuan, model, capped } )
278301}
279302
303+ /// 失败终态记账(网络/解析/落库错误路径不残留 running 行——任务中心/审计可查)。
304+ fn record_proofread_failure (
305+ db : & crate :: db:: Db ,
306+ task_id : u64 ,
307+ session_id : i64 ,
308+ model : & str ,
309+ cost_yuan : f64 ,
310+ created_at : i64 ,
311+ elapsed_ms : i64 ,
312+ error : & str ,
313+ ) {
314+ let rec = crate :: db_ai_tasks:: AiTaskRecord {
315+ task_id,
316+ op_type : "proofread" . to_string ( ) ,
317+ ref_id : session_id,
318+ state : "failed" . to_string ( ) ,
319+ target_kind : Some ( "session" . to_string ( ) ) ,
320+ result_json : None ,
321+ cost_yuan : Some ( cost_yuan) ,
322+ elapsed_ms : Some ( elapsed_ms) ,
323+ model : Some ( model. to_string ( ) ) ,
324+ error : Some ( error. to_string ( ) ) ,
325+ slices : None ,
326+ created_at,
327+ finished_at : Some ( crate :: db:: unix_seconds ( ) ) ,
328+ adopted : false ,
329+ } ;
330+ if let Err ( e) = db. insert_ai_task ( & rec) {
331+ eprintln ! ( "[Proofread] 失败任务记账失败: {e}" ) ;
332+ }
333+ }
334+
280335/// 校对草稿列表(origin=proofread;裁决走 second_pass_decide——裁决与来源解耦)。
281336#[ tauri:: command]
282337pub fn proofread_list (
0 commit comments