* feat(gemini): add Gemini provider integration - Add gemini_config.rs module for .env file parsing - Extend AppType enum to support Gemini - Implement GeminiConfigEditor and GeminiFormFields components - Add GeminiIcon with standardized 1024x1024 viewBox - Add Gemini provider presets configuration - Update i18n translations for Gemini support - Extend ProviderService and McpService for Gemini * fix(gemini): resolve TypeScript errors, add i18n support, and fix MCP logic **Critical Fixes:** - Fix TS2741 errors in tests/msw/state.ts by adding missing Gemini type definitions - Fix ProviderCard.extractApiUrl to support GOOGLE_GEMINI_BASE_URL display - Add missing apps.gemini i18n keys (zh/en) for proper app name display - Fix MCP service Gemini cross-app duplication logic to prevent self-copy **Technical Details:** - tests/msw/state.ts: Add gemini default providers, current ID, and MCP config - ProviderCard.tsx: Check both ANTHROPIC_BASE_URL and GOOGLE_GEMINI_BASE_URL - services/mcp.rs: Skip Gemini in sync_other_side logic with unreachable!() guards - Run pnpm format to auto-fix code style issues **Verification:** - ✅ pnpm typecheck passes - ✅ pnpm format completed * feat(gemini): enhance authentication and config parsing - Add strict and lenient .env parsing modes - Implement PackyCode partner authentication detection - Support Google OAuth official authentication - Auto-configure security.auth.selectedType for PackyCode - Add comprehensive test coverage for all auth types - Update i18n for OAuth hints and Gemini config --------- Co-authored-by: Jason <farion1231@gmail.com>
138 lines
4.2 KiB
Rust
138 lines
4.2 KiB
Rust
use serde_json::Value;
|
||
use std::path::PathBuf;
|
||
use std::sync::{OnceLock, RwLock};
|
||
use tauri_plugin_store::StoreExt;
|
||
|
||
use crate::error::AppError;
|
||
|
||
/// Store 中的键名
|
||
const STORE_KEY_APP_CONFIG_DIR: &str = "app_config_dir_override";
|
||
|
||
/// 缓存当前的 app_config_dir 覆盖路径,避免存储 AppHandle
|
||
static APP_CONFIG_DIR_OVERRIDE: OnceLock<RwLock<Option<PathBuf>>> = OnceLock::new();
|
||
|
||
fn override_cache() -> &'static RwLock<Option<PathBuf>> {
|
||
APP_CONFIG_DIR_OVERRIDE.get_or_init(|| RwLock::new(None))
|
||
}
|
||
|
||
fn update_cached_override(value: Option<PathBuf>) {
|
||
if let Ok(mut guard) = override_cache().write() {
|
||
*guard = value;
|
||
}
|
||
}
|
||
|
||
/// 获取缓存中的 app_config_dir 覆盖路径
|
||
pub fn get_app_config_dir_override() -> Option<PathBuf> {
|
||
override_cache().read().ok()?.clone()
|
||
}
|
||
|
||
fn read_override_from_store(app: &tauri::AppHandle) -> Option<PathBuf> {
|
||
let store = match app.store_builder("app_paths.json").build() {
|
||
Ok(store) => store,
|
||
Err(e) => {
|
||
log::warn!("无法创建 Store: {e}");
|
||
return None;
|
||
}
|
||
};
|
||
|
||
match store.get(STORE_KEY_APP_CONFIG_DIR) {
|
||
Some(Value::String(path_str)) => {
|
||
let path_str = path_str.trim();
|
||
if path_str.is_empty() {
|
||
return None;
|
||
}
|
||
|
||
let path = resolve_path(path_str);
|
||
|
||
if !path.exists() {
|
||
log::warn!(
|
||
"Store 中配置的 app_config_dir 不存在: {path:?}\n\
|
||
将使用默认路径。"
|
||
);
|
||
return None;
|
||
}
|
||
|
||
log::info!("使用 Store 中的 app_config_dir: {path:?}");
|
||
Some(path)
|
||
}
|
||
Some(_) => {
|
||
log::warn!(
|
||
"Store 中的 {STORE_KEY_APP_CONFIG_DIR} 类型不正确,应为字符串"
|
||
);
|
||
None
|
||
}
|
||
None => None,
|
||
}
|
||
}
|
||
|
||
/// 从 Store 刷新 app_config_dir 覆盖值并更新缓存
|
||
pub fn refresh_app_config_dir_override(app: &tauri::AppHandle) -> Option<PathBuf> {
|
||
let value = read_override_from_store(app);
|
||
update_cached_override(value.clone());
|
||
value
|
||
}
|
||
|
||
/// 写入 app_config_dir 到 Tauri Store
|
||
pub fn set_app_config_dir_to_store(
|
||
app: &tauri::AppHandle,
|
||
path: Option<&str>,
|
||
) -> Result<(), AppError> {
|
||
let store = app
|
||
.store_builder("app_paths.json")
|
||
.build()
|
||
.map_err(|e| AppError::Message(format!("创建 Store 失败: {e}")))?;
|
||
|
||
match path {
|
||
Some(p) => {
|
||
let trimmed = p.trim();
|
||
if !trimmed.is_empty() {
|
||
store.set(STORE_KEY_APP_CONFIG_DIR, Value::String(trimmed.to_string()));
|
||
log::info!("已将 app_config_dir 写入 Store: {trimmed}");
|
||
} else {
|
||
store.delete(STORE_KEY_APP_CONFIG_DIR);
|
||
log::info!("已从 Store 中删除 app_config_dir 配置");
|
||
}
|
||
}
|
||
None => {
|
||
store.delete(STORE_KEY_APP_CONFIG_DIR);
|
||
log::info!("已从 Store 中删除 app_config_dir 配置");
|
||
}
|
||
}
|
||
|
||
store
|
||
.save()
|
||
.map_err(|e| AppError::Message(format!("保存 Store 失败: {e}")))?;
|
||
|
||
refresh_app_config_dir_override(app);
|
||
Ok(())
|
||
}
|
||
|
||
/// 解析路径,支持 ~ 开头的相对路径
|
||
fn resolve_path(raw: &str) -> PathBuf {
|
||
if raw == "~" {
|
||
if let Some(home) = dirs::home_dir() {
|
||
return home;
|
||
}
|
||
} else if let Some(stripped) = raw.strip_prefix("~/") {
|
||
if let Some(home) = dirs::home_dir() {
|
||
return home.join(stripped);
|
||
}
|
||
} else if let Some(stripped) = raw.strip_prefix("~\\") {
|
||
if let Some(home) = dirs::home_dir() {
|
||
return home.join(stripped);
|
||
}
|
||
}
|
||
|
||
PathBuf::from(raw)
|
||
}
|
||
|
||
/// 从旧的 settings.json 迁移 app_config_dir 到 Store
|
||
pub fn migrate_app_config_dir_from_settings(app: &tauri::AppHandle) -> Result<(), AppError> {
|
||
// app_config_dir 已从 settings.json 移除,此函数保留但不再执行迁移
|
||
// 如果用户在旧版本设置过 app_config_dir,需要在 Store 中手动配置
|
||
log::info!("app_config_dir 迁移功能已移除,请在设置中重新配置");
|
||
|
||
let _ = refresh_app_config_dir_override(app);
|
||
Ok(())
|
||
}
|