diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index bc6be2206..2d62d5b0a 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -206,6 +206,11 @@ impl ProviderService { id ); + // 获取新供应商的完整配置(用于更新备份) + let provider = providers + .get(id) + .ok_or_else(|| AppError::Message(format!("供应商 {id} 不存在")))?; + // Update database is_current state.db.set_current_provider(app_type.as_str(), id)?; @@ -215,6 +220,14 @@ impl ProviderService { // Update local settings for consistency crate::settings::set_current_provider(&app_type, Some(id))?; + // 更新 Live 备份(确保代理关闭时恢复正确的供应商配置) + futures::executor::block_on( + state + .proxy_service + .update_live_backup_from_provider(app_type.as_str(), provider), + ) + .map_err(|e| AppError::Message(format!("更新 Live 备份失败: {e}")))?; + // Note: No Live config write, no MCP sync // The proxy server will route requests to the new provider via is_proxy_target return Ok(()); diff --git a/src-tauri/src/services/proxy.rs b/src-tauri/src/services/proxy.rs index 111154f72..aff8bbc00 100644 --- a/src-tauri/src/services/proxy.rs +++ b/src-tauri/src/services/proxy.rs @@ -5,6 +5,7 @@ use crate::app_config::AppType; use crate::config::{get_claude_settings_path, read_json_file, write_json_file}; use crate::database::Database; +use crate::provider::Provider; use crate::proxy::server::ProxyServer; use crate::proxy::types::*; use serde_json::{json, Value}; @@ -434,6 +435,49 @@ impl ProxyService { .map_err(|e| format!("检查接管状态失败: {e}")) } + /// 从供应商配置更新 Live 备份(用于代理模式下的热切换) + /// + /// 与 backup_live_configs() 不同,此方法从供应商的 settings_config 生成备份, + /// 而不是从 Live 文件读取(因为 Live 文件已被代理接管)。 + pub async fn update_live_backup_from_provider( + &self, + app_type: &str, + provider: &Provider, + ) -> Result<(), String> { + let backup_json = match app_type { + "claude" => { + // Claude: settings_config 直接作为备份 + serde_json::to_string(&provider.settings_config) + .map_err(|e| format!("序列化 Claude 配置失败: {e}"))? + } + "codex" => { + // Codex: settings_config 包含 {"auth": ..., "config": ...},直接使用 + serde_json::to_string(&provider.settings_config) + .map_err(|e| format!("序列化 Codex 配置失败: {e}"))? + } + "gemini" => { + // Gemini: 只提取 env 字段(与原始备份格式一致) + // proxy.rs 的 read_gemini_live() 返回 {"env": {...}} + let env_backup = if let Some(env) = provider.settings_config.get("env") { + json!({ "env": env }) + } else { + json!({ "env": {} }) + }; + serde_json::to_string(&env_backup) + .map_err(|e| format!("序列化 Gemini 配置失败: {e}"))? + } + _ => return Err(format!("未知的应用类型: {app_type}")), + }; + + self.db + .save_live_backup(app_type, &backup_json) + .await + .map_err(|e| format!("更新 {app_type} 备份失败: {e}"))?; + + log::info!("已更新 {app_type} Live 备份(热切换)"); + Ok(()) + } + /// 代理模式下切换供应商(热切换,不写 Live) pub async fn switch_proxy_target( &self, diff --git a/src-tauri/tests/deeplink_import.rs b/src-tauri/tests/deeplink_import.rs index 9a349d20a..5a0238c61 100644 --- a/src-tauri/tests/deeplink_import.rs +++ b/src-tauri/tests/deeplink_import.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use cc_switch_lib::{import_provider_from_deeplink, parse_deeplink_url, AppState, Database, ProxyService}; +use cc_switch_lib::{ + import_provider_from_deeplink, parse_deeplink_url, AppState, Database, ProxyService, +}; #[path = "support.rs"] mod support; @@ -17,7 +19,10 @@ fn deeplink_import_claude_provider_persists_to_db() { let db = Arc::new(Database::memory().expect("create memory db")); let proxy_service = ProxyService::new(db.clone()); - let state = AppState { db: db.clone(), proxy_service }; + let state = AppState { + db: db.clone(), + proxy_service, + }; let provider_id = import_provider_from_deeplink(&state, request.clone()) .expect("import provider from deeplink"); @@ -54,7 +59,10 @@ fn deeplink_import_codex_provider_builds_auth_and_config() { let db = Arc::new(Database::memory().expect("create memory db")); let proxy_service = ProxyService::new(db.clone()); - let state = AppState { db: db.clone(), proxy_service }; + let state = AppState { + db: db.clone(), + proxy_service, + }; let provider_id = import_provider_from_deeplink(&state, request.clone()) .expect("import provider from deeplink"); diff --git a/src-tauri/tests/support.rs b/src-tauri/tests/support.rs index bbe603c44..bc5c8b716 100644 --- a/src-tauri/tests/support.rs +++ b/src-tauri/tests/support.rs @@ -1,7 +1,9 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, OnceLock}; -use cc_switch_lib::{update_settings, AppSettings, AppState, Database, MultiAppConfig, ProxyService}; +use cc_switch_lib::{ + update_settings, AppSettings, AppState, Database, MultiAppConfig, ProxyService, +}; /// 为测试设置隔离的 HOME 目录,避免污染真实用户数据。 pub fn ensure_test_home() -> &'static Path {