Skip to content

Commit 0a45e78

Browse files
committed
feat(ai): AppState 装配 Provider 存储与旧配置自动迁移(v0.11.6 M1)
1 parent f4060b8 commit 0a45e78

6 files changed

Lines changed: 39 additions & 4 deletions

File tree

‎app/src-tauri/src/ai_refine_task.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ fn run_refine_task_inner(
205205
.map(|k| format!("{}:{}..", k.len(), &k[..6.min(k.len())]))
206206
.unwrap_or_else(|| "无".to_string()),
207207
);
208-
let client = AiClient::from_settings(&settings, stored_key);
208+
let store = st.ai_providers.lock().map_err(|e| AiTaskFailure::Other(e.to_string()))?.clone();
209+
let client = AiClient::from_settings_with_store(&settings, stored_key, &store);
209210
let adapter = AiNoteRefineAdapter::new(client.clone());
210211
let mock_adapter = AiMockAdapter;
211212
// F2-B4:并发精修(worker 池消费切片队列;按片上报进度;失败片重试后

‎app/src-tauri/src/app_setup.rs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,29 @@ pub fn setup_app_state(app: &mut tauri::App) -> Result<(), String> {
155155
crate::ai_settings::AiSettings::load(&ai_settings_path),
156156
));
157157
let ai_credentials = crate::ai_credentials::platform_store(&data_dir.join("ai_credentials.bin"));
158+
// v0.11.6 M1:AI Provider 装配——首启迁移(ai_providers.json 不存在且
159+
// 旧配置非默认值 → 生成 SiliconFlow Provider);密钥迁移同处完成
160+
// (旧凭据条目 → provider:legacy-siliconflow 新条目)
161+
let ai_providers_path = data_dir.join("ai_providers.json");
162+
let ai_providers = std::sync::Arc::new(std::sync::Mutex::new(
163+
crate::ai_provider::AiProviderStore::load(&ai_providers_path),
164+
));
165+
{
166+
let mut store = ai_providers.lock().map_err(|e| e.to_string())?;
167+
if store.providers.is_empty() {
168+
let legacy = ai_settings.lock().map_err(|e| e.to_string())?.clone();
169+
let (providers, default_id) = crate::ai_provider::migrate_from_legacy(&legacy);
170+
store.providers = providers;
171+
store.default_provider_id = default_id;
172+
// 密钥迁移:旧凭据存在且新 scope 不存在 → 复制(旧条目保留供回退)
173+
if let Ok(Some(key)) = ai_credentials.load_key("default") {
174+
if ai_credentials.load_key("provider:legacy-siliconflow").ok().flatten().is_none() {
175+
let _ = ai_credentials.save_key("provider:legacy-siliconflow", &key);
176+
}
177+
}
178+
store.save(&ai_providers_path)?;
179+
}
180+
}
158181
// v0.8.0 M2(REQ-145):AI 异步任务注册表 + id 序列(spawn_blocking 后台
159182
// 执行,前端轮询/事件双通道——禁止同步阻塞 30s+ 长会话精修)
160183
let ai_tasks = crate::commands_ai_refine::task_registry();
@@ -238,6 +261,9 @@ pub fn setup_app_state(app: &mut tauri::App) -> Result<(), String> {
238261
ai_settings,
239262
ai_settings_path,
240263
ai_credentials,
264+
// v0.11.6 M1:AI Provider 存储 + 配置路径
265+
ai_providers,
266+
ai_providers_path,
241267
// v0.8.0 M2(REQ-145):AI 异步任务注册表 + id 序列
242268
ai_tasks,
243269
ai_task_seq,

‎app/src-tauri/src/commands.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ pub struct AppState {
154154
/// v0.8.0 M1(REQ-138):AI 密钥凭据存储(Windows DPAPI 加密文件;
155155
/// 密钥不落 SQLite/明文文件——安全红线)
156156
pub ai_credentials: std::sync::Arc<dyn crate::ai_credentials::CredentialStore>,
157+
/// v0.11.6 M1:AI Provider 存储(多 Provider 配置内存态单点;持久化见
158+
/// ai_providers_path——密钥不在 JSON,走 ai_credentials scope 化凭据)
159+
pub ai_providers: std::sync::Arc<std::sync::Mutex<crate::ai_provider::AiProviderStore>>,
160+
/// v0.11.6 M1:AI Provider 配置文件路径(应用数据目录 ai_providers.json)
161+
pub ai_providers_path: std::path::PathBuf,
157162
/// v0.8.0 M2(REQ-145):AI 异步任务注册表(任务 id → 状态/结果;
158163
/// 容量守卫防无界增长——见 commands_ai_refine::trim_tasks)
159164
pub ai_tasks: std::sync::Arc<std::sync::Mutex<std::collections::HashMap<u64, crate::commands_ai_refine::AiTaskEntry>>>,

‎app/src-tauri/src/commands_ai_enrich.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ fn run_enrich_task(st: AppState, task_id: u64, note_id: i64, selected: Vec<AiEnr
274274
None => String::new(),
275275
};
276276
let settings = st.ai_settings.lock().map_err(|e| AiTaskFailure::Other(e.to_string()))?.clone();
277-
let client = AiClient::from_settings(&settings, st.ai_credentials.load_key("default").ok().flatten());
277+
let store = st.ai_providers.lock().map_err(|e| AiTaskFailure::Other(e.to_string()))?.clone();
278+
let client = AiClient::from_settings_with_store(&settings, st.ai_credentials.load_key("default").ok().flatten(), &store);
278279
let adapter = AiNoteEnrichAdapter::new(client.clone());
279280
let mock_adapter = AiMockAdapter;
280281
// 切片(长笔记按章节切——REQ-145 基建复用;锚点跨片=全局章节标题)

‎app/src-tauri/src/commands_ai_refine.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,8 @@ pub(crate) fn ensure_balance_for(st: &AppState, chars: usize, model: &str) -> Re
499499
return Err("未配置 API 密钥(设置页保存密钥或配置环境变量 SILICONFLOW_API_KEY)".to_string());
500500
}
501501
let settings = st.ai_settings.lock().map_err(|e| format!("AI 设置锁中毒: {}", e))?.clone();
502-
let cfg = crate::ai_client::AiClient::from_settings(&settings, Some(api_key)).config;
502+
let store = st.ai_providers.lock().map_err(|e| format!("AI Provider 存储锁中毒: {}", e))?.clone();
503+
let cfg = crate::ai_client::AiClient::from_settings_with_store(&settings, Some(api_key), &store).config;
503504
let adapter = crate::ai_balance::AiBalanceAdapter {
504505
base_url: cfg.base_url,
505506
api_key: cfg.api_key,

‎app/src-tauri/src/commands_ai_settings.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ fn balance_adapter(state: &AppState) -> Result<AiBalanceAdapter, String> {
218218
let api_key = env_api_key()
219219
.or(state.ai_credentials.load_key("default")?)
220220
.unwrap_or_default();
221-
let cfg = crate::ai_client::AiClient::from_settings(&s, Some(api_key)).config;
221+
let store = state.ai_providers.lock().map_err(|e| e.to_string())?.clone();
222+
let cfg = crate::ai_client::AiClient::from_settings_with_store(&s, Some(api_key), &store).config;
222223
Ok(AiBalanceAdapter {
223224
base_url: cfg.base_url,
224225
api_key: cfg.api_key,

0 commit comments

Comments
 (0)