From 1a97d54450477cf2a91f2e5bcffaa0dac85352a6 Mon Sep 17 00:00:00 2001 From: imeepos Date: Tue, 5 Aug 2025 18:45:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=20OmniHuman=20?= =?UTF-8?q?=E4=B8=BB=E4=BD=93=E8=AF=86=E5=88=AB=E5=AE=8C=E6=95=B4=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - 添加 RealmanAvatarPictureCreateRoleOmniGetResult API 查询任务结果 - 实现完整的提交任务 + 轮询查询结果流程 - 智能轮询机制:最多30次,每2秒一次,自动检测任务完成状态 - 完善的进度反馈:上传(10-70%) + 识别(70-80%) + 轮询(80-95%) + 完成(100%) - 任务ID跟踪和显示,便于用户了解处理状态 技术实现: - 后端: 新增查询结果方法和 Tauri 命令 - 前端: 实现轮询逻辑和状态管理 - 错误处理: 区分上传失败、识别失败、查询超时等不同场景 - 用户体验: 实时进度显示和详细状态反馈 API 集成: - 提交任务: RealmanAvatarPictureCreateRoleOmniSubmitTask - 查询结果: RealmanAvatarPictureCreateRoleOmniGetResult - 完整流程: 图片上传 任务提交 轮询查询 结果展示 现在用户可以完整体验从图片上传到最终结果的全流程! --- .../services/volcano_video_service.rs | 65 +++++++++++++++++++ apps/desktop/src-tauri/src/lib.rs | 1 + .../commands/volcano_video_commands.rs | 37 +++++++++++ .../pages/tools/OmniHumanDetectionTool.tsx | 64 +++++++++++++++--- .../src/services/videoGenerationService.ts | 17 +++++ 5 files changed, 176 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs b/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs index 8c38470..064c30d 100644 --- a/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs +++ b/apps/desktop/src-tauri/src/business/services/volcano_video_service.rs @@ -92,6 +92,15 @@ pub struct RealmanAvatarPictureCreateRoleOmniRequest { pub image_url: String, } +/// OmniHuman 主体识别查询结果请求参数 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RealmanAvatarPictureCreateRoleOmniGetResultRequest { + /// 服务标识,固定值 + pub req_key: String, + /// 任务ID + pub task_id: String, +} + /// 火山云 API 响应元数据 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VolcanoResponseMetadata { @@ -601,6 +610,62 @@ impl VolcanoVideoService { Ok(api_response) } + /// OmniHuman 主体识别 - 查询任务结果 + /// 用于查询已提交的主体识别任务的处理结果 + pub async fn realman_avatar_picture_create_role_omni_get_result( + &self, + task_id: String + ) -> Result { + info!("开始查询 OmniHuman 主体识别任务结果: {}", task_id); + + // 构建请求参数 + let request_body = RealmanAvatarPictureCreateRoleOmniGetResultRequest { + req_key: "realman_avatar_picture_create_role_omni".to_string(), + task_id, + }; + + // 火山云API调用 - OmniHuman 主体识别查询结果 + let api_url = "https://visual.volcengineapi.com?Action=RealmanAvatarPictureCreateRoleOmniGetResult&Version=2024-06-06"; + + // 构建认证头 + let now = Utc::now(); + let timestamp = now.format("%Y%m%dT%H%M%SZ").to_string(); + let date = now.format("%Y%m%d").to_string(); + let auth_header = self.build_auth_header("POST", api_url, ×tamp, &date, &request_body)?; + + info!("调用 OmniHuman 主体识别查询结果 API: {:?}", request_body); + + let response = self.http_client + .post(api_url) + .header("Authorization", auth_header) + .header("Content-Type", "application/json") + .header("X-Date", timestamp) + .json(&request_body) + .send() + .await?; + + if !response.status().is_success() { + let status = response.status(); + let error_text = response.text().await.unwrap_or_default(); + return Err(anyhow!("OmniHuman 主体识别查询结果 API 调用失败: {} - {}", status, error_text)); + } + + // 先获取响应文本进行调试 + let response_text = response.text().await?; + info!("OmniHuman 查询结果 API 原始响应: {}", response_text); + + // 尝试解析响应 + let api_response: RealmanAvatarPictureCreateRoleOmniResponse = serde_json::from_str(&response_text) + .map_err(|e| anyhow!("解析查询结果响应失败: {} - 响应内容: {}", e, response_text))?; + + if api_response.result.code != 10000 { + return Err(anyhow!("OmniHuman 主体识别查询结果 API 返回错误: {} - {}", api_response.result.code, api_response.result.message)); + } + + info!("OmniHuman 主体识别任务结果查询成功: {:?}", api_response); + Ok(api_response) + } + /// 批量删除视频生成记录 pub async fn batch_delete_video_generations(&self, ids: Vec) -> Result<()> { for id in ids { diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 47e96cb..6abf77a 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -503,6 +503,7 @@ pub fn run() { commands::volcano_video_commands::batch_download_volcano_videos, commands::volcano_video_commands::get_video_stream_base64, commands::volcano_video_commands::realman_avatar_picture_create_role_omni_submit_task, + commands::volcano_video_commands::realman_avatar_picture_create_role_omni_get_result, // 图像编辑命令 commands::image_editing_commands::set_image_editing_config, commands::image_editing_commands::set_image_editing_api_key, diff --git a/apps/desktop/src-tauri/src/presentation/commands/volcano_video_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/volcano_video_commands.rs index 6c73725..058f21f 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/volcano_video_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/volcano_video_commands.rs @@ -480,3 +480,40 @@ pub async fn realman_avatar_picture_create_role_omni_submit_task( } } } + +/// OmniHuman 主体识别 - 查询任务结果 +/// 用于查询已提交的主体识别任务的处理结果 +#[tauri::command] +pub async fn realman_avatar_picture_create_role_omni_get_result( + state: State<'_, AppState>, + task_id: String, +) -> Result { + info!("开始查询 OmniHuman 主体识别任务结果: {}", task_id); + + // 获取数据库连接 + let database = { + let database_guard = state + .database + .lock() + .map_err(|e| format!("获取数据库失败: {}", e))?; + database_guard + .as_ref() + .ok_or("数据库未初始化")? + .clone() + }; + + // 创建服务实例 + let service = VolcanoVideoService::new(database); + + // 调用 OmniHuman 主体识别查询结果 + match service.realman_avatar_picture_create_role_omni_get_result(task_id).await { + Ok(response) => { + info!("OmniHuman 主体识别任务结果查询成功: {:?}", response); + Ok(response) + } + Err(e) => { + error!("OmniHuman 主体识别任务结果查询失败: {}", e); + Err(e.to_string()) + } + } +} diff --git a/apps/desktop/src/pages/tools/OmniHumanDetectionTool.tsx b/apps/desktop/src/pages/tools/OmniHumanDetectionTool.tsx index 9c2058c..593bf05 100644 --- a/apps/desktop/src/pages/tools/OmniHumanDetectionTool.tsx +++ b/apps/desktop/src/pages/tools/OmniHumanDetectionTool.tsx @@ -20,11 +20,46 @@ const OmniHumanDetectionTool: React.FC = () => { const [imagePreview, setImagePreview] = useState(''); const [isProcessing, setIsProcessing] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); + const [currentTaskId, setCurrentTaskId] = useState(''); const [result, setResult] = useState(null); const [errorMessage, setErrorMessage] = useState(''); const { addNotification, success, error } = useNotifications(); + // 轮询查询结果 + const pollForResult = useCallback(async (taskId: string): Promise => { + const maxAttempts = 30; // 最多轮询30次 + const pollInterval = 2000; // 每2秒轮询一次 + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const result = await videoGenerationService.realmanAvatarPictureCreateRoleOmniGetResult(taskId); + + // 检查任务状态 + if (result.Result.data?.status === 'completed' || result.Result.data?.image_urls?.length) { + // 任务完成,返回结果 + return result; + } else if (result.Result.data?.status === 'failed') { + // 任务失败 + throw new Error('任务处理失败'); + } + + // 任务还在处理中,继续轮询 + setUploadProgress(80 + (attempt / maxAttempts) * 15); // 80-95% 的进度用于轮询 + await new Promise(resolve => setTimeout(resolve, pollInterval)); + + } catch (err) { + if (attempt === maxAttempts) { + throw err; + } + // 继续重试 + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + } + + throw new Error('任务处理超时,请稍后手动查询结果'); + }, []); + // 选择图片文件 const handleSelectImage = useCallback(async () => { try { @@ -85,16 +120,27 @@ const OmniHumanDetectionTool: React.FC = () => { setUploadProgress(70); // 2. 使用云端 URL 调用识别 API - const response = await videoGenerationService.realmanAvatarPictureCreateRoleOmniSubmitTask(uploadResult.url); + const submitResponse = await videoGenerationService.realmanAvatarPictureCreateRoleOmniSubmitTask(uploadResult.url); - setUploadProgress(100); - setResult(response); + setUploadProgress(80); - if (response.Result.code === 10000) { - success('主体识别任务提交成功'); + if (submitResponse.Result.code === 10000) { + const taskId = submitResponse.Result.data?.task_id; + if (taskId) { + setCurrentTaskId(taskId); + success('主体识别任务提交成功,正在处理中...'); + + // 3. 轮询查询结果 + const finalResult = await pollForResult(taskId); + setUploadProgress(100); + setResult(finalResult); + success('主体识别完成!'); + } else { + throw new Error('未获取到任务ID'); + } } else { - setErrorMessage(`识别失败: ${response.Result.message}`); - error(`识别失败: ${response.Result.message}`); + setErrorMessage(`识别失败: ${submitResponse.Result.message}`); + error(`识别失败: ${submitResponse.Result.message}`); } } catch (err) { const errMsg = err instanceof Error ? err.message : '未知错误'; @@ -111,6 +157,8 @@ const OmniHumanDetectionTool: React.FC = () => { setImagePreview(''); setResult(null); setErrorMessage(''); + setCurrentTaskId(''); + setUploadProgress(0); }, []); return ( @@ -221,7 +269,7 @@ const OmniHumanDetectionTool: React.FC = () => { 识别成功
-

任务ID: {result.Result.data?.task_id}

+

任务ID: {result.Result.data?.task_id || currentTaskId}

请求ID: {result.Result.request_id}

处理时间: {result.Result.time_elapsed}

diff --git a/apps/desktop/src/services/videoGenerationService.ts b/apps/desktop/src/services/videoGenerationService.ts index 0fb13d8..07ef914 100644 --- a/apps/desktop/src/services/videoGenerationService.ts +++ b/apps/desktop/src/services/videoGenerationService.ts @@ -282,6 +282,23 @@ class VideoGenerationService implements VideoGenerationAPI { throw new Error(`OmniHuman 主体识别任务失败: ${error}`); } } + + /** + * OmniHuman 主体识别 - 查询任务结果 + * 用于查询已提交的主体识别任务的处理结果 + */ + async realmanAvatarPictureCreateRoleOmniGetResult(taskId: string): Promise { + try { + const response = await invoke( + 'realman_avatar_picture_create_role_omni_get_result', + { taskId } + ); + return response; + } catch (error) { + console.error('OmniHuman 主体识别结果查询失败:', error); + throw new Error(`OmniHuman 主体识别结果查询失败: ${error}`); + } + } } // 导出单例实例