mixvideo-v2/apps/desktop/src/pages/tools/OmniHumanDetectionTool.tsx

281 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useCallback } from 'react';
import {
User,
Upload,
CheckCircle,
XCircle,
Loader2,
Image as ImageIcon,
AlertCircle,
Info
} from 'lucide-react';
import { open } from '@tauri-apps/plugin-dialog';
import { useNotifications } from '../../components/NotificationSystem';
import videoGenerationService from '../../services/videoGenerationService';
import fileUploadService from '../../services/fileUploadService';
import { RealmanAvatarPictureCreateRoleOmniResponse } from '../../types/videoGeneration';
const OmniHumanDetectionTool: React.FC = () => {
const [selectedImage, setSelectedImage] = useState<string>('');
const [imagePreview, setImagePreview] = useState<string>('');
const [isProcessing, setIsProcessing] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [result, setResult] = useState<RealmanAvatarPictureCreateRoleOmniResponse | null>(null);
const [errorMessage, setErrorMessage] = useState<string>('');
const { addNotification, success, error } = useNotifications();
// 选择图片文件
const handleSelectImage = useCallback(async () => {
try {
const selected = await open({
multiple: false,
filters: [
{
name: 'Images',
extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp']
}
]
});
if (selected) {
const imagePath = Array.isArray(selected) ? selected[0] : selected;
setSelectedImage(imagePath);
// 创建预览URL
const previewUrl = `file://${imagePath}`;
setImagePreview(previewUrl);
setResult(null);
setErrorMessage('');
}
} catch (err) {
console.error('选择图片失败:', err);
error('选择图片失败');
}
}, [addNotification]);
// 提交主体识别任务
const handleSubmitDetection = useCallback(async () => {
if (!selectedImage) {
addNotification({ type: 'warning', title: '请先选择图片' });
return;
}
setIsProcessing(true);
setErrorMessage('');
setResult(null);
setUploadProgress(0);
try {
// 1. 先上传图片到云端
setUploadProgress(10);
const uploadResult = await fileUploadService.uploadFileToCloud(
selectedImage,
undefined,
(progress) => {
setUploadProgress(10 + progress * 0.6); // 上传占 60% 进度
}
);
if (uploadResult.status !== 'success' || !uploadResult.url) {
throw new Error(uploadResult.error || '图片上传失败');
}
setUploadProgress(70);
// 2. 使用云端 URL 调用识别 API
const response = await videoGenerationService.realmanAvatarPictureCreateRoleOmniSubmitTask(uploadResult.url);
setUploadProgress(100);
setResult(response);
if (response.Result.code === 10000) {
success('主体识别任务提交成功');
} else {
setErrorMessage(`识别失败: ${response.Result.message}`);
error(`识别失败: ${response.Result.message}`);
}
} catch (err) {
const errMsg = err instanceof Error ? err.message : '未知错误';
setErrorMessage(errMsg);
error(`主体识别失败: ${errMsg}`);
} finally {
setIsProcessing(false);
}
}, [selectedImage, success, error]);
// 清除结果
const handleClear = useCallback(() => {
setSelectedImage('');
setImagePreview('');
setResult(null);
setErrorMessage('');
}, []);
return (
<div className="p-6 max-w-4xl mx-auto">
<div className="bg-white rounded-lg shadow-sm border border-gray-200">
{/* 标题 */}
<div className="border-b border-gray-200 p-6">
<div className="flex items-center gap-3">
<User className="w-6 h-6 text-blue-600" />
<div>
<h1 className="text-2xl font-bold text-gray-900">OmniHuman </h1>
<p className="text-gray-600 mt-1"></p>
</div>
</div>
</div>
{/* 功能说明 */}
<div className="p-6 bg-blue-50 border-b border-gray-200">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-blue-600 mt-0.5" />
<div className="text-sm text-blue-800">
<p className="font-medium mb-2"></p>
<ul className="list-disc list-inside space-y-1">
<li></li>
<li></li>
<li>PNGJPGJPEGGIFBMPWebP</li>
</ul>
</div>
</div>
</div>
{/* 图片选择区域 */}
<div className="p-6 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900 mb-4"></h3>
<div className="flex gap-4">
<button
onClick={handleSelectImage}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Upload className="w-4 h-4" />
</button>
{selectedImage && (
<button
onClick={handleClear}
className="flex items-center gap-2 px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
>
</button>
)}
</div>
{/* 图片预览 */}
{imagePreview && (
<div className="mt-4">
<div className="flex items-center gap-2 mb-2">
<ImageIcon className="w-4 h-4 text-gray-600" />
<span className="text-sm text-gray-600"></span>
</div>
<div className="border border-gray-200 rounded-lg p-4 bg-gray-50">
<img
src={imagePreview}
alt="预览图片"
className="max-w-full max-h-64 object-contain mx-auto rounded"
/>
</div>
</div>
)}
</div>
{/* 操作按钮 */}
<div className="p-6 border-b border-gray-200">
<button
onClick={handleSubmitDetection}
disabled={!selectedImage || isProcessing}
className="flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
>
{isProcessing ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<User className="w-4 h-4" />
)}
{isProcessing ? `处理中... ${uploadProgress}%` : '开始识别'}
</button>
</div>
{/* 结果显示 */}
<div className="p-6">
<h3 className="text-lg font-medium text-gray-900 mb-4"></h3>
{errorMessage && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<div className="flex items-center gap-2">
<XCircle className="w-5 h-5 text-red-600" />
<span className="text-red-800 font-medium"></span>
</div>
<p className="text-red-700 mt-2">{errorMessage}</p>
</div>
)}
{result && (
<div className="space-y-4">
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<CheckCircle className="w-5 h-5 text-green-600" />
<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.request_id}</p>
<p>: {result.Result.time_elapsed}</p>
</div>
</div>
{result.Result.data?.image_urls && result.Result.data.image_urls.length > 0 && (
<div>
<h4 className="font-medium text-gray-900 mb-2"></h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{result.Result.data.image_urls.map((url, index) => (
<div key={index} className="border border-gray-200 rounded-lg p-4 bg-gray-50">
<img
src={url}
alt={`处理结果 ${index + 1}`}
className="max-w-full max-h-64 object-contain mx-auto rounded"
/>
<div className="mt-2 text-center">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 text-sm"
>
</a>
</div>
</div>
))}
</div>
</div>
)}
{result.Result.data?.resp_data && (
<div>
<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">
{result.Result.data.resp_data}
</div>
</div>
)}
</div>
)}
{!result && !errorMessage && !isProcessing && (
<div className="text-center py-8 text-gray-500">
<AlertCircle className="w-12 h-12 mx-auto mb-3 text-gray-400" />
<p></p>
</div>
)}
</div>
</div>
</div>
);
};
export default OmniHumanDetectionTool;