diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 001e5c0..e217200 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -420,6 +420,18 @@ pub async fn switch_provider( // 不做归档,直接写入 write_json_file(&settings_path, &provider.settings_config)?; + + // 写入后回读 live,并回填到目标供应商的 SSOT,保证一致 + if settings_path.exists() { + if let Ok(live_after) = read_json_file::(&settings_path) { + let m = config + .get_manager_mut(&app_type) + .ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?; + if let Some(target) = m.providers.get_mut(&id) { + target.settings_config = live_after; + } + } + } } } @@ -431,9 +443,32 @@ pub async fn switch_provider( manager.current = id; } - // 对 Codex:切换完成且释放可变借用后,再依据 SSOT 同步 MCP 到 config.toml + // 对 Codex:切换完成后,同步 MCP 到 config.toml,并将最新的 config.toml 回填到当前供应商 settings_config.config if let AppType::Codex = app_type { + // 1) 依据 SSOT 将启用的 MCP 投影到 ~/.codex/config.toml crate::mcp::sync_enabled_to_codex(&config)?; + + // 2) 读取投影后的 live config.toml 文本 + let cfg_text_after = crate::codex_config::read_and_validate_codex_config_text()?; + + // 3) 回填到当前(目标)供应商的 settings_config.config,确保编辑面板读取到最新 MCP + let cur_id = { + let m = config + .get_manager(&app_type) + .ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?; + m.current.clone() + }; + let m = config + .get_manager_mut(&app_type) + .ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?; + if let Some(p) = m.providers.get_mut(&cur_id) { + if let Some(obj) = p.settings_config.as_object_mut() { + obj.insert( + "config".to_string(), + serde_json::Value::String(cfg_text_after), + ); + } + } } log::info!("成功切换到供应商: {}", provider.name); @@ -874,6 +909,41 @@ pub async fn import_mcp_from_codex(state: State<'_, AppState>) -> Result, + app: Option, + appType: Option, +) -> Result { + let app_type = app_type + .or_else(|| app.as_deref().map(|s| s.into())) + .or_else(|| appType.as_deref().map(|s| s.into())) + .unwrap_or(AppType::Claude); + + match app_type { + AppType::Codex => { + let auth_path = crate::codex_config::get_codex_auth_path(); + if !auth_path.exists() { + return Err("Codex 配置文件不存在:缺少 auth.json".to_string()); + } + let auth: serde_json::Value = crate::config::read_json_file(&auth_path)?; + let cfg_text = crate::codex_config::read_and_validate_codex_config_text()?; + Ok(serde_json::json!({ "auth": auth, "config": cfg_text })) + } + AppType::Claude => { + let path = crate::config::get_claude_settings_path(); + if !path.exists() { + return Err("Claude Code 配置文件不存在".to_string()); + } + let v: serde_json::Value = crate::config::read_json_file(&path)?; + Ok(v) + } + } +} + /// 获取设置 #[tauri::command] pub async fn get_settings() -> Result { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e593f42..e89ecb7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -415,6 +415,7 @@ pub fn run() { commands::open_external, commands::get_app_config_path, commands::open_app_config_folder, + commands::read_live_provider_settings, commands::get_settings, commands::save_settings, commands::check_for_updates, diff --git a/src/components/EditProviderModal.tsx b/src/components/EditProviderModal.tsx index 3844443..474699b 100644 --- a/src/components/EditProviderModal.tsx +++ b/src/components/EditProviderModal.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Provider } from "../types"; import { AppType } from "../lib/tauri-api"; @@ -18,6 +18,31 @@ const EditProviderModal: React.FC = ({ onClose, }) => { const { t } = useTranslation(); + const [effectiveProvider, setEffectiveProvider] = useState(provider); + + // 若为当前应用且正在编辑“当前供应商”,则优先读取 live 配置作为初始值(Claude/Codex 均适用) + useEffect(() => { + let mounted = true; + const maybeLoadLive = async () => { + try { + const currentId = await window.api.getCurrentProvider(appType); + if (currentId && currentId === provider.id) { + const live = await window.api.getLiveProviderSettings(appType); + if (!mounted) return; + setEffectiveProvider({ ...provider, settingsConfig: live }); + } else { + setEffectiveProvider(provider); + } + } catch (e) { + // 读取失败则回退到原 provider + setEffectiveProvider(provider); + } + }; + maybeLoadLive(); + return () => { + mounted = false; + }; + }, [appType, provider]); const handleSubmit = (data: Omit) => { onSave({ @@ -31,7 +56,7 @@ const EditProviderModal: React.FC = ({ appType={appType} title={t("common.edit")} submitText={t("common.save")} - initialData={provider} + initialData={effectiveProvider} showPresets={false} onSubmit={handleSubmit} onClose={onClose} diff --git a/src/lib/tauri-api.ts b/src/lib/tauri-api.ts index 131e9b5..55e2b07 100644 --- a/src/lib/tauri-api.ts +++ b/src/lib/tauri-api.ts @@ -429,6 +429,22 @@ export const tauriAPI = { } }, + // 读取当前生效(live)的 provider settings(根据 appType) + // Codex: { auth: object, config: string } + // Claude: settings.json 内容 + getLiveProviderSettings: async (app?: AppType): Promise => { + try { + return await invoke("read_live_provider_settings", { + app_type: app, + app, + appType: app, + }); + } catch (error) { + console.error("读取 live 配置失败:", error); + throw error; + } + }, + // ours: 第三方/自定义供应商——测速与端点管理 // 第三方/自定义供应商:批量测试端点延迟 testApiEndpoints: async ( diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index b34d517..7780ca8 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -96,6 +96,10 @@ declare global { syncEnabledMcpToCodex: () => Promise; importMcpFromClaude: () => Promise; importMcpFromCodex: () => Promise; + // 读取当前生效(live)的 provider settings(根据 appType) + // Codex: { auth: object, config: string } + // Claude: settings.json 内容 + getLiveProviderSettings: (app?: AppType) => Promise; testApiEndpoints: ( urls: string[], options?: { timeoutSecs?: number },