feat: 完成 OmniHuman 主体识别完整功能
新增功能: - 添加 RealmanAvatarPictureCreateRoleOmniGetResult API 查询任务结果 - 实现完整的提交任务 + 轮询查询结果流程 - 智能轮询机制:最多30次,每2秒一次,自动检测任务完成状态 - 完善的进度反馈:上传(10-70%) + 识别(70-80%) + 轮询(80-95%) + 完成(100%) - 任务ID跟踪和显示,便于用户了解处理状态 技术实现: - 后端: 新增查询结果方法和 Tauri 命令 - 前端: 实现轮询逻辑和状态管理 - 错误处理: 区分上传失败、识别失败、查询超时等不同场景 - 用户体验: 实时进度显示和详细状态反馈 API 集成: - 提交任务: RealmanAvatarPictureCreateRoleOmniSubmitTask - 查询结果: RealmanAvatarPictureCreateRoleOmniGetResult - 完整流程: 图片上传 任务提交 轮询查询 结果展示 现在用户可以完整体验从图片上传到最终结果的全流程!
This commit is contained in:
parent
d58499b564
commit
1a97d54450
|
|
@ -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<RealmanAvatarPictureCreateRoleOmniResponse> {
|
||||
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<String>) -> Result<()> {
|
||||
for id in ids {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<RealmanAvatarPictureCreateRoleOmniResponse, String> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,46 @@ const OmniHumanDetectionTool: React.FC = () => {
|
|||
const [imagePreview, setImagePreview] = useState<string>('');
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [currentTaskId, setCurrentTaskId] = useState<string>('');
|
||||
const [result, setResult] = useState<RealmanAvatarPictureCreateRoleOmniResponse | null>(null);
|
||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||
|
||||
const { addNotification, success, error } = useNotifications();
|
||||
|
||||
// 轮询查询结果
|
||||
const pollForResult = useCallback(async (taskId: string): Promise<RealmanAvatarPictureCreateRoleOmniResponse> => {
|
||||
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 = () => {
|
|||
<span className="text-green-800 font-medium">识别成功</span>
|
||||
</div>
|
||||
<div className="text-sm text-green-700">
|
||||
<p>任务ID: {result.Result.data?.task_id}</p>
|
||||
<p>任务ID: {result.Result.data?.task_id || currentTaskId}</p>
|
||||
<p>请求ID: {result.Result.request_id}</p>
|
||||
<p>处理时间: {result.Result.time_elapsed}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -282,6 +282,23 @@ class VideoGenerationService implements VideoGenerationAPI {
|
|||
throw new Error(`OmniHuman 主体识别任务失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OmniHuman 主体识别 - 查询任务结果
|
||||
* 用于查询已提交的主体识别任务的处理结果
|
||||
*/
|
||||
async realmanAvatarPictureCreateRoleOmniGetResult(taskId: string): Promise<RealmanAvatarPictureCreateRoleOmniResponse> {
|
||||
try {
|
||||
const response = await invoke<RealmanAvatarPictureCreateRoleOmniResponse>(
|
||||
'realman_avatar_picture_create_role_omni_get_result',
|
||||
{ taskId }
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('OmniHuman 主体识别结果查询失败:', error);
|
||||
throw new Error(`OmniHuman 主体识别结果查询失败: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
|
|
|||
Loading…
Reference in New Issue