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:
@@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 插件打开文件夹
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,23 +16,22 @@ 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
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
cc_switch_lib::run();
|
cc_switch_lib::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(¤t_provider.name));
|
let current_provider_path =
|
||||||
|
get_provider_config_path(&self.current, Some(¤t_provider.name));
|
||||||
backup_config(&settings_path, ¤t_provider_path)?;
|
backup_config(&settings_path, ¤t_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))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制新供应商配置到主配置
|
// 复制新供应商配置到主配置
|
||||||
|
|||||||
@@ -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,11 +11,10 @@ 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()
|
});
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
provider_manager: Mutex::new(provider_manager),
|
provider_manager: Mutex::new(provider_manager),
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
30
src/App.tsx
30
src/App.tsx
@@ -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();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
display: flex; /* 纵向布局,便于底栏固定 */
|
display: flex; /* 纵向布局,便于底栏固定 */
|
||||||
flex-direction: column;
|
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 {
|
.modal-title {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -69,16 +72,17 @@
|
|||||||
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;
|
||||||
min-height: 0; /* 允许子元素正确计算高度 */
|
min-height: 0; /* 允许子元素正确计算高度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
padding: 1.25rem 1.5rem 1.5rem;
|
padding: 1.25rem 1.5rem 1.5rem;
|
||||||
overflow: auto; /* 仅内容区滚动 */
|
overflow: auto; /* 仅内容区滚动 */
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -63,5 +63,4 @@ export const providerPresets: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
24
src/main.tsx
24
src/main.tsx
@@ -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>,
|
||||||
)
|
);
|
||||||
|
|||||||
@@ -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 Key(env.ANTHROPIC_AUTH_TOKEN)
|
// 读取配置中的 API Key(env.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
2
src/vite-env.d.ts
vendored
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user