import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { ArrowLeftIcon, PhotoIcon, PlusIcon, PlayIcon, TrashIcon, EyeIcon, ArrowPathIcon, SparklesIcon } from '@heroicons/react/24/outline'; import { Model, PhotoType } from '../types/model'; import { VideoGenerationTask, VideoPromptConfig, VideoGenerationStatus, TEMPLATE_OPTIONS, SCENE_OPTIONS, PRODUCT_OPTIONS, VIDEO_GENERATION_STATUS_CONFIG } from '../types/videoGeneration'; import { videoGenerationService } from '../services/videoGenerationService'; import { open } from '@tauri-apps/plugin-dialog'; import { invoke } from '@tauri-apps/api/core'; import { CustomSelect } from '../components/CustomSelect'; import { LoadingSpinner } from '../components/LoadingSpinner'; import { DeleteConfirmDialog } from '../components/DeleteConfirmDialog'; const ModelDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); // 状态管理 const [model, setModel] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedPhotos, setSelectedPhotos] = useState([]); const [promptConfig, setPromptConfig] = useState({ product: '', scene: '', model_desc: '', template: '抚媚眼神', duplicate: 1 }); const [videoTasks, setVideoTasks] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [uploadingPhotos, setUploadingPhotos] = useState(false); const [deletePhotoConfirm, setDeletePhotoConfirm] = useState<{ show: boolean; photoId: string | null; photoName: string | null; deleting: boolean; }>({ show: false, photoId: null, photoName: null, deleting: false, }); const [_deleteTaskConfirm, setDeleteTaskConfirm] = useState<{ show: boolean; taskId: string | null; taskName: string | null; deleting: boolean; }>({ show: false, taskId: null, taskName: null, deleting: false, }); // 加载模特详情 const loadModelDetail = async () => { if (!id) return; try { setLoading(true); setError(null); const modelData = await videoGenerationService.getModelDetailWithPhotos(id); if (modelData) { setModel(modelData); // 设置默认的模特描述 setPromptConfig(prev => ({ ...prev, model_desc: modelData.description || `${modelData.name},${modelData.tags.join('、')}` })); } else { setError('模特不存在'); } } catch (err) { setError(err as string); } finally { setLoading(false); } }; // 加载视频生成任务 const loadVideoTasks = async () => { if (!id) return; try { const tasks = await videoGenerationService.getVideoGenerationTasks({ model_id: id }); setVideoTasks(tasks); } catch (err) { console.error('加载视频任务失败:', err); } }; // 上传照片 const handleUploadPhotos = async () => { if (!id) return; try { setUploadingPhotos(true); const selected = await open({ multiple: true, filters: [{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'] }] }); if (selected && Array.isArray(selected)) { const photos = await videoGenerationService.batchUploadModelPhotos( id, selected, PhotoType.Portrait, '模特照片', ['视频生成'] ); // 重新加载模特数据 await loadModelDetail(); console.log(`成功上传 ${photos.length} 张照片`); } } catch (err) { setError(`上传照片失败: ${err}`); } finally { setUploadingPhotos(false); } }; // 选择/取消选择照片 const togglePhotoSelection = (photoId: string) => { setSelectedPhotos(prev => prev.includes(photoId) ? prev.filter(id => id !== photoId) : [...prev, photoId] ); }; // 生成视频 const handleGenerateVideo = async () => { if (!id || selectedPhotos.length === 0) { setError('请至少选择一张照片'); return; } if (!promptConfig.product.trim() || !promptConfig.scene.trim()) { setError('请填写产品描述和场景描述'); return; } try { setIsGenerating(true); setError(null); const request = { model_id: id, prompt_config: promptConfig, selected_photos: selectedPhotos }; // 创建并执行任务 const task = await videoGenerationService.createAndExecuteVideoGeneration(request); // 重新加载任务列表 await loadVideoTasks(); // 开始轮询任务状态 videoGenerationService.pollTaskUntilComplete( task.id, (updatedTask) => { // 更新任务列表中的任务状态 setVideoTasks(prev => prev.map(t => t.id === updatedTask.id ? updatedTask : t) ); } ).then(() => { // 任务完成后重新加载任务列表 loadVideoTasks(); }).catch(err => { console.error('轮询任务状态失败:', err); }); } catch (err) { setError(`生成视频失败: ${err}`); } finally { setIsGenerating(false); } }; // 重试任务 const handleRetryTask = async (taskId: string) => { try { await videoGenerationService.retryVideoGenerationTask(taskId); await loadVideoTasks(); } catch (err) { setError(`重试任务失败: ${err}`); } }; // 显示删除任务确认弹框 const showDeleteTaskConfirm = (taskId: string) => { const task = videoTasks.find(t => t.id === taskId); const taskName = task ? `${task.prompt_config.product} - ${task.prompt_config.template}` : '未知任务'; setDeleteTaskConfirm({ show: true, taskId, taskName, deleting: false, }); }; // 显示删除照片确认弹框 const showDeletePhotoConfirm = (photoId: string) => { if (!model) return; const photo = model.photos.find(p => p.id === photoId); const photoName = photo?.description || photo?.file_name || '未知照片'; setDeletePhotoConfirm({ show: true, photoId, photoName, deleting: false, }); }; // 确认删除照片 const confirmDeletePhoto = async () => { if (!deletePhotoConfirm.photoId || !id) return; setDeletePhotoConfirm(prev => ({ ...prev, deleting: true })); try { await invoke('delete_model_photo', { modelId: id, photoId: deletePhotoConfirm.photoId }); // 重新加载模特数据 await loadModelDetail(); // 如果删除的照片在选中列表中,移除它 setSelectedPhotos(prev => prev.filter(selectedId => selectedId !== deletePhotoConfirm.photoId)); console.log('照片删除成功'); // 关闭确认弹框 setDeletePhotoConfirm({ show: false, photoId: null, photoName: null, deleting: false, }); } catch (err) { setError(`删除照片失败: ${err}`); setDeletePhotoConfirm(prev => ({ ...prev, deleting: false })); } }; // 取消删除照片 const cancelDeletePhoto = () => { setDeletePhotoConfirm({ show: false, photoId: null, photoName: null, deleting: false, }); }; // 初始化 useEffect(() => { loadModelDetail(); loadVideoTasks(); }, [id]); if (loading) { return (
); } if (error) { return (

{error}

); } if (!model) { return (

模特不存在

); } return (
{/* 头部 - 优化设计 */}

{model.name}

{model.stage_name && ( ({model.stage_name}) )}
{/* 左侧:模特信息和照片 */}
{/* 模特基本信息 - 优化设计 */}

基本信息

性别: {model.gender}
{model.age && (
年龄: {model.age}岁
)} {model.height && (
身高: {model.height}cm
)} {model.weight && (
体重: {model.weight}kg
)}
{model.description && (
描述:

{model.description}

)} {model.tags.length > 0 && (
标签:
{model.tags.map((tag, index) => ( {tag} ))}
)}
{/* 照片选择 */}

选择照片

已选择 {selectedPhotos.length} 张照片
{model.photos.length === 0 ? (

暂无照片,请先上传照片

) : (
{model.photos.map((photo) => (
togglePhotoSelection(photo.id)} > {photo.description
{/* 删除按钮 */} {/* 选中状态显示 */} {selectedPhotos.includes(photo.id) && (
{selectedPhotos.indexOf(photo.id) + 1}
)}
))}
)}
{/* 右侧:视频生成配置和任务 */}
{/* 视频生成配置 */}

视频生成配置

{/* 产品描述 */}
setPromptConfig(prev => ({ ...prev, product: e.target.value }))} className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200" placeholder="输入产品描述,如:超短牛仔裙(白色紧身蕾丝短袖)" list="product-options" /> {PRODUCT_OPTIONS.map((option, index) => (
{/* 场景描述 */}
setPromptConfig(prev => ({ ...prev, scene: e.target.value }))} className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200" placeholder="输入场景描述,如:室内可爱简约的女性卧室" list="scene-options" /> {SCENE_OPTIONS.map((option, index) => (
{/* 模特描述 */}