281 lines
10 KiB
TypeScript
281 lines
10 KiB
TypeScript
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>支持多种图片格式:PNG、JPG、JPEG、GIF、BMP、WebP</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;
|