diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 29b3754..4a7b88d 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -2,46 +2,48 @@ use std::collections::HashMap; use tauri::State; use tauri_plugin_opener::OpenerExt; -use crate::config::{ - import_current_config_as_default, get_claude_settings_path, - ConfigStatus, -}; +use crate::config::{ConfigStatus, get_claude_settings_path, import_current_config_as_default}; use crate::provider::Provider; use crate::store::AppState; /// 获取所有供应商 #[tauri::command] -pub async fn get_providers(state: State<'_, AppState>) -> Result, String> { - let manager = state.provider_manager.lock() +pub async fn get_providers( + state: State<'_, AppState>, +) -> Result, String> { + let manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + Ok(manager.get_all_providers().clone()) } /// 获取当前供应商ID #[tauri::command] pub async fn get_current_provider(state: State<'_, AppState>) -> Result { - let manager = state.provider_manager.lock() + let manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + Ok(manager.current.clone()) } /// 添加供应商 #[tauri::command] -pub async fn add_provider( - state: State<'_, AppState>, - provider: Provider, -) -> Result { - let mut manager = state.provider_manager.lock() +pub async fn add_provider(state: State<'_, AppState>, provider: Provider) -> Result { + let mut manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + manager.add_provider(provider)?; - + // 保存配置 drop(manager); // 释放锁 state.save()?; - + Ok(true) } @@ -51,59 +53,57 @@ pub async fn update_provider( state: State<'_, AppState>, provider: Provider, ) -> Result { - let mut manager = state.provider_manager.lock() + let mut manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + manager.update_provider(provider)?; - + // 保存配置 drop(manager); // 释放锁 state.save()?; - + Ok(true) } /// 删除供应商 #[tauri::command] -pub async fn delete_provider( - state: State<'_, AppState>, - id: String, -) -> Result { - let mut manager = state.provider_manager.lock() +pub async fn delete_provider(state: State<'_, AppState>, id: String) -> Result { + let mut manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + manager.delete_provider(&id)?; - + // 保存配置 drop(manager); // 释放锁 state.save()?; - + Ok(true) } /// 切换供应商 #[tauri::command] -pub async fn switch_provider( - state: State<'_, AppState>, - id: String, -) -> Result { - let mut manager = state.provider_manager.lock() +pub async fn switch_provider(state: State<'_, AppState>, id: String) -> Result { + let mut manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + manager.switch_provider(&id)?; - + // 保存配置 drop(manager); // 释放锁 state.save()?; - + Ok(true) } /// 导入当前配置为默认供应商 #[tauri::command] -pub async fn import_default_config( - state: State<'_, AppState>, -) -> Result { +pub async fn import_default_config(state: State<'_, AppState>) -> Result { // 若已存在 default 供应商,则直接返回,避免重复导入 { let manager = state @@ -117,7 +117,7 @@ pub async fn import_default_config( // 导入配置 let settings_config = import_current_config_as_default()?; - + // 创建默认供应商 let provider = Provider::with_id( "default".to_string(), @@ -125,22 +125,24 @@ pub async fn import_default_config( settings_config, None, ); - + // 添加到管理器 - let mut manager = state.provider_manager.lock() + let mut manager = state + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + manager.add_provider(provider)?; - + // 如果没有当前供应商,设置为 default if manager.current.is_empty() { manager.current = "default".to_string(); } - + // 保存配置 drop(manager); // 释放锁 state.save()?; - + Ok(true) } @@ -160,18 +162,17 @@ pub async fn get_claude_code_config_path() -> Result { #[tauri::command] pub async fn open_config_folder(app: tauri::AppHandle) -> Result { let config_dir = crate::config::get_claude_config_dir(); - + // 确保目录存在 if !config_dir.exists() { - std::fs::create_dir_all(&config_dir) - .map_err(|e| format!("创建目录失败: {}", e))?; + std::fs::create_dir_all(&config_dir).map_err(|e| format!("创建目录失败: {}", e))?; } - + // 使用 opener 插件打开文件夹 app.opener() .open_path(config_dir.to_string_lossy().to_string(), None::) .map_err(|e| format!("打开文件夹失败: {}", e))?; - + Ok(true) } @@ -184,11 +185,11 @@ pub async fn open_external(app: tauri::AppHandle, url: String) -> Result) .map_err(|e| format!("打开链接失败: {}", e))?; - + Ok(true) } diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index d6d8ac6..2b52dd4 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -1,7 +1,7 @@ -use std::fs; -use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::fs; +use std::path::{Path, PathBuf}; /// 获取 Claude Code 配置目录路径 pub fn get_claude_config_dir() -> PathBuf { @@ -49,7 +49,7 @@ pub fn get_provider_config_path(provider_id: &str, provider_name: Option<&str>) 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)) } @@ -58,41 +58,35 @@ pub fn read_json_file Deserialize<'a>>(path: &Path) -> Result(path: &Path, data: &T) -> Result<(), String> { // 确保目录存在 if let Some(parent) = path.parent() { - fs::create_dir_all(parent) - .map_err(|e| format!("创建目录失败: {}", e))?; + 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)) + + 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))?; + 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))?; + fs::remove_file(path).map_err(|e| format!("删除文件失败: {}", e))?; } Ok(()) } @@ -125,18 +119,18 @@ pub fn backup_config(from: &Path, to: &Path) -> Result<(), String> { /// 导入当前 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) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a85eae6..289c652 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,7 @@ +mod commands; mod config; mod provider; mod store; -mod commands; use store::AppState; use tauri::Manager; @@ -16,33 +16,32 @@ pub fn run() { { // 设置 macOS 标题栏背景色为主界面蓝色 if let Some(window) = app.get_webview_window("main") { - use objc2::runtime::AnyObject; use objc2::rc::Retained; + use objc2::runtime::AnyObject; use objc2_app_kit::NSColor; - + let ns_window_ptr = window.ns_window().unwrap(); - let ns_window: Retained = unsafe { - Retained::retain(ns_window_ptr as *mut AnyObject).unwrap() - }; - + let ns_window: Retained = + unsafe { Retained::retain(ns_window_ptr as *mut AnyObject).unwrap() }; + // 使用与主界面 banner 相同的蓝色 #3498db // #3498db = RGB(52, 152, 219) let bg_color = unsafe { NSColor::colorWithRed_green_blue_alpha( - 52.0/255.0, // R: 52 - 152.0/255.0, // G: 152 - 219.0/255.0, // B: 219 - 1.0, // Alpha: 1.0 + 52.0 / 255.0, // R: 52 + 152.0 / 255.0, // G: 152 + 219.0 / 255.0, // B: 219 + 1.0, // Alpha: 1.0 ) }; - + unsafe { use objc2::msg_send; let _: () = msg_send![&*ns_window, setBackgroundColor: &*bg_color]; } } } - + // 初始化日志 if cfg!(debug_assertions) { app.handle().plugin( @@ -51,20 +50,20 @@ pub fn run() { .build(), )?; } - + // 初始化应用状态(仅创建一次,并在本函数末尾注入 manage) let app_state = AppState::new(); - + // 如果没有供应商且存在 Claude Code 配置,自动导入 { let manager = app_state.provider_manager.lock().unwrap(); if manager.providers.is_empty() { drop(manager); // 释放锁 - + let settings_path = config::get_claude_settings_path(); if settings_path.exists() { log::info!("检测到 Claude Code 配置,自动导入为默认供应商"); - + if let Ok(settings_config) = config::import_current_config_as_default() { let mut manager = app_state.provider_manager.lock().unwrap(); let provider = provider::Provider::with_id( @@ -73,7 +72,7 @@ pub fn run() { settings_config, None, ); - + if manager.add_provider(provider).is_ok() { manager.current = "default".to_string(); drop(manager); @@ -84,7 +83,7 @@ pub fn run() { } } } - + // 将同一个实例注入到全局状态,避免重复创建导致的不一致 app.manage(app_state); Ok(()) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 57dafae..2cf8dcf 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - cc_switch_lib::run(); + cc_switch_lib::run(); } diff --git a/src-tauri/src/provider.rs b/src-tauri/src/provider.rs index b27aeb7..0287b37 100644 --- a/src-tauri/src/provider.rs +++ b/src-tauri/src/provider.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::path::Path; use crate::config::{ - copy_file, delete_file, get_provider_config_path, read_json_file, write_json_file, - get_claude_settings_path, backup_config + backup_config, copy_file, delete_file, get_claude_settings_path, get_provider_config_path, + read_json_file, write_json_file, }; /// 供应商结构体 @@ -22,7 +22,12 @@ pub struct Provider { impl Provider { /// 从现有ID创建供应商 - pub fn with_id(id: String, name: String, settings_config: Value, website_url: Option) -> Self { + pub fn with_id( + id: String, + name: String, + settings_config: Value, + website_url: Option, + ) -> Self { Self { id, name, @@ -55,110 +60,118 @@ impl ProviderManager { 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)); + 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) + 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) + 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())); + 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)); + 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); } } - + // 确保主配置父目录存在 if let Some(parent) = settings_path.parent() { - std::fs::create_dir_all(parent) - .map_err(|e| format!("创建目录失败: {}", e))?; + std::fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?; } // 复制新供应商配置到主配置 copy_file(&provider_config_path, &settings_path)?; - + // 更新当前供应商 self.current = provider_id.to_string(); - + log::info!("成功切换到供应商: {}", provider.name); Ok(()) } - + /// 获取所有供应商 pub fn get_all_providers(&self) -> &HashMap { &self.providers diff --git a/src-tauri/src/store.rs b/src-tauri/src/store.rs index 4f84131..77382b8 100644 --- a/src-tauri/src/store.rs +++ b/src-tauri/src/store.rs @@ -1,6 +1,6 @@ -use std::sync::Mutex; use crate::config::get_app_config_path; use crate::provider::ProviderManager; +use std::sync::Mutex; /// 全局应用状态 pub struct AppState { @@ -11,25 +11,26 @@ 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() - }); - + 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() + let manager = self + .provider_manager + .lock() .map_err(|e| format!("获取锁失败: {}", e))?; - + manager.save_to_file(&config_path) } - + // 保留按需扩展:若未来需要热加载,可在此实现 } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7b5e442..c7aac19 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "CC Switch", "version": "3.0.0", - "identifier": "com.ccswitch.app", + "identifier": "com.ccswitch.desktop", "build": { "frontendDist": "../dist", "devUrl": "http://localhost:3000", diff --git a/src/App.css b/src/App.css index 054c28f..8c79ef0 100644 --- a/src/App.css +++ b/src/App.css @@ -26,7 +26,8 @@ gap: 1rem; } -.refresh-btn, .add-btn { +.refresh-btn, +.add-btn { padding: 0.5rem 1rem; border: none; border-radius: 4px; @@ -123,12 +124,12 @@ left: 50%; transform: translateX(-50%); z-index: 100; - + padding: 0.75rem 1.25rem; border-radius: 6px; font-size: 0.9rem; font-weight: 500; - + width: fit-content; white-space: nowrap; } diff --git a/src/App.tsx b/src/App.tsx index 6cd3e99..3f8975c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,9 +10,12 @@ function App() { const [providers, setProviders] = useState>({}); const [currentProviderId, setCurrentProviderId] = useState(""); const [isAddModalOpen, setIsAddModalOpen] = useState(false); - const [configStatus, setConfigStatus] = useState<{ exists: boolean; path: string } | null>(null); + const [configStatus, setConfigStatus] = useState<{ + exists: boolean; + path: string; + } | null>(null); const [editingProviderId, setEditingProviderId] = useState( - null + null, ); const [notification, setNotification] = useState<{ message: string; @@ -31,7 +34,7 @@ function App() { const showNotification = ( message: string, type: "success" | "error", - duration = 3000 + duration = 3000, ) => { // 清除之前的定时器 if (timeoutRef.current) { @@ -73,7 +76,7 @@ function App() { const currentId = await window.api.getCurrentProvider(); setProviders(loadedProviders); setCurrentProviderId(currentId); - + // 如果供应商列表为空,尝试自动导入现有配置为"default"供应商 if (Object.keys(loadedProviders).length === 0) { await handleAutoImportDefault(); @@ -82,7 +85,10 @@ function App() { const loadConfigStatus = async () => { const status = await window.api.getClaudeConfigStatus(); - setConfigStatus({ exists: Boolean(status?.exists), path: String(status?.path || "") }); + setConfigStatus({ + exists: Boolean(status?.exists), + path: String(status?.path || ""), + }); }; // 生成唯一ID @@ -137,7 +143,7 @@ function App() { showNotification( "切换成功!请重启 Claude Code 终端以生效", "success", - 2000 + 2000, ); } else { showNotification("切换失败,请检查配置", "error"); @@ -147,18 +153,22 @@ function App() { // 自动导入现有配置为"default"供应商 const handleAutoImportDefault = async () => { try { - const result = await window.api.importCurrentConfigAsDefault() - + const result = await window.api.importCurrentConfigAsDefault(); + if (result.success) { - await loadProviders() - showNotification("已自动导入现有配置为 default 供应商", "success", 3000) + await loadProviders(); + showNotification( + "已自动导入现有配置为 default 供应商", + "success", + 3000, + ); } // 如果导入失败(比如没有现有配置),静默处理,不显示错误 } catch (error) { - console.error('自动导入默认配置失败:', error) + console.error("自动导入默认配置失败:", error); // 静默处理,不影响用户体验 } - } + }; const handleOpenConfigFolder = async () => { await window.api.openConfigFolder(); diff --git a/src/components/AddProviderModal.css b/src/components/AddProviderModal.css index 521cfbc..173769c 100644 --- a/src/components/AddProviderModal.css +++ b/src/components/AddProviderModal.css @@ -22,7 +22,7 @@ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2); position: relative; z-index: 1001; - display: flex; /* 纵向布局,便于底栏固定 */ + display: flex; /* 纵向布局,便于底栏固定 */ flex-direction: column; } @@ -40,7 +40,10 @@ } /* 左侧占位以保证标题居中(与右侧关闭按钮宽度相当) */ -.modal-spacer { width: 32px; flex: 0 0 32px; } +.modal-spacer { + width: 32px; + flex: 0 0 32px; +} .modal-title { flex: 1; @@ -69,16 +72,17 @@ color: #fff; } -.modal-form { /* 表单外层包裹 body + footer */ +.modal-form { + /* 表单外层包裹 body + footer */ display: flex; flex-direction: column; flex: 1 1 auto; - min-height: 0; /* 允许子元素正确计算高度 */ + min-height: 0; /* 允许子元素正确计算高度 */ } .modal-body { padding: 1.25rem 1.5rem 1.5rem; - overflow: auto; /* 仅内容区滚动 */ + overflow: auto; /* 仅内容区滚动 */ flex: 1 1 auto; min-height: 0; } @@ -175,7 +179,8 @@ border-color: #3498db; } -.modal-footer { /* 固定在弹窗底部(非滚动区) */ +.modal-footer { + /* 固定在弹窗底部(非滚动区) */ display: flex; gap: 1rem; justify-content: flex-end; diff --git a/src/components/ConfirmDialog.css b/src/components/ConfirmDialog.css index 814dc27..d93721a 100644 --- a/src/components/ConfirmDialog.css +++ b/src/components/ConfirmDialog.css @@ -68,7 +68,9 @@ cursor: pointer; font-size: 0.9rem; font-weight: 500; - transition: background-color 0.2s, transform 0.1s; + transition: + background-color 0.2s, + transform 0.1s; min-width: 70px; } @@ -102,4 +104,4 @@ .confirm-btn:focus { outline: 2px solid #007bff; outline-offset: 2px; -} \ No newline at end of file +} diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx index 765c0b8..6981fef 100644 --- a/src/components/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import './ConfirmDialog.css'; +import React from "react"; +import "./ConfirmDialog.css"; interface ConfirmDialogProps { isOpen: boolean; @@ -15,10 +15,10 @@ export const ConfirmDialog: React.FC = ({ isOpen, title, message, - confirmText = '确定', - cancelText = '取消', + confirmText = "确定", + cancelText = "取消", onConfirm, - onCancel + onCancel, }) => { if (!isOpen) return null; @@ -32,15 +32,15 @@ export const ConfirmDialog: React.FC = ({

{message}

- -
); -}; \ No newline at end of file +}; diff --git a/src/components/EditProviderModal.tsx b/src/components/EditProviderModal.tsx index 877ff3d..9743e4e 100644 --- a/src/components/EditProviderModal.tsx +++ b/src/components/EditProviderModal.tsx @@ -1,20 +1,24 @@ -import React from 'react' -import { Provider } from '../types' -import ProviderForm from './ProviderForm' +import React from "react"; +import { Provider } from "../types"; +import ProviderForm from "./ProviderForm"; interface EditProviderModalProps { - provider: Provider - onSave: (provider: Provider) => void - onClose: () => void + provider: Provider; + onSave: (provider: Provider) => void; + onClose: () => void; } -const EditProviderModal: React.FC = ({ provider, onSave, onClose }) => { - const handleSubmit = (data: Omit) => { +const EditProviderModal: React.FC = ({ + provider, + onSave, + onClose, +}) => { + const handleSubmit = (data: Omit) => { onSave({ ...provider, - ...data - }) - } + ...data, + }); + }; return ( = ({ provider, onSave, onSubmit={handleSubmit} onClose={onClose} /> - ) -} + ); +}; -export default EditProviderModal +export default EditProviderModal; diff --git a/src/components/ProviderForm.tsx b/src/components/ProviderForm.tsx index 8176f65..48d4687 100644 --- a/src/components/ProviderForm.tsx +++ b/src/components/ProviderForm.tsx @@ -80,7 +80,7 @@ const ProviderForm: React.FC = ({ }; const handleChange = ( - e: React.ChangeEvent + e: React.ChangeEvent, ) => { const { name, value } = e.target; @@ -117,7 +117,7 @@ const ProviderForm: React.FC = ({ // 更新JSON配置 const updatedConfig = updateCoAuthoredSetting( formData.settingsConfig, - checked + checked, ); setFormData({ ...formData, @@ -152,7 +152,7 @@ const ProviderForm: React.FC = ({ const configString = setApiKeyInConfig( formData.settingsConfig, key.trim(), - { createIfMissing: selectedPreset !== null } + { createIfMissing: selectedPreset !== null }, ); // 更新表单配置 @@ -174,7 +174,7 @@ const ProviderForm: React.FC = ({ useEffect(() => { if (initialData) { const parsedKey = getApiKeyFromConfig( - JSON.stringify(initialData.settingsConfig) + JSON.stringify(initialData.settingsConfig), ); if (parsedKey) setApiKey(parsedKey); } @@ -255,7 +255,9 @@ const ProviderForm: React.FC = ({ /> -
+
- currentProviderId: string - onSwitch: (id: string) => void - onDelete: (id: string) => void - onEdit: (id: string) => void + providers: Record; + currentProviderId: string; + onSwitch: (id: string) => void; + onDelete: (id: string) => void; + onEdit: (id: string) => void; } const ProviderList: React.FC = ({ @@ -15,28 +15,28 @@ const ProviderList: React.FC = ({ currentProviderId, onSwitch, onDelete, - onEdit + onEdit, }) => { // 提取API地址 const getApiUrl = (provider: Provider): string => { try { - const config = provider.settingsConfig + const config = provider.settingsConfig; if (config?.env?.ANTHROPIC_BASE_URL) { - return config.env.ANTHROPIC_BASE_URL + return config.env.ANTHROPIC_BASE_URL; } - return '未设置' + return "未设置"; } catch { - return '配置错误' + return "配置错误"; } - } + }; const handleUrlClick = async (url: string) => { try { - await window.api.openExternal(url) + await window.api.openExternal(url); } catch (error) { - console.error('打开链接失败:', error) + console.error("打开链接失败:", error); } - } + }; return (
@@ -48,25 +48,27 @@ const ProviderList: React.FC = ({ ) : ( )}
- ) -} + ); +}; -export default ProviderList +export default ProviderList; diff --git a/src/config/providerPresets.ts b/src/config/providerPresets.ts index 87080ab..9976968 100644 --- a/src/config/providerPresets.ts +++ b/src/config/providerPresets.ts @@ -63,5 +63,4 @@ export const providerPresets: ProviderPreset[] = [ }, }, }, - ]; diff --git a/src/index.css b/src/index.css index 3ec02d3..054f270 100644 --- a/src/index.css +++ b/src/index.css @@ -5,9 +5,9 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", + "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #f5f5f5; diff --git a/src/lib/tauri-api.ts b/src/lib/tauri-api.ts index e7713b0..d10a11d 100644 --- a/src/lib/tauri-api.ts +++ b/src/lib/tauri-api.ts @@ -1,5 +1,5 @@ -import { invoke } from '@tauri-apps/api/core'; -import { Provider } from '../types'; +import { invoke } from "@tauri-apps/api/core"; +import { Provider } from "../types"; // 定义配置状态类型 interface ConfigStatus { @@ -19,9 +19,9 @@ export const tauriAPI = { // 获取所有供应商 getProviders: async (): Promise> => { try { - return await invoke('get_providers'); + return await invoke("get_providers"); } catch (error) { - console.error('获取供应商列表失败:', error); + console.error("获取供应商列表失败:", error); return {}; } }, @@ -29,19 +29,19 @@ export const tauriAPI = { // 获取当前供应商ID getCurrentProvider: async (): Promise => { try { - return await invoke('get_current_provider'); + return await invoke("get_current_provider"); } catch (error) { - console.error('获取当前供应商失败:', error); - return ''; + console.error("获取当前供应商失败:", error); + return ""; } }, // 添加供应商 addProvider: async (provider: Provider): Promise => { try { - return await invoke('add_provider', { provider }); + return await invoke("add_provider", { provider }); } catch (error) { - console.error('添加供应商失败:', error); + console.error("添加供应商失败:", error); throw error; } }, @@ -49,9 +49,9 @@ export const tauriAPI = { // 更新供应商 updateProvider: async (provider: Provider): Promise => { try { - return await invoke('update_provider', { provider }); + return await invoke("update_provider", { provider }); } catch (error) { - console.error('更新供应商失败:', error); + console.error("更新供应商失败:", error); throw error; } }, @@ -59,9 +59,9 @@ export const tauriAPI = { // 删除供应商 deleteProvider: async (id: string): Promise => { try { - return await invoke('delete_provider', { id }); + return await invoke("delete_provider", { id }); } catch (error) { - console.error('删除供应商失败:', error); + console.error("删除供应商失败:", error); throw error; } }, @@ -69,9 +69,9 @@ export const tauriAPI = { // 切换供应商 switchProvider: async (providerId: string): Promise => { try { - return await invoke('switch_provider', { id: providerId }); + return await invoke("switch_provider", { id: providerId }); } catch (error) { - console.error('切换供应商失败:', error); + console.error("切换供应商失败:", error); return false; } }, @@ -79,16 +79,16 @@ export const tauriAPI = { // 导入当前配置为默认供应商 importCurrentConfigAsDefault: async (): Promise => { try { - const success = await invoke('import_default_config'); + const success = await invoke("import_default_config"); return { success, - message: success ? '成功导入默认配置' : '导入失败' + message: success ? "成功导入默认配置" : "导入失败", }; } catch (error) { - console.error('导入默认配置失败:', error); + console.error("导入默认配置失败:", error); return { success: false, - message: String(error) + message: String(error), }; } }, @@ -96,23 +96,23 @@ export const tauriAPI = { // 获取 Claude Code 配置文件路径 getClaudeCodeConfigPath: async (): Promise => { try { - return await invoke('get_claude_code_config_path'); + return await invoke("get_claude_code_config_path"); } catch (error) { - console.error('获取配置路径失败:', error); - return ''; + console.error("获取配置路径失败:", error); + return ""; } }, // 获取 Claude Code 配置状态 getClaudeConfigStatus: async (): Promise => { try { - return await invoke('get_claude_config_status'); + return await invoke("get_claude_config_status"); } catch (error) { - console.error('获取配置状态失败:', error); + console.error("获取配置状态失败:", error); return { exists: false, - path: '', - error: String(error) + path: "", + error: String(error), }; } }, @@ -120,34 +120,33 @@ export const tauriAPI = { // 打开配置文件夹 openConfigFolder: async (): Promise => { try { - await invoke('open_config_folder'); + await invoke("open_config_folder"); } catch (error) { - console.error('打开配置文件夹失败:', error); + console.error("打开配置文件夹失败:", error); } }, // 打开外部链接 openExternal: async (url: string): Promise => { try { - await invoke('open_external', { url }); + await invoke("open_external", { url }); } catch (error) { - console.error('打开外部链接失败:', error); + console.error("打开外部链接失败:", error); } }, // 选择配置文件(Tauri 暂不实现,保留接口兼容性) selectConfigFile: async (): Promise => { - console.warn('selectConfigFile 在 Tauri 版本中暂不支持'); + console.warn("selectConfigFile 在 Tauri 版本中暂不支持"); return null; - } + }, }; // 创建全局 API 对象,兼容现有代码 -if (typeof window !== 'undefined') { +if (typeof window !== "undefined") { // 绑定到 window.api,避免 Electron 命名造成误解 // API 内部已做 try/catch,非 Tauri 环境下也会安全返回默认值 (window as any).api = tauriAPI; - } export default tauriAPI; diff --git a/src/main.tsx b/src/main.tsx index b197261..caefd86 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,24 +1,24 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; // 导入 Tauri API(自动绑定到 window.api) -import './lib/tauri-api' +import "./lib/tauri-api"; // 根据平台添加 body class,便于平台特定样式 try { - const ua = navigator.userAgent || '' - const plat = (navigator.platform || '').toLowerCase() - const isMac = /mac/i.test(ua) || plat.includes('mac') + const ua = navigator.userAgent || ""; + const plat = (navigator.platform || "").toLowerCase(); + const isMac = /mac/i.test(ua) || plat.includes("mac"); if (isMac) { - document.body.classList.add('is-mac') + document.body.classList.add("is-mac"); } } catch { // 忽略平台检测失败 } -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById("root")!).render( - -) + , +); diff --git a/src/utils/providerConfigUtils.ts b/src/utils/providerConfigUtils.ts index 6ea3c29..4e45e7e 100644 --- a/src/utils/providerConfigUtils.ts +++ b/src/utils/providerConfigUtils.ts @@ -1,91 +1,97 @@ // 供应商配置处理工具函数 // 处理includeCoAuthoredBy字段的添加/删除 -export const updateCoAuthoredSetting = (jsonString: string, disable: boolean): string => { +export const updateCoAuthoredSetting = ( + jsonString: string, + disable: boolean, +): string => { try { - const config = JSON.parse(jsonString) - + const config = JSON.parse(jsonString); + if (disable) { // 添加或更新includeCoAuthoredBy字段 - config.includeCoAuthoredBy = false + config.includeCoAuthoredBy = false; } else { // 删除includeCoAuthoredBy字段 - delete config.includeCoAuthoredBy + delete config.includeCoAuthoredBy; } - - return JSON.stringify(config, null, 2) + + return JSON.stringify(config, null, 2); } catch (err) { // 如果JSON解析失败,返回原始字符串 - return jsonString + return jsonString; } -} +}; // 从JSON配置中检查是否包含includeCoAuthoredBy设置 export const checkCoAuthoredSetting = (jsonString: string): boolean => { try { - const config = JSON.parse(jsonString) - return config.includeCoAuthoredBy === false + const config = JSON.parse(jsonString); + return config.includeCoAuthoredBy === false; } catch (err) { - return false + return false; } -} +}; // 从JSON配置中提取并处理官网地址 export const extractWebsiteUrl = (jsonString: string): string => { try { - const config = JSON.parse(jsonString) - const baseUrl = config?.env?.ANTHROPIC_BASE_URL - - if (baseUrl && typeof baseUrl === 'string') { + const config = JSON.parse(jsonString); + const baseUrl = config?.env?.ANTHROPIC_BASE_URL; + + if (baseUrl && typeof baseUrl === "string") { // 去掉 "api." 前缀 - return baseUrl.replace(/^https?:\/\/api\./, 'https://') + return baseUrl.replace(/^https?:\/\/api\./, "https://"); } } catch (err) { // 忽略JSON解析错误 } - return '' -} + return ""; +}; // 读取配置中的 API Key(env.ANTHROPIC_AUTH_TOKEN) export const getApiKeyFromConfig = (jsonString: string): string => { try { - const config = JSON.parse(jsonString) - const key = config?.env?.ANTHROPIC_AUTH_TOKEN - return typeof key === 'string' ? key : '' + const config = JSON.parse(jsonString); + const key = config?.env?.ANTHROPIC_AUTH_TOKEN; + return typeof key === "string" ? key : ""; } catch (err) { - return '' + return ""; } -} +}; // 判断配置中是否存在 API Key 字段 export const hasApiKeyField = (jsonString: string): boolean => { try { - const config = JSON.parse(jsonString) - return Object.prototype.hasOwnProperty.call(config?.env ?? {}, 'ANTHROPIC_AUTH_TOKEN') + const config = JSON.parse(jsonString); + return Object.prototype.hasOwnProperty.call( + config?.env ?? {}, + "ANTHROPIC_AUTH_TOKEN", + ); } catch (err) { - return false + return false; } -} +}; // 写入/更新配置中的 API Key,默认不新增缺失字段 export const setApiKeyInConfig = ( jsonString: string, apiKey: string, - options: { createIfMissing?: boolean } = {} + options: { createIfMissing?: boolean } = {}, ): string => { - const { createIfMissing = false } = options + const { createIfMissing = false } = options; try { - const config = JSON.parse(jsonString) + const config = JSON.parse(jsonString); if (!config.env) { - if (!createIfMissing) return jsonString - config.env = {} + if (!createIfMissing) return jsonString; + config.env = {}; } - if (!('ANTHROPIC_AUTH_TOKEN' in config.env) && !createIfMissing) { - return jsonString + if (!("ANTHROPIC_AUTH_TOKEN" in config.env) && !createIfMissing) { + return jsonString; } - config.env.ANTHROPIC_AUTH_TOKEN = apiKey - return JSON.stringify(config, null, 2) + config.env.ANTHROPIC_AUTH_TOKEN = apiKey; + return JSON.stringify(config, null, 2); } catch (err) { - return jsonString + return jsonString; } -} +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index b63597e..d71ffba 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,6 +1,6 @@ /// -import { Provider } from './types'; +import { Provider } from "./types"; interface ImportResult { success: boolean;