feat: persist custom endpoints to settings.json
- Extend AppSettings to store custom endpoints for Claude and Codex - Add Tauri commands: get/add/remove/update custom endpoints - Update frontend API with endpoint persistence methods - Modify EndpointSpeedTest to load/save custom endpoints via API - Track endpoint last used time for future sorting/cleanup - Store endpoints per app type in settings.json instead of localStorage
This commit is contained in:
@@ -739,3 +739,100 @@ pub async fn test_api_endpoints(
|
||||
.collect();
|
||||
speedtest::test_endpoints(filtered, timeout_secs).await
|
||||
}
|
||||
|
||||
/// 获取自定义端点列表
|
||||
#[tauri::command]
|
||||
pub async fn get_custom_endpoints(app_type: AppType) -> Result<Vec<crate::settings::CustomEndpoint>, String> {
|
||||
let settings = crate::settings::get_settings();
|
||||
let endpoints = match app_type {
|
||||
AppType::Claude => &settings.custom_endpoints_claude,
|
||||
AppType::Codex => &settings.custom_endpoints_codex,
|
||||
};
|
||||
|
||||
let mut result: Vec<crate::settings::CustomEndpoint> = endpoints.values().cloned().collect();
|
||||
// 按添加时间降序排序(最新的在前)
|
||||
result.sort_by(|a, b| b.added_at.cmp(&a.added_at));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 添加自定义端点
|
||||
#[tauri::command]
|
||||
pub async fn add_custom_endpoint(
|
||||
app_type: AppType,
|
||||
url: String,
|
||||
) -> Result<(), String> {
|
||||
let normalized = url.trim().trim_end_matches('/').to_string();
|
||||
if normalized.is_empty() {
|
||||
return Err("URL 不能为空".to_string());
|
||||
}
|
||||
|
||||
let mut settings = crate::settings::get_settings();
|
||||
let endpoints = match app_type {
|
||||
AppType::Claude => &mut settings.custom_endpoints_claude,
|
||||
AppType::Codex => &mut settings.custom_endpoints_codex,
|
||||
};
|
||||
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let endpoint = crate::settings::CustomEndpoint {
|
||||
url: normalized.clone(),
|
||||
added_at: timestamp,
|
||||
last_used: None,
|
||||
};
|
||||
|
||||
endpoints.insert(normalized, endpoint);
|
||||
crate::settings::update_settings(settings)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 删除自定义端点
|
||||
#[tauri::command]
|
||||
pub async fn remove_custom_endpoint(
|
||||
app_type: AppType,
|
||||
url: String,
|
||||
) -> Result<(), String> {
|
||||
let normalized = url.trim().trim_end_matches('/').to_string();
|
||||
|
||||
let mut settings = crate::settings::get_settings();
|
||||
let endpoints = match app_type {
|
||||
AppType::Claude => &mut settings.custom_endpoints_claude,
|
||||
AppType::Codex => &mut settings.custom_endpoints_codex,
|
||||
};
|
||||
|
||||
endpoints.remove(&normalized);
|
||||
crate::settings::update_settings(settings)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新端点最后使用时间
|
||||
#[tauri::command]
|
||||
pub async fn update_endpoint_last_used(
|
||||
app_type: AppType,
|
||||
url: String,
|
||||
) -> Result<(), String> {
|
||||
let normalized = url.trim().trim_end_matches('/').to_string();
|
||||
|
||||
let mut settings = crate::settings::get_settings();
|
||||
let endpoints = match app_type {
|
||||
AppType::Claude => &mut settings.custom_endpoints_claude,
|
||||
AppType::Codex => &mut settings.custom_endpoints_codex,
|
||||
};
|
||||
|
||||
if let Some(endpoint) = endpoints.get_mut(&normalized) {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
endpoint.last_used = Some(timestamp);
|
||||
crate::settings::update_settings(settings)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -421,6 +421,10 @@ pub fn run() {
|
||||
commands::apply_claude_plugin_config,
|
||||
commands::is_claude_plugin_applied,
|
||||
commands::test_api_endpoints,
|
||||
commands::get_custom_endpoints,
|
||||
commands::add_custom_endpoint,
|
||||
commands::remove_custom_endpoint,
|
||||
commands::update_endpoint_last_used,
|
||||
update_tray_menu,
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
|
||||
/// 自定义端点配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CustomEndpoint {
|
||||
pub url: String,
|
||||
pub added_at: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_used: Option<i64>,
|
||||
}
|
||||
|
||||
/// 应用设置结构,允许覆盖默认配置目录
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -17,6 +28,12 @@ pub struct AppSettings {
|
||||
pub codex_config_dir: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub language: Option<String>,
|
||||
/// Claude 自定义端点列表
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub custom_endpoints_claude: HashMap<String, CustomEndpoint>,
|
||||
/// Codex 自定义端点列表
|
||||
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
||||
pub custom_endpoints_codex: HashMap<String, CustomEndpoint>,
|
||||
}
|
||||
|
||||
fn default_show_in_tray() -> bool {
|
||||
@@ -35,6 +52,8 @@ impl Default for AppSettings {
|
||||
claude_config_dir: None,
|
||||
codex_config_dir: None,
|
||||
language: None,
|
||||
custom_endpoints_claude: HashMap::new(),
|
||||
custom_endpoints_codex: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user