import React, { useState, useEffect, useRef } from 'react'; import { Layout, Button, Input, Upload, message, Card, Modal, Table, Space, Pagination, Popconfirm, } from 'antd'; import { PlusOutlined, VideoCameraOutlined, PictureOutlined, LoadingOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { uploadVideo, uploadImage, generateReplication, getReplicationList, getone, getReplicationDetail, deleteHotOpeningReplicationTask } from '../api'; const { Header, Content } = Layout; const { TextArea } = Input; const GenerateConver: React.FC = () => { const navigate = useNavigate(); const [tableData, setTableData] = useState( [] ); // 分页状态 const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [total, setTotal] = useState(0); const [searchKeyword, setSearchKeyword] = useState(''); // 卡片列表专用状态 const [cardData, setCardData] = useState([]); const [cardCurrentPage, setCardCurrentPage] = useState(1); const [cardPageSize, setCardPageSize] = useState(8); const [cardTotal, setCardTotal] = useState(0); // 表单数据 const [originalProductName, setOriginalProductName] = useState(''); const [ownProductName, setOwnProductName] = useState(''); const [productSellingPoints, setProductSellingPoints] = useState(''); // 文件状态 const [videoFile, setVideoFile] = useState(null); const [videoUrl, setVideoUrl] = useState(''); const [imageFile, setImageFile] = useState(null); const [imageUrl, setImageUrl] = useState(''); const [videoUploading, setVideoUploading] = useState(false); const [imageUploading, setImageUploading] = useState(false); // 弹窗状态 const [isModalOpen, setIsModalOpen] = useState(false); // 卡片列表轮询定时器(使用 ref 避免闭包问题) const cardPollingTimer = useRef(null); // 表格轮询定时器(使用 ref 避免闭包问题) const tablePollingTimer = useRef(null); // 获取卡片列表数据(独立于表格) const fetchCardList = (page: number, size: number, isPolling = false) => { getReplicationList(page, size).then((res: any) => { if (res.items) { if (isPolling) { // 轮询时:增量更新,只更新状态发生变化的项目 setCardData(prevData => { return prevData.map(existingItem => { const newItem = res.items.find((item: any) => item.id === existingItem.id); if (newItem && newItem.status !== existingItem.status) { return newItem; } return existingItem; }); }); } else { // 首次加载或分页变化时:直接替换 setCardData(res.items); } // 检查是否有 processing 状态 const hasProcessing = res.items.some(item => item.status === 'processing'); // 如果有 processing 状态且轮询未启动,启动轮询 if (hasProcessing && !cardPollingTimer.current) { cardPollingTimer.current = setInterval(() => { fetchCardList(cardCurrentPage, cardPageSize, true); }, 5000); } // 如果没有 processing 状态且轮询正在运行,停止轮询 else if (!hasProcessing && cardPollingTimer.current) { clearInterval(cardPollingTimer.current); cardPollingTimer.current = null; } else { } } if (res.total !== undefined) { setCardTotal(res.total); } }).catch((error: any) => { }); }; // 卡片列表分页变化处理 const handleCardPageChange = (page: number) => { setCardCurrentPage(page); fetchCardList(page, cardPageSize); }; // 获取列表数据的函数 const fetchList = (page: number, size: number, isPolling = false, keyword?: string) => { getReplicationList(page, size, keyword).then((res: any) => { if (res.items) { if (isPolling) { // 轮询时:增量更新,只更新状态发生变化的项目 setTableData(prevData => { return prevData.map(existingItem => { const newItem = res.items.find((item: any) => item.id === existingItem.id); if (newItem && newItem.status !== existingItem.status) { return newItem; } return existingItem; }); }); } else { // 首次加载或分页变化时:直接替换 setTableData(res.items); } // 检查是否有 processing 状态 const hasProcessing = res.items.some(item => item.status === 'processing'); // 如果有 processing 状态且轮询未启动,启动轮询 if (hasProcessing && !tablePollingTimer.current) { tablePollingTimer.current = setInterval(() => { fetchList(currentPage, pageSize, true, searchKeyword); }, 5000); } // 如果没有 processing 状态且轮询正在运行,停止轮询 else if (!hasProcessing && tablePollingTimer.current) { clearInterval(tablePollingTimer.current); tablePollingTimer.current = null; } } if (res.total !== undefined) { setTotal(res.total); } }).catch((error: any) => { }); }; // 组件挂载时获取复刻列表 useEffect(() => { fetchList(currentPage, pageSize); fetchCardList(cardCurrentPage, cardPageSize); // 组件卸载时清理定时器 return () => { if (cardPollingTimer.current) { clearInterval(cardPollingTimer.current); cardPollingTimer.current = null; } if (tablePollingTimer.current) { clearInterval(tablePollingTimer.current); tablePollingTimer.current = null; } }; }, []); // 分页变化处理 const handlePageChange = (page: number, size: number) => { setCurrentPage(page); setPageSize(size); fetchList(page, size, false, searchKeyword); }; // 搜索处理 const handleSearch = () => { setCurrentPage(1); fetchList(1, pageSize, false, searchKeyword); }; // 文件上传前校验 const beforeVideoUpload = async (file: File): Promise => { const isVideo = file.type.startsWith('video/'); if (!isVideo) { message.error('只能上传视频文件'); return false; } const isLt50M = file.size / 1024 / 1024 < 50; if (!isLt50M) { message.error('视频大小不能超过50MB'); return false; } // 检查视频时长 const video = document.createElement('video'); video.preload = 'metadata'; return new Promise((resolve) => { video.onloadedmetadata = async () => { if (video.duration < 2) { message.error('视频时长不能少于2秒'); URL.revokeObjectURL(video.src); resolve(false); return; } if (video.duration >= 16) { message.error('视频时长不能超过15秒'); URL.revokeObjectURL(video.src); resolve(false); return; } // 调用上传接口 setVideoUploading(true); try { const res = await uploadVideo(file); setVideoFile(file); setVideoUrl(`${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}${res.url}`); message.success('视频上传成功'); resolve(false); } catch (error) { // 获取接口返回的错误信息 let errorMsg = ''; if (error instanceof Error) { try { const errorData = JSON.parse(error.message); errorMsg = errorData.detail || error.message; } catch { errorMsg = error.message; } } message.error(errorMsg || '视频上传失败'); resolve(false); } finally { setVideoUploading(false); URL.revokeObjectURL(video.src); } }; video.onerror = () => { message.error('视频文件无效'); URL.revokeObjectURL(video.src); resolve(false); }; video.src = URL.createObjectURL(file); }); }; const beforeImageUpload = async (file: File): Promise => { const isImage = file.type.startsWith('image/'); if (!isImage) { message.error('只能上传图片文件'); return false; } const isLt10M = file.size / 1024 / 1024 < 10; if (!isLt10M) { message.error('图片大小不能超过10MB'); return false; } // 检查图片比例 const img = new Image(); return new Promise((resolve) => { img.onload = async () => { const ratio = img.width / img.height; const isRatioValid = Math.abs(ratio - 0.75) < 0.1 || Math.abs(ratio - 0.5625) < 0.1; // if (!isRatioValid) { // message.warning('建议使用3:4或9:16比例的图片以获得最佳效果'); // } // 调用上传接口 setImageUploading(true); try { const res = await uploadImage(file); setImageFile(file); setImageUrl(`${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}${res.url}`); message.success('图片上传成功'); resolve(false); } catch (error) { // 获取接口返回的错误信息 let errorMsg = ''; if (error instanceof Error) { try { const errorData = JSON.parse(error.message); errorMsg = errorData.detail || error.message; } catch { errorMsg = error.message; } } message.error(errorMsg || '图片上传失败'); resolve(false); } finally { setImageUploading(false); URL.revokeObjectURL(img.src); } }; img.onerror = () => { message.error('图片文件无效'); URL.revokeObjectURL(img.src); resolve(false); }; img.src = URL.createObjectURL(file); }); }; // 删除视频 const handleRemoveVideo = () => { if (videoUrl) { URL.revokeObjectURL(videoUrl); } setVideoFile(null); setVideoUrl(''); }; // 删除图片 const handleRemoveImage = () => { if (imageUrl) { URL.revokeObjectURL(imageUrl); } setImageFile(null); setImageUrl(''); }; // 处理生成 const handleGenerate = () => { if (!videoUrl) { message.warning('请上传复刻视频'); return; } if (!imageUrl) { message.warning('请上传产品图片'); return; } if (!originalProductName.trim()) { message.warning('请输入原视频产品名称'); return; } if (!ownProductName.trim()) { message.warning('请输入自有产品名称'); return; } if (!productSellingPoints.trim()) { message.warning('请输入产品卖点'); return; } // message.success('正在生成爆款开头复刻视频...'); // console.log('生成参数:', { // videoUrl, // imageUrl, // originalProductName, // ownProductName, // productSellingPoints, // }); let params = { material_video_url: videoUrl, material_image_url: imageUrl, source_project_name: originalProductName, target_project_name: ownProductName, core_content_point: productSellingPoints, idempotency_key: Date.now().toString(), } // 在这里调用生成视频的API,并传递上述参数 generateReplication(params).then((res) => { let projectId = ''; let childId = ''; // console.log('生成视频成功:', res); // message.success('任务开始'); // 清空上传的媒体和文本 setVideoUrl(''); setImageUrl(''); setOriginalProductName(''); setOwnProductName(''); setProductSellingPoints(''); fetchCardList(1, 8); // 获取第一个的id直接进行下一步 getReplicationList(1, 20).then((res: any) => { if (res.items) { const firstId = res.items[0].id; setTableData(res.items); getReplicationDetail(res.items[0].id).then((res: any) => { projectId = res.id; childId = res.steps[0].id; getone(projectId, childId).then((res: any) => { message.loading('创建中...', 3); setTimeout(() => { navigate(`/initial/${firstId}/initialinfo`); }, 3000); }) }).catch((error: any) => { }); } }).catch((error: any) => { }); }).catch((error) => { message.error('视频开头复刻失败'); }); }; return (
{/* 顶部标题栏 */}
{/*
*/}

爆款复刻

上传参考视频与产品图片,一键复刻爆款视频开头

{/*
*/}
{/* 左侧预览区域 */}
{cardData.length > 0 ? (

历史生成作品

{cardData.map((item) => (
navigate(`/initial/${item.id}/initialinfo`)} onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-6px)'; e.currentTarget.style.boxShadow = '0 12px 32px rgba(99, 102, 241, 0.12)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = '0 4px 16px rgba(99, 102, 241, 0.06)'; }} >
{item.finalVideoUrl ? ( {item.targetProjectName} ) : (
)}
{(() => { const status = item.status; const currentStepCode = item.current_step_code || item.currentStepCode; if (currentStepCode === 'image_prompt_optimize') { switch (status) { case 'waiting_user': return '等待融合图生成'; case 'processing': return '图片提示词生成中'; case 'completed': return '图片提示词生成成功'; case 'failed': return '图片提示词生成失败'; default: return status || '待处理'; } } else if (currentStepCode === 'image_generate') { switch (status) { case 'waiting_user': return '等待生成视频提示词'; case 'processing': return '融合图生成中'; case 'completed': return '融合图生成成功'; case 'failed': return '融合图生成失败'; default: return status || '待处理'; } } else if (currentStepCode === 'video_prompt_optimize') { switch (status) { case 'waiting_user': return '等待最终视频生成'; case 'processing': return '视频提示词生成中'; case 'completed': return '视频提示词生成成功'; case 'failed': return '视频提示词生成失败'; default: return status || '待处理'; } } else if (currentStepCode === 'video_generate') { switch (status) { case 'waiting_user': return ''; case 'processing': return '最终视频生成中'; case 'completed': return '最终视频生成成功'; case 'failed': return '最终视频生成失败'; default: return status || '待处理'; } } else if (currentStepCode === 'material_input') { switch (status) { case 'waiting_user': return '等待生成图片提示词'; case 'processing': return '素材处理中'; case 'completed': return '素材上传成功'; case 'failed': return '素材上传失败'; default: return status || '待处理'; } } else { const statusMap: Record = { 'pending': '子任务待处理', 'waiting_user': '等待用户确认或触发', 'processing': '子任务处理中', 'completed': '子任务完成', 'failed': '子任务失败', 'cancelled': '子任务取消', }; return statusMap[status] || status || '待处理'; } })()}

{item.targetProjectName || '未命名作品'}

{item.createdAt ? new Date(item.createdAt).toLocaleDateString('zh-CN') : ''}

{(() => { const status = item.status; if (status === 'waiting_user') { return 等待下一步; } else if (status === 'processing') { return ( 进行中 ... ); } else if (status === 'failed') { return 失败; } else if (status === 'completed' && item.finalVideoUrl) { return 任务完成; } return null; })()}
))}
`共 ${total} 条`} itemRender={(current, type, originalElement) => { if (type === 'prev') { return ( ); } if (type === 'next') { return ( ); } if (type === 'page') { return ( ); } return originalElement; }} />
) : (

暂无生成内容

请完善素材与卖点,生成专属爆款复刻视频

)}
{/* 右侧表单区域 */}
{/* 上传视频 */}

上传复刻视频

{videoUrl ? (
) : (
{ (e.currentTarget as HTMLElement).style.borderColor = '#6366f1'; (e.currentTarget as HTMLElement).style.background = 'rgba(99, 102, 241, 0.05)'; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(99, 102, 241, 0.3)'; (e.currentTarget as HTMLElement).style.background = 'rgba(99, 102, 241, 0.02)'; }} > {videoUploading ? (

上传中...

支持的文件类型:MP4、MOV | 视频最大时长:15 秒 | 最大大小:50M

) : ( <>

点击或拖拽上传视频

支持的文件类型:MP4、MOV | 视频最大时长:15 秒 | 最大大小:50M

)}
)}
{/* 上传产品图片 */}

上传产品图片

{imageUrl ? (
产品图片
{imageFile?.name} ({(imageFile?.size ? (imageFile.size / 1024 / 1024).toFixed(2) : 0)}MB)
) : (
{ (e.currentTarget as HTMLElement).style.borderColor = '#6366f1'; (e.currentTarget as HTMLElement).style.background = 'rgba(99, 102, 241, 0.05)'; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.borderColor = 'rgba(99, 102, 241, 0.3)'; (e.currentTarget as HTMLElement).style.background = 'rgba(99, 102, 241, 0.02)'; }} > {imageUploading ? (

上传中...

支持 JPG, JPEG, PNG 格式,图片比例为 3:4 或 9:16 效果最佳

) : ( <>

+ 点击上传

支持 JPG, JPEG, PNG 格式,图片比例为 3:4 或 9:16 效果最佳

)}
)}
{/* 原视频产品名称 */}

原视频产品名称

setOriginalProductName(e.target.value)} placeholder="请输入原视频产品名称" style={{ borderRadius: 10, height: 40, fontSize: 13, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)', }} maxLength={10} suffix={{originalProductName.length}/10} />
{/* 自有产品名称 */}

自有产品名称

setOwnProductName(e.target.value)} placeholder="请输入自有产品名称" style={{ borderRadius: 10, height: 40, fontSize: 13, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)', }} maxLength={10} suffix={{ownProductName.length}/10} />
{/* 产品卖点 */}

产品卖点