feat(usage): add test script API with refactored execution logic

- Add private helper method `execute_and_format_usage_result` to eliminate code duplication
- Refactor `query_usage` to use helper method instead of duplicating result processing
- Add new `test_usage_script` method to test temporary script without saving
- Add backend command `test_usage_script` accepting script content as parameter
- Register new command in lib.rs invoke_handler
- Add frontend `usageApi.testScript` method to call the new backend API
- Update `UsageScriptModal.handleTest` to test current editor content instead of saved script
- Improve DX: users can now test script changes before saving
This commit is contained in:
Jason
2025-11-04 23:04:44 +08:00
parent 05e58e9e14
commit bafddb8e52
5 changed files with 152 additions and 25 deletions

View File

@@ -121,6 +121,35 @@ pub async fn query_provider_usage(
.map_err(|e| e.to_string())
}
/// 测试用量脚本(使用当前编辑器中的脚本,不保存)
#[tauri::command]
pub async fn test_usage_script(
state: State<'_, AppState>,
#[allow(non_snake_case)]
providerId: String,
app: String,
#[allow(non_snake_case)]
scriptCode: String,
timeout: Option<u64>,
#[allow(non_snake_case)]
accessToken: Option<String>,
#[allow(non_snake_case)]
userId: Option<String>,
) -> Result<crate::provider::UsageResult, String> {
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
ProviderService::test_usage_script(
state.inner(),
app_type,
&providerId,
&scriptCode,
timeout.unwrap_or(10),
accessToken.as_deref(),
userId.as_deref(),
)
.await
.map_err(|e| e.to_string())
}
/// 读取当前生效的配置内容
#[tauri::command]
pub fn read_live_provider_settings(app: String) -> Result<serde_json::Value, String> {

View File

@@ -540,6 +540,7 @@ pub fn run() {
commands::validate_mcp_command,
// usage query
commands::query_provider_usage,
commands::test_usage_script,
// New MCP via config.json (SSOT)
commands::get_mcp_config,
commands::upsert_mcp_server_in_config,

View File

@@ -706,7 +706,50 @@ impl ProviderService {
Ok(true)
}
/// 查询供应商用量
/// 执行用量脚本并格式化结果(私有辅助方法)
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::Message(format!("数据格式错误: {}", e)))?
} else {
let single: UsageData = serde_json::from_value(data)
.map_err(|e| AppError::Message(format!("数据格式错误: {}", e)))?;
vec![single]
};
Ok(UsageResult {
success: true,
data: Some(usage_list),
error: None,
})
}
Err(err) => Ok(UsageResult {
success: false,
data: None,
error: Some(err.to_string()),
}),
}
}
/// 查询供应商用量(使用已保存的脚本配置)
pub async fn query_usage(
state: &AppState,
app_type: AppType,
@@ -754,7 +797,7 @@ impl ProviderService {
let (api_key, base_url) = Self::extract_credentials(&provider, &app_type)?;
match usage_script::execute_usage_script(
Self::execute_and_format_usage_result(
&script_code,
&api_key,
&base_url,
@@ -763,29 +806,42 @@ impl ProviderService {
user_id.as_deref(),
)
.await
{
Ok(data) => {
let usage_list: Vec<UsageData> = if data.is_array() {
serde_json::from_value(data)
.map_err(|e| AppError::Message(format!("数据格式错误: {}", e)))?
} else {
let single: UsageData = serde_json::from_value(data)
.map_err(|e| AppError::Message(format!("数据格式错误: {}", e)))?;
vec![single]
};
}
Ok(UsageResult {
success: true,
data: Some(usage_list),
error: None,
})
}
Err(err) => Ok(UsageResult {
success: false,
data: None,
error: Some(err.to_string()),
}),
}
/// 测试用量脚本(使用临时脚本内容,不保存)
pub async fn test_usage_script(
state: &AppState,
app_type: AppType,
provider_id: &str,
script_code: &str,
timeout: u64,
access_token: Option<&str>,
user_id: Option<&str>,
) -> Result<UsageResult, AppError> {
// 获取 provider 的 API 凭证
let provider = {
let config = state.config.read().map_err(AppError::from)?;
let manager = config
.get_manager(&app_type)
.ok_or_else(|| Self::app_not_found(&app_type))?;
manager
.providers
.get(provider_id)
.cloned()
.ok_or_else(|| AppError::ProviderNotFound(provider_id.to_string()))?
};
let (api_key, base_url) = Self::extract_credentials(&provider, &app_type)?;
Self::execute_and_format_usage_result(
script_code,
&api_key,
&base_url,
timeout,
access_token,
user_id,
)
.await
}
/// 切换指定应用的供应商