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
This commit is contained in:
Jason
2026-01-04 09:43:46 +08:00
parent 058f86aff3
commit 63bb673bf2
12 changed files with 105 additions and 42 deletions
+33 -8
View File
@@ -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<ConfigStatus, String> {
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<ConfigStatus, String> {
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::<serde_json::Value>(&snippet)
.map_err(|e| format!("无效的 JSON 格式: {e}"))?;
serde_json::from_str::<serde_json::Value>(&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::<serde_json::Value>(&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<String>,
state: tauri::State<'_, crate::store::AppState>,
) -> Result<String, String> {
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())
+12
View File
@@ -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<String, AppError> {
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<String, AppError> {
let mut config = settings.clone();
@@ -67,7 +67,7 @@ export const CodexCommonConfigModal: React.FC<CodexCommonConfigModalProps> = ({
<Download className="w-4 h-4" />
)}
{t("codexConfig.extractFromCurrent", {
defaultValue: "从当前供应商提取",
defaultValue: "从编辑内容提取",
})}
</Button>
)}
@@ -129,7 +129,7 @@ export function CommonConfigEditor({
<Download className="w-4 h-4" />
)}
{t("claudeConfig.extractFromCurrent", {
defaultValue: "从当前供应商提取",
defaultValue: "从编辑内容提取",
})}
</Button>
)}
@@ -63,7 +63,7 @@ export const GeminiCommonConfigModal: React.FC<
<Download className="w-4 h-4" />
)}
{t("geminiConfig.extractFromCurrent", {
defaultValue: "从当前供应商提取",
defaultValue: "从编辑内容提取",
})}
</Button>
)}
@@ -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,
@@ -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,
@@ -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,
+6 -6
View File
@@ -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",
+6 -6
View File
@@ -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": "抽出した設定のフォーマットが不正です",
+6 -6
View File
@@ -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": "提取的配置格式错误",
+17 -4
View File
@@ -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<string> {
return invoke<string>("extract_common_config_snippet", { appType });
const args: Record<string, unknown> = { appType };
const settingsConfig = options?.settingsConfig;
if (typeof settingsConfig === "string" && settingsConfig.trim()) {
args.settingsConfig = settingsConfig;
}
return invoke<string>("extract_common_config_snippet", args);
}