From 1f3627add3a8b2bc0fc3abf8e91cb8071085df63 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 20 Nov 2025 20:23:22 +0800 Subject: [PATCH] fix(gemini): persist settings json edits --- src-tauri/src/gemini_config.rs | 14 ++++ src-tauri/src/services/config.rs | 46 ++++-------- src-tauri/src/services/provider.rs | 117 +++++++++++++++++++++++++---- 3 files changed, 128 insertions(+), 49 deletions(-) diff --git a/src-tauri/src/gemini_config.rs b/src-tauri/src/gemini_config.rs index 3a7926c..8e453ba 100644 --- a/src-tauri/src/gemini_config.rs +++ b/src-tauri/src/gemini_config.rs @@ -236,6 +236,17 @@ pub fn validate_gemini_settings(settings: &Value) -> Result<(), AppError> { } } + // 如果有 config 字段,验证它是对象或 null + if let Some(config) = settings.get("config") { + if !(config.is_object() || config.is_null()) { + return Err(AppError::localized( + "gemini.validation.invalid_config", + "Gemini 配置格式错误: config 必须是对象", + "Gemini config invalid: config must be an object", + )); + } + } + Ok(()) } @@ -244,6 +255,9 @@ pub fn validate_gemini_settings(settings: &Value) -> Result<(), AppError> { /// 此函数在切换供应商时使用,确保配置包含所有必需的字段。 /// 对于需要 API Key 的供应商(如 PackyCode),会验证 GEMINI_API_KEY 字段。 pub fn validate_gemini_settings_strict(settings: &Value) -> Result<(), AppError> { + // 先做基础格式验证(包含 env/config 类型) + validate_gemini_settings(settings)?; + let env_map = json_to_env(settings)?; // 如果 env 为空,表示使用 OAuth(如 Google 官方),跳过验证 diff --git a/src-tauri/src/services/config.rs b/src-tauri/src/services/config.rs index 6f8f91a..0c35eca 100644 --- a/src-tauri/src/services/config.rs +++ b/src-tauri/src/services/config.rs @@ -229,42 +229,22 @@ impl ConfigService { provider_id: &str, provider: &Provider, ) -> Result<(), AppError> { - use crate::gemini_config::{ - env_to_json, json_to_env, read_gemini_env, write_gemini_env_atomic, - }; + use crate::gemini_config::{env_to_json, read_gemini_env}; - let env_path = crate::gemini_config::get_gemini_env_path(); - if let Some(parent) = env_path.parent() { - fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?; - } + ProviderService::write_gemini_live(provider)?; - // 转换 JSON 配置为 .env 格式 - let env_map = json_to_env(&provider.settings_config)?; - - // Google 官方(OAuth): env 为空,写入空文件并设置安全标志后返回 - if env_map.is_empty() { - write_gemini_env_atomic(&env_map)?; - ProviderService::ensure_google_oauth_security_flag(provider)?; - - let live_after_env = read_gemini_env()?; - let live_after = env_to_json(&live_after_env); - - if let Some(manager) = config.get_manager_mut(&AppType::Gemini) { - if let Some(target) = manager.providers.get_mut(provider_id) { - target.settings_config = live_after; - } - } - - return Ok(()); - } - - // 非 OAuth:按常规写入,并在必要时设置 Packycode 安全标志 - write_gemini_env_atomic(&env_map)?; - ProviderService::ensure_packycode_security_flag(provider)?; - - // 读回实际写入的内容并更新到配置中 + // 读回实际写入的内容并更新到配置中(包含 settings.json) let live_after_env = read_gemini_env()?; - let live_after = env_to_json(&live_after_env); + let settings_path = crate::gemini_config::get_gemini_settings_path(); + let live_after_config = if settings_path.exists() { + crate::config::read_json_file(&settings_path)? + } else { + serde_json::json!({}) + }; + let mut live_after = env_to_json(&live_after_env); + if let Some(obj) = live_after.as_object_mut() { + obj.insert("config".to_string(), live_after_config); + } if let Some(manager) = config.get_manager_mut(&AppType::Gemini) { if let Some(target) = manager.providers.get_mut(provider_id) { diff --git a/src-tauri/src/services/provider.rs b/src-tauri/src/services/provider.rs index 06738e9..2bc7f29 100644 --- a/src-tauri/src/services/provider.rs +++ b/src-tauri/src/services/provider.rs @@ -30,6 +30,7 @@ enum LiveSnapshot { }, Gemini { env: Option>, // 新增 + config: Option, // 新增:settings.json 内容 }, } @@ -68,15 +69,30 @@ impl LiveSnapshot { delete_file(&config_path)?; } } - LiveSnapshot::Gemini { env } => { + LiveSnapshot::Gemini { env, .. } => { // 新增 - use crate::gemini_config::{get_gemini_env_path, write_gemini_env_atomic}; + use crate::gemini_config::{ + get_gemini_env_path, get_gemini_settings_path, write_gemini_env_atomic, + }; let path = get_gemini_env_path(); if let Some(env_map) = env { write_gemini_env_atomic(env_map)?; } else if path.exists() { delete_file(&path)?; } + + let settings_path = get_gemini_settings_path(); + match self { + LiveSnapshot::Gemini { + config: Some(cfg), .. + } => { + write_json_file(&settings_path, cfg)?; + } + LiveSnapshot::Gemini { config: None, .. } if settings_path.exists() => { + delete_file(&settings_path)?; + } + _ => {} + } } } Ok(()) @@ -612,7 +628,9 @@ impl ProviderService { state.save()?; } AppType::Gemini => { - use crate::gemini_config::{env_to_json, get_gemini_env_path, read_gemini_env}; + use crate::gemini_config::{ + env_to_json, get_gemini_env_path, get_gemini_settings_path, read_gemini_env, + }; let env_path = get_gemini_env_path(); if !env_path.exists() { @@ -623,7 +641,18 @@ impl ProviderService { )); } let env_map = read_gemini_env()?; - let live_after = env_to_json(&env_map); + let mut live_after = env_to_json(&env_map); + + let settings_path = get_gemini_settings_path(); + let config_value = if settings_path.exists() { + read_json_file(&settings_path)? + } else { + json!({}) + }; + + if let Some(obj) = live_after.as_object_mut() { + obj.insert("config".to_string(), config_value); + } { let mut guard = state.config.write().map_err(AppError::from)?; @@ -670,14 +699,22 @@ impl ProviderService { } AppType::Gemini => { // 新增 - use crate::gemini_config::{get_gemini_env_path, read_gemini_env}; + use crate::gemini_config::{ + get_gemini_env_path, get_gemini_settings_path, read_gemini_env, + }; let path = get_gemini_env_path(); let env = if path.exists() { Some(read_gemini_env()?) } else { None }; - Ok(LiveSnapshot::Gemini { env }) + let settings_path = get_gemini_settings_path(); + let config = if settings_path.exists() { + Some(read_json_file(&settings_path)?) + } else { + None + }; + Ok(LiveSnapshot::Gemini { env, config }) } } } @@ -1461,7 +1498,9 @@ impl ProviderService { config: &mut MultiAppConfig, next_provider: &str, ) -> Result<(), AppError> { - use crate::gemini_config::{env_to_json, get_gemini_env_path, read_gemini_env}; + use crate::gemini_config::{ + env_to_json, get_gemini_env_path, get_gemini_settings_path, read_gemini_env, + }; let env_path = get_gemini_env_path(); if !env_path.exists() { @@ -1477,7 +1516,18 @@ impl ProviderService { } let env_map = read_gemini_env()?; - let live = env_to_json(&env_map); + let mut live = env_to_json(&env_map); + + let settings_path = get_gemini_settings_path(); + let config_value = if settings_path.exists() { + read_json_file(&settings_path)? + } else { + json!({}) + }; + if let Some(obj) = live.as_object_mut() { + obj.insert("config".to_string(), config_value); + } + if let Some(manager) = config.get_manager_mut(&AppType::Gemini) { if let Some(current) = manager.providers.get_mut(¤t_id) { current.settings_config = live; @@ -1495,36 +1545,71 @@ impl ProviderService { Ok(()) } - fn write_gemini_live(provider: &Provider) -> Result<(), AppError> { + pub(crate) fn write_gemini_live(provider: &Provider) -> Result<(), AppError> { use crate::gemini_config::{ - json_to_env, validate_gemini_settings_strict, write_gemini_env_atomic, + get_gemini_settings_path, json_to_env, validate_gemini_settings_strict, + write_gemini_env_atomic, }; // 一次性检测认证类型,避免重复检测 let auth_type = Self::detect_gemini_auth_type(provider); + let mut env_map = json_to_env(&provider.settings_config)?; + + // 准备要写入 ~/.gemini/settings.json 的配置(缺省时保留现有文件内容) + let mut config_to_write = if let Some(config_value) = provider.settings_config.get("config") + { + if config_value.is_null() { + Some(json!({})) + } else if config_value.is_object() { + Some(config_value.clone()) + } else { + return Err(AppError::localized( + "gemini.validation.invalid_config", + "Gemini 配置格式错误: config 必须是对象或 null", + "Gemini config invalid: config must be an object or null", + )); + } + } else { + None + }; + + if config_to_write.is_none() { + let settings_path = get_gemini_settings_path(); + if settings_path.exists() { + config_to_write = Some(read_json_file(&settings_path)?); + } + } + match auth_type { GeminiAuthType::GoogleOfficial => { // Google 官方使用 OAuth,清空 env - let empty_env = std::collections::HashMap::new(); - write_gemini_env_atomic(&empty_env)?; - Self::ensure_google_oauth_security_flag(provider)?; + env_map.clear(); + write_gemini_env_atomic(&env_map)?; } GeminiAuthType::Packycode => { // PackyCode 供应商,使用 API Key(切换时严格验证) validate_gemini_settings_strict(&provider.settings_config)?; - let env_map = json_to_env(&provider.settings_config)?; write_gemini_env_atomic(&env_map)?; - Self::ensure_packycode_security_flag(provider)?; } GeminiAuthType::Generic => { // 通用供应商,使用 API Key(切换时严格验证) validate_gemini_settings_strict(&provider.settings_config)?; - let env_map = json_to_env(&provider.settings_config)?; write_gemini_env_atomic(&env_map)?; } } + if let Some(config_value) = config_to_write { + let settings_path = get_gemini_settings_path(); + write_json_file(&settings_path, &config_value)?; + } + + match auth_type { + GeminiAuthType::GoogleOfficial => Self::ensure_google_oauth_security_flag(provider)?, + GeminiAuthType::Packycode => Self::ensure_packycode_security_flag(provider)?, + GeminiAuthType::Generic => {} + } + Ok(()) }