mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-25 13:45:03 +08:00
1172209f49
- Make usage script credential fields optional with provider config fallback - Optimize multi-plan card display: show plan count by default, expandable for details - Add hint text to explain credential fallback mechanism
223 lines
6.8 KiB
Rust
223 lines
6.8 KiB
Rust
//! Usage script execution
|
|
//!
|
|
//! Handles executing and formatting usage query results.
|
|
|
|
use crate::app_config::AppType;
|
|
use crate::error::AppError;
|
|
use crate::provider::{UsageData, UsageResult, UsageScript};
|
|
use crate::settings;
|
|
use crate::store::AppState;
|
|
use crate::usage_script;
|
|
|
|
/// Execute usage script and format result (private helper method)
|
|
pub(crate) async fn execute_and_format_usage_result(
|
|
script_code: &str,
|
|
api_key: &str,
|
|
base_url: &str,
|
|
timeout: u64,
|
|
access_token: Option<&str>,
|
|
user_id: Option<&str>,
|
|
) -> Result<UsageResult, AppError> {
|
|
match usage_script::execute_usage_script(
|
|
script_code,
|
|
api_key,
|
|
base_url,
|
|
timeout,
|
|
access_token,
|
|
user_id,
|
|
)
|
|
.await
|
|
{
|
|
Ok(data) => {
|
|
let usage_list: Vec<UsageData> = if data.is_array() {
|
|
serde_json::from_value(data).map_err(|e| {
|
|
AppError::localized(
|
|
"usage_script.data_format_error",
|
|
format!("数据格式错误: {e}"),
|
|
format!("Data format error: {e}"),
|
|
)
|
|
})?
|
|
} else {
|
|
let single: UsageData = serde_json::from_value(data).map_err(|e| {
|
|
AppError::localized(
|
|
"usage_script.data_format_error",
|
|
format!("数据格式错误: {e}"),
|
|
format!("Data format error: {e}"),
|
|
)
|
|
})?;
|
|
vec![single]
|
|
};
|
|
|
|
Ok(UsageResult {
|
|
success: true,
|
|
data: Some(usage_list),
|
|
error: None,
|
|
})
|
|
}
|
|
Err(err) => {
|
|
let lang = settings::get_settings()
|
|
.language
|
|
.unwrap_or_else(|| "zh".to_string());
|
|
|
|
let msg = match err {
|
|
AppError::Localized { zh, en, .. } => {
|
|
if lang == "en" {
|
|
en
|
|
} else {
|
|
zh
|
|
}
|
|
}
|
|
other => other.to_string(),
|
|
};
|
|
|
|
Ok(UsageResult {
|
|
success: false,
|
|
data: None,
|
|
error: Some(msg),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extract API key from provider configuration
|
|
fn extract_api_key_from_provider(provider: &crate::provider::Provider) -> Option<String> {
|
|
if let Some(env) = provider.settings_config.get("env") {
|
|
// Try multiple possible API key fields
|
|
env.get("ANTHROPIC_AUTH_TOKEN")
|
|
.or_else(|| env.get("ANTHROPIC_API_KEY"))
|
|
.or_else(|| env.get("OPENROUTER_API_KEY"))
|
|
.or_else(|| env.get("GOOGLE_API_KEY"))
|
|
.and_then(|v| v.as_str())
|
|
.map(|s| s.to_string())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Extract base URL from provider configuration
|
|
fn extract_base_url_from_provider(provider: &crate::provider::Provider) -> Option<String> {
|
|
if let Some(env) = provider.settings_config.get("env") {
|
|
// Try multiple possible base URL fields
|
|
env.get("ANTHROPIC_BASE_URL")
|
|
.or_else(|| env.get("GOOGLE_GEMINI_BASE_URL"))
|
|
.and_then(|v| v.as_str())
|
|
.map(|s| s.trim_end_matches('/').to_string())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Query provider usage (using saved script configuration)
|
|
pub async fn query_usage(
|
|
state: &AppState,
|
|
app_type: AppType,
|
|
provider_id: &str,
|
|
) -> Result<UsageResult, AppError> {
|
|
let (script_code, timeout, api_key, base_url, access_token, user_id) = {
|
|
let providers = state.db.get_all_providers(app_type.as_str())?;
|
|
let provider = providers.get(provider_id).ok_or_else(|| {
|
|
AppError::localized(
|
|
"provider.not_found",
|
|
format!("供应商不存在: {provider_id}"),
|
|
format!("Provider not found: {provider_id}"),
|
|
)
|
|
})?;
|
|
|
|
let usage_script = provider
|
|
.meta
|
|
.as_ref()
|
|
.and_then(|m| m.usage_script.as_ref())
|
|
.ok_or_else(|| {
|
|
AppError::localized(
|
|
"provider.usage.script.missing",
|
|
"未配置用量查询脚本",
|
|
"Usage script is not configured",
|
|
)
|
|
})?;
|
|
if !usage_script.enabled {
|
|
return Err(AppError::localized(
|
|
"provider.usage.disabled",
|
|
"用量查询未启用",
|
|
"Usage query is disabled",
|
|
));
|
|
}
|
|
|
|
// Get credentials: prioritize UsageScript values, fallback to provider config
|
|
let api_key = usage_script
|
|
.api_key
|
|
.clone()
|
|
.filter(|k| !k.is_empty())
|
|
.or_else(|| extract_api_key_from_provider(provider))
|
|
.unwrap_or_default();
|
|
|
|
let base_url = usage_script
|
|
.base_url
|
|
.clone()
|
|
.filter(|u| !u.is_empty())
|
|
.or_else(|| extract_base_url_from_provider(provider))
|
|
.unwrap_or_default();
|
|
|
|
(
|
|
usage_script.code.clone(),
|
|
usage_script.timeout.unwrap_or(10),
|
|
api_key,
|
|
base_url,
|
|
usage_script.access_token.clone(),
|
|
usage_script.user_id.clone(),
|
|
)
|
|
};
|
|
|
|
execute_and_format_usage_result(
|
|
&script_code,
|
|
&api_key,
|
|
&base_url,
|
|
timeout,
|
|
access_token.as_deref(),
|
|
user_id.as_deref(),
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Test usage script (using temporary script content, not saved)
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn test_usage_script(
|
|
_state: &AppState,
|
|
_app_type: AppType,
|
|
_provider_id: &str,
|
|
script_code: &str,
|
|
timeout: u64,
|
|
api_key: Option<&str>,
|
|
base_url: Option<&str>,
|
|
access_token: Option<&str>,
|
|
user_id: Option<&str>,
|
|
) -> Result<UsageResult, AppError> {
|
|
// Use provided credential parameters directly for testing
|
|
execute_and_format_usage_result(
|
|
script_code,
|
|
api_key.unwrap_or(""),
|
|
base_url.unwrap_or(""),
|
|
timeout,
|
|
access_token,
|
|
user_id,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Validate UsageScript configuration (boundary checks)
|
|
pub(crate) fn validate_usage_script(script: &UsageScript) -> Result<(), AppError> {
|
|
// Validate auto query interval (0-1440 minutes, max 24 hours)
|
|
if let Some(interval) = script.auto_query_interval {
|
|
if interval > 1440 {
|
|
return Err(AppError::localized(
|
|
"usage_script.interval_too_large",
|
|
format!("自动查询间隔不能超过 1440 分钟(24小时),当前值: {interval}"),
|
|
format!(
|
|
"Auto query interval cannot exceed 1440 minutes (24 hours), current: {interval}"
|
|
),
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|