diff --git a/src-tauri/src/app_config.rs b/src-tauri/src/app_config.rs index 2daace5..dd748ea 100644 --- a/src-tauri/src/app_config.rs +++ b/src-tauri/src/app_config.rs @@ -317,7 +317,9 @@ impl MultiAppConfig { // 迁移通用配置片段:claude_common_config_snippet → common_config_snippets.claude if let Some(old_claude_snippet) = config.claude_common_config_snippet.take() { - log::info!("迁移通用配置:claude_common_config_snippet → common_config_snippets.claude"); + log::info!( + "迁移通用配置:claude_common_config_snippet → common_config_snippets.claude" + ); config.common_config_snippets.claude = Some(old_claude_snippet); updated = true; } @@ -414,9 +416,7 @@ impl MultiAppConfig { return Ok(false); } - log::info!( - "检测到已存在配置文件且 Prompt 列表为空,将尝试从现有提示词文件自动导入" - ); + log::info!("检测到已存在配置文件且 Prompt 列表为空,将尝试从现有提示词文件自动导入"); let mut imported = false; for app in [AppType::Claude, AppType::Codex, AppType::Gemini] { diff --git a/src-tauri/src/claude_mcp.rs b/src-tauri/src/claude_mcp.rs index 8a8bb35..0369e91 100644 --- a/src-tauri/src/claude_mcp.rs +++ b/src-tauri/src/claude_mcp.rs @@ -139,13 +139,11 @@ pub fn upsert_mcp_server(id: &str, spec: Value) -> Result { if is_http || is_sse { let url = spec.get("url").and_then(|x| x.as_str()).unwrap_or(""); if url.is_empty() { - return Err(AppError::McpValidation( - if is_http { - "http 类型的 MCP 服务器缺少 url 字段".into() - } else { - "sse 类型的 MCP 服务器缺少 url 字段".into() - }, - )); + return Err(AppError::McpValidation(if is_http { + "http 类型的 MCP 服务器缺少 url 字段".into() + } else { + "sse 类型的 MCP 服务器缺少 url 字段".into() + })); } } diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 4724430..2f81def 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -184,13 +184,12 @@ pub async fn get_common_config_snippet( use crate::app_config::AppType; use std::str::FromStr; - let app = AppType::from_str(&app_type) - .map_err(|e| format!("无效的应用类型: {}", e))?; + let app = AppType::from_str(&app_type).map_err(|e| format!("无效的应用类型: {e}"))?; let guard = state .config .read() - .map_err(|e| format!("读取配置锁失败: {}", e))?; + .map_err(|e| format!("读取配置锁失败: {e}"))?; Ok(guard.common_config_snippets.get(&app).cloned()) } @@ -205,13 +204,12 @@ pub async fn set_common_config_snippet( use crate::app_config::AppType; use std::str::FromStr; - let app = AppType::from_str(&app_type) - .map_err(|e| format!("无效的应用类型: {}", e))?; + let app = AppType::from_str(&app_type).map_err(|e| format!("无效的应用类型: {e}"))?; let mut guard = state .config .write() - .map_err(|e| format!("写入配置锁失败: {}", e))?; + .map_err(|e| format!("写入配置锁失败: {e}"))?; // 验证格式(根据应用类型) if !snippet.trim().is_empty() { @@ -219,7 +217,7 @@ pub async fn set_common_config_snippet( AppType::Claude | AppType::Gemini => { // 验证 JSON 格式 serde_json::from_str::(&snippet) - .map_err(|e| format!("无效的 JSON 格式: {}", e))?; + .map_err(|e| format!("无效的 JSON 格式: {e}"))?; } AppType::Codex => { // TOML 格式暂不验证(或可使用 toml crate) diff --git a/src-tauri/src/gemini_mcp.rs b/src-tauri/src/gemini_mcp.rs index 6213d35..b2eb91a 100644 --- a/src-tauri/src/gemini_mcp.rs +++ b/src-tauri/src/gemini_mcp.rs @@ -48,8 +48,6 @@ pub fn read_mcp_json() -> Result, AppError> { Ok(Some(content)) } - - /// 读取 Gemini settings.json 中的 mcpServers 映射 pub fn read_mcp_servers_map() -> Result, AppError> { let path = user_config_path(); diff --git a/src-tauri/src/mcp.rs b/src-tauri/src/mcp.rs index 360bbec..e104054 100644 --- a/src-tauri/src/mcp.rs +++ b/src-tauri/src/mcp.rs @@ -396,11 +396,7 @@ pub fn import_from_claude(config: &mut MultiAppConfig) -> Result Result= 2, "should import both servers"); // v3.7.0: 检查统一结构 - let servers = config.mcp.servers.as_ref().expect("unified servers should exist"); + let servers = config + .mcp + .servers + .as_ref() + .expect("unified servers should exist"); let echo = servers.get("echo_server").expect("echo server"); - assert_eq!(echo.apps.codex, true, "Codex app should be enabled for echo_server"); + assert_eq!( + echo.apps.codex, true, + "Codex app should be enabled for echo_server" + ); let server_spec = echo.server.as_object().expect("server spec"); assert_eq!( server_spec @@ -502,7 +509,10 @@ url = "https://example.com" ); let http = servers.get("http_server").expect("http server"); - assert_eq!(http.apps.codex, true, "Codex app should be enabled for http_server"); + assert_eq!( + http.apps.codex, true, + "Codex app should be enabled for http_server" + ); let http_spec = http.server.as_object().expect("http spec"); assert_eq!( http_spec.get("url").and_then(|v| v.as_str()).unwrap_or(""), @@ -541,7 +551,7 @@ command = "echo" }), apps: cc_switch_lib::McpApps { claude: false, - codex: false, // 初始未启用 + codex: false, // 初始未启用 gemini: false, }, description: None, @@ -564,7 +574,10 @@ command = "echo" .expect("existing entry"); // 验证 Codex 应用已启用 - assert_eq!(entry.apps.codex, true, "Codex app should be enabled after import"); + assert_eq!( + entry.apps.codex, true, + "Codex app should be enabled after import" + ); // 验证现有配置被保留(server 不应被覆盖) let spec = entry.server.as_object().expect("server spec"); @@ -662,7 +675,7 @@ fn import_from_claude_merges_into_config() { "command": "prev" }), apps: cc_switch_lib::McpApps { - claude: false, // 初始未启用 + claude: false, // 初始未启用 codex: false, gemini: false, }, @@ -686,7 +699,10 @@ fn import_from_claude_merges_into_config() { .expect("entry exists"); // 验证 Claude 应用已启用 - assert_eq!(entry.apps.claude, true, "Claude app should be enabled after import"); + assert_eq!( + entry.apps.claude, true, + "Claude app should be enabled after import" + ); // 验证现有配置被保留(server 不应被覆盖) let server = entry.server.as_object().expect("server obj"); diff --git a/src-tauri/tests/mcp_commands.rs b/src-tauri/tests/mcp_commands.rs index 30160c7..ad342c4 100644 --- a/src-tauri/tests/mcp_commands.rs +++ b/src-tauri/tests/mcp_commands.rs @@ -127,8 +127,14 @@ fn import_mcp_from_claude_creates_config_and_enables_servers() { let guard = state.config.read().expect("lock config"); // v3.7.0: 检查统一结构 - let servers = guard.mcp.servers.as_ref().expect("unified servers should exist"); - let entry = servers.get("echo").expect("server imported into unified structure"); + let servers = guard + .mcp + .servers + .as_ref() + .expect("unified servers should exist"); + let entry = servers + .get("echo") + .expect("server imported into unified structure"); assert!( entry.apps.claude, "imported server should have Claude app enabled" @@ -182,10 +188,12 @@ fn set_mcp_enabled_for_codex_writes_live_config() { // 创建 Codex 配置目录和文件 let codex_dir = home.join(".codex"); fs::create_dir_all(&codex_dir).expect("create codex dir"); - fs::write(codex_dir.join("auth.json"), r#"{"OPENAI_API_KEY":"test-key"}"#) - .expect("create auth.json"); - fs::write(codex_dir.join("config.toml"), "") - .expect("create empty config.toml"); + fs::write( + codex_dir.join("auth.json"), + r#"{"OPENAI_API_KEY":"test-key"}"#, + ) + .expect("create auth.json"); + fs::write(codex_dir.join("config.toml"), "").expect("create empty config.toml"); let mut config = MultiAppConfig::default(); config.ensure_app(&AppType::Codex); @@ -203,7 +211,7 @@ fn set_mcp_enabled_for_codex_writes_live_config() { }), apps: McpApps { claude: false, - codex: false, // 初始未启用 + codex: false, // 初始未启用 gemini: false, }, description: None, diff --git a/src/components/mcp/McpFormModal.tsx b/src/components/mcp/McpFormModal.tsx index ba46279..50291f9 100644 --- a/src/components/mcp/McpFormModal.tsx +++ b/src/components/mcp/McpFormModal.tsx @@ -1,7 +1,14 @@ import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; -import { Save, Plus, AlertCircle, ChevronDown, ChevronUp, Wand2 } from "lucide-react"; +import { + Save, + Plus, + AlertCircle, + ChevronDown, + ChevronUp, + Wand2, +} from "lucide-react"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { diff --git a/src/components/mcp/McpWizardModal.tsx b/src/components/mcp/McpWizardModal.tsx index 9a663e4..f30e90f 100644 --- a/src/components/mcp/McpWizardModal.tsx +++ b/src/components/mcp/McpWizardModal.tsx @@ -80,7 +80,9 @@ const McpWizardModal: React.FC = ({ initialServer, }) => { const { t } = useTranslation(); - const [wizardType, setWizardType] = useState<"stdio" | "http" | "sse">("stdio"); + const [wizardType, setWizardType] = useState<"stdio" | "http" | "sse">( + "stdio", + ); const [wizardTitle, setWizardTitle] = useState(""); // stdio 字段 const [wizardCommand, setWizardCommand] = useState(""); diff --git a/src/components/mcp/useMcpValidation.ts b/src/components/mcp/useMcpValidation.ts index 53169dd..e65fcbf 100644 --- a/src/components/mcp/useMcpValidation.ts +++ b/src/components/mcp/useMcpValidation.ts @@ -76,10 +76,7 @@ export function useMcpValidation() { if (typ === "stdio" && !(obj as any)?.command?.trim()) { return t("mcp.error.commandRequired"); } - if ( - (typ === "http" || typ === "sse") && - !(obj as any)?.url?.trim() - ) { + if ((typ === "http" || typ === "sse") && !(obj as any)?.url?.trim()) { return t("mcp.wizard.urlRequired"); } } diff --git a/src/components/providers/AddProviderDialog.tsx b/src/components/providers/AddProviderDialog.tsx index 53aa0e2..1f2b3d6 100644 --- a/src/components/providers/AddProviderDialog.tsx +++ b/src/components/providers/AddProviderDialog.tsx @@ -45,6 +45,7 @@ export function AddProviderDialog({ // 构造基础提交数据 const providerData: Omit = { name: values.name.trim(), + notes: values.notes?.trim() || undefined, websiteUrl: values.websiteUrl?.trim() || undefined, settingsConfig: parsedConfig, ...(values.presetCategory ? { category: values.presetCategory } : {}), diff --git a/src/components/providers/EditProviderDialog.tsx b/src/components/providers/EditProviderDialog.tsx index 46a79d9..aa221f7 100644 --- a/src/components/providers/EditProviderDialog.tsx +++ b/src/components/providers/EditProviderDialog.tsx @@ -93,6 +93,7 @@ export function EditProviderDialog({ const updatedProvider: Provider = { ...provider, name: values.name.trim(), + notes: values.notes?.trim() || undefined, websiteUrl: values.websiteUrl?.trim() || undefined, settingsConfig: parsedConfig, ...(values.presetCategory ? { category: values.presetCategory } : {}), @@ -129,6 +130,7 @@ export function EditProviderDialog({ onCancel={() => onOpenChange(false)} initialData={{ name: provider.name, + notes: provider.notes, websiteUrl: provider.websiteUrl, // 若读取到实时配置则优先使用 settingsConfig: initialSettingsConfig, diff --git a/src/components/providers/forms/ProviderForm.tsx b/src/components/providers/forms/ProviderForm.tsx index e4d057d..8ed4e5e 100644 --- a/src/components/providers/forms/ProviderForm.tsx +++ b/src/components/providers/forms/ProviderForm.tsx @@ -623,7 +623,6 @@ export function ProviderForm({ presetCategoryLabels={presetCategoryLabels} onPresetChange={handlePresetChange} category={category} - appId={appId} /> )} diff --git a/src/components/providers/forms/ProviderPresetSelector.tsx b/src/components/providers/forms/ProviderPresetSelector.tsx index dfd4c68..1927c5e 100644 --- a/src/components/providers/forms/ProviderPresetSelector.tsx +++ b/src/components/providers/forms/ProviderPresetSelector.tsx @@ -6,7 +6,6 @@ import type { ProviderPreset } from "@/config/claudeProviderPresets"; import type { CodexProviderPreset } from "@/config/codexProviderPresets"; import type { GeminiProviderPreset } from "@/config/geminiProviderPresets"; import type { ProviderCategory } from "@/types"; -import type { AppId } from "@/lib/api"; type PresetEntry = { id: string; @@ -20,7 +19,6 @@ interface ProviderPresetSelectorProps { presetCategoryLabels: Record; onPresetChange: (value: string) => void; category?: ProviderCategory; // 当前选中的分类 - appId?: AppId; } export function ProviderPresetSelector({ @@ -30,7 +28,6 @@ export function ProviderPresetSelector({ presetCategoryLabels, onPresetChange, category, - appId, }: ProviderPresetSelectorProps) { const { t } = useTranslation(); diff --git a/tests/components/McpFormModal.test.tsx b/tests/components/McpFormModal.test.tsx index 4f75237..ce01b5d 100644 --- a/tests/components/McpFormModal.test.tsx +++ b/tests/components/McpFormModal.test.tsx @@ -220,7 +220,7 @@ describe("McpFormModal", () => { }); it("缺少配置命令时阻止提交并提示错误", async () => { - const { onSave } = renderForm(); + renderForm(); fireEvent.change(screen.getByPlaceholderText("mcp.form.titlePlaceholder"), { target: { value: "no-command" }, @@ -288,7 +288,7 @@ command = "run" }); it("TOML 模式下缺少命令时展示错误提示并阻止提交", async () => { - const { onSave } = renderForm({ defaultFormat: "toml" }); + renderForm({ defaultFormat: "toml" }); // 填写 ID 字段 fireEvent.change(screen.getByPlaceholderText("mcp.form.titlePlaceholder"), {