From 63bb673bf259737464fdf0995932d85c9cfff76e Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 4 Jan 2026 09:43:46 +0800 Subject: [PATCH] refactor(common-config): extract snippet from editor content instead of active provider - Change extraction source from current active provider to editor's live content - Add i18n support for JSON parse error messages via invalid_json_format_error() - Simplify API by removing unused providerId parameter - Update button labels and error messages in zh/en/ja locales --- src-tauri/src/commands/config.rs | 41 +++++++++++++++---- src-tauri/src/services/provider/mod.rs | 12 ++++++ .../forms/CodexCommonConfigModal.tsx | 2 +- .../providers/forms/CommonConfigEditor.tsx | 2 +- .../forms/GeminiCommonConfigModal.tsx | 2 +- .../forms/hooks/useCodexCommonConfig.ts | 10 +++-- .../forms/hooks/useCommonConfigSnippet.ts | 8 ++-- .../forms/hooks/useGeminiCommonConfig.ts | 13 ++++-- src/i18n/locales/en.json | 12 +++--- src/i18n/locales/ja.json | 12 +++--- src/i18n/locales/zh.json | 12 +++--- src/lib/api/config.ts | 21 ++++++++-- 12 files changed, 105 insertions(+), 42 deletions(-) diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 7f4276d4f..c4fa8682c 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -7,6 +7,7 @@ use tauri_plugin_opener::OpenerExt; use crate::app_config::AppType; use crate::codex_config; use crate::config::{self, get_claude_settings_path, ConfigStatus}; +use crate::settings; /// 获取 Claude Code 配置状态 #[tauri::command] @@ -16,6 +17,18 @@ pub async fn get_claude_config_status() -> Result { use std::str::FromStr; +fn invalid_json_format_error(error: serde_json::Error) -> String { + let lang = settings::get_settings() + .language + .unwrap_or_else(|| "zh".to_string()); + + match lang.as_str() { + "en" => format!("Invalid JSON format: {error}"), + "ja" => format!("JSON形式が無効です: {error}"), + _ => format!("无效的 JSON 格式: {error}"), + } +} + #[tauri::command] pub async fn get_config_status(app: String) -> Result { match AppType::from_str(&app).map_err(|e| e.to_string())? { @@ -155,8 +168,7 @@ pub async fn set_claude_common_config_snippet( ) -> Result<(), String> { // 验证是否为有效的 JSON(如果不为空) if !snippet.trim().is_empty() { - serde_json::from_str::(&snippet) - .map_err(|e| format!("无效的 JSON 格式: {e}"))?; + serde_json::from_str::(&snippet).map_err(invalid_json_format_error)?; } let value = if snippet.trim().is_empty() { @@ -197,7 +209,7 @@ pub async fn set_common_config_snippet( "claude" | "gemini" => { // 验证 JSON 格式 serde_json::from_str::(&snippet) - .map_err(|e| format!("无效的 JSON 格式: {e}"))?; + .map_err(invalid_json_format_error)?; } "codex" => { // TOML 格式暂不验证(或可使用 toml crate) @@ -220,16 +232,29 @@ pub async fn set_common_config_snippet( Ok(()) } -/// 从当前供应商提取通用配置片段 +/// 提取通用配置片段 /// -/// 读取当前激活供应商的配置,自动排除差异化字段(API Key、模型配置、端点等), -/// 返回可复用的通用配置片段。 +/// 优先从 `settingsConfig`(编辑器当前内容)提取;若未提供,则从当前激活供应商提取。 +/// +/// 提取时会自动排除差异化字段(API Key、模型配置、端点等),返回可复用的通用配置片段。 #[tauri::command] pub async fn extract_common_config_snippet( - app_type: String, + appType: String, + settingsConfig: Option, state: tauri::State<'_, crate::store::AppState>, ) -> Result { - let app = AppType::from_str(&app_type).map_err(|e| e.to_string())?; + let app = AppType::from_str(&appType).map_err(|e| e.to_string())?; + + if let Some(settings_config) = settingsConfig.filter(|s| !s.trim().is_empty()) { + let settings: serde_json::Value = + serde_json::from_str(&settings_config).map_err(invalid_json_format_error)?; + + return crate::services::provider::ProviderService::extract_common_config_snippet_from_settings( + app, + &settings, + ) + .map_err(|e| e.to_string()); + } crate::services::provider::ProviderService::extract_common_config_snippet(&state, app) .map_err(|e| e.to_string()) diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 4ba1369b7..110d8c01a 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -375,6 +375,18 @@ impl ProviderService { } } + /// Extract common config snippet from a config value (e.g. editor content). + pub fn extract_common_config_snippet_from_settings( + app_type: AppType, + settings_config: &Value, + ) -> Result { + match app_type { + AppType::Claude => Self::extract_claude_common_config(settings_config), + AppType::Codex => Self::extract_codex_common_config(settings_config), + AppType::Gemini => Self::extract_gemini_common_config(settings_config), + } + } + /// Extract common config for Claude (JSON format) fn extract_claude_common_config(settings: &Value) -> Result { let mut config = settings.clone(); diff --git a/src/components/providers/forms/CodexCommonConfigModal.tsx b/src/components/providers/forms/CodexCommonConfigModal.tsx index 7484b90a6..e8ddb8664 100644 --- a/src/components/providers/forms/CodexCommonConfigModal.tsx +++ b/src/components/providers/forms/CodexCommonConfigModal.tsx @@ -67,7 +67,7 @@ export const CodexCommonConfigModal: React.FC = ({ )} {t("codexConfig.extractFromCurrent", { - defaultValue: "从当前供应商提取", + defaultValue: "从编辑内容提取", })} )} diff --git a/src/components/providers/forms/CommonConfigEditor.tsx b/src/components/providers/forms/CommonConfigEditor.tsx index 99ceb81f1..04d175569 100644 --- a/src/components/providers/forms/CommonConfigEditor.tsx +++ b/src/components/providers/forms/CommonConfigEditor.tsx @@ -129,7 +129,7 @@ export function CommonConfigEditor({ )} {t("claudeConfig.extractFromCurrent", { - defaultValue: "从当前供应商提取", + defaultValue: "从编辑内容提取", })} )} diff --git a/src/components/providers/forms/GeminiCommonConfigModal.tsx b/src/components/providers/forms/GeminiCommonConfigModal.tsx index 969a8cbe2..c7413217c 100644 --- a/src/components/providers/forms/GeminiCommonConfigModal.tsx +++ b/src/components/providers/forms/GeminiCommonConfigModal.tsx @@ -63,7 +63,7 @@ export const GeminiCommonConfigModal: React.FC< )} {t("geminiConfig.extractFromCurrent", { - defaultValue: "从当前供应商提取", + defaultValue: "从编辑内容提取", })} )} diff --git a/src/components/providers/forms/hooks/useCodexCommonConfig.ts b/src/components/providers/forms/hooks/useCodexCommonConfig.ts index 183505915..0c0e1811a 100644 --- a/src/components/providers/forms/hooks/useCodexCommonConfig.ts +++ b/src/components/providers/forms/hooks/useCodexCommonConfig.ts @@ -246,13 +246,17 @@ export function useCodexCommonConfig({ setUseCommonConfig(hasCommon); }, [codexConfig, commonConfigSnippet, isLoading]); - // 从当前供应商提取通用配置片段 + // 从编辑器当前内容提取通用配置片段 const handleExtract = useCallback(async () => { setIsExtracting(true); setCommonConfigError(""); try { - const extracted = await configApi.extractCommonConfigSnippet("codex"); + const extracted = await configApi.extractCommonConfigSnippet("codex", { + settingsConfig: JSON.stringify({ + config: codexConfig ?? "", + }), + }); if (!extracted || !extracted.trim()) { setCommonConfigError(t("codexConfig.extractNoCommonConfig")); @@ -270,7 +274,7 @@ export function useCodexCommonConfig({ } finally { setIsExtracting(false); } - }, []); + }, [codexConfig, t]); return { useCommonConfig, diff --git a/src/components/providers/forms/hooks/useCommonConfigSnippet.ts b/src/components/providers/forms/hooks/useCommonConfigSnippet.ts index 007571431..ddc8e4e17 100644 --- a/src/components/providers/forms/hooks/useCommonConfigSnippet.ts +++ b/src/components/providers/forms/hooks/useCommonConfigSnippet.ts @@ -252,13 +252,15 @@ export function useCommonConfigSnippet({ setUseCommonConfig(hasCommon); }, [settingsConfig, commonConfigSnippet, isLoading]); - // 从当前供应商提取通用配置片段 + // 从编辑器当前内容提取通用配置片段 const handleExtract = useCallback(async () => { setIsExtracting(true); setCommonConfigError(""); try { - const extracted = await configApi.extractCommonConfigSnippet("claude"); + const extracted = await configApi.extractCommonConfigSnippet("claude", { + settingsConfig, + }); if (!extracted || extracted === "{}") { setCommonConfigError(t("claudeConfig.extractNoCommonConfig")); @@ -283,7 +285,7 @@ export function useCommonConfigSnippet({ } finally { setIsExtracting(false); } - }, []); + }, [settingsConfig, t]); return { useCommonConfig, diff --git a/src/components/providers/forms/hooks/useGeminiCommonConfig.ts b/src/components/providers/forms/hooks/useGeminiCommonConfig.ts index 55f24ca28..5e5011371 100644 --- a/src/components/providers/forms/hooks/useGeminiCommonConfig.ts +++ b/src/components/providers/forms/hooks/useGeminiCommonConfig.ts @@ -402,13 +402,20 @@ export function useGeminiCommonConfig({ parseSnippetEnv, ]); - // 从当前供应商提取通用配置片段 + // 从编辑器当前内容提取通用配置片段 const handleExtract = useCallback(async () => { setIsExtracting(true); setCommonConfigError(""); try { - const extracted = await configApi.extractCommonConfigSnippet("gemini"); + const extracted = await configApi.extractCommonConfigSnippet( + "gemini", + { + settingsConfig: JSON.stringify({ + env: envStringToObj(envValue), + }), + }, + ); if (!extracted || extracted === "{}") { setCommonConfigError(t("geminiConfig.extractNoCommonConfig")); @@ -433,7 +440,7 @@ export function useGeminiCommonConfig({ } finally { setIsExtracting(false); } - }, [parseSnippetEnv, t]); + }, [envStringToObj, envValue, parseSnippetEnv, t]); return { useCommonConfig, diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index f34a21f64..95731f88e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -54,8 +54,8 @@ "editCommonConfigTitle": "Edit Common Config Snippet", "commonConfigHint": "This snippet will be merged into settings.json when 'Write Common Config' is checked", "fullSettingsHint": "Full Claude Code settings.json content", - "extractFromCurrent": "Extract from Current Provider", - "extractNoCommonConfig": "No common config available to extract from current provider", + "extractFromCurrent": "Extract from Editor", + "extractNoCommonConfig": "No common config available to extract from editor", "extractFailed": "Extract failed: {{error}}", "saveFailed": "Save failed: {{error}}" }, @@ -398,8 +398,8 @@ "editCommonConfigTitle": "Edit Codex Common Config Snippet", "commonConfigHint": "This snippet will be appended to the end of config.toml when 'Write Common Config' is checked", "apiUrlLabel": "API Request URL", - "extractFromCurrent": "Extract from Current Provider", - "extractNoCommonConfig": "No common config available to extract from current provider", + "extractFromCurrent": "Extract from Editor", + "extractNoCommonConfig": "No common config available to extract from editor", "extractFailed": "Extract failed: {{error}}", "saveFailed": "Save failed: {{error}}" }, @@ -412,8 +412,8 @@ "editCommonConfig": "Edit Common Config", "editCommonConfigTitle": "Edit Gemini Common Config Snippet", "commonConfigHint": "This snippet writes to Gemini .env (GOOGLE_GEMINI_BASE_URL and GEMINI_API_KEY are not allowed)", - "extractFromCurrent": "Extract from Current Provider", - "extractNoCommonConfig": "No common config available to extract from current provider", + "extractFromCurrent": "Extract from Editor", + "extractNoCommonConfig": "No common config available to extract from editor", "extractFailed": "Extract failed: {{error}}", "saveFailed": "Save failed: {{error}}", "extractedConfigInvalid": "Extracted config format is invalid", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index d79ce91f7..1370ea960 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -54,8 +54,8 @@ "editCommonConfigTitle": "共通設定スニペットを編集", "commonConfigHint": "「共通設定を書き込む」がオンのとき settings.json にマージされます", "fullSettingsHint": "Claude Code の settings.json 全文", - "extractFromCurrent": "現在のプロバイダーから抽出", - "extractNoCommonConfig": "現在のプロバイダーから抽出できる共通設定がありません", + "extractFromCurrent": "編集内容から抽出", + "extractNoCommonConfig": "編集内容から抽出できる共通設定がありません", "extractFailed": "抽出に失敗しました: {{error}}", "saveFailed": "保存に失敗しました: {{error}}" }, @@ -398,8 +398,8 @@ "editCommonConfigTitle": "Codex 共通設定スニペットを編集", "commonConfigHint": "「共通設定を書き込む」がオンの場合、config.toml の末尾に追記されます", "apiUrlLabel": "API リクエスト URL", - "extractFromCurrent": "現在のプロバイダーから抽出", - "extractNoCommonConfig": "現在のプロバイダーから抽出できる共通設定がありません", + "extractFromCurrent": "編集内容から抽出", + "extractNoCommonConfig": "編集内容から抽出できる共通設定がありません", "extractFailed": "抽出に失敗しました: {{error}}", "saveFailed": "保存に失敗しました: {{error}}" }, @@ -412,8 +412,8 @@ "editCommonConfig": "共通設定を編集", "editCommonConfigTitle": "Gemini 共通設定スニペットを編集", "commonConfigHint": "このスニペットは Gemini の .env に書き込みます(GOOGLE_GEMINI_BASE_URL、GEMINI_API_KEY は使用できません)", - "extractFromCurrent": "現在のプロバイダーから抽出", - "extractNoCommonConfig": "現在のプロバイダーから抽出できる共通設定がありません", + "extractFromCurrent": "編集内容から抽出", + "extractNoCommonConfig": "編集内容から抽出できる共通設定がありません", "extractFailed": "抽出に失敗しました: {{error}}", "saveFailed": "保存に失敗しました: {{error}}", "extractedConfigInvalid": "抽出した設定のフォーマットが不正です", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index c6277870e..051083063 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -54,8 +54,8 @@ "editCommonConfigTitle": "编辑通用配置片段", "commonConfigHint": "该片段会在勾选\"写入通用配置\"时合并到 settings.json 中", "fullSettingsHint": "完整的 Claude Code settings.json 配置内容", - "extractFromCurrent": "从当前供应商提取", - "extractNoCommonConfig": "当前供应商没有可提取的通用配置", + "extractFromCurrent": "从编辑内容提取", + "extractNoCommonConfig": "当前编辑内容没有可提取的通用配置", "extractFailed": "提取失败: {{error}}", "saveFailed": "保存失败: {{error}}" }, @@ -398,8 +398,8 @@ "editCommonConfigTitle": "编辑 Codex 通用配置片段", "commonConfigHint": "该片段会在勾选'写入通用配置'时追加到 config.toml 末尾", "apiUrlLabel": "API 请求地址", - "extractFromCurrent": "从当前供应商提取", - "extractNoCommonConfig": "当前供应商没有可提取的通用配置", + "extractFromCurrent": "从编辑内容提取", + "extractNoCommonConfig": "当前编辑内容没有可提取的通用配置", "extractFailed": "提取失败: {{error}}", "saveFailed": "保存失败: {{error}}" }, @@ -412,8 +412,8 @@ "editCommonConfig": "编辑通用配置", "editCommonConfigTitle": "编辑 Gemini 通用配置片段", "commonConfigHint": "该片段会写入 Gemini 的 .env(不允许包含 GOOGLE_GEMINI_BASE_URL、GEMINI_API_KEY)", - "extractFromCurrent": "从当前供应商提取", - "extractNoCommonConfig": "当前供应商没有可提取的通用配置", + "extractFromCurrent": "从编辑内容提取", + "extractNoCommonConfig": "当前编辑内容没有可提取的通用配置", "extractFailed": "提取失败: {{error}}", "saveFailed": "保存失败: {{error}}", "extractedConfigInvalid": "提取的配置格式错误", diff --git a/src/lib/api/config.ts b/src/lib/api/config.ts index ed252d1b2..89a547bb7 100644 --- a/src/lib/api/config.ts +++ b/src/lib/api/config.ts @@ -49,16 +49,29 @@ export async function setCommonConfigSnippet( } /** - * 从当前供应商提取通用配置片段 + * 提取通用配置片段 * - * 读取当前激活供应商的配置,自动排除差异化字段(API Key、模型配置、端点等), - * 返回可复用的通用配置片段。 + * 默认读取当前激活供应商的配置;若传入 `options.settingsConfig`,则从编辑器当前内容提取。 + * 会自动排除差异化字段(API Key、模型配置、端点等),返回可复用的通用配置片段。 * * @param appType - 应用类型(claude/codex/gemini) + * @param options - 可选:提取来源 * @returns 提取的通用配置片段(JSON/TOML 字符串) */ +export type ExtractCommonConfigSnippetOptions = { + settingsConfig?: string; +}; + export async function extractCommonConfigSnippet( appType: AppType, + options?: ExtractCommonConfigSnippetOptions, ): Promise { - return invoke("extract_common_config_snippet", { appType }); + const args: Record = { appType }; + const settingsConfig = options?.settingsConfig; + + if (typeof settingsConfig === "string" && settingsConfig.trim()) { + args.settingsConfig = settingsConfig; + } + + return invoke("extract_common_config_snippet", args); }