refactor(types): introduce Settings and apply in API
- style(prettier): format src files - style(rustfmt): format Rust sources - refactor(tauri-api): type-safe getSettings/saveSettings - refactor(d.ts): declare window.api with Settings [skip ci]
This commit is contained in:
@@ -4,9 +4,9 @@ use std::path::PathBuf;
|
|||||||
use crate::config::{
|
use crate::config::{
|
||||||
atomic_write, delete_file, sanitize_provider_name, write_json_file, write_text_file,
|
atomic_write, delete_file, sanitize_provider_name, write_json_file, write_text_file,
|
||||||
};
|
};
|
||||||
|
use serde_json::Value;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
/// 获取 Codex 配置目录路径
|
/// 获取 Codex 配置目录路径
|
||||||
pub fn get_codex_config_dir() -> PathBuf {
|
pub fn get_codex_config_dir() -> PathBuf {
|
||||||
@@ -77,7 +77,8 @@ pub fn write_codex_live_atomic(auth: &Value, config_text_opt: Option<&str>) -> R
|
|||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
if !cfg_text.trim().is_empty() {
|
if !cfg_text.trim().is_empty() {
|
||||||
toml::from_str::<toml::Table>(&cfg_text).map_err(|e| format!("config.toml 格式错误: {}", e))?;
|
toml::from_str::<toml::Table>(&cfg_text)
|
||||||
|
.map_err(|e| format!("config.toml 格式错误: {}", e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第一步:写 auth.json
|
// 第一步:写 auth.json
|
||||||
@@ -121,7 +122,9 @@ pub fn validate_config_toml(text: &str) -> Result<(), String> {
|
|||||||
if text.trim().is_empty() {
|
if text.trim().is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
toml::from_str::<toml::Table>(text).map(|_| ()).map_err(|e| format!("config.toml 语法错误: {}", e))
|
toml::from_str::<toml::Table>(text)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|e| format!("config.toml 语法错误: {}", e))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 读取并校验 `~/.codex/config.toml`,返回文本(可能为空)
|
/// 读取并校验 `~/.codex/config.toml`,返回文本(可能为空)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use tauri_plugin_opener::OpenerExt;
|
|||||||
|
|
||||||
use crate::app_config::AppType;
|
use crate::app_config::AppType;
|
||||||
use crate::codex_config;
|
use crate::codex_config;
|
||||||
use crate::config::{ConfigStatus, get_claude_settings_path};
|
use crate::config::{get_claude_settings_path, ConfigStatus};
|
||||||
use crate::provider::Provider;
|
use crate::provider::Provider;
|
||||||
use crate::store::AppState;
|
use crate::store::AppState;
|
||||||
|
|
||||||
@@ -116,7 +116,9 @@ pub async fn add_provider(
|
|||||||
let manager = config
|
let manager = config
|
||||||
.get_manager_mut(&app_type)
|
.get_manager_mut(&app_type)
|
||||||
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
||||||
manager.providers.insert(provider.id.clone(), provider.clone());
|
manager
|
||||||
|
.providers
|
||||||
|
.insert(provider.id.clone(), provider.clone());
|
||||||
}
|
}
|
||||||
state.save()?;
|
state.save()?;
|
||||||
|
|
||||||
@@ -146,7 +148,10 @@ pub async fn update_provider(
|
|||||||
let manager = config
|
let manager = config
|
||||||
.get_manager(&app_type)
|
.get_manager(&app_type)
|
||||||
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
||||||
(manager.providers.contains_key(&provider.id), manager.current == provider.id)
|
(
|
||||||
|
manager.providers.contains_key(&provider.id),
|
||||||
|
manager.current == provider.id,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
if !exists {
|
if !exists {
|
||||||
return Err(format!("供应商不存在: {}", provider.id));
|
return Err(format!("供应商不存在: {}", provider.id));
|
||||||
@@ -182,7 +187,9 @@ pub async fn update_provider(
|
|||||||
let manager = config
|
let manager = config
|
||||||
.get_manager_mut(&app_type)
|
.get_manager_mut(&app_type)
|
||||||
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
||||||
manager.providers.insert(provider.id.clone(), provider.clone());
|
manager
|
||||||
|
.providers
|
||||||
|
.insert(provider.id.clone(), provider.clone());
|
||||||
}
|
}
|
||||||
state.save()?;
|
state.save()?;
|
||||||
|
|
||||||
@@ -390,7 +397,8 @@ pub async fn import_default_config(
|
|||||||
if !auth_path.exists() {
|
if !auth_path.exists() {
|
||||||
return Err("Codex 配置文件不存在".to_string());
|
return Err("Codex 配置文件不存在".to_string());
|
||||||
}
|
}
|
||||||
let auth: serde_json::Value = crate::config::read_json_file::<serde_json::Value>(&auth_path)?;
|
let auth: serde_json::Value =
|
||||||
|
crate::config::read_json_file::<serde_json::Value>(&auth_path)?;
|
||||||
let config_str = match crate::codex_config::read_and_validate_codex_config_text() {
|
let config_str = match crate::codex_config::read_and_validate_codex_config_text() {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
@@ -500,7 +508,8 @@ pub async fn open_config_folder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用 opener 插件打开文件夹
|
// 使用 opener 插件打开文件夹
|
||||||
handle.opener()
|
handle
|
||||||
|
.opener()
|
||||||
.open_path(config_dir.to_string_lossy().to_string(), None::<String>)
|
.open_path(config_dir.to_string_lossy().to_string(), None::<String>)
|
||||||
.map_err(|e| format!("打开文件夹失败: {}", e))?;
|
.map_err(|e| format!("打开文件夹失败: {}", e))?;
|
||||||
|
|
||||||
@@ -524,3 +533,68 @@ pub async fn open_external(app: tauri::AppHandle, url: String) -> Result<bool, S
|
|||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取应用配置文件路径
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_app_config_path() -> Result<String, String> {
|
||||||
|
use crate::config::get_app_config_path;
|
||||||
|
|
||||||
|
let config_path = get_app_config_path();
|
||||||
|
Ok(config_path.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 打开应用配置文件夹
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn open_app_config_folder(handle: tauri::AppHandle) -> Result<bool, String> {
|
||||||
|
use crate::config::get_app_config_dir;
|
||||||
|
|
||||||
|
let config_dir = get_app_config_dir();
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
if !config_dir.exists() {
|
||||||
|
std::fs::create_dir_all(&config_dir).map_err(|e| format!("创建目录失败: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 opener 插件打开文件夹
|
||||||
|
handle
|
||||||
|
.opener()
|
||||||
|
.open_path(config_dir.to_string_lossy().to_string(), None::<String>)
|
||||||
|
.map_err(|e| format!("打开文件夹失败: {}", e))?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取设置
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_settings(_state: State<'_, AppState>) -> Result<serde_json::Value, String> {
|
||||||
|
// 暂时返回默认设置
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"showInDock": true
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 保存设置
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn save_settings(
|
||||||
|
_state: State<'_, AppState>,
|
||||||
|
settings: serde_json::Value,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
// TODO: 实现设置保存逻辑
|
||||||
|
log::info!("保存设置: {:?}", settings);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查更新
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn check_for_updates(handle: tauri::AppHandle) -> Result<bool, String> {
|
||||||
|
// 打开 GitHub releases 页面
|
||||||
|
handle
|
||||||
|
.opener()
|
||||||
|
.open_url(
|
||||||
|
"https://github.com/yungookim/cc-switch/releases",
|
||||||
|
None::<String>,
|
||||||
|
)
|
||||||
|
.map_err(|e| format!("打开更新页面失败: {}", e))?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ pub fn archive_file(ts: u64, category: &str, src: &Path) -> Result<Option<PathBu
|
|||||||
Ok(Some(dest))
|
Ok(Some(dest))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 清理供应商名称,确保文件名安全
|
/// 清理供应商名称,确保文件名安全
|
||||||
pub fn sanitize_provider_name(name: &str) -> String {
|
pub fn sanitize_provider_name(name: &str) -> String {
|
||||||
name.chars()
|
name.chars()
|
||||||
|
|||||||
@@ -326,6 +326,11 @@ pub fn run() {
|
|||||||
commands::get_claude_code_config_path,
|
commands::get_claude_code_config_path,
|
||||||
commands::open_config_folder,
|
commands::open_config_folder,
|
||||||
commands::open_external,
|
commands::open_external,
|
||||||
|
commands::get_app_config_path,
|
||||||
|
commands::open_app_config_folder,
|
||||||
|
commands::get_settings,
|
||||||
|
commands::save_settings,
|
||||||
|
commands::check_for_updates,
|
||||||
update_tray_menu,
|
update_tray_menu,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ fn extract_claude_api_key(value: &Value) -> Option<String> {
|
|||||||
fn extract_codex_api_key(value: &Value) -> Option<String> {
|
fn extract_codex_api_key(value: &Value) -> Option<String> {
|
||||||
value
|
value
|
||||||
.get("auth")
|
.get("auth")
|
||||||
.and_then(|auth| auth.get("OPENAI_API_KEY").or_else(|| auth.get("openai_api_key")))
|
.and_then(|auth| {
|
||||||
|
auth.get("OPENAI_API_KEY")
|
||||||
|
.or_else(|| auth.get("openai_api_key"))
|
||||||
|
})
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
}
|
}
|
||||||
@@ -77,7 +80,9 @@ fn scan_claude_copies() -> Vec<(String, PathBuf, Value)> {
|
|||||||
if !fname.starts_with("settings-") || !fname.ends_with(".json") {
|
if !fname.starts_with("settings-") || !fname.ends_with(".json") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let name = fname.trim_start_matches("settings-").trim_end_matches(".json");
|
let name = fname
|
||||||
|
.trim_start_matches("settings-")
|
||||||
|
.trim_end_matches(".json");
|
||||||
if let Ok(val) = crate::config::read_json_file::<Value>(&p) {
|
if let Ok(val) = crate::config::read_json_file::<Value>(&p) {
|
||||||
items.push((name.to_string(), p, val));
|
items.push((name.to_string(), p, val));
|
||||||
}
|
}
|
||||||
@@ -104,7 +109,9 @@ fn scan_codex_copies() -> Vec<(String, Option<PathBuf>, Option<PathBuf>, Value)>
|
|||||||
let entry = by_name.entry(name.to_string()).or_default();
|
let entry = by_name.entry(name.to_string()).or_default();
|
||||||
entry.0 = Some(p);
|
entry.0 = Some(p);
|
||||||
} else if fname.starts_with("config-") && fname.ends_with(".toml") {
|
} else if fname.starts_with("config-") && fname.ends_with(".toml") {
|
||||||
let name = fname.trim_start_matches("config-").trim_end_matches(".toml");
|
let name = fname
|
||||||
|
.trim_start_matches("config-")
|
||||||
|
.trim_end_matches(".toml");
|
||||||
let entry = by_name.entry(name.to_string()).or_default();
|
let entry = by_name.entry(name.to_string()).or_default();
|
||||||
entry.1 = Some(p);
|
entry.1 = Some(p);
|
||||||
}
|
}
|
||||||
@@ -183,17 +190,14 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
|
|||||||
|
|
||||||
if let Some((name, value)) = &live_claude {
|
if let Some((name, value)) = &live_claude {
|
||||||
let cand_key = extract_claude_api_key(value);
|
let cand_key = extract_claude_api_key(value);
|
||||||
let exist_id = manager
|
let exist_id = manager.providers.iter().find_map(|(id, p)| {
|
||||||
.providers
|
let pk = extract_claude_api_key(&p.settings_config);
|
||||||
.iter()
|
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
||||||
.find_map(|(id, p)| {
|
Some(id.clone())
|
||||||
let pk = extract_claude_api_key(&p.settings_config);
|
} else {
|
||||||
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
None
|
||||||
Some(id.clone())
|
}
|
||||||
} else {
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(exist_id) = exist_id {
|
if let Some(exist_id) = exist_id {
|
||||||
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
||||||
log::info!("合并到已存在 Claude 供应商 '{}' (by name+key)", name);
|
log::info!("合并到已存在 Claude 供应商 '{}' (by name+key)", name);
|
||||||
@@ -203,43 +207,36 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
|
|||||||
} else {
|
} else {
|
||||||
let id = next_unique_id(&ids, name);
|
let id = next_unique_id(&ids, name);
|
||||||
ids.insert(id.clone());
|
ids.insert(id.clone());
|
||||||
let provider = crate::provider::Provider::with_id(
|
let provider =
|
||||||
id.clone(),
|
crate::provider::Provider::with_id(id.clone(), name.clone(), value.clone(), None);
|
||||||
name.clone(),
|
|
||||||
value.clone(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
manager.providers.insert(provider.id.clone(), provider);
|
manager.providers.insert(provider.id.clone(), provider);
|
||||||
live_claude_id = Some(id);
|
live_claude_id = Some(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (name, path, value) in claude_items.iter() {
|
for (name, path, value) in claude_items.iter() {
|
||||||
let cand_key = extract_claude_api_key(value);
|
let cand_key = extract_claude_api_key(value);
|
||||||
let exist_id = manager
|
let exist_id = manager.providers.iter().find_map(|(id, p)| {
|
||||||
.providers
|
let pk = extract_claude_api_key(&p.settings_config);
|
||||||
.iter()
|
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
||||||
.find_map(|(id, p)| {
|
Some(id.clone())
|
||||||
let pk = extract_claude_api_key(&p.settings_config);
|
} else {
|
||||||
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
None
|
||||||
Some(id.clone())
|
}
|
||||||
} else {
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(exist_id) = exist_id {
|
if let Some(exist_id) = exist_id {
|
||||||
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
||||||
log::info!("覆盖 Claude 供应商 '{}' 来自 {} (by name+key)", name, path.display());
|
log::info!(
|
||||||
|
"覆盖 Claude 供应商 '{}' 来自 {} (by name+key)",
|
||||||
|
name,
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
prov.settings_config = value.clone();
|
prov.settings_config = value.clone();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let id = next_unique_id(&ids, name);
|
let id = next_unique_id(&ids, name);
|
||||||
ids.insert(id.clone());
|
ids.insert(id.clone());
|
||||||
let provider = crate::provider::Provider::with_id(
|
let provider =
|
||||||
id.clone(),
|
crate::provider::Provider::with_id(id.clone(), name.clone(), value.clone(), None);
|
||||||
name.clone(),
|
|
||||||
value.clone(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
manager.providers.insert(provider.id.clone(), provider);
|
manager.providers.insert(provider.id.clone(), provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +254,10 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
|
|||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(("default".to_string(), serde_json::json!({"auth": auth, "config": cfg})))
|
Some((
|
||||||
|
"default".to_string(),
|
||||||
|
serde_json::json!({"auth": auth, "config": cfg}),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("读取 Codex live auth.json 失败: {}", e);
|
log::warn!("读取 Codex live auth.json 失败: {}", e);
|
||||||
@@ -277,17 +277,14 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
|
|||||||
|
|
||||||
if let Some((name, value)) = &live_codex {
|
if let Some((name, value)) = &live_codex {
|
||||||
let cand_key = extract_codex_api_key(value);
|
let cand_key = extract_codex_api_key(value);
|
||||||
let exist_id = manager
|
let exist_id = manager.providers.iter().find_map(|(id, p)| {
|
||||||
.providers
|
let pk = extract_codex_api_key(&p.settings_config);
|
||||||
.iter()
|
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
||||||
.find_map(|(id, p)| {
|
Some(id.clone())
|
||||||
let pk = extract_codex_api_key(&p.settings_config);
|
} else {
|
||||||
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
None
|
||||||
Some(id.clone())
|
}
|
||||||
} else {
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(exist_id) = exist_id {
|
if let Some(exist_id) = exist_id {
|
||||||
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
||||||
log::info!("合并到已存在 Codex 供应商 '{}' (by name+key)", name);
|
log::info!("合并到已存在 Codex 供应商 '{}' (by name+key)", name);
|
||||||
@@ -297,43 +294,37 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
|
|||||||
} else {
|
} else {
|
||||||
let id = next_unique_id(&ids, name);
|
let id = next_unique_id(&ids, name);
|
||||||
ids.insert(id.clone());
|
ids.insert(id.clone());
|
||||||
let provider = crate::provider::Provider::with_id(
|
let provider =
|
||||||
id.clone(),
|
crate::provider::Provider::with_id(id.clone(), name.clone(), value.clone(), None);
|
||||||
name.clone(),
|
|
||||||
value.clone(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
manager.providers.insert(provider.id.clone(), provider);
|
manager.providers.insert(provider.id.clone(), provider);
|
||||||
live_codex_id = Some(id);
|
live_codex_id = Some(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (name, authp, cfgp, value) in codex_items.iter() {
|
for (name, authp, cfgp, value) in codex_items.iter() {
|
||||||
let cand_key = extract_codex_api_key(value);
|
let cand_key = extract_codex_api_key(value);
|
||||||
let exist_id = manager
|
let exist_id = manager.providers.iter().find_map(|(id, p)| {
|
||||||
.providers
|
let pk = extract_codex_api_key(&p.settings_config);
|
||||||
.iter()
|
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
||||||
.find_map(|(id, p)| {
|
Some(id.clone())
|
||||||
let pk = extract_codex_api_key(&p.settings_config);
|
} else {
|
||||||
if norm_name(&p.name) == norm_name(name) && pk == cand_key {
|
None
|
||||||
Some(id.clone())
|
}
|
||||||
} else {
|
});
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(exist_id) = exist_id {
|
if let Some(exist_id) = exist_id {
|
||||||
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
if let Some(prov) = manager.providers.get_mut(&exist_id) {
|
||||||
log::info!("覆盖 Codex 供应商 '{}' 来自 {:?}/{:?} (by name+key)", name, authp, cfgp);
|
log::info!(
|
||||||
|
"覆盖 Codex 供应商 '{}' 来自 {:?}/{:?} (by name+key)",
|
||||||
|
name,
|
||||||
|
authp,
|
||||||
|
cfgp
|
||||||
|
);
|
||||||
prov.settings_config = value.clone();
|
prov.settings_config = value.clone();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let id = next_unique_id(&ids, name);
|
let id = next_unique_id(&ids, name);
|
||||||
ids.insert(id.clone());
|
ids.insert(id.clone());
|
||||||
let provider = crate::provider::Provider::with_id(
|
let provider =
|
||||||
id.clone(),
|
crate::provider::Provider::with_id(id.clone(), name.clone(), value.clone(), None);
|
||||||
name.clone(),
|
|
||||||
value.clone(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
manager.providers.insert(provider.id.clone(), provider);
|
manager.providers.insert(provider.id.clone(), provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,13 +361,17 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result<bool, S
|
|||||||
for (_, ap, cp, _) in codex_items.into_iter() {
|
for (_, ap, cp, _) in codex_items.into_iter() {
|
||||||
if let Some(ap) = ap {
|
if let Some(ap) = ap {
|
||||||
match archive_file(ts, "codex", &ap) {
|
match archive_file(ts, "codex", &ap) {
|
||||||
Ok(Some(_)) => { let _ = delete_file(&ap); }
|
Ok(Some(_)) => {
|
||||||
|
let _ = delete_file(&ap);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(cp) = cp {
|
if let Some(cp) = cp {
|
||||||
match archive_file(ts, "codex", &cp) {
|
match archive_file(ts, "codex", &cp) {
|
||||||
Ok(Some(_)) => { let _ = delete_file(&cp); }
|
Ok(Some(_)) => {
|
||||||
|
let _ = delete_file(&cp);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +399,11 @@ pub fn dedupe_config(config: &mut MultiAppConfig) -> usize {
|
|||||||
let mut keep: Map<String, String> = Map::new(); // key -> id 保留
|
let mut keep: Map<String, String> = Map::new(); // key -> id 保留
|
||||||
let mut remove: Vec<String> = Vec::new();
|
let mut remove: Vec<String> = Vec::new();
|
||||||
for (id, p) in mgr.providers.iter() {
|
for (id, p) in mgr.providers.iter() {
|
||||||
let k = format!("{}|{}", norm_name(&p.name), extract_key(&p.settings_config).unwrap_or_default());
|
let k = format!(
|
||||||
|
"{}|{}",
|
||||||
|
norm_name(&p.name),
|
||||||
|
extract_key(&p.settings_config).unwrap_or_default()
|
||||||
|
);
|
||||||
if let Some(exist_id) = keep.get(&k) {
|
if let Some(exist_id) = keep.get(&k) {
|
||||||
// 若当前是正在使用的,则用当前替换之前的,反之丢弃当前
|
// 若当前是正在使用的,则用当前替换之前的,反之丢弃当前
|
||||||
if *id == mgr.current {
|
if *id == mgr.current {
|
||||||
|
|||||||
@@ -330,9 +330,7 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isSettingsOpen && (
|
{isSettingsOpen && (
|
||||||
<SettingsModal
|
<SettingsModal onClose={() => setIsSettingsOpen(false)} />
|
||||||
onClose={() => setIsSettingsOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ const ProviderList: React.FC<ProviderListProps> = ({
|
|||||||
{provider.websiteUrl}
|
{provider.websiteUrl}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[var(--color-text-secondary)]" title={apiUrl}>
|
<span
|
||||||
|
className="text-[var(--color-text-secondary)]"
|
||||||
|
title={apiUrl}
|
||||||
|
>
|
||||||
{apiUrl}
|
{apiUrl}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { X, Info, RefreshCw, FolderOpen } from "lucide-react";
|
import { X, Info, RefreshCw, FolderOpen } from "lucide-react";
|
||||||
import "../lib/tauri-api";
|
import "../lib/tauri-api";
|
||||||
|
import type { Settings } from "../types";
|
||||||
interface Settings {
|
|
||||||
showInDock: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SettingsModalProps {
|
interface SettingsModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -36,9 +33,9 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
|
|
||||||
const loadConfigPath = async () => {
|
const loadConfigPath = async () => {
|
||||||
try {
|
try {
|
||||||
const status = await window.api.getConfigStatus("claude");
|
const path = await window.api.getAppConfigPath();
|
||||||
if (status?.path) {
|
if (path) {
|
||||||
setConfigPath(status.path.replace("/claude_code_config.json", ""));
|
setConfigPath(path);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取配置路径失败:", error);
|
console.error("获取配置路径失败:", error);
|
||||||
@@ -67,7 +64,7 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {
|
|||||||
|
|
||||||
const handleOpenConfigFolder = async () => {
|
const handleOpenConfigFolder = async () => {
|
||||||
try {
|
try {
|
||||||
await window.api.openConfigFolder("claude");
|
await window.api.openAppConfigFolder();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("打开配置文件夹失败:", error);
|
console.error("打开配置文件夹失败:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
||||||
import { Provider } from "../types";
|
import { Provider, Settings } from "../types";
|
||||||
|
|
||||||
// 应用类型
|
// 应用类型
|
||||||
export type AppType = "claude" | "codex";
|
export type AppType = "claude" | "codex";
|
||||||
@@ -196,7 +196,7 @@ export const tauriAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 获取设置
|
// 获取设置
|
||||||
getSettings: async (): Promise<any> => {
|
getSettings: async (): Promise<Settings> => {
|
||||||
try {
|
try {
|
||||||
return await invoke("get_settings");
|
return await invoke("get_settings");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -206,7 +206,7 @@ export const tauriAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 保存设置
|
// 保存设置
|
||||||
saveSettings: async (settings: any): Promise<boolean> => {
|
saveSettings: async (settings: Settings): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
return await invoke("save_settings", { settings });
|
return await invoke("save_settings", { settings });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -223,6 +223,25 @@ export const tauriAPI = {
|
|||||||
console.error("检查更新失败:", error);
|
console.error("检查更新失败:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 获取应用配置文件路径
|
||||||
|
getAppConfigPath: async (): Promise<string> => {
|
||||||
|
try {
|
||||||
|
return await invoke("get_app_config_path");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取应用配置路径失败:", error);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 打开应用配置文件夹
|
||||||
|
openAppConfigFolder: async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await invoke("open_app_config_folder");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("打开应用配置文件夹失败:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建全局 API 对象,兼容现有代码
|
// 创建全局 API 对象,兼容现有代码
|
||||||
|
|||||||
@@ -9,3 +9,8 @@ export interface AppConfig {
|
|||||||
providers: Record<string, Provider>;
|
providers: Record<string, Provider>;
|
||||||
current: string;
|
current: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用设置类型(用于 SettingsModal 与 Tauri API)
|
||||||
|
export interface Settings {
|
||||||
|
showInDock: boolean;
|
||||||
|
}
|
||||||
|
|||||||
8
src/vite-env.d.ts
vendored
8
src/vite-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
import { Provider } from "./types";
|
import { Provider, Settings } from "./types";
|
||||||
import { AppType } from "./lib/tauri-api";
|
import { AppType } from "./lib/tauri-api";
|
||||||
import type { UnlistenFn } from "@tauri-apps/api/event";
|
import type { UnlistenFn } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
@@ -35,9 +35,11 @@ declare global {
|
|||||||
onProviderSwitched: (
|
onProviderSwitched: (
|
||||||
callback: (data: { appType: string; providerId: string }) => void,
|
callback: (data: { appType: string; providerId: string }) => void,
|
||||||
) => Promise<UnlistenFn>;
|
) => Promise<UnlistenFn>;
|
||||||
getSettings: () => Promise<any>;
|
getSettings: () => Promise<Settings>;
|
||||||
saveSettings: (settings: any) => Promise<boolean>;
|
saveSettings: (settings: Settings) => Promise<boolean>;
|
||||||
checkForUpdates: () => Promise<void>;
|
checkForUpdates: () => Promise<void>;
|
||||||
|
getAppConfigPath: () => Promise<string>;
|
||||||
|
openAppConfigFolder: () => Promise<void>;
|
||||||
};
|
};
|
||||||
platform: {
|
platform: {
|
||||||
isMac: boolean;
|
isMac: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user