chore: format code and fix bundle identifier for v3.0.0 release

- Format all TypeScript/React code with Prettier
- Format all Rust code with cargo fmt
- Fix bundle identifier from .app to .desktop to avoid macOS conflicts
- Prepare codebase for v3.0.0 Tauri release
This commit is contained in:
Jason
2025-08-27 11:00:53 +08:00
parent 5e2e80b00d
commit 642e7a3817
23 changed files with 359 additions and 321 deletions

View File

@@ -2,17 +2,18 @@ use std::collections::HashMap;
use tauri::State; use tauri::State;
use tauri_plugin_opener::OpenerExt; use tauri_plugin_opener::OpenerExt;
use crate::config::{ use crate::config::{ConfigStatus, get_claude_settings_path, import_current_config_as_default};
import_current_config_as_default, get_claude_settings_path,
ConfigStatus,
};
use crate::provider::Provider; use crate::provider::Provider;
use crate::store::AppState; use crate::store::AppState;
/// 获取所有供应商 /// 获取所有供应商
#[tauri::command] #[tauri::command]
pub async fn get_providers(state: State<'_, AppState>) -> Result<HashMap<String, Provider>, String> { pub async fn get_providers(
let manager = state.provider_manager.lock() state: State<'_, AppState>,
) -> Result<HashMap<String, Provider>, String> {
let manager = state
.provider_manager
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
Ok(manager.get_all_providers().clone()) Ok(manager.get_all_providers().clone())
@@ -21,7 +22,9 @@ pub async fn get_providers(state: State<'_, AppState>) -> Result<HashMap<String,
/// 获取当前供应商ID /// 获取当前供应商ID
#[tauri::command] #[tauri::command]
pub async fn get_current_provider(state: State<'_, AppState>) -> Result<String, String> { pub async fn get_current_provider(state: State<'_, AppState>) -> Result<String, String> {
let manager = state.provider_manager.lock() let manager = state
.provider_manager
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
Ok(manager.current.clone()) Ok(manager.current.clone())
@@ -29,11 +32,10 @@ pub async fn get_current_provider(state: State<'_, AppState>) -> Result<String,
/// 添加供应商 /// 添加供应商
#[tauri::command] #[tauri::command]
pub async fn add_provider( pub async fn add_provider(state: State<'_, AppState>, provider: Provider) -> Result<bool, String> {
state: State<'_, AppState>, let mut manager = state
provider: Provider, .provider_manager
) -> Result<bool, String> { .lock()
let mut manager = state.provider_manager.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
manager.add_provider(provider)?; manager.add_provider(provider)?;
@@ -51,7 +53,9 @@ pub async fn update_provider(
state: State<'_, AppState>, state: State<'_, AppState>,
provider: Provider, provider: Provider,
) -> Result<bool, String> { ) -> Result<bool, String> {
let mut manager = state.provider_manager.lock() let mut manager = state
.provider_manager
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
manager.update_provider(provider)?; manager.update_provider(provider)?;
@@ -65,11 +69,10 @@ pub async fn update_provider(
/// 删除供应商 /// 删除供应商
#[tauri::command] #[tauri::command]
pub async fn delete_provider( pub async fn delete_provider(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state: State<'_, AppState>, let mut manager = state
id: String, .provider_manager
) -> Result<bool, String> { .lock()
let mut manager = state.provider_manager.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
manager.delete_provider(&id)?; manager.delete_provider(&id)?;
@@ -83,11 +86,10 @@ pub async fn delete_provider(
/// 切换供应商 /// 切换供应商
#[tauri::command] #[tauri::command]
pub async fn switch_provider( pub async fn switch_provider(state: State<'_, AppState>, id: String) -> Result<bool, String> {
state: State<'_, AppState>, let mut manager = state
id: String, .provider_manager
) -> Result<bool, String> { .lock()
let mut manager = state.provider_manager.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
manager.switch_provider(&id)?; manager.switch_provider(&id)?;
@@ -101,9 +103,7 @@ pub async fn switch_provider(
/// 导入当前配置为默认供应商 /// 导入当前配置为默认供应商
#[tauri::command] #[tauri::command]
pub async fn import_default_config( pub async fn import_default_config(state: State<'_, AppState>) -> Result<bool, String> {
state: State<'_, AppState>,
) -> Result<bool, String> {
// 若已存在 default 供应商,则直接返回,避免重复导入 // 若已存在 default 供应商,则直接返回,避免重复导入
{ {
let manager = state let manager = state
@@ -127,7 +127,9 @@ pub async fn import_default_config(
); );
// 添加到管理器 // 添加到管理器
let mut manager = state.provider_manager.lock() let mut manager = state
.provider_manager
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
manager.add_provider(provider)?; manager.add_provider(provider)?;
@@ -163,8 +165,7 @@ pub async fn open_config_folder(app: tauri::AppHandle) -> Result<bool, String> {
// 确保目录存在 // 确保目录存在
if !config_dir.exists() { if !config_dir.exists() {
std::fs::create_dir_all(&config_dir) std::fs::create_dir_all(&config_dir).map_err(|e| format!("创建目录失败: {}", e))?;
.map_err(|e| format!("创建目录失败: {}", e))?;
} }
// 使用 opener 插件打开文件夹 // 使用 opener 插件打开文件夹

View File

@@ -1,7 +1,7 @@
use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
/// 获取 Claude Code 配置目录路径 /// 获取 Claude Code 配置目录路径
pub fn get_claude_config_dir() -> PathBuf { pub fn get_claude_config_dir() -> PathBuf {
@@ -59,40 +59,34 @@ pub fn read_json_file<T: for<'a> Deserialize<'a>>(path: &Path) -> Result<T, Stri
return Err(format!("文件不存在: {}", path.display())); return Err(format!("文件不存在: {}", path.display()));
} }
let content = fs::read_to_string(path) let content = fs::read_to_string(path).map_err(|e| format!("读取文件失败: {}", e))?;
.map_err(|e| format!("读取文件失败: {}", e))?;
serde_json::from_str(&content) serde_json::from_str(&content).map_err(|e| format!("解析 JSON 失败: {}", e))
.map_err(|e| format!("解析 JSON 失败: {}", e))
} }
/// 写入 JSON 配置文件 /// 写入 JSON 配置文件
pub fn write_json_file<T: Serialize>(path: &Path, data: &T) -> Result<(), String> { pub fn write_json_file<T: Serialize>(path: &Path, data: &T) -> Result<(), String> {
// 确保目录存在 // 确保目录存在
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
fs::create_dir_all(parent) fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?;
.map_err(|e| format!("创建目录失败: {}", e))?;
} }
let json = serde_json::to_string_pretty(data) let json =
.map_err(|e| format!("序列化 JSON 失败: {}", e))?; serde_json::to_string_pretty(data).map_err(|e| format!("序列化 JSON 失败: {}", e))?;
fs::write(path, json) fs::write(path, json).map_err(|e| format!("写入文件失败: {}", e))
.map_err(|e| format!("写入文件失败: {}", e))
} }
/// 复制文件 /// 复制文件
pub fn copy_file(from: &Path, to: &Path) -> Result<(), String> { pub fn copy_file(from: &Path, to: &Path) -> Result<(), String> {
fs::copy(from, to) fs::copy(from, to).map_err(|e| format!("复制文件失败: {}", e))?;
.map_err(|e| format!("复制文件失败: {}", e))?;
Ok(()) Ok(())
} }
/// 删除文件 /// 删除文件
pub fn delete_file(path: &Path) -> Result<(), String> { pub fn delete_file(path: &Path) -> Result<(), String> {
if path.exists() { if path.exists() {
fs::remove_file(path) fs::remove_file(path).map_err(|e| format!("删除文件失败: {}", e))?;
.map_err(|e| format!("删除文件失败: {}", e))?;
} }
Ok(()) Ok(())
} }

View File

@@ -1,7 +1,7 @@
mod commands;
mod config; mod config;
mod provider; mod provider;
mod store; mod store;
mod commands;
use store::AppState; use store::AppState;
use tauri::Manager; use tauri::Manager;
@@ -16,22 +16,21 @@ pub fn run() {
{ {
// 设置 macOS 标题栏背景色为主界面蓝色 // 设置 macOS 标题栏背景色为主界面蓝色
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
use objc2::runtime::AnyObject;
use objc2::rc::Retained; use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2_app_kit::NSColor; use objc2_app_kit::NSColor;
let ns_window_ptr = window.ns_window().unwrap(); let ns_window_ptr = window.ns_window().unwrap();
let ns_window: Retained<AnyObject> = unsafe { let ns_window: Retained<AnyObject> =
Retained::retain(ns_window_ptr as *mut AnyObject).unwrap() unsafe { Retained::retain(ns_window_ptr as *mut AnyObject).unwrap() };
};
// 使用与主界面 banner 相同的蓝色 #3498db // 使用与主界面 banner 相同的蓝色 #3498db
// #3498db = RGB(52, 152, 219) // #3498db = RGB(52, 152, 219)
let bg_color = unsafe { let bg_color = unsafe {
NSColor::colorWithRed_green_blue_alpha( NSColor::colorWithRed_green_blue_alpha(
52.0/255.0, // R: 52 52.0 / 255.0, // R: 52
152.0/255.0, // G: 152 152.0 / 255.0, // G: 152
219.0/255.0, // B: 219 219.0 / 255.0, // B: 219
1.0, // Alpha: 1.0 1.0, // Alpha: 1.0
) )
}; };

View File

@@ -4,8 +4,8 @@ use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use crate::config::{ use crate::config::{
copy_file, delete_file, get_provider_config_path, read_json_file, write_json_file, backup_config, copy_file, delete_file, get_claude_settings_path, get_provider_config_path,
get_claude_settings_path, backup_config read_json_file, write_json_file,
}; };
/// 供应商结构体 /// 供应商结构体
@@ -22,7 +22,12 @@ pub struct Provider {
impl Provider { impl Provider {
/// 从现有ID创建供应商 /// 从现有ID创建供应商
pub fn with_id(id: String, name: String, settings_config: Value, website_url: Option<String>) -> Self { pub fn with_id(
id: String,
name: String,
settings_config: Value,
website_url: Option<String>,
) -> Self {
Self { Self {
id, id,
name, name,
@@ -86,7 +91,8 @@ impl ProviderManager {
if let Some(old_provider) = self.providers.get(&provider.id) { if let Some(old_provider) = self.providers.get(&provider.id) {
if old_provider.name != provider.name { 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(); // 忽略删除错误 delete_file(&old_config_path).ok(); // 忽略删除错误
} }
} }
@@ -108,7 +114,9 @@ impl ProviderManager {
} }
// 获取供应商信息 // 获取供应商信息
let provider = self.providers.get(provider_id) let provider = self
.providers
.get(provider_id)
.ok_or_else(|| format!("供应商不存在: {}", provider_id))?; .ok_or_else(|| format!("供应商不存在: {}", provider_id))?;
// 删除配置文件 // 删除配置文件
@@ -123,7 +131,9 @@ impl ProviderManager {
/// 切换供应商 /// 切换供应商
pub fn switch_provider(&mut self, provider_id: &str) -> Result<(), String> { 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))?; .ok_or_else(|| format!("供应商不存在: {}", provider_id))?;
let settings_path = get_claude_settings_path(); let settings_path = get_claude_settings_path();
@@ -131,13 +141,17 @@ impl ProviderManager {
// 检查供应商配置文件是否存在 // 检查供应商配置文件是否存在
if !provider_config_path.exists() { 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 settings_path.exists() && !self.current.is_empty() {
if let Some(current_provider) = self.providers.get(&self.current) { if let Some(current_provider) = self.providers.get(&self.current) {
let current_provider_path = get_provider_config_path(&self.current, Some(&current_provider.name)); let current_provider_path =
get_provider_config_path(&self.current, Some(&current_provider.name));
backup_config(&settings_path, &current_provider_path)?; backup_config(&settings_path, &current_provider_path)?;
log::info!("已备份当前供应商配置: {}", current_provider.name); log::info!("已备份当前供应商配置: {}", current_provider.name);
} }
@@ -145,8 +159,7 @@ impl ProviderManager {
// 确保主配置父目录存在 // 确保主配置父目录存在
if let Some(parent) = settings_path.parent() { if let Some(parent) = settings_path.parent() {
std::fs::create_dir_all(parent) std::fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?;
.map_err(|e| format!("创建目录失败: {}", e))?;
} }
// 复制新供应商配置到主配置 // 复制新供应商配置到主配置

View File

@@ -1,6 +1,6 @@
use std::sync::Mutex;
use crate::config::get_app_config_path; use crate::config::get_app_config_path;
use crate::provider::ProviderManager; use crate::provider::ProviderManager;
use std::sync::Mutex;
/// 全局应用状态 /// 全局应用状态
pub struct AppState { pub struct AppState {
@@ -11,8 +11,7 @@ impl AppState {
/// 创建新的应用状态 /// 创建新的应用状态
pub fn new() -> Self { pub fn new() -> Self {
let config_path = get_app_config_path(); let config_path = get_app_config_path();
let provider_manager = ProviderManager::load_from_file(&config_path) let provider_manager = ProviderManager::load_from_file(&config_path).unwrap_or_else(|e| {
.unwrap_or_else(|e| {
log::warn!("加载配置失败: {}, 使用默认配置", e); log::warn!("加载配置失败: {}, 使用默认配置", e);
ProviderManager::default() ProviderManager::default()
}); });
@@ -25,7 +24,9 @@ impl AppState {
/// 保存配置到文件 /// 保存配置到文件
pub fn save(&self) -> Result<(), String> { pub fn save(&self) -> Result<(), String> {
let config_path = get_app_config_path(); let config_path = get_app_config_path();
let manager = self.provider_manager.lock() let manager = self
.provider_manager
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?; .map_err(|e| format!("获取锁失败: {}", e))?;
manager.save_to_file(&config_path) manager.save_to_file(&config_path)

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "CC Switch", "productName": "CC Switch",
"version": "3.0.0", "version": "3.0.0",
"identifier": "com.ccswitch.app", "identifier": "com.ccswitch.desktop",
"build": { "build": {
"frontendDist": "../dist", "frontendDist": "../dist",
"devUrl": "http://localhost:3000", "devUrl": "http://localhost:3000",

View File

@@ -26,7 +26,8 @@
gap: 1rem; gap: 1rem;
} }
.refresh-btn, .add-btn { .refresh-btn,
.add-btn {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: none; border: none;
border-radius: 4px; border-radius: 4px;

View File

@@ -10,9 +10,12 @@ function App() {
const [providers, setProviders] = useState<Record<string, Provider>>({}); const [providers, setProviders] = useState<Record<string, Provider>>({});
const [currentProviderId, setCurrentProviderId] = useState<string>(""); const [currentProviderId, setCurrentProviderId] = useState<string>("");
const [isAddModalOpen, setIsAddModalOpen] = useState(false); 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<string | null>( const [editingProviderId, setEditingProviderId] = useState<string | null>(
null null,
); );
const [notification, setNotification] = useState<{ const [notification, setNotification] = useState<{
message: string; message: string;
@@ -31,7 +34,7 @@ function App() {
const showNotification = ( const showNotification = (
message: string, message: string,
type: "success" | "error", type: "success" | "error",
duration = 3000 duration = 3000,
) => { ) => {
// 清除之前的定时器 // 清除之前的定时器
if (timeoutRef.current) { if (timeoutRef.current) {
@@ -82,7 +85,10 @@ function App() {
const loadConfigStatus = async () => { const loadConfigStatus = async () => {
const status = await window.api.getClaudeConfigStatus(); 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 // 生成唯一ID
@@ -137,7 +143,7 @@ function App() {
showNotification( showNotification(
"切换成功!请重启 Claude Code 终端以生效", "切换成功!请重启 Claude Code 终端以生效",
"success", "success",
2000 2000,
); );
} else { } else {
showNotification("切换失败,请检查配置", "error"); showNotification("切换失败,请检查配置", "error");
@@ -147,18 +153,22 @@ function App() {
// 自动导入现有配置为"default"供应商 // 自动导入现有配置为"default"供应商
const handleAutoImportDefault = async () => { const handleAutoImportDefault = async () => {
try { try {
const result = await window.api.importCurrentConfigAsDefault() const result = await window.api.importCurrentConfigAsDefault();
if (result.success) { if (result.success) {
await loadProviders() await loadProviders();
showNotification("已自动导入现有配置为 default 供应商", "success", 3000) showNotification(
"已自动导入现有配置为 default 供应商",
"success",
3000,
);
} }
// 如果导入失败(比如没有现有配置),静默处理,不显示错误 // 如果导入失败(比如没有现有配置),静默处理,不显示错误
} catch (error) { } catch (error) {
console.error('自动导入默认配置失败:', error) console.error("自动导入默认配置失败:", error);
// 静默处理,不影响用户体验 // 静默处理,不影响用户体验
} }
} };
const handleOpenConfigFolder = async () => { const handleOpenConfigFolder = async () => {
await window.api.openConfigFolder(); await window.api.openConfigFolder();

View File

@@ -40,7 +40,10 @@
} }
/* 左侧占位以保证标题居中(与右侧关闭按钮宽度相当) */ /* 左侧占位以保证标题居中(与右侧关闭按钮宽度相当) */
.modal-spacer { width: 32px; flex: 0 0 32px; } .modal-spacer {
width: 32px;
flex: 0 0 32px;
}
.modal-title { .modal-title {
flex: 1; flex: 1;
@@ -69,7 +72,8 @@
color: #fff; color: #fff;
} }
.modal-form { /* 表单外层包裹 body + footer */ .modal-form {
/* 表单外层包裹 body + footer */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 1 auto; flex: 1 1 auto;
@@ -175,7 +179,8 @@
border-color: #3498db; border-color: #3498db;
} }
.modal-footer { /* 固定在弹窗底部(非滚动区) */ .modal-footer {
/* 固定在弹窗底部(非滚动区) */
display: flex; display: flex;
gap: 1rem; gap: 1rem;
justify-content: flex-end; justify-content: flex-end;

View File

@@ -68,7 +68,9 @@
cursor: pointer; cursor: pointer;
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 500; font-weight: 500;
transition: background-color 0.2s, transform 0.1s; transition:
background-color 0.2s,
transform 0.1s;
min-width: 70px; min-width: 70px;
} }

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from "react";
import './ConfirmDialog.css'; import "./ConfirmDialog.css";
interface ConfirmDialogProps { interface ConfirmDialogProps {
isOpen: boolean; isOpen: boolean;
@@ -15,10 +15,10 @@ export const ConfirmDialog: React.FC<ConfirmDialogProps> = ({
isOpen, isOpen,
title, title,
message, message,
confirmText = '确定', confirmText = "确定",
cancelText = '取消', cancelText = "取消",
onConfirm, onConfirm,
onCancel onCancel,
}) => { }) => {
if (!isOpen) return null; if (!isOpen) return null;

View File

@@ -1,20 +1,24 @@
import React from 'react' import React from "react";
import { Provider } from '../types' import { Provider } from "../types";
import ProviderForm from './ProviderForm' import ProviderForm from "./ProviderForm";
interface EditProviderModalProps { interface EditProviderModalProps {
provider: Provider provider: Provider;
onSave: (provider: Provider) => void onSave: (provider: Provider) => void;
onClose: () => void onClose: () => void;
} }
const EditProviderModal: React.FC<EditProviderModalProps> = ({ provider, onSave, onClose }) => { const EditProviderModal: React.FC<EditProviderModalProps> = ({
const handleSubmit = (data: Omit<Provider, 'id'>) => { provider,
onSave,
onClose,
}) => {
const handleSubmit = (data: Omit<Provider, "id">) => {
onSave({ onSave({
...provider, ...provider,
...data ...data,
}) });
} };
return ( return (
<ProviderForm <ProviderForm
@@ -25,7 +29,7 @@ const EditProviderModal: React.FC<EditProviderModalProps> = ({ provider, onSave,
onSubmit={handleSubmit} onSubmit={handleSubmit}
onClose={onClose} onClose={onClose}
/> />
) );
} };
export default EditProviderModal export default EditProviderModal;

View File

@@ -80,7 +80,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
}; };
const handleChange = ( const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => { ) => {
const { name, value } = e.target; const { name, value } = e.target;
@@ -117,7 +117,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
// 更新JSON配置 // 更新JSON配置
const updatedConfig = updateCoAuthoredSetting( const updatedConfig = updateCoAuthoredSetting(
formData.settingsConfig, formData.settingsConfig,
checked checked,
); );
setFormData({ setFormData({
...formData, ...formData,
@@ -152,7 +152,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const configString = setApiKeyInConfig( const configString = setApiKeyInConfig(
formData.settingsConfig, formData.settingsConfig,
key.trim(), key.trim(),
{ createIfMissing: selectedPreset !== null } { createIfMissing: selectedPreset !== null },
); );
// 更新表单配置 // 更新表单配置
@@ -174,7 +174,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
const parsedKey = getApiKeyFromConfig( const parsedKey = getApiKeyFromConfig(
JSON.stringify(initialData.settingsConfig) JSON.stringify(initialData.settingsConfig),
); );
if (parsedKey) setApiKey(parsedKey); if (parsedKey) setApiKey(parsedKey);
} }
@@ -255,7 +255,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
/> />
</div> </div>
<div className={`form-group api-key-group ${!showApiKey ? 'hidden' : ''}`}> <div
className={`form-group api-key-group ${!showApiKey ? "hidden" : ""}`}
>
<label htmlFor="apiKey">API Key *</label> <label htmlFor="apiKey">API Key *</label>
<input <input
type="text" type="text"

View File

@@ -1,13 +1,13 @@
import React from 'react' import React from "react";
import { Provider } from '../types' import { Provider } from "../types";
import './ProviderList.css' import "./ProviderList.css";
interface ProviderListProps { interface ProviderListProps {
providers: Record<string, Provider> providers: Record<string, Provider>;
currentProviderId: string currentProviderId: string;
onSwitch: (id: string) => void onSwitch: (id: string) => void;
onDelete: (id: string) => void onDelete: (id: string) => void;
onEdit: (id: string) => void onEdit: (id: string) => void;
} }
const ProviderList: React.FC<ProviderListProps> = ({ const ProviderList: React.FC<ProviderListProps> = ({
@@ -15,28 +15,28 @@ const ProviderList: React.FC<ProviderListProps> = ({
currentProviderId, currentProviderId,
onSwitch, onSwitch,
onDelete, onDelete,
onEdit onEdit,
}) => { }) => {
// 提取API地址 // 提取API地址
const getApiUrl = (provider: Provider): string => { const getApiUrl = (provider: Provider): string => {
try { try {
const config = provider.settingsConfig const config = provider.settingsConfig;
if (config?.env?.ANTHROPIC_BASE_URL) { if (config?.env?.ANTHROPIC_BASE_URL) {
return config.env.ANTHROPIC_BASE_URL return config.env.ANTHROPIC_BASE_URL;
} }
return '未设置' return "未设置";
} catch { } catch {
return '配置错误' return "配置错误";
}
} }
};
const handleUrlClick = async (url: string) => { const handleUrlClick = async (url: string) => {
try { try {
await window.api.openExternal(url) await window.api.openExternal(url);
} catch (error) { } catch (error) {
console.error('打开链接失败:', error) console.error("打开链接失败:", error);
}
} }
};
return ( return (
<div className="provider-list"> <div className="provider-list">
@@ -48,25 +48,27 @@ const ProviderList: React.FC<ProviderListProps> = ({
) : ( ) : (
<div className="provider-items"> <div className="provider-items">
{Object.values(providers).map((provider) => { {Object.values(providers).map((provider) => {
const isCurrent = provider.id === currentProviderId const isCurrent = provider.id === currentProviderId;
return ( return (
<div <div
key={provider.id} key={provider.id}
className={`provider-item ${isCurrent ? 'current' : ''}`} className={`provider-item ${isCurrent ? "current" : ""}`}
> >
<div className="provider-info"> <div className="provider-info">
<div className="provider-name"> <div className="provider-name">
<span>{provider.name}</span> <span>{provider.name}</span>
{isCurrent && <span className="current-badge">使</span>} {isCurrent && (
<span className="current-badge">使</span>
)}
</div> </div>
<div className="provider-url"> <div className="provider-url">
{provider.websiteUrl ? ( {provider.websiteUrl ? (
<a <a
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault() e.preventDefault();
handleUrlClick(provider.websiteUrl!) handleUrlClick(provider.websiteUrl!);
}} }}
className="url-link" className="url-link"
title={`访问 ${provider.websiteUrl}`} title={`访问 ${provider.websiteUrl}`}
@@ -105,12 +107,12 @@ const ProviderList: React.FC<ProviderListProps> = ({
</button> </button>
</div> </div>
</div> </div>
) );
})} })}
</div> </div>
)} )}
</div> </div>
) );
} };
export default ProviderList export default ProviderList;

View File

@@ -63,5 +63,4 @@ export const providerPresets: ProviderPreset[] = [
}, },
}, },
}, },
]; ];

View File

@@ -5,9 +5,9 @@
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family:
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
sans-serif; "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5; background-color: #f5f5f5;

View File

@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { Provider } from '../types'; import { Provider } from "../types";
// 定义配置状态类型 // 定义配置状态类型
interface ConfigStatus { interface ConfigStatus {
@@ -19,9 +19,9 @@ export const tauriAPI = {
// 获取所有供应商 // 获取所有供应商
getProviders: async (): Promise<Record<string, Provider>> => { getProviders: async (): Promise<Record<string, Provider>> => {
try { try {
return await invoke('get_providers'); return await invoke("get_providers");
} catch (error) { } catch (error) {
console.error('获取供应商列表失败:', error); console.error("获取供应商列表失败:", error);
return {}; return {};
} }
}, },
@@ -29,19 +29,19 @@ export const tauriAPI = {
// 获取当前供应商ID // 获取当前供应商ID
getCurrentProvider: async (): Promise<string> => { getCurrentProvider: async (): Promise<string> => {
try { try {
return await invoke('get_current_provider'); return await invoke("get_current_provider");
} catch (error) { } catch (error) {
console.error('获取当前供应商失败:', error); console.error("获取当前供应商失败:", error);
return ''; return "";
} }
}, },
// 添加供应商 // 添加供应商
addProvider: async (provider: Provider): Promise<boolean> => { addProvider: async (provider: Provider): Promise<boolean> => {
try { try {
return await invoke('add_provider', { provider }); return await invoke("add_provider", { provider });
} catch (error) { } catch (error) {
console.error('添加供应商失败:', error); console.error("添加供应商失败:", error);
throw error; throw error;
} }
}, },
@@ -49,9 +49,9 @@ export const tauriAPI = {
// 更新供应商 // 更新供应商
updateProvider: async (provider: Provider): Promise<boolean> => { updateProvider: async (provider: Provider): Promise<boolean> => {
try { try {
return await invoke('update_provider', { provider }); return await invoke("update_provider", { provider });
} catch (error) { } catch (error) {
console.error('更新供应商失败:', error); console.error("更新供应商失败:", error);
throw error; throw error;
} }
}, },
@@ -59,9 +59,9 @@ export const tauriAPI = {
// 删除供应商 // 删除供应商
deleteProvider: async (id: string): Promise<boolean> => { deleteProvider: async (id: string): Promise<boolean> => {
try { try {
return await invoke('delete_provider', { id }); return await invoke("delete_provider", { id });
} catch (error) { } catch (error) {
console.error('删除供应商失败:', error); console.error("删除供应商失败:", error);
throw error; throw error;
} }
}, },
@@ -69,9 +69,9 @@ export const tauriAPI = {
// 切换供应商 // 切换供应商
switchProvider: async (providerId: string): Promise<boolean> => { switchProvider: async (providerId: string): Promise<boolean> => {
try { try {
return await invoke('switch_provider', { id: providerId }); return await invoke("switch_provider", { id: providerId });
} catch (error) { } catch (error) {
console.error('切换供应商失败:', error); console.error("切换供应商失败:", error);
return false; return false;
} }
}, },
@@ -79,16 +79,16 @@ export const tauriAPI = {
// 导入当前配置为默认供应商 // 导入当前配置为默认供应商
importCurrentConfigAsDefault: async (): Promise<ImportResult> => { importCurrentConfigAsDefault: async (): Promise<ImportResult> => {
try { try {
const success = await invoke<boolean>('import_default_config'); const success = await invoke<boolean>("import_default_config");
return { return {
success, success,
message: success ? '成功导入默认配置' : '导入失败' message: success ? "成功导入默认配置" : "导入失败",
}; };
} catch (error) { } catch (error) {
console.error('导入默认配置失败:', error); console.error("导入默认配置失败:", error);
return { return {
success: false, success: false,
message: String(error) message: String(error),
}; };
} }
}, },
@@ -96,23 +96,23 @@ export const tauriAPI = {
// 获取 Claude Code 配置文件路径 // 获取 Claude Code 配置文件路径
getClaudeCodeConfigPath: async (): Promise<string> => { getClaudeCodeConfigPath: async (): Promise<string> => {
try { try {
return await invoke('get_claude_code_config_path'); return await invoke("get_claude_code_config_path");
} catch (error) { } catch (error) {
console.error('获取配置路径失败:', error); console.error("获取配置路径失败:", error);
return ''; return "";
} }
}, },
// 获取 Claude Code 配置状态 // 获取 Claude Code 配置状态
getClaudeConfigStatus: async (): Promise<ConfigStatus> => { getClaudeConfigStatus: async (): Promise<ConfigStatus> => {
try { try {
return await invoke('get_claude_config_status'); return await invoke("get_claude_config_status");
} catch (error) { } catch (error) {
console.error('获取配置状态失败:', error); console.error("获取配置状态失败:", error);
return { return {
exists: false, exists: false,
path: '', path: "",
error: String(error) error: String(error),
}; };
} }
}, },
@@ -120,34 +120,33 @@ export const tauriAPI = {
// 打开配置文件夹 // 打开配置文件夹
openConfigFolder: async (): Promise<void> => { openConfigFolder: async (): Promise<void> => {
try { try {
await invoke('open_config_folder'); await invoke("open_config_folder");
} catch (error) { } catch (error) {
console.error('打开配置文件夹失败:', error); console.error("打开配置文件夹失败:", error);
} }
}, },
// 打开外部链接 // 打开外部链接
openExternal: async (url: string): Promise<void> => { openExternal: async (url: string): Promise<void> => {
try { try {
await invoke('open_external', { url }); await invoke("open_external", { url });
} catch (error) { } catch (error) {
console.error('打开外部链接失败:', error); console.error("打开外部链接失败:", error);
} }
}, },
// 选择配置文件Tauri 暂不实现,保留接口兼容性) // 选择配置文件Tauri 暂不实现,保留接口兼容性)
selectConfigFile: async (): Promise<string | null> => { selectConfigFile: async (): Promise<string | null> => {
console.warn('selectConfigFile 在 Tauri 版本中暂不支持'); console.warn("selectConfigFile 在 Tauri 版本中暂不支持");
return null; return null;
} },
}; };
// 创建全局 API 对象,兼容现有代码 // 创建全局 API 对象,兼容现有代码
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
// 绑定到 window.api避免 Electron 命名造成误解 // 绑定到 window.api避免 Electron 命名造成误解
// API 内部已做 try/catch非 Tauri 环境下也会安全返回默认值 // API 内部已做 try/catch非 Tauri 环境下也会安全返回默认值
(window as any).api = tauriAPI; (window as any).api = tauriAPI;
} }
export default tauriAPI; export default tauriAPI;

View File

@@ -1,24 +1,24 @@
import React from 'react' import React from "react";
import ReactDOM from 'react-dom/client' import ReactDOM from "react-dom/client";
import App from './App' import App from "./App";
import './index.css' import "./index.css";
// 导入 Tauri API自动绑定到 window.api // 导入 Tauri API自动绑定到 window.api
import './lib/tauri-api' import "./lib/tauri-api";
// 根据平台添加 body class便于平台特定样式 // 根据平台添加 body class便于平台特定样式
try { try {
const ua = navigator.userAgent || '' const ua = navigator.userAgent || "";
const plat = (navigator.platform || '').toLowerCase() const plat = (navigator.platform || "").toLowerCase();
const isMac = /mac/i.test(ua) || plat.includes('mac') const isMac = /mac/i.test(ua) || plat.includes("mac");
if (isMac) { if (isMac) {
document.body.classList.add('is-mac') document.body.classList.add("is-mac");
} }
} catch { } catch {
// 忽略平台检测失败 // 忽略平台检测失败
} }
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode> </React.StrictMode>,
) );

View File

@@ -1,91 +1,97 @@
// 供应商配置处理工具函数 // 供应商配置处理工具函数
// 处理includeCoAuthoredBy字段的添加/删除 // 处理includeCoAuthoredBy字段的添加/删除
export const updateCoAuthoredSetting = (jsonString: string, disable: boolean): string => { export const updateCoAuthoredSetting = (
jsonString: string,
disable: boolean,
): string => {
try { try {
const config = JSON.parse(jsonString) const config = JSON.parse(jsonString);
if (disable) { if (disable) {
// 添加或更新includeCoAuthoredBy字段 // 添加或更新includeCoAuthoredBy字段
config.includeCoAuthoredBy = false config.includeCoAuthoredBy = false;
} else { } else {
// 删除includeCoAuthoredBy字段 // 删除includeCoAuthoredBy字段
delete config.includeCoAuthoredBy delete config.includeCoAuthoredBy;
} }
return JSON.stringify(config, null, 2) return JSON.stringify(config, null, 2);
} catch (err) { } catch (err) {
// 如果JSON解析失败返回原始字符串 // 如果JSON解析失败返回原始字符串
return jsonString return jsonString;
} }
} };
// 从JSON配置中检查是否包含includeCoAuthoredBy设置 // 从JSON配置中检查是否包含includeCoAuthoredBy设置
export const checkCoAuthoredSetting = (jsonString: string): boolean => { export const checkCoAuthoredSetting = (jsonString: string): boolean => {
try { try {
const config = JSON.parse(jsonString) const config = JSON.parse(jsonString);
return config.includeCoAuthoredBy === false return config.includeCoAuthoredBy === false;
} catch (err) { } catch (err) {
return false return false;
} }
} };
// 从JSON配置中提取并处理官网地址 // 从JSON配置中提取并处理官网地址
export const extractWebsiteUrl = (jsonString: string): string => { export const extractWebsiteUrl = (jsonString: string): string => {
try { try {
const config = JSON.parse(jsonString) const config = JSON.parse(jsonString);
const baseUrl = config?.env?.ANTHROPIC_BASE_URL const baseUrl = config?.env?.ANTHROPIC_BASE_URL;
if (baseUrl && typeof baseUrl === 'string') { if (baseUrl && typeof baseUrl === "string") {
// 去掉 "api." 前缀 // 去掉 "api." 前缀
return baseUrl.replace(/^https?:\/\/api\./, 'https://') return baseUrl.replace(/^https?:\/\/api\./, "https://");
} }
} catch (err) { } catch (err) {
// 忽略JSON解析错误 // 忽略JSON解析错误
} }
return '' return "";
} };
// 读取配置中的 API Keyenv.ANTHROPIC_AUTH_TOKEN // 读取配置中的 API Keyenv.ANTHROPIC_AUTH_TOKEN
export const getApiKeyFromConfig = (jsonString: string): string => { export const getApiKeyFromConfig = (jsonString: string): string => {
try { try {
const config = JSON.parse(jsonString) const config = JSON.parse(jsonString);
const key = config?.env?.ANTHROPIC_AUTH_TOKEN const key = config?.env?.ANTHROPIC_AUTH_TOKEN;
return typeof key === 'string' ? key : '' return typeof key === "string" ? key : "";
} catch (err) { } catch (err) {
return '' return "";
} }
} };
// 判断配置中是否存在 API Key 字段 // 判断配置中是否存在 API Key 字段
export const hasApiKeyField = (jsonString: string): boolean => { export const hasApiKeyField = (jsonString: string): boolean => {
try { try {
const config = JSON.parse(jsonString) const config = JSON.parse(jsonString);
return Object.prototype.hasOwnProperty.call(config?.env ?? {}, 'ANTHROPIC_AUTH_TOKEN') return Object.prototype.hasOwnProperty.call(
config?.env ?? {},
"ANTHROPIC_AUTH_TOKEN",
);
} catch (err) { } catch (err) {
return false return false;
} }
} };
// 写入/更新配置中的 API Key默认不新增缺失字段 // 写入/更新配置中的 API Key默认不新增缺失字段
export const setApiKeyInConfig = ( export const setApiKeyInConfig = (
jsonString: string, jsonString: string,
apiKey: string, apiKey: string,
options: { createIfMissing?: boolean } = {} options: { createIfMissing?: boolean } = {},
): string => { ): string => {
const { createIfMissing = false } = options const { createIfMissing = false } = options;
try { try {
const config = JSON.parse(jsonString) const config = JSON.parse(jsonString);
if (!config.env) { if (!config.env) {
if (!createIfMissing) return jsonString if (!createIfMissing) return jsonString;
config.env = {} config.env = {};
} }
if (!('ANTHROPIC_AUTH_TOKEN' in config.env) && !createIfMissing) { if (!("ANTHROPIC_AUTH_TOKEN" in config.env) && !createIfMissing) {
return jsonString return jsonString;
} }
config.env.ANTHROPIC_AUTH_TOKEN = apiKey config.env.ANTHROPIC_AUTH_TOKEN = apiKey;
return JSON.stringify(config, null, 2) return JSON.stringify(config, null, 2);
} catch (err) { } catch (err) {
return jsonString return jsonString;
} }
} };

2
src/vite-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
import { Provider } from './types'; import { Provider } from "./types";
interface ImportResult { interface ImportResult {
success: boolean; success: boolean;