Files
cc-switch/src-tauri/src/commands.rs

468 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::collections::HashMap;
use tauri::State;
use tauri_plugin_opener::OpenerExt;
use crate::app_config::AppType;
use crate::codex_config;
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>,
app: Option<String>,
) -> Result<HashMap<String, Provider>, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
let config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
Ok(manager.get_all_providers().clone())
}
/// 获取当前供应商ID
#[tauri::command]
pub async fn get_current_provider(
state: State<'_, AppState>,
app: Option<String>,
) -> Result<String, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
let config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
Ok(manager.current.clone())
}
/// 添加供应商
#[tauri::command]
pub async fn add_provider(
state: State<'_, AppState>,
app: Option<String>,
provider: Provider,
) -> Result<bool, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
let mut config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
// 根据应用类型保存配置文件
match app_type {
AppType::Codex => {
// Codex: 保存两个文件
codex_config::save_codex_provider_config(
&provider.id,
&provider.name,
&provider.settings_config,
)?;
}
AppType::Claude => {
// Claude: 使用原有逻辑
use crate::config::{get_provider_config_path, write_json_file};
let config_path = get_provider_config_path(&provider.id, Some(&provider.name));
write_json_file(&config_path, &provider.settings_config)?;
}
}
manager.providers.insert(provider.id.clone(), provider);
// 保存配置
drop(config); // 释放锁
state.save()?;
Ok(true)
}
/// 更新供应商
#[tauri::command]
pub async fn update_provider(
state: State<'_, AppState>,
app: Option<String>,
provider: Provider,
) -> Result<bool, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
let mut config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
// 检查供应商是否存在
if !manager.providers.contains_key(&provider.id) {
return Err(format!("供应商不存在: {}", provider.id));
}
// 如果名称改变了,需要处理配置文件
if let Some(old_provider) = manager.providers.get(&provider.id) {
if old_provider.name != provider.name {
// 删除旧配置文件
match app_type {
AppType::Codex => {
codex_config::delete_codex_provider_config(&provider.id, &old_provider.name)
.ok();
}
AppType::Claude => {
use crate::config::{delete_file, get_provider_config_path};
let old_config_path =
get_provider_config_path(&provider.id, Some(&old_provider.name));
delete_file(&old_config_path).ok();
}
}
}
}
// 保存新配置文件
match app_type {
AppType::Codex => {
codex_config::save_codex_provider_config(
&provider.id,
&provider.name,
&provider.settings_config,
)?;
}
AppType::Claude => {
use crate::config::{get_provider_config_path, write_json_file};
let config_path = get_provider_config_path(&provider.id, Some(&provider.name));
write_json_file(&config_path, &provider.settings_config)?;
}
}
manager.providers.insert(provider.id.clone(), provider);
// 保存配置
drop(config); // 释放锁
state.save()?;
Ok(true)
}
/// 删除供应商
#[tauri::command]
pub async fn delete_provider(
state: State<'_, AppState>,
app: Option<String>,
id: String,
) -> Result<bool, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
let mut config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
// 检查是否为当前供应商
if manager.current == id {
return Err("不能删除当前正在使用的供应商".to_string());
}
// 获取供应商信息
let provider = manager
.providers
.get(&id)
.ok_or_else(|| format!("供应商不存在: {}", id))?
.clone();
// 删除配置文件
match app_type {
AppType::Codex => {
codex_config::delete_codex_provider_config(&id, &provider.name)?;
}
AppType::Claude => {
use crate::config::{delete_file, get_provider_config_path};
let config_path = get_provider_config_path(&id, Some(&provider.name));
delete_file(&config_path)?;
}
}
// 从管理器删除
manager.providers.remove(&id);
// 保存配置
drop(config); // 释放锁
state.save()?;
Ok(true)
}
/// 切换供应商
#[tauri::command]
pub async fn switch_provider(
state: State<'_, AppState>,
app: Option<String>,
id: String,
) -> Result<bool, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
let mut config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
// 检查供应商是否存在
let provider = manager
.providers
.get(&id)
.ok_or_else(|| format!("供应商不存在: {}", id))?
.clone();
// 根据应用类型执行切换
match app_type {
AppType::Codex => {
// 备份当前配置(如果存在)
if !manager.current.is_empty() {
if let Some(current_provider) = manager.providers.get(&manager.current) {
codex_config::backup_codex_config(&manager.current, &current_provider.name)?;
log::info!("已备份当前 Codex 供应商配置: {}", current_provider.name);
}
}
// 恢复目标供应商配置
codex_config::restore_codex_provider_config(&id, &provider.name)?;
}
AppType::Claude => {
// 使用原有的 Claude 切换逻辑
use crate::config::{
backup_config, copy_file, get_claude_settings_path, get_provider_config_path,
};
let settings_path = get_claude_settings_path();
let provider_config_path = get_provider_config_path(&id, Some(&provider.name));
// 检查供应商配置文件是否存在
if !provider_config_path.exists() {
return Err(format!(
"供应商配置文件不存在: {}",
provider_config_path.display()
));
}
// 如果当前有配置,先备份到当前供应商
if settings_path.exists() && !manager.current.is_empty() {
if let Some(current_provider) = manager.providers.get(&manager.current) {
let current_provider_path =
get_provider_config_path(&manager.current, Some(&current_provider.name));
backup_config(&settings_path, &current_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))?;
}
// 复制新供应商配置到主配置
copy_file(&provider_config_path, &settings_path)?;
}
}
// 更新当前供应商
manager.current = id;
log::info!("成功切换到供应商: {}", provider.name);
// 保存配置
drop(config); // 释放锁
state.save()?;
Ok(true)
}
/// 导入当前配置为默认供应商
#[tauri::command]
pub async fn import_default_config(
state: State<'_, AppState>,
app: Option<String>,
) -> Result<bool, String> {
let app_type = app.as_deref().map(|s| s.into()).unwrap_or(AppType::Claude);
// 若已存在 default 供应商,则直接返回,避免重复导入
{
let config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
if let Some(manager) = config.get_manager(&app_type) {
if manager.get_all_providers().contains_key("default") {
return Ok(true);
}
}
}
// 根据应用类型导入配置
let settings_config = match app_type {
AppType::Codex => codex_config::import_current_codex_config()?,
AppType::Claude => import_current_config_as_default()?,
};
// 创建默认供应商
let provider = Provider::with_id(
"default".to_string(),
"default".to_string(),
settings_config,
None,
);
// 添加到管理器
let mut config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
let manager = config
.get_manager_mut(&app_type)
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
// 根据应用类型保存配置文件
match app_type {
AppType::Codex => {
codex_config::save_codex_provider_config(
&provider.id,
&provider.name,
&provider.settings_config,
)?;
}
AppType::Claude => {
use crate::config::{get_provider_config_path, write_json_file};
let config_path = get_provider_config_path(&provider.id, Some(&provider.name));
write_json_file(&config_path, &provider.settings_config)?;
}
}
manager.providers.insert(provider.id.clone(), provider);
// 如果没有当前供应商,设置为 default
if manager.current.is_empty() {
manager.current = "default".to_string();
}
// 保存配置
drop(config); // 释放锁
state.save()?;
Ok(true)
}
/// 获取 Claude Code 配置状态
#[tauri::command]
pub async fn get_claude_config_status() -> Result<ConfigStatus, String> {
Ok(crate::config::get_claude_config_status())
}
/// 获取应用配置状态(通用)
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
#[tauri::command]
pub async fn get_config_status(
app_type: Option<AppType>,
app: Option<String>,
appType: Option<String>,
) -> Result<ConfigStatus, String> {
let app = app_type
.or_else(|| app.as_deref().map(|s| s.into()))
.or_else(|| appType.as_deref().map(|s| s.into()))
.unwrap_or(AppType::Claude);
match app {
AppType::Claude => Ok(crate::config::get_claude_config_status()),
AppType::Codex => {
use crate::codex_config::{get_codex_auth_path, get_codex_config_dir};
let auth_path = get_codex_auth_path();
// 放宽:只要 auth.json 存在即可认为已配置config.toml 允许为空
let exists = auth_path.exists();
let path = get_codex_config_dir().to_string_lossy().to_string();
Ok(ConfigStatus { exists, path })
}
}
}
/// 获取 Claude Code 配置文件路径
#[tauri::command]
pub async fn get_claude_code_config_path() -> Result<String, String> {
Ok(get_claude_settings_path().to_string_lossy().to_string())
}
/// 打开配置文件夹
/// 兼容两种参数:`app_type`(推荐)或 `app`(字符串)
#[tauri::command]
pub async fn open_config_folder(
app: tauri::AppHandle,
app_type: Option<AppType>,
app_str: Option<String>,
appType: Option<String>,
) -> Result<bool, String> {
let app_type = app_type
.or_else(|| app_str.as_deref().map(|s| s.into()))
.or_else(|| appType.as_deref().map(|s| s.into()))
.unwrap_or(AppType::Claude);
let config_dir = match app_type {
AppType::Claude => crate::config::get_claude_config_dir(),
AppType::Codex => crate::codex_config::get_codex_config_dir(),
};
// 确保目录存在
if !config_dir.exists() {
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::<String>)
.map_err(|e| format!("打开文件夹失败: {}", e))?;
Ok(true)
}
/// 打开外部链接
#[tauri::command]
pub async fn open_external(app: tauri::AppHandle, url: String) -> Result<bool, String> {
// 规范化 URL缺少协议时默认加 https://
let url = if url.starts_with("http://") || url.starts_with("https://") {
url
} else {
format!("https://{}", url)
};
// 使用 opener 插件打开链接
app.opener()
.open_url(&url, None::<String>)
.map_err(|e| format!("打开链接失败: {}", e))?;
Ok(true)
}