diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 166d71c..faf7d3d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,3 +23,6 @@ serde = { version = "1.0", features = ["derive"] } log = "0.4" tauri = { version = "2.8.2" } tauri-plugin-log = "2" +tauri-plugin-store = "2" +dirs = "5.0" +uuid = { version = "1.11", features = ["v4", "serde"] } diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs new file mode 100644 index 0000000..e6afa78 --- /dev/null +++ b/src-tauri/src/config.rs @@ -0,0 +1,137 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use std::io::{self, Write}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// 获取 Claude Code 配置目录路径 +pub fn get_claude_config_dir() -> PathBuf { + dirs::home_dir() + .expect("无法获取用户主目录") + .join(".claude") +} + +/// 获取 Claude Code 主配置文件路径 +pub fn get_claude_settings_path() -> PathBuf { + get_claude_config_dir().join("settings.json") +} + +/// 获取应用配置目录路径 (~/.cc-switch) +pub fn get_app_config_dir() -> PathBuf { + dirs::home_dir() + .expect("无法获取用户主目录") + .join(".cc-switch") +} + +/// 获取应用配置文件路径 +pub fn get_app_config_path() -> PathBuf { + get_app_config_dir().join("config.json") +} + +/// 清理供应商名称,确保文件名安全 +pub fn sanitize_provider_name(name: &str) -> String { + name.chars() + .map(|c| match c { + '<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*' => '-', + _ => c, + }) + .collect::() + .to_lowercase() +} + +/// 获取供应商配置文件路径 +pub fn get_provider_config_path(provider_id: &str, provider_name: Option<&str>) -> PathBuf { + let base_name = provider_name + .map(|name| sanitize_provider_name(name)) + .unwrap_or_else(|| sanitize_provider_name(provider_id)); + + get_claude_config_dir().join(format!("settings-{}.json", base_name)) +} + +/// 读取 JSON 配置文件 +pub fn read_json_file Deserialize<'a>>(path: &Path) -> Result { + if !path.exists() { + return Err(format!("文件不存在: {}", path.display())); + } + + let content = fs::read_to_string(path) + .map_err(|e| format!("读取文件失败: {}", e))?; + + serde_json::from_str(&content) + .map_err(|e| format!("解析 JSON 失败: {}", e)) +} + +/// 写入 JSON 配置文件 +pub fn write_json_file(path: &Path, data: &T) -> Result<(), String> { + // 确保目录存在 + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|e| format!("创建目录失败: {}", e))?; + } + + let json = serde_json::to_string_pretty(data) + .map_err(|e| format!("序列化 JSON 失败: {}", e))?; + + fs::write(path, json) + .map_err(|e| format!("写入文件失败: {}", e)) +} + +/// 复制文件 +pub fn copy_file(from: &Path, to: &Path) -> Result<(), String> { + fs::copy(from, to) + .map_err(|e| format!("复制文件失败: {}", e))?; + Ok(()) +} + +/// 删除文件 +pub fn delete_file(path: &Path) -> Result<(), String> { + if path.exists() { + fs::remove_file(path) + .map_err(|e| format!("删除文件失败: {}", e))?; + } + Ok(()) +} + +/// 检查 Claude Code 配置状态 +#[derive(Serialize, Deserialize)] +pub struct ConfigStatus { + pub exists: bool, + pub path: String, +} + +/// 获取 Claude Code 配置状态 +pub fn get_claude_config_status() -> ConfigStatus { + let path = get_claude_settings_path(); + ConfigStatus { + exists: path.exists(), + path: path.to_string_lossy().to_string(), + } +} + +/// 备份配置文件 +pub fn backup_config(from: &Path, to: &Path) -> Result<(), String> { + if from.exists() { + copy_file(from, to)?; + log::info!("已备份配置文件: {} -> {}", from.display(), to.display()); + } + Ok(()) +} + +/// 导入当前 Claude Code 配置为默认供应商 +pub fn import_current_config_as_default() -> Result { + let settings_path = get_claude_settings_path(); + + if !settings_path.exists() { + return Err("Claude Code 配置文件不存在".to_string()); + } + + // 读取当前配置 + let settings_config: Value = read_json_file(&settings_path)?; + + // 保存为 default 供应商 + let default_provider_path = get_provider_config_path("default", Some("default")); + write_json_file(&default_provider_path, &settings_config)?; + + log::info!("已导入当前配置为默认供应商"); + Ok(settings_config) +} \ No newline at end of file diff --git a/src-tauri/src/provider.rs b/src-tauri/src/provider.rs new file mode 100644 index 0000000..dacc759 --- /dev/null +++ b/src-tauri/src/provider.rs @@ -0,0 +1,180 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::path::Path; +use uuid::Uuid; + +use crate::config::{ + copy_file, delete_file, get_provider_config_path, read_json_file, write_json_file, + get_claude_settings_path, backup_config, sanitize_provider_name +}; + +/// 供应商结构体 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Provider { + pub id: String, + pub name: String, + #[serde(rename = "settingsConfig")] + pub settings_config: Value, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "websiteUrl")] + pub website_url: Option, +} + +impl Provider { + /// 创建新的供应商 + pub fn new(name: String, settings_config: Value, website_url: Option) -> Self { + Self { + id: Uuid::new_v4().to_string(), + name, + settings_config, + website_url, + } + } + + /// 从现有ID创建供应商 + pub fn with_id(id: String, name: String, settings_config: Value, website_url: Option) -> Self { + Self { + id, + name, + settings_config, + website_url, + } + } +} + +/// 供应商管理器 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProviderManager { + pub providers: HashMap, + pub current: String, +} + +impl Default for ProviderManager { + fn default() -> Self { + Self { + providers: HashMap::new(), + current: String::new(), + } + } +} + +impl ProviderManager { + /// 加载供应商列表 + pub fn load_from_file(path: &Path) -> Result { + if !path.exists() { + log::info!("配置文件不存在,创建新的供应商管理器"); + return Ok(Self::default()); + } + + read_json_file(path) + } + + /// 保存供应商列表 + pub fn save_to_file(&self, path: &Path) -> Result<(), String> { + write_json_file(path, self) + } + + /// 添加供应商 + pub fn add_provider(&mut self, provider: Provider) -> Result<(), String> { + // 保存供应商配置到独立文件 + let config_path = get_provider_config_path(&provider.id, Some(&provider.name)); + write_json_file(&config_path, &provider.settings_config)?; + + // 添加到管理器 + self.providers.insert(provider.id.clone(), provider); + Ok(()) + } + + /// 更新供应商 + pub fn update_provider(&mut self, provider: Provider) -> Result<(), String> { + // 检查供应商是否存在 + if !self.providers.contains_key(&provider.id) { + return Err(format!("供应商不存在: {}", provider.id)); + } + + // 如果名称改变了,需要处理配置文件 + if let Some(old_provider) = self.providers.get(&provider.id) { + if old_provider.name != provider.name { + // 删除旧配置文件 + let old_config_path = get_provider_config_path(&provider.id, Some(&old_provider.name)); + delete_file(&old_config_path).ok(); // 忽略删除错误 + } + } + + // 保存新配置文件 + let config_path = get_provider_config_path(&provider.id, Some(&provider.name)); + write_json_file(&config_path, &provider.settings_config)?; + + // 更新管理器 + self.providers.insert(provider.id.clone(), provider); + Ok(()) + } + + /// 删除供应商 + pub fn delete_provider(&mut self, provider_id: &str) -> Result<(), String> { + // 检查是否为当前供应商 + if self.current == provider_id { + return Err("不能删除当前正在使用的供应商".to_string()); + } + + // 获取供应商信息 + let provider = self.providers.get(provider_id) + .ok_or_else(|| format!("供应商不存在: {}", provider_id))?; + + // 删除配置文件 + let config_path = get_provider_config_path(provider_id, Some(&provider.name)); + delete_file(&config_path)?; + + // 从管理器删除 + self.providers.remove(provider_id); + Ok(()) + } + + /// 切换供应商 + pub fn switch_provider(&mut self, provider_id: &str) -> Result<(), String> { + // 检查供应商是否存在 + let provider = self.providers.get(provider_id) + .ok_or_else(|| format!("供应商不存在: {}", provider_id))?; + + let settings_path = get_claude_settings_path(); + let provider_config_path = get_provider_config_path(provider_id, Some(&provider.name)); + + // 检查供应商配置文件是否存在 + if !provider_config_path.exists() { + return Err(format!("供应商配置文件不存在: {}", provider_config_path.display())); + } + + // 如果当前有配置,先备份到当前供应商 + if settings_path.exists() && !self.current.is_empty() { + if let Some(current_provider) = self.providers.get(&self.current) { + let current_provider_path = get_provider_config_path(&self.current, Some(¤t_provider.name)); + backup_config(&settings_path, ¤t_provider_path)?; + log::info!("已备份当前供应商配置: {}", current_provider.name); + } + } + + // 复制新供应商配置到主配置 + copy_file(&provider_config_path, &settings_path)?; + + // 更新当前供应商 + self.current = provider_id.to_string(); + + log::info!("成功切换到供应商: {}", provider.name); + Ok(()) + } + + /// 获取当前供应商 + pub fn get_current_provider(&self) -> Option<&Provider> { + if self.current.is_empty() { + None + } else { + self.providers.get(&self.current) + } + } + + /// 获取所有供应商 + pub fn get_all_providers(&self) -> &HashMap { + &self.providers + } +} \ No newline at end of file diff --git a/src-tauri/src/store.rs b/src-tauri/src/store.rs new file mode 100644 index 0000000..448a0f9 --- /dev/null +++ b/src-tauri/src/store.rs @@ -0,0 +1,45 @@ +use std::sync::Mutex; +use crate::config::{get_app_config_path, read_json_file, write_json_file}; +use crate::provider::{Provider, ProviderManager}; + +/// 全局应用状态 +pub struct AppState { + pub provider_manager: Mutex, +} + +impl AppState { + /// 创建新的应用状态 + pub fn new() -> Self { + let config_path = get_app_config_path(); + let provider_manager = ProviderManager::load_from_file(&config_path) + .unwrap_or_else(|e| { + log::warn!("加载配置失败: {}, 使用默认配置", e); + ProviderManager::default() + }); + + Self { + provider_manager: Mutex::new(provider_manager), + } + } + + /// 保存配置到文件 + pub fn save(&self) -> Result<(), String> { + let config_path = get_app_config_path(); + let manager = self.provider_manager.lock() + .map_err(|e| format!("获取锁失败: {}", e))?; + + manager.save_to_file(&config_path) + } + + /// 重新加载配置 + pub fn reload(&self) -> Result<(), String> { + let config_path = get_app_config_path(); + let new_manager = ProviderManager::load_from_file(&config_path)?; + + let mut manager = self.provider_manager.lock() + .map_err(|e| format!("获取锁失败: {}", e))?; + + *manager = new_manager; + Ok(()) + } +} \ No newline at end of file