diff --git a/src-tauri/src/claude_plugin.rs b/src-tauri/src/claude_plugin.rs index 21483db..e6797ea 100644 --- a/src-tauri/src/claude_plugin.rs +++ b/src-tauri/src/claude_plugin.rs @@ -3,9 +3,12 @@ use std::path::PathBuf; const CLAUDE_DIR: &str = ".claude"; const CLAUDE_CONFIG_FILE: &str = "config.json"; -const CLAUDE_CONFIG_PAYLOAD: &str = "{\n \"primaryApiKey\": \"any\"\n}\n"; fn claude_dir() -> Result { + // 优先使用设置中的覆盖目录 + if let Some(dir) = crate::settings::get_claude_override_dir() { + return Ok(dir); + } let home = dirs::home_dir().ok_or_else(|| "无法获取用户主目录".to_string())?; Ok(home.join(CLAUDE_DIR)) } @@ -45,17 +48,37 @@ fn is_managed_config(content: &str) -> bool { } pub fn write_claude_config() -> Result { + // 增量写入:仅设置 primaryApiKey = "any",保留其它字段 let path = claude_config_path()?; ensure_claude_dir_exists()?; - let need_write = match read_claude_config()? { - Some(existing) => existing != CLAUDE_CONFIG_PAYLOAD, - None => true, + + // 尝试读取并解析为对象 + let mut obj = match read_claude_config()? { + Some(existing) => match serde_json::from_str::(&existing) { + Ok(serde_json::Value::Object(map)) => serde_json::Value::Object(map), + _ => serde_json::json!({}), + }, + None => serde_json::json!({}), }; - if need_write { - fs::write(&path, CLAUDE_CONFIG_PAYLOAD) - .map_err(|e| format!("写入 Claude 配置失败: {}", e))?; + + let mut changed = false; + if let Some(map) = obj.as_object_mut() { + let cur = map.get("primaryApiKey").and_then(|v| v.as_str()).unwrap_or(""); + if cur != "any" { + map.insert("primaryApiKey".to_string(), serde_json::Value::String("any".to_string())); + changed = true; + } + } + + if changed || !path.exists() { + let serialized = serde_json::to_string_pretty(&obj) + .map_err(|e| format!("序列化 Claude 配置失败: {}", e))?; + fs::write(&path, format!("{}\n", serialized)) + .map_err(|e| format!("写入 Claude 配置失败: {}", e))?; + Ok(true) + } else { + Ok(false) } - Ok(need_write) } pub fn clear_claude_config() -> Result { diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 7dec859..4d9491b 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -22,6 +22,9 @@ pub struct AppSettings { pub show_in_tray: bool, #[serde(default = "default_minimize_to_tray_on_close")] pub minimize_to_tray_on_close: bool, + /// 是否启用 Claude 插件联动 + #[serde(default)] + pub enable_claude_plugin_integration: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub claude_config_dir: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -49,6 +52,7 @@ impl Default for AppSettings { Self { show_in_tray: true, minimize_to_tray_on_close: true, + enable_claude_plugin_integration: false, claude_config_dir: None, codex_config_dir: None, language: None, diff --git a/src/App.tsx b/src/App.tsx index 8ded4db..7b96058 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -183,9 +183,14 @@ function App() { }); }; - // 同步 Claude 插件配置(写入/移除固定 JSON) + // 同步 Claude 插件配置(按设置决定是否联动;开启时:非官方写入,官方移除) const syncClaudePlugin = async (providerId: string, silent = false) => { try { + const settings = await window.api.getSettings(); + if (!(settings as any)?.enableClaudePluginIntegration) { + // 未开启联动:不执行写入/移除 + return; + } const provider = providers[providerId]; if (!provider) return; const isOfficial = provider.category === "official"; diff --git a/src/components/ProviderList.tsx b/src/components/ProviderList.tsx index 7529c2c..fadc2d7 100644 --- a/src/components/ProviderList.tsx +++ b/src/components/ProviderList.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { Provider } from "../types"; import { Play, Edit3, Trash2, CheckCircle2, Users, Check } from "lucide-react"; @@ -58,55 +58,7 @@ const ProviderList: React.FC = ({ } }; - const [claudeApplied, setClaudeApplied] = useState(false); - - // 检查 Claude 插件配置是否已应用 - useEffect(() => { - const checkClaude = async () => { - if (appType !== "claude" || !currentProviderId) { - setClaudeApplied(false); - return; - } - try { - const applied = await window.api.isClaudePluginApplied(); - setClaudeApplied(applied); - } catch (error) { - console.error(t("console.setupListenerFailed"), error); - setClaudeApplied(false); - } - }; - checkClaude(); - }, [appType, currentProviderId, providers, t]); - - const handleApplyToClaudePlugin = async () => { - try { - await window.api.applyClaudePluginConfig({ official: false }); - onNotify?.(t("notifications.appliedToClaudePlugin"), "success", 3000); - setClaudeApplied(true); - } catch (error: any) { - console.error(error); - const msg = - error && error.message - ? error.message - : t("notifications.syncClaudePluginFailed"); - onNotify?.(msg, "error", 5000); - } - }; - - const handleRemoveFromClaudePlugin = async () => { - try { - await window.api.applyClaudePluginConfig({ official: true }); - onNotify?.(t("notifications.removedFromClaudePlugin"), "success", 3000); - setClaudeApplied(false); - } catch (error: any) { - console.error(error); - const msg = - error && error.message - ? error.message - : t("notifications.syncClaudePluginFailed"); - onNotify?.(msg, "error", 5000); - } - }; + // 列表页不再提供 Claude 插件按钮,统一在“设置”中控制 // 对供应商列表进行排序 const sortedProviders = Object.values(providers).sort((a, b) => { @@ -201,34 +153,6 @@ const ProviderList: React.FC = ({
- {appType === "claude" ? ( -
- {provider.category !== "official" && isCurrent && ( - - )} -
- ) : null}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 35da935..b744281 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -98,6 +98,8 @@ "windowBehavior": "Window Behavior", "minimizeToTray": "Minimize to tray on close", "minimizeToTrayDescription": "When checked, clicking the close button will hide to system tray, otherwise the app will exit directly.", + "enableClaudePluginIntegration": "Apply to Claude Code extension", + "enableClaudePluginIntegrationDescription": "When enabled, you can use third-party providers in the VS Code Claude Code extension", "configFileLocation": "Configuration File Location", "openFolder": "Open Folder", "configDirectoryOverride": "Configuration Directory Override (Advanced)", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 66f442c..12f9e1a 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -98,6 +98,8 @@ "windowBehavior": "窗口行为", "minimizeToTray": "关闭时最小化到托盘", "minimizeToTrayDescription": "勾选后点击关闭按钮会隐藏到系统托盘,取消则直接退出应用。", + "enableClaudePluginIntegration": "应用到Claude Code 插件", + "enableClaudePluginIntegrationDescription": "开启后可以在 Vscode Claude Code 插件里使用第三方供应商", "configFileLocation": "配置文件位置", "openFolder": "打开文件夹", "configDirectoryOverride": "配置目录覆盖(高级)", diff --git a/src/types.ts b/src/types.ts index 247234e..2df4635 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,6 +41,8 @@ export interface Settings { showInTray: boolean; // 点击关闭按钮时是否最小化到托盘而不是关闭应用 minimizeToTrayOnClose: boolean; + // 启用 Claude 插件联动(写入 ~/.claude/config.json 的 primaryApiKey) + enableClaudePluginIntegration?: boolean; // 覆盖 Claude Code 配置目录(可选) claudeConfigDir?: string; // 覆盖 Codex 配置目录(可选)