From bafddb8e52e0973d7805535d26bd1bb61653c492 Mon Sep 17 00:00:00 2001 From: Jason Date: Tue, 4 Nov 2025 23:04:44 +0800 Subject: [PATCH] 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 --- src-tauri/src/commands/provider.rs | 29 ++++++++ src-tauri/src/lib.rs | 1 + src-tauri/src/services/provider.rs | 104 +++++++++++++++++++++------- src/components/UsageScriptModal.tsx | 10 ++- src/lib/api/usage.ts | 33 +++++++++ 5 files changed, 152 insertions(+), 25 deletions(-) diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index fb6f5b1..6732c6d 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -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, + #[allow(non_snake_case)] + accessToken: Option, + #[allow(non_snake_case)] + userId: Option, +) -> Result { + 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 { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 78092a3..7a58f06 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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, diff --git a/src-tauri/src/services/provider.rs b/src-tauri/src/services/provider.rs index e1afb35..564e706 100644 --- a/src-tauri/src/services/provider.rs +++ b/src-tauri/src/services/provider.rs @@ -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 { + match usage_script::execute_usage_script( + script_code, + api_key, + base_url, + timeout, + access_token, + user_id, + ) + .await + { + Ok(data) => { + let usage_list: Vec = 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 = 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 { + // 获取 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 } /// 切换指定应用的供应商 diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index 5df0342..359aa83 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -144,7 +144,15 @@ const UsageScriptModal: React.FC = ({ const handleTest = async () => { setTesting(true); try { - const result = await usageApi.query(provider.id, appId); + // 使用当前编辑器中的脚本内容进行测试 + const result = await usageApi.testScript( + provider.id, + appId, + script.code, + script.timeout, + script.accessToken, + script.userId + ); if (result.success && result.data && result.data.length > 0) { // 显示所有套餐数据 const summary = result.data diff --git a/src/lib/api/usage.ts b/src/lib/api/usage.ts index eb08730..4751b68 100644 --- a/src/lib/api/usage.ts +++ b/src/lib/api/usage.ts @@ -26,4 +26,37 @@ export const usageApi = { }; } }, + + async testScript( + providerId: string, + appId: AppId, + scriptCode: string, + timeout?: number, + accessToken?: string, + userId?: string + ): Promise { + try { + return await invoke("test_usage_script", { + providerId: providerId, + app: appId, + scriptCode: scriptCode, + timeout: timeout, + accessToken: accessToken, + userId: userId, + }); + } catch (error: unknown) { + const message = + typeof error === "string" + ? error + : error instanceof Error + ? error.message + : ""; + + return { + success: false, + error: message || i18n.t("errors.usage_query_failed"), + }; + } + }, }; +