@@ -59,16 +59,27 @@ pub fn ai_provider_presets() -> Vec<AiProviderView> {
5959/// 当前 Provider 列表(含默认标记与密钥存在性)。
6060#[ tauri:: command]
6161pub fn ai_provider_list ( state : State < ' _ , AppState > ) -> Result < Vec < AiProviderView > , String > {
62- let store = state. ai_providers . lock ( ) . map_err ( |e| format ! ( "AI Provider 存储锁中毒: {}" , e) ) ?;
63- let default_id = store. effective_default_id ( ) ;
62+ // 快照后释放锁——load_key 为 DPAPI 文件 I/O,不持配置锁(短锁契约)
63+ let snapshot = {
64+ let store = state. ai_providers . lock ( ) . map_err ( |e| format ! ( "AI Provider 存储锁中毒: {}" , e) ) ?;
65+ let default_id = store. effective_default_id ( ) ;
66+ let ids: Vec < ( String , bool ) > = store
67+ . providers
68+ . iter ( )
69+ . map ( |p| ( p. id . clone ( ) , Some ( p. id . as_str ( ) ) == default_id. as_deref ( ) ) )
70+ . collect ( ) ;
71+ ( store. providers . clone ( ) , ids)
72+ } ;
6473 let mut views = Vec :: new ( ) ;
65- for p in & store . providers {
74+ for ( id , is_default ) in & snapshot . 1 {
6675 let has_key = state
6776 . ai_credentials
68- . load_key ( & provider_scope ( & p . id ) )
77+ . load_key ( & provider_scope ( id) )
6978 . map ( |k| k. is_some ( ) )
7079 . unwrap_or ( false ) ;
71- views. push ( to_view ( p, has_key, Some ( p. id . as_str ( ) ) == default_id. as_deref ( ) ) ) ;
80+ if let Some ( p) = snapshot. 0 . iter ( ) . find ( |p| & p. id == id) {
81+ views. push ( to_view ( p, has_key, * is_default) ) ;
82+ }
7283 }
7384 Ok ( views)
7485}
@@ -82,6 +93,9 @@ pub fn ai_provider_add(state: State<'_, AppState>, input: AiProviderInput) -> Re
8293 } ) ;
8394 provider. validate ( ) ?;
8495 let mut store = state. ai_providers . lock ( ) . map_err ( |e| format ! ( "AI Provider 存储锁中毒: {}" , e) ) ?;
96+ // 持锁跨 save_key + save:保证凭据与配置写入原子性(凭据先写——
97+ // 若 JSON 写入失败,凭据已存的残留可通过重试修复;反向序会留下
98+ // 无凭据的 Provider)
8599 if store. get ( & provider. id ) . is_some ( ) {
86100 return Err ( format ! ( "Provider id 已存在: {}" , provider. id) ) ;
87101 }
@@ -215,7 +229,10 @@ fn resolve_input(input: &AiProviderInput) -> Result<AiProviderConfig, String> {
215229 models : input. models . iter ( ) . map ( |m| m. trim ( ) . to_string ( ) ) . filter ( |m| !m. is_empty ( ) ) . collect ( ) ,
216230 default_model : input. default_model . trim ( ) . to_string ( ) ,
217231 enabled : input. enabled ,
218- fallback_order : input. fallback_order . clone ( ) ,
232+ fallback_order : input. fallback_order . iter ( )
233+ . map ( |s| s. trim ( ) . to_string ( ) )
234+ . filter ( |s| !s. is_empty ( ) )
235+ . collect ( ) ,
219236 } )
220237}
221238
0 commit comments