fix: 修复查询结果 API 响应格式解析问题

问题修复:
-  修复查询结果响应中缺少 task_id 字段导致的解析失败
-  重构数据结构,区分提交任务和查询结果的不同响应格式
-  添加类型守卫确保类型安全的数据访问
-  优化轮询逻辑,正确检测任务完成状态 (status: 'done')

技术改进:
- 分离提交和查询的数据结构:
  * RealmanAvatarPictureCreateRoleOmniSubmitData (包含 task_id)
  * RealmanAvatarPictureCreateRoleOmniResultData (包含 status, image_urls, resp_data)
- 使用 TypeScript 联合类型和类型守卫确保类型安全
- 更新前端代码使用正确的数据访问路径
- 完善错误处理和状态检测逻辑

现在 OmniHuman 主体识别功能可以正确解析查询结果响应了!
This commit is contained in:
imeepos 2025-08-05 18:55:31 +08:00
parent 1a97d54450
commit 365e2c4615
3 changed files with 57 additions and 23 deletions

View File

@ -136,21 +136,32 @@ pub struct RealmanAvatarPictureCreateRoleOmniResult {
pub time_elapsed: String, pub time_elapsed: String,
} }
/// OmniHuman 主体识别返回数据 /// OmniHuman 主体识别提交任务返回数据
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealmanAvatarPictureCreateRoleOmniData { pub struct RealmanAvatarPictureCreateRoleOmniSubmitData {
/// 任务ID
pub task_id: String,
}
/// OmniHuman 主体识别查询结果返回数据
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealmanAvatarPictureCreateRoleOmniResultData {
/// 输出处理过的图片url数组单张图 /// 输出处理过的图片url数组单张图
pub image_urls: Option<Vec<String>>, pub image_urls: Option<Vec<String>>,
/// 返回图base64数组 /// 返回图base64数组
pub binary_data_base64: Option<Vec<String>>, pub binary_data_base64: Option<Vec<String>>,
/// 任务ID
pub task_id: String,
/// 算法返回数据 /// 算法返回数据
pub resp_data: Option<String>, pub resp_data: Option<String>,
/// 任务状态 /// 任务状态
pub status: Option<String>, pub status: Option<String>,
/// 算法返回信息 }
pub algorithm_base_resp: Option<serde_json::Value>,
/// OmniHuman 主体识别返回数据(兼容两种情况)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RealmanAvatarPictureCreateRoleOmniData {
Submit(RealmanAvatarPictureCreateRoleOmniSubmitData),
Result(RealmanAvatarPictureCreateRoleOmniResultData),
} }
/// 火山云视频生成服务 /// 火山云视频生成服务

View File

@ -13,7 +13,11 @@ import { open } from '@tauri-apps/plugin-dialog';
import { useNotifications } from '../../components/NotificationSystem'; import { useNotifications } from '../../components/NotificationSystem';
import videoGenerationService from '../../services/videoGenerationService'; import videoGenerationService from '../../services/videoGenerationService';
import fileUploadService from '../../services/fileUploadService'; import fileUploadService from '../../services/fileUploadService';
import { RealmanAvatarPictureCreateRoleOmniResponse } from '../../types/videoGeneration'; import {
RealmanAvatarPictureCreateRoleOmniResponse,
RealmanAvatarPictureCreateRoleOmniResultData,
RealmanAvatarPictureCreateRoleOmniSubmitData
} from '../../types/videoGeneration';
const OmniHumanDetectionTool: React.FC = () => { const OmniHumanDetectionTool: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<string>(''); const [selectedImage, setSelectedImage] = useState<string>('');
@ -26,6 +30,16 @@ const OmniHumanDetectionTool: React.FC = () => {
const { addNotification, success, error } = useNotifications(); const { addNotification, success, error } = useNotifications();
// 类型守卫:检查是否为查询结果数据
const isResultData = (data: any): data is RealmanAvatarPictureCreateRoleOmniResultData => {
return data && typeof data === 'object' && ('status' in data || 'image_urls' in data || 'resp_data' in data);
};
// 类型守卫:检查是否为提交任务数据
const isSubmitData = (data: any): data is RealmanAvatarPictureCreateRoleOmniSubmitData => {
return data && typeof data === 'object' && 'task_id' in data;
};
// 轮询查询结果 // 轮询查询结果
const pollForResult = useCallback(async (taskId: string): Promise<RealmanAvatarPictureCreateRoleOmniResponse> => { const pollForResult = useCallback(async (taskId: string): Promise<RealmanAvatarPictureCreateRoleOmniResponse> => {
const maxAttempts = 30; // 最多轮询30次 const maxAttempts = 30; // 最多轮询30次
@ -36,13 +50,16 @@ const OmniHumanDetectionTool: React.FC = () => {
const result = await videoGenerationService.realmanAvatarPictureCreateRoleOmniGetResult(taskId); const result = await videoGenerationService.realmanAvatarPictureCreateRoleOmniGetResult(taskId);
// 检查任务状态 // 检查任务状态
if (result.Result.data?.status === 'completed' || result.Result.data?.image_urls?.length) { if (result.Result.data && isResultData(result.Result.data)) {
const resultData = result.Result.data;
if (resultData.status === 'done' || resultData.image_urls?.length) {
// 任务完成,返回结果 // 任务完成,返回结果
return result; return result;
} else if (result.Result.data?.status === 'failed') { } else if (resultData.status === 'failed') {
// 任务失败 // 任务失败
throw new Error('任务处理失败'); throw new Error('任务处理失败');
} }
}
// 任务还在处理中,继续轮询 // 任务还在处理中,继续轮询
setUploadProgress(80 + (attempt / maxAttempts) * 15); // 80-95% 的进度用于轮询 setUploadProgress(80 + (attempt / maxAttempts) * 15); // 80-95% 的进度用于轮询
@ -125,7 +142,8 @@ const OmniHumanDetectionTool: React.FC = () => {
setUploadProgress(80); setUploadProgress(80);
if (submitResponse.Result.code === 10000) { if (submitResponse.Result.code === 10000) {
const taskId = submitResponse.Result.data?.task_id; const data = submitResponse.Result.data;
const taskId = data && isSubmitData(data) ? data.task_id : undefined;
if (taskId) { if (taskId) {
setCurrentTaskId(taskId); setCurrentTaskId(taskId);
success('主体识别任务提交成功,正在处理中...'); success('主体识别任务提交成功,正在处理中...');
@ -269,17 +287,17 @@ const OmniHumanDetectionTool: React.FC = () => {
<span className="text-green-800 font-medium"></span> <span className="text-green-800 font-medium"></span>
</div> </div>
<div className="text-sm text-green-700"> <div className="text-sm text-green-700">
<p>ID: {result.Result.data?.task_id || currentTaskId}</p> <p>ID: {currentTaskId || (result.Result.data && isSubmitData(result.Result.data) ? result.Result.data.task_id : '未知')}</p>
<p>ID: {result.Result.request_id}</p> <p>ID: {result.Result.request_id}</p>
<p>: {result.Result.time_elapsed}</p> <p>: {result.Result.time_elapsed}</p>
</div> </div>
</div> </div>
{result.Result.data?.image_urls && result.Result.data.image_urls.length > 0 && ( {result.Result.data && isResultData(result.Result.data) && result.Result.data.image_urls && result.Result.data.image_urls.length > 0 && (
<div> <div>
<h4 className="font-medium text-gray-900 mb-2"></h4> <h4 className="font-medium text-gray-900 mb-2"></h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{result.Result.data.image_urls.map((url, index) => ( {result.Result.data.image_urls.map((url: string, index: number) => (
<div key={index} className="border border-gray-200 rounded-lg p-4 bg-gray-50"> <div key={index} className="border border-gray-200 rounded-lg p-4 bg-gray-50">
<img <img
src={url} src={url}
@ -302,7 +320,7 @@ const OmniHumanDetectionTool: React.FC = () => {
</div> </div>
)} )}
{result.Result.data?.resp_data && ( {result.Result.data && isResultData(result.Result.data) && result.Result.data.resp_data && (
<div> <div>
<h4 className="font-medium text-gray-900 mb-2"></h4> <h4 className="font-medium text-gray-900 mb-2"></h4>
<div className="p-3 bg-gray-50 border border-gray-200 rounded text-sm font-mono"> <div className="p-3 bg-gray-50 border border-gray-200 rounded text-sm font-mono">

View File

@ -397,21 +397,26 @@ export interface RealmanAvatarPictureCreateRoleOmniRequest {
image_url: string; image_url: string;
} }
export interface RealmanAvatarPictureCreateRoleOmniData { export interface RealmanAvatarPictureCreateRoleOmniSubmitData {
/** 任务ID */
task_id: string;
}
export interface RealmanAvatarPictureCreateRoleOmniResultData {
/** 输出处理过的图片url数组单张图 */ /** 输出处理过的图片url数组单张图 */
image_urls?: string[]; image_urls?: string[];
/** 返回图base64数组 */ /** 返回图base64数组 */
binary_data_base64?: string[]; binary_data_base64?: string[];
/** 任务ID */
task_id: string;
/** 算法返回数据 */ /** 算法返回数据 */
resp_data?: string; resp_data?: string;
/** 任务状态 */ /** 任务状态 */
status?: string; status?: string;
/** 算法返回信息 */
algorithm_base_resp?: any;
} }
export type RealmanAvatarPictureCreateRoleOmniData =
| RealmanAvatarPictureCreateRoleOmniSubmitData
| RealmanAvatarPictureCreateRoleOmniResultData;
export interface VolcanoResponseMetadata { export interface VolcanoResponseMetadata {
Action: string; Action: string;
Region: string; Region: string;