- feat(codex): 引入 Codex 应用与供应商切换(管理 auth.json/config.toml,支持备份与恢复)

- feat(core): 多应用配置 v2(claude/codex)与 ProviderManager;支持 v1→v2 自动迁移
- feat(ui): 新增 Codex 页签与双编辑器表单;统一 window.api 支持 app 参数
- feat(tauri): 新增 get_config_status/open_config_folder/open_external 命令并适配 Codex
- fix(codex): 主配置缺失时不执行默认导入(对齐 Claude 行为)
- chore: 配置目录展示与重启提示等细节优化
This commit is contained in:
Jason
2025-08-30 21:54:11 +08:00
parent 0e803b53d8
commit c10ace7a84
13 changed files with 891 additions and 279 deletions

View File

@@ -2,6 +2,8 @@ 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;
@@ -10,38 +12,82 @@ use crate::store::AppState;
#[tauri::command]
pub async fn get_providers(
state: State<'_, AppState>,
app: Option<String>,
) -> Result<HashMap<String, Provider>, String> {
let manager = state
.provider_manager
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>) -> Result<String, String> {
let manager = state
.provider_manager
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>, provider: Provider) -> Result<bool, String> {
let mut manager = state
.provider_manager
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))?;
manager.add_provider(provider)?;
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(manager); // 释放锁
drop(config); // 释放锁
state.save()?;
Ok(true)
@@ -51,17 +97,64 @@ pub async fn add_provider(state: State<'_, AppState>, provider: Provider) -> Res
#[tauri::command]
pub async fn update_provider(
state: State<'_, AppState>,
app: Option<String>,
provider: Provider,
) -> Result<bool, String> {
let mut manager = state
.provider_manager
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))?;
manager.update_provider(provider)?;
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(manager); // 释放锁
drop(config); // 释放锁
state.save()?;
Ok(true)
@@ -69,16 +162,51 @@ pub async fn update_provider(
/// 删除供应商
#[tauri::command]
pub async fn delete_provider(state: State<'_, AppState>, id: String) -> Result<bool, String> {
let mut manager = state
.provider_manager
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))?;
manager.delete_provider(&id)?;
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(manager); // 释放锁
drop(config); // 释放锁
state.save()?;
Ok(true)
@@ -86,16 +214,87 @@ pub async fn delete_provider(state: State<'_, AppState>, id: String) -> Result<b
/// 切换供应商
#[tauri::command]
pub async fn switch_provider(state: State<'_, AppState>, id: String) -> Result<bool, String> {
let mut manager = state
.provider_manager
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))?;
manager.switch_provider(&id)?;
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(manager); // 释放锁
drop(config); // 释放锁
state.save()?;
Ok(true)
@@ -103,20 +302,31 @@ pub async fn switch_provider(state: State<'_, AppState>, id: String) -> Result<b
/// 导入当前配置为默认供应商
#[tauri::command]
pub async fn import_default_config(state: State<'_, AppState>) -> Result<bool, String> {
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 manager = state
.provider_manager
let config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
if manager.get_all_providers().contains_key("default") {
return Ok(true);
if let Some(manager) = config.get_manager(&app_type) {
if manager.get_all_providers().contains_key("default") {
return Ok(true);
}
}
}
// 导入配置
let settings_config = import_current_config_as_default()?;
// 根据应用类型导入配置
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(
@@ -127,12 +337,32 @@ pub async fn import_default_config(state: State<'_, AppState>) -> Result<bool, S
);
// 添加到管理器
let mut manager = state
.provider_manager
let mut config = state
.config
.lock()
.map_err(|e| format!("获取锁失败: {}", e))?;
manager.add_provider(provider)?;
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() {
@@ -140,7 +370,7 @@ pub async fn import_default_config(state: State<'_, AppState>) -> Result<bool, S
}
// 保存配置
drop(manager); // 释放锁
drop(config); // 释放锁
state.save()?;
Ok(true)
@@ -152,6 +382,27 @@ pub async fn get_claude_config_status() -> Result<ConfigStatus, String> {
Ok(crate::config::get_claude_config_status())
}
/// 获取应用配置状态(通用)
#[tauri::command]
pub async fn get_config_status(app_type: Option<AppType>) -> Result<ConfigStatus, String> {
let app = app_type.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_path};
let auth_path = get_codex_auth_path();
let config_path = get_codex_config_path();
// Codex 需要两个文件都存在才算配置存在
let exists = auth_path.exists() && config_path.exists();
let path = format!("~/.codex/");
Ok(ConfigStatus { exists, path })
}
}
}
/// 获取 Claude Code 配置文件路径
#[tauri::command]
pub async fn get_claude_code_config_path() -> Result<String, String> {
@@ -160,8 +411,13 @@ pub async fn get_claude_code_config_path() -> Result<String, String> {
/// 打开配置文件夹
#[tauri::command]
pub async fn open_config_folder(app: tauri::AppHandle) -> Result<bool, String> {
let config_dir = crate::config::get_claude_config_dir();
pub async fn open_config_folder(app: tauri::AppHandle, app_type: Option<AppType>) -> Result<bool, String> {
let app_type = app_type.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() {