修改计算规则
This commit is contained in:
@@ -12,24 +12,20 @@ pub struct ClaudeConfig {
|
||||
pub env: ClaudeEnv,
|
||||
#[serde(default)]
|
||||
pub permissions: Option<ClaudePermissions>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model: Option<String>,
|
||||
#[serde(rename = "apiKeyHelper")]
|
||||
#[serde(rename = "apiKeyHelper", skip_serializing_if = "Option::is_none")]
|
||||
pub api_key_helper: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub other: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ClaudeEnv {
|
||||
#[serde(rename = "ANTHROPIC_AUTH_TOKEN")]
|
||||
#[serde(rename = "ANTHROPIC_AUTH_TOKEN", skip_serializing_if = "Option::is_none")]
|
||||
pub anthropic_auth_token: Option<String>,
|
||||
#[serde(rename = "ANTHROPIC_BASE_URL")]
|
||||
#[serde(rename = "ANTHROPIC_BASE_URL", skip_serializing_if = "Option::is_none")]
|
||||
pub anthropic_base_url: Option<String>,
|
||||
#[serde(rename = "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC")]
|
||||
#[serde(rename = "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", skip_serializing_if = "Option::is_none")]
|
||||
pub disable_nonessential_traffic: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub other: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
@@ -63,32 +59,63 @@ pub fn read_claude_config() -> Result<ClaudeConfig, String> {
|
||||
permissions: Some(ClaudePermissions::default()),
|
||||
model: None,
|
||||
api_key_helper: None,
|
||||
other: json!({}),
|
||||
});
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&config_path)
|
||||
.map_err(|e| format!("读取配置文件失败: {}", e))?;
|
||||
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| format!("解析配置文件失败: {}", e))
|
||||
// 首先尝试解析为 JSON Value,以便处理可能的格式问题
|
||||
let mut json_value: Value = serde_json::from_str(&content)
|
||||
.map_err(|e| format!("解析配置文件失败: {}", e))?;
|
||||
|
||||
// 如果JSON解析成功,再转换为ClaudeConfig
|
||||
if let Some(obj) = json_value.as_object_mut() {
|
||||
// 确保必要的字段存在
|
||||
if !obj.contains_key("env") {
|
||||
obj.insert("env".to_string(), json!({}));
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::from_value(json_value)
|
||||
.map_err(|e| format!("转换配置结构失败: {}", e))
|
||||
}
|
||||
|
||||
/// 写入 Claude 配置文件
|
||||
pub fn write_claude_config(config: &ClaudeConfig) -> Result<(), String> {
|
||||
let config_path = get_claude_config_path()?;
|
||||
|
||||
log::info!("尝试写入配置文件到: {:?}", config_path);
|
||||
|
||||
// 确保目录存在
|
||||
if let Some(parent) = config_path.parent() {
|
||||
log::info!("确保目录存在: {:?}", parent);
|
||||
fs::create_dir_all(parent)
|
||||
.map_err(|e| format!("创建配置目录失败: {}", e))?;
|
||||
.map_err(|e| {
|
||||
let error_msg = format!("创建配置目录失败: {}", e);
|
||||
log::error!("{}", error_msg);
|
||||
error_msg
|
||||
})?;
|
||||
}
|
||||
|
||||
let content = serde_json::to_string_pretty(config)
|
||||
.map_err(|e| format!("序列化配置失败: {}", e))?;
|
||||
.map_err(|e| {
|
||||
let error_msg = format!("序列化配置失败: {}", e);
|
||||
log::error!("{}", error_msg);
|
||||
error_msg
|
||||
})?;
|
||||
|
||||
fs::write(&config_path, content)
|
||||
.map_err(|e| format!("写入配置文件失败: {}", e))
|
||||
log::info!("准备写入内容:\n{}", content);
|
||||
|
||||
fs::write(&config_path, &content)
|
||||
.map_err(|e| {
|
||||
let error_msg = format!("写入配置文件失败: {} (路径: {:?})", e, config_path);
|
||||
log::error!("{}", error_msg);
|
||||
error_msg
|
||||
})?;
|
||||
|
||||
log::info!("配置文件写入成功: {:?}", config_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 备份当前配置
|
||||
@@ -137,8 +164,11 @@ pub fn apply_relay_station_to_config(station: &RelayStation) -> Result<(), Strin
|
||||
// 格式:echo 'token'
|
||||
config.api_key_helper = Some(format!("echo '{}'", station.system_token));
|
||||
|
||||
// 如果是自定义适配器,可能需要特殊处理
|
||||
// 如果是特定适配器,可能需要特殊处理
|
||||
match station.adapter.as_str() {
|
||||
"packycode" => {
|
||||
// PackyCode 使用原始配置,不做特殊处理
|
||||
}
|
||||
"newapi" | "oneapi" => {
|
||||
// NewAPI 和 OneAPI 兼容 OpenAI 格式,不需要特殊处理
|
||||
}
|
||||
|
@@ -8,3 +8,4 @@ pub mod proxy;
|
||||
pub mod language;
|
||||
pub mod relay_stations;
|
||||
pub mod relay_adapters;
|
||||
pub mod packycode_nodes;
|
||||
|
216
src-tauri/src/commands/packycode_nodes.rs
Normal file
216
src-tauri/src/commands/packycode_nodes.rs
Normal file
@@ -0,0 +1,216 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, Instant};
|
||||
use reqwest::Client;
|
||||
use tauri::command;
|
||||
use anyhow::Result;
|
||||
|
||||
/// PackyCode 节点类型
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NodeType {
|
||||
Direct, // 直连节点
|
||||
Backup, // 备用节点
|
||||
Emergency, // 紧急节点(非紧急情况不要使用)
|
||||
}
|
||||
|
||||
/// PackyCode 节点信息
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PackycodeNode {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
pub node_type: NodeType,
|
||||
pub description: String,
|
||||
pub response_time: Option<u64>, // 响应时间(毫秒)
|
||||
pub available: Option<bool>, // 是否可用
|
||||
}
|
||||
|
||||
/// 节点测速结果
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NodeSpeedTestResult {
|
||||
pub node: PackycodeNode,
|
||||
pub response_time: u64,
|
||||
pub success: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// 获取所有 PackyCode 节点
|
||||
pub fn get_all_nodes() -> Vec<PackycodeNode> {
|
||||
vec![
|
||||
// 直连节点
|
||||
PackycodeNode {
|
||||
name: "直连1".to_string(),
|
||||
url: "https://api.packycode.com".to_string(),
|
||||
node_type: NodeType::Direct,
|
||||
description: "默认直连节点".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
PackycodeNode {
|
||||
name: "直连2 (HK-CN2)".to_string(),
|
||||
url: "https://api-hk-cn2.packycode.com".to_string(),
|
||||
node_type: NodeType::Direct,
|
||||
description: "香港 CN2 线路".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
PackycodeNode {
|
||||
name: "直连3 (US-CMIN2)".to_string(),
|
||||
url: "https://api-us-cmin2.packycode.com".to_string(),
|
||||
node_type: NodeType::Direct,
|
||||
description: "美国 CMIN2 线路".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
PackycodeNode {
|
||||
name: "直连4 (US-4837)".to_string(),
|
||||
url: "https://api-us-4837.packycode.com".to_string(),
|
||||
node_type: NodeType::Direct,
|
||||
description: "美国 4837 线路".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
// 备用节点
|
||||
PackycodeNode {
|
||||
name: "备用1 (US-CN2)".to_string(),
|
||||
url: "https://api-us-cn2.packycode.com".to_string(),
|
||||
node_type: NodeType::Backup,
|
||||
description: "美国 CN2 备用线路".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
PackycodeNode {
|
||||
name: "备用2 (CF-Pro)".to_string(),
|
||||
url: "https://api-cf-pro.packycode.com".to_string(),
|
||||
node_type: NodeType::Backup,
|
||||
description: "CloudFlare Pro 备用线路".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
// 紧急节点
|
||||
PackycodeNode {
|
||||
name: "测试节点1".to_string(),
|
||||
url: "https://api-test.packyme.com".to_string(),
|
||||
node_type: NodeType::Emergency,
|
||||
description: "测试节点(非紧急情况勿用)".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
PackycodeNode {
|
||||
name: "测试节点2".to_string(),
|
||||
url: "https://api-test-custom.packycode.com".to_string(),
|
||||
node_type: NodeType::Emergency,
|
||||
description: "自定义测试节点(非紧急情况勿用)".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
PackycodeNode {
|
||||
name: "测试节点3".to_string(),
|
||||
url: "https://api-tmp-test.dzz.ai".to_string(),
|
||||
node_type: NodeType::Emergency,
|
||||
description: "临时测试节点(非紧急情况勿用)".to_string(),
|
||||
response_time: None,
|
||||
available: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/// 测试单个节点速度(仅测试网络延时,不需要认证)
|
||||
async fn test_node_speed(node: &PackycodeNode, _token: &str) -> NodeSpeedTestResult {
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.unwrap_or_else(|_| Client::new());
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
// 只需要测试服务器的可达性和延时,使用简单的 HEAD 请求
|
||||
match client
|
||||
.head(&node.url)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(_response) => {
|
||||
let response_time = start_time.elapsed().as_millis() as u64;
|
||||
|
||||
// 只要能连接到服务器就算成功,不管返回什么状态码
|
||||
NodeSpeedTestResult {
|
||||
node: PackycodeNode {
|
||||
response_time: Some(response_time),
|
||||
available: Some(true),
|
||||
..node.clone()
|
||||
},
|
||||
response_time,
|
||||
success: true,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let response_time = start_time.elapsed().as_millis() as u64;
|
||||
NodeSpeedTestResult {
|
||||
node: PackycodeNode {
|
||||
response_time: Some(response_time),
|
||||
available: Some(false),
|
||||
..node.clone()
|
||||
},
|
||||
response_time,
|
||||
success: false,
|
||||
error: Some(format!("连接失败: {}", e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试所有节点速度
|
||||
#[command]
|
||||
pub async fn test_all_packycode_nodes(token: String) -> Result<Vec<NodeSpeedTestResult>, String> {
|
||||
let nodes = get_all_nodes();
|
||||
let mut results = Vec::new();
|
||||
|
||||
for node in nodes {
|
||||
let result = test_node_speed(&node, &token).await;
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// 按响应时间排序
|
||||
results.sort_by(|a, b| {
|
||||
match (a.success, b.success) {
|
||||
(true, false) => std::cmp::Ordering::Less,
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
_ => a.response_time.cmp(&b.response_time),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// 自动选择最快的节点(仅从直连和备用中选择)
|
||||
#[command]
|
||||
pub async fn auto_select_best_node(token: String) -> Result<PackycodeNode, String> {
|
||||
let nodes = get_all_nodes();
|
||||
let mut best_node: Option<(PackycodeNode, u64)> = None;
|
||||
|
||||
// 只测试直连和备用节点
|
||||
for node in nodes.iter().filter(|n| matches!(n.node_type, NodeType::Direct | NodeType::Backup)) {
|
||||
let result = test_node_speed(node, &token).await;
|
||||
|
||||
if result.success {
|
||||
match &best_node {
|
||||
None => best_node = Some((result.node, result.response_time)),
|
||||
Some((_, best_time)) if result.response_time < *best_time => {
|
||||
best_node = Some((result.node, result.response_time));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best_node
|
||||
.map(|(node, _)| node)
|
||||
.ok_or_else(|| "No available nodes found".to_string())
|
||||
}
|
||||
|
||||
/// 获取节点列表(不测速)
|
||||
#[command]
|
||||
pub fn get_packycode_nodes() -> Vec<PackycodeNode> {
|
||||
get_all_nodes()
|
||||
}
|
@@ -51,6 +51,237 @@ pub trait StationAdapter: Send + Sync {
|
||||
async fn delete_token(&self, station: &RelayStation, token_id: &str) -> Result<String>;
|
||||
}
|
||||
|
||||
/// PackyCode 适配器(默认使用 API Key 认证)
|
||||
pub struct PackycodeAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl StationAdapter for PackycodeAdapter {
|
||||
async fn get_station_info(&self, station: &RelayStation) -> Result<StationInfo> {
|
||||
let url = format!("{}/api/status", station.api_url.trim_end_matches('/'));
|
||||
|
||||
let response = HTTP_CLIENT
|
||||
.get(&url)
|
||||
.header("Authorization", format!("sk-{}", station.system_token))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let data: Value = response.json().await?;
|
||||
|
||||
if !data.get("success").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
return Err(anyhow::anyhow!("API Error: {}",
|
||||
data.get("message").and_then(|v| v.as_str()).unwrap_or("Unknown error")));
|
||||
}
|
||||
|
||||
let default_data = json!({});
|
||||
let data = data.get("data").unwrap_or(&default_data);
|
||||
|
||||
Ok(StationInfo {
|
||||
name: data.get("system_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("PackyCode")
|
||||
.to_string(),
|
||||
announcement: data.get("announcement")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string()),
|
||||
api_url: station.api_url.clone(),
|
||||
version: data.get("version")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string()),
|
||||
metadata: Some({
|
||||
let mut map = HashMap::new();
|
||||
map.insert("adapter_type".to_string(), json!("packycode"));
|
||||
map.insert("service_type".to_string(), json!(
|
||||
if station.api_url.contains("share.packycode.com") {
|
||||
"bus" // 公交车
|
||||
} else {
|
||||
"taxi" // 滴滴车
|
||||
}
|
||||
));
|
||||
if let Some(quota_per_unit) = data.get("quota_per_unit").and_then(|v| v.as_i64()) {
|
||||
map.insert("quota_per_unit".to_string(), json!(quota_per_unit));
|
||||
}
|
||||
map
|
||||
}),
|
||||
quota_per_unit: data.get("quota_per_unit").and_then(|v| v.as_i64()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_user_info(&self, station: &RelayStation, user_id: &str) -> Result<UserInfo> {
|
||||
let url = format!("{}/api/user/self", station.api_url.trim_end_matches('/'));
|
||||
|
||||
let response = HTTP_CLIENT
|
||||
.get(&url)
|
||||
.header("Authorization", format!("sk-{}", station.system_token))
|
||||
.header("X-User-Id", user_id)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let data: Value = response.json().await?;
|
||||
|
||||
if !data.get("success").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
return Err(anyhow::anyhow!("API Error: {}",
|
||||
data.get("message").and_then(|v| v.as_str()).unwrap_or("Unknown error")));
|
||||
}
|
||||
|
||||
let user_data = data.get("data").ok_or_else(|| anyhow!("No user data returned"))?;
|
||||
|
||||
Ok(UserInfo {
|
||||
user_id: user_data.get("id")
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_else(|| user_id.to_string()),
|
||||
username: user_data.get("username")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string()),
|
||||
email: user_data.get("email")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string()),
|
||||
balance_remaining: user_data.get("quota")
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(|q| q as f64 / 500000.0), // 转换为美元
|
||||
amount_used: user_data.get("used_quota")
|
||||
.and_then(|v| v.as_i64())
|
||||
.map(|q| q as f64 / 500000.0),
|
||||
request_count: user_data.get("request_count")
|
||||
.and_then(|v| v.as_i64()),
|
||||
status: match user_data.get("status").and_then(|v| v.as_i64()) {
|
||||
Some(1) => Some("active".to_string()),
|
||||
Some(0) => Some("disabled".to_string()),
|
||||
_ => Some("unknown".to_string()),
|
||||
},
|
||||
metadata: Some({
|
||||
let mut map = HashMap::new();
|
||||
map.insert("raw_data".to_string(), user_data.clone());
|
||||
map
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
async fn test_connection(&self, station: &RelayStation) -> Result<ConnectionTestResult> {
|
||||
let start_time = Instant::now();
|
||||
let url = format!("{}/api/status", station.api_url.trim_end_matches('/'));
|
||||
|
||||
match HTTP_CLIENT
|
||||
.get(&url)
|
||||
.header("Authorization", format!("sk-{}", station.system_token))
|
||||
.timeout(Duration::from_secs(10))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let response_time = start_time.elapsed().as_millis() as u64;
|
||||
|
||||
if response.status().is_success() {
|
||||
match response.json::<Value>().await {
|
||||
Ok(data) => {
|
||||
let success = data.get("success").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
if success {
|
||||
Ok(ConnectionTestResult {
|
||||
success: true,
|
||||
response_time: Some(response_time),
|
||||
message: i18n::t("relay_adapter.connection_success"),
|
||||
error: None,
|
||||
})
|
||||
} else {
|
||||
let error_msg = data.get("message").and_then(|v| v.as_str()).unwrap_or("Unknown error");
|
||||
Ok(ConnectionTestResult {
|
||||
success: false,
|
||||
response_time: Some(response_time),
|
||||
message: i18n::t("relay_adapter.api_error"),
|
||||
error: Some(error_msg.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(e) => Ok(ConnectionTestResult {
|
||||
success: false,
|
||||
response_time: Some(response_time),
|
||||
message: i18n::t("relay_adapter.parse_error"),
|
||||
error: Some(e.to_string()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Ok(ConnectionTestResult {
|
||||
success: false,
|
||||
response_time: Some(response_time),
|
||||
message: i18n::t("relay_adapter.http_error"),
|
||||
error: Some(format!("HTTP {}", response.status())),
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let response_time = start_time.elapsed().as_millis() as u64;
|
||||
Ok(ConnectionTestResult {
|
||||
success: false,
|
||||
response_time: Some(response_time),
|
||||
message: i18n::t("relay_adapter.network_error"),
|
||||
error: Some(e.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_usage_logs(&self, station: &RelayStation, user_id: &str, page: Option<usize>, size: Option<usize>) -> Result<Value> {
|
||||
let page = page.unwrap_or(1);
|
||||
let size = size.unwrap_or(10);
|
||||
let url = format!("{}/api/log/self?page={}&size={}",
|
||||
station.api_url.trim_end_matches('/'), page, size);
|
||||
|
||||
let response = HTTP_CLIENT
|
||||
.get(&url)
|
||||
.header("Authorization", format!("sk-{}", station.system_token))
|
||||
.header("X-User-Id", user_id)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let data: Value = response.json().await?;
|
||||
|
||||
if !data.get("success").and_then(|v| v.as_bool()).unwrap_or(false) {
|
||||
return Err(anyhow::anyhow!("API Error: {}",
|
||||
data.get("message").and_then(|v| v.as_str()).unwrap_or("Unknown error")));
|
||||
}
|
||||
|
||||
Ok(data.get("data").cloned().unwrap_or(json!([])))
|
||||
}
|
||||
|
||||
async fn list_tokens(&self, station: &RelayStation, page: Option<usize>, size: Option<usize>) -> Result<TokenPaginationResponse> {
|
||||
// PackyCode 使用简化的 Token 管理
|
||||
let page = page.unwrap_or(1);
|
||||
let size = size.unwrap_or(10);
|
||||
|
||||
// 返回当前使用的 API Key 作为唯一 Token
|
||||
let token = TokenInfo {
|
||||
id: "1".to_string(),
|
||||
name: "API Key".to_string(),
|
||||
token: format!("sk-{}...", &station.system_token[..8]),
|
||||
quota: None,
|
||||
used_quota: None,
|
||||
status: "active".to_string(),
|
||||
created_at: station.created_at,
|
||||
updated_at: station.updated_at,
|
||||
};
|
||||
|
||||
Ok(TokenPaginationResponse {
|
||||
tokens: vec![token],
|
||||
total: 1,
|
||||
page,
|
||||
size,
|
||||
has_more: false,
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_token(&self, _station: &RelayStation, _name: &str, _quota: Option<i64>) -> Result<TokenInfo> {
|
||||
Err(anyhow::anyhow!(i18n::t("relay_adapter.packycode_single_token")))
|
||||
}
|
||||
|
||||
async fn update_token(&self, _station: &RelayStation, _token_id: &str, _name: Option<&str>, _quota: Option<i64>) -> Result<TokenInfo> {
|
||||
Err(anyhow::anyhow!(i18n::t("relay_adapter.packycode_single_token")))
|
||||
}
|
||||
|
||||
async fn delete_token(&self, _station: &RelayStation, _token_id: &str) -> Result<String> {
|
||||
Err(anyhow::anyhow!(i18n::t("relay_adapter.packycode_single_token")))
|
||||
}
|
||||
}
|
||||
|
||||
/// NewAPI 适配器(支持 NewAPI 和 OneAPI)
|
||||
pub struct NewApiAdapter;
|
||||
|
||||
@@ -575,7 +806,7 @@ impl StationAdapter for CustomAdapter {
|
||||
// Custom 适配器跳过连接测试,直接返回成功
|
||||
Ok(ConnectionTestResult {
|
||||
success: true,
|
||||
response_time: Some(0),
|
||||
response_time: None, // 不显示响应时间
|
||||
message: i18n::t("relay_adapter.custom_no_test"),
|
||||
error: None,
|
||||
})
|
||||
@@ -605,6 +836,7 @@ impl StationAdapter for CustomAdapter {
|
||||
/// 适配器工厂函数
|
||||
pub fn create_adapter(adapter_type: &RelayStationAdapter) -> Box<dyn StationAdapter> {
|
||||
match adapter_type {
|
||||
RelayStationAdapter::Packycode => Box::new(PackycodeAdapter),
|
||||
RelayStationAdapter::Newapi => Box::new(NewApiAdapter),
|
||||
RelayStationAdapter::Oneapi => Box::new(NewApiAdapter), // OneAPI 兼容 NewAPI
|
||||
RelayStationAdapter::Yourapi => Box::new(YourApiAdapter::new()),
|
||||
|
@@ -14,6 +14,7 @@ use crate::claude_config;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RelayStationAdapter {
|
||||
Packycode, // PackyCode 平台(放在第一位)
|
||||
Newapi, // NewAPI 兼容平台
|
||||
Oneapi, // OneAPI 兼容平台
|
||||
Yourapi, // YourAPI 特定平台
|
||||
@@ -23,6 +24,7 @@ pub enum RelayStationAdapter {
|
||||
impl RelayStationAdapter {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
RelayStationAdapter::Packycode => "packycode",
|
||||
RelayStationAdapter::Newapi => "newapi",
|
||||
RelayStationAdapter::Oneapi => "oneapi",
|
||||
RelayStationAdapter::Yourapi => "yourapi",
|
||||
@@ -520,12 +522,10 @@ pub async fn relay_station_toggle_enable(
|
||||
let station = relay_station_get_internal(&conn, &id)?;
|
||||
|
||||
// 将中转站配置应用到 Claude 配置文件
|
||||
if let Err(e) = claude_config::apply_relay_station_to_config(&station) {
|
||||
claude_config::apply_relay_station_to_config(&station).map_err(|e| {
|
||||
log::error!("Failed to apply relay station config: {}", e);
|
||||
// 不中断流程,但记录错误
|
||||
} else {
|
||||
log::info!("Applied relay station config to Claude settings");
|
||||
}
|
||||
format!("配置文件写入失败: {}", e)
|
||||
})?;
|
||||
} else {
|
||||
// 如果禁用中转站,清除 Claude 配置中的相关设置
|
||||
if let Err(e) = claude_config::clear_relay_station_from_config() {
|
||||
|
@@ -45,6 +45,7 @@ impl SimpleI18n {
|
||||
|
||||
// Relay Station English translations
|
||||
("en-US", "relay_adapter.custom_no_test") => "Custom configuration, connection test skipped".to_string(),
|
||||
("en-US", "relay_adapter.packycode_single_token") => "PackyCode only supports single API key".to_string(),
|
||||
("en-US", "relay_adapter.user_info_not_available") => "User info not available for this configuration".to_string(),
|
||||
("en-US", "relay_adapter.usage_logs_not_available") => "Usage logs not available for this configuration".to_string(),
|
||||
("en-US", "relay_adapter.token_management_not_available") => "Token management not available for this configuration".to_string(),
|
||||
@@ -65,6 +66,7 @@ impl SimpleI18n {
|
||||
|
||||
// Relay Station Chinese translations
|
||||
("zh-CN", "relay_adapter.custom_no_test") => "自定义配置,跳过连接测试".to_string(),
|
||||
("zh-CN", "relay_adapter.packycode_single_token") => "PackyCode 仅支持单个 API 密钥".to_string(),
|
||||
("zh-CN", "relay_adapter.user_info_not_available") => "该配置不支持用户信息查询".to_string(),
|
||||
("zh-CN", "relay_adapter.usage_logs_not_available") => "该配置不支持使用日志查询".to_string(),
|
||||
("zh-CN", "relay_adapter.token_management_not_available") => "该配置不支持 Token 管理".to_string(),
|
||||
|
@@ -56,6 +56,9 @@ use commands::relay_adapters::{
|
||||
relay_station_test_connection, relay_station_get_usage_logs, relay_station_list_tokens,
|
||||
relay_station_create_token, relay_station_update_token, relay_station_delete_token,
|
||||
};
|
||||
use commands::packycode_nodes::{
|
||||
test_all_packycode_nodes, auto_select_best_node, get_packycode_nodes,
|
||||
};
|
||||
use process::ProcessRegistryState;
|
||||
use std::sync::Mutex;
|
||||
use tauri::Manager;
|
||||
@@ -286,6 +289,11 @@ fn main() {
|
||||
relay_station_create_token,
|
||||
relay_station_update_token,
|
||||
relay_station_delete_token,
|
||||
|
||||
// PackyCode Nodes
|
||||
test_all_packycode_nodes,
|
||||
auto_select_best_node,
|
||||
get_packycode_nodes,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
Reference in New Issue
Block a user