@@ -2423,11 +2425,13 @@ const AIChatPage: React.FC = () => {
×
}
- bodyStyle={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- minHeight: '400px',
+ styles={{
+ body: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ minHeight: '400px',
+ }
}}
>
diff --git a/video-gen-app/src/pages/InitialInfo.tsx b/video-gen-app/src/pages/InitialInfo.tsx
index 51002bda..cf2db716 100644
--- a/video-gen-app/src/pages/InitialInfo.tsx
+++ b/video-gen-app/src/pages/InitialInfo.tsx
@@ -1055,7 +1055,10 @@ function InitialInfo() {
key: 'action',
render: (_, record) => (
)
},
{
- title: '画面内容',
+ title: '视频内容',
width: 250,
-
- render: (text: any, record: any) => (
-
- {record.content}
-
- )
- },
- {
- title: '台词',
- width: 250,
-
- render: (text: any, record: any) => (
-
- {record.lines}
-
- )
- },
- {
- title: '内容策略',
- width: 120,
align: 'left' as const,
+
+ render: (text: any, record: any) => (
+
+ {record.segmentContent}
+
+ )
+ },
+ // {
+ // title: '台词',
+ // width: 250,
+
+ // render: (text: any, record: any) => (
+ //
+ // {record.lines}
+ //
+ // )
+ // },
+ {
+ title: '视频类型',
+
+ width: 120,
+ align: 'center' as const,
render: (text: any, record: any) => (
- {record.contentStrategy || '-'}
+ {record.segmentCategory || '-'}
)
},
{
title: '素材',
width: 140,
+
align: 'center' as const,
render: (text: any, record: any) => (
-
- 等待生成
-
-
+
+
+ {record.moduleProjectId ? (
+
+ ) : (
+
+ )}
)
}
];
return (
-
-
-
-
-
}
- onClick={() => navigate(-1)}
- style={{ fontSize: 16, color: '#666' }}
- />
-
{mockData.productName}
+ <>
+
+
+
+ }
+ onClick={() => navigate(-1)}
+ style={{ fontSize: 16, color: '#666' }}
+ />
+
-
-
-
-
-
-

+
+
+ {/* 213123 */}
+
+
+
+
+
+
+
+
+
视频总结
+
+ 上传时间: {taskDetail.createdAt}
+
+
+
+
+ 产品名称:
+ {taskDetail.title}
+
+
+
受众群体:
+
+ {(taskDetail.originalVideoAudience?.split('、') || []).map((point, index) => (
+
+ {point}
+
+ ))}
+
+
+
+ 视频内容:
+ {taskDetail.originalVideoContent}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+ ) : (
+
+ 加载中...
+
+ )}
+
+
+
+
+ 智能视频复刻
+ -片段{currentSegment}
+
+ }
+ onClick={handleCloseDrawer}
+ style={{ padding: 0 }}
+ />
+
+ }
+ placement="right"
+ closable={false}
+ onClose={handleCloseDrawer}
+ open={drawerVisible}
+ size={480}
+ styles={{
+ body: { padding: '24px' }
+ }}
+ >
+
+
+
+
+
+ {!productImage && (
+
+ )}
+
+
+
+
+
+
+ setProductName(e.target.value)}
+ placeholder="请输入产品名称"
+ style={{ height: 48, borderRadius: 8 }}
+ maxLength={10}
+ showCount
+ />
+
+
+
+
+
+
+
-
+ borderRadius: 8,
+ borderColor: '#6366f1',
+ color: '#6366f1',
+ fontWeight: 500
+ }}
+ >
+ {loading ? '生成中...' : '手动生成'}
+
-
-
-
-
视频总结
-
- 上传时间: {mockData.uploadTime}
-
-
-
-
- 产品名称:
- {mockData.productName}
-
-
-
卖点词:
-
- {mockData.sellingPoints.map((point, index) => (
-
- {point}
-
- ))}
-
-
-
- 受众群体:
- {mockData.audience}
-
-
- 受众分析:
- {mockData.audienceAnalysis}
-
-
-
-
-
-
-
-
-
-
-
-
- 智能视频复刻
- -片段{currentSegment}
-
- }
- onClick={handleCloseDrawer}
- style={{ padding: 0 }}
- />
-
- }
- placement="right"
- closable={false}
- onClose={handleCloseDrawer}
- open={drawerVisible}
- width={480}
- bodyStyle={{ padding: '24px' }}
- >
-
-
-
-
-
- {!productImage && (
-
- )}
-
-
- {!detailImage && (
-
- )}
-
-
-
-
-
-
- setProductName(e.target.value)}
- placeholder="请输入产品名称"
- style={{ height: 48, borderRadius: 8 }}
- maxLength={10}
- showCount
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ >
);
}
diff --git a/video-gen-app/src/pages/RemoveLens.tsx b/video-gen-app/src/pages/RemoveLens.tsx
index 6c073242..8a010d02 100644
--- a/video-gen-app/src/pages/RemoveLens.tsx
+++ b/video-gen-app/src/pages/RemoveLens.tsx
@@ -1,7 +1,8 @@
-import { useState, useRef, useCallback, useEffect } from 'react';
-import { Button, Modal, Input, Table, Upload, Popconfirm } from 'antd';
+import { useState, useRef, useCallback } from 'react';
+import { Button, Modal, Input, Table, Upload, Popconfirm, message } from 'antd';
import { FileTextOutlined, CloudUploadOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
+import { uploadVideo, createShotReplication, getShotReplicationList } from '../api';
export default function VideoFrameExtractor() {
const navigate = useNavigate();
@@ -10,8 +11,12 @@ export default function VideoFrameExtractor() {
const [error, setError] = useState
('');
const [isModalOpen, setIsModalOpen] = useState(false);
const [productName, setProductName] = useState('');
-
const [videoDuration, setVideoDuration] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [tableData, setTableData] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [pageSize, setPageSize] = useState(10);
+ const [total, setTotal] = useState(0);
const videoRef = useRef(null);
const canvasRef = useRef(null);
@@ -26,7 +31,7 @@ export default function VideoFrameExtractor() {
setProductName('');
}, [videoUrl]);
- const handleFileChange = (file: File) => {
+ const handleFileChange = async (file: File) => {
if (!file.type.startsWith('video/')) {
setError('请选择视频文件');
return false;
@@ -38,10 +43,20 @@ export default function VideoFrameExtractor() {
}
cleanupResources();
+ setLoading(true);
+
+ try {
+ const uploadResult = await uploadVideo(file);
+ setVideoUrl(uploadResult.url);
+ setError('');
+ message.success('视频上传成功');
+ } catch (err) {
+ setError('视频上传失败,请重试');
+ message.error('视频上传失败');
+ } finally {
+ setLoading(false);
+ }
- const url = URL.createObjectURL(file);
- setVideoUrl(url);
- setError('');
return false;
};
@@ -51,27 +66,57 @@ export default function VideoFrameExtractor() {
}
}, []);
- const tableData = [
- {
- id: 1,
- image: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=product%20image%20red%20gift%20box%20with%20hearts&image_size=square',
- originalName: '进圈',
- productName: '他趣',
- status: '视频成功',
- createTime: '2026-05-14 17:49:20',
- },
- {
- id: 2,
- image: 'https://neeko-copilot.bytedance.net/api/text_to_image?prompt=luxury%20perfume%20bottle%20golden%20elegant&image_size=square',
- originalName: '香水',
- productName: '面霜',
- status: '视频提示词成功',
- createTime: '2026-05-08 08:57:46',
- },
- ];
+ const handleCreate = async () => {
+ if (!videoUrl || !productName.trim()) {
+ message.warning('请先上传视频并输入产品名称');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const params = {
+ video_url: videoUrl,
+ video_duration_seconds: videoDuration,
+ title: productName.trim(),
+ idempotency_key: `shot_${Date.now()}`,
+ };
+
+ await createShotReplication(params);
+ message.success('任务创建成功');
+ // 清空上传内容和输入框
+ cleanupResources();
+ navigate('/');
+ } catch (err) {
+ message.error('任务创建失败,请重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const fetchList = async (page: number, size: number) => {
+ try {
+ const res = await getShotReplicationList(page, size);
+ setTableData(res.items || []);
+ setTotal(res.total || 0);
+ setCurrentPage(page);
+ setPageSize(size);
+ } catch (err) {
+ message.error('获取列表失败');
+ }
+ };
+
+ const handlePageChange = (page: number, size: number) => {
+ fetchList(page, size);
+ };
+
+ // 打开弹窗时获取列表数据
+ const handleOpenModal = () => {
+ setIsModalOpen(true);
+ fetchList(1, 10);
+ };
return (
-
+
@@ -107,7 +152,7 @@ export default function VideoFrameExtractor() {
diff --git a/video-gen-app/src/pages/RemoveRw.tsx b/video-gen-app/src/pages/RemoveRw.tsx
new file mode 100644
index 00000000..3504f1de
--- /dev/null
+++ b/video-gen-app/src/pages/RemoveRw.tsx
@@ -0,0 +1,1232 @@
+import React, { useState, useEffect } from 'react';
+import { Button, Typography, Collapse, Space, Modal, Input, Table, message } from 'antd';
+import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
+import { useNavigate, useParams } from 'react-router-dom';
+import {getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine } from '../api/index';
+import './css/InitialInfo.css';
+
+const { Title, Text } = Typography;
+const { TextArea } = Input;
+
+function InitialInfo() {
+ const navigate = useNavigate();
+ const { creatID } = useParams<{ creatID: string }>();
+
+ const [modalVisible, setModalVisible] = useState(false);
+ const [promptText, setPromptText] = useState('');
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [currentType, setCurrentType] = useState
('image');
+ const [formData, setFormData] = useState({});
+ const [pollingTimer, setPollingTimer] = useState(null);
+
+ // 引擎和视频参数相关状态
+ const [enginesele, setEnginesele] = useState({});
+ const [countType, setCountType] = useState('');
+ const [showEngineModal, setShowEngineModal] = useState(false);
+ const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
+ const [videoDuration, setVideoDuration] = useState(5);
+ const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
+ const [videoResolution, setVideoResolution] = useState('480p');
+ const [engineOptions, setEngineOptions] = useState<{
+ ratios: string[];
+ resolutions: string[];
+ durations: number[];
+ }>({
+ ratios: ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
+ resolutions: ['480p', '720p', '1080p'],
+ durations: [5, 8, 10, 12, 15],
+ });
+
+ // 创作记录表格数据
+ const [tableData, setTableData] = useState([]);
+ // 分页状态
+ const [currentPage, setCurrentPage] = useState(1);
+ const [pageSize, setPageSize] = useState(20);
+ const [total, setTotal] = useState(0);
+ // 任务详情数据
+ const [taskDetail, setTaskDetail] = useState({});
+ // API 返回的步骤数据
+ const [apiSteps, setApiSteps] = useState([]);
+
+ //
+ const baseSteps = [
+ { id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
+ { id: 2, title: '生成提示词', description: '根据素材生成描述词', childId: 2 },
+ { id: 3, title: '生成产品融合图', description: '生成产品与场景融合图', childId: 3 },
+ { id: 4, title: '生成视频提示词', description: '生成视频生成提示词', childId: 4 },
+ { id: 5, title: '生成最终视频', description: '合成最终视频', childId: 5 },
+ ];
+
+ // 合并基础步骤和API返回的状态
+ const steps = baseSteps.map((step, index) => ({
+ ...step,
+ status: apiSteps[index]?.status || '',
+ id: apiSteps[index]?.id || index,
+ output: apiSteps[index]?.output || '',
+ engineId: apiSteps[index]?.input?.payload?.videoConfig?.engineId || '',
+ }));
+
+
+
+
+
+
+
+ // 获取列表数据的函数
+ const fetchList = (page: number, size: number) => {
+ getShotReplicationList(page, size).then((res: any) => {
+ if (res.items) {
+ setTableData(res.items);
+ }
+ if (res.total !== undefined) {
+ setTotal(res.total);
+ }
+ }).catch((error: any) => {
+ });
+ };
+
+ // 分页变化处理
+ const handlePageChange = (page: number, size: number) => {
+ setCurrentPage(page);
+ setPageSize(size);
+ fetchList(page, size);
+ };
+
+ // 获取复刻列表数据
+ useEffect(() => {
+ fetchList(currentPage, pageSize);
+ }, []);
+
+ // 获取引擎列表
+ useEffect(() => {
+ getEngine()
+ .then((data: any) => {
+ setEnginesele(data.engine || {});
+ // 默认选中第一个视频引擎
+ if (data.engine?.video && data.engine.video.length > 0) {
+ setCountType(data.engine.video[0].id);
+ // 设置默认引擎参数
+ const firstEngine = data.engine.video[0];
+ setEngineOptions({
+ ratios: firstEngine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
+ resolutions: firstEngine.supportedResolutions || ['480p', '720p', '1080p'],
+ durations: firstEngine.supportedDurations || [5, 8, 10, 12, 15],
+ });
+ }
+ })
+ .catch((error: any) => {
+ });
+ }, []);
+
+ // 组件卸载时清理定时器
+ useEffect(() => {
+ return () => {
+ if (pollingTimer) {
+ clearInterval(pollingTimer);
+ setPollingTimer(null);
+ }
+ };
+ }, [pollingTimer]);
+
+ // 点击外部关闭弹窗
+ useEffect(() => {
+ const handleClickOutside = (e: MouseEvent) => {
+ const target = e.target as HTMLElement;
+ if (!target.closest('.image-settings-trigger') && !target.closest('.image-settings-popover')) {
+ setShowEngineModal(false);
+ setShowVideoSettingsModal(false);
+ }
+ };
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, []);
+
+ // 获取任务详情数据
+ useEffect(() => {
+
+ console.log('creatID', creatID);
+
+ if (creatID) {
+ removeDetail(creatID).then((res: any) => {
+ setTaskDetail(res);
+ if (res.steps) {
+ setApiSteps(res.steps);
+ }
+ }).catch((error: any) => {
+ });
+ }
+ }, [creatID]);
+
+ // 监听合并后的 steps 数据,判断第五步状态并启动/停止轮询
+ useEffect(() => {
+
+ // 检查第五步的状态(索引 4)
+ const fifthStep = steps[4];
+
+ if (fifthStep && fifthStep.status !== 'completed' && fifthStep.status !== 'failed') {
+ // 第五步未完成,启动轮询
+ if (!pollingTimer) {
+ const timer = setInterval(pollTaskDetail, 30000);
+ setPollingTimer(timer);
+ } else {
+ }
+ } else {
+ // 第五步已完成或失败,停止轮询
+ if (fifthStep) {
+ if (pollingTimer) {
+ clearInterval(pollingTimer);
+ setPollingTimer(null);
+ }
+ }
+ }
+ }, [steps]);
+
+ const handleOpenModal = (prompt?: any, type?: string) => {
+
+ setCurrentType(type || 'image');
+
+ if (type === 'video' && typeof prompt === 'object') {
+ setFormData(prompt);
+ setPromptText('');
+ } else {
+ setPromptText(prompt || '');
+ setFormData({});
+ }
+
+ setModalVisible(true);
+ };
+
+ const handleConfirm = () => {
+ setModalVisible(false);
+ };
+
+ // 轮询任务详情
+ const pollTaskDetail = () => {
+ if (!creatID) {
+ return;
+ }
+
+ removeDetail(creatID).then((res: any) => {
+ setTaskDetail(res);
+ if (res.steps) {
+ setApiSteps(res.steps);
+ }
+ }).catch((error: any) => {
+ });
+ };
+
+ // 刷新任务详情(用于点击下一步后更新数据)
+ const refreshTaskDetail = () => {
+ if (!creatID) return;
+
+ removeDetail(creatID).then((res: any) => {
+ setTaskDetail(res);
+ if (res.steps) {
+ setApiSteps(res.steps);
+ }
+ }).catch((error: any) => {
+ });
+ };
+ const createone = (stepId: number) => {
+
+ removeone(taskDetail.id, stepId.toString()).then((res: any) => {
+ // 重新获取任务详情以更新数据
+ refreshTaskDetail();
+ }).catch((error: any) => {
+ });
+ }
+
+ const createimage = (stepId: number) => {
+ let params = {
+ engine_id: "0019e3dac0b795b925b",
+ image_proportion: "1:1",
+ image_px: "2048x2048",
+ image_size: "2K"
+ }
+ removetwo(taskDetail.id, stepId.toString(), params).then((res: any) => {
+ // 重新获取任务详情以更新数据
+ refreshTaskDetail();
+ }).catch((error: any) => {
+ });
+ }
+ const newcreateimage = () => {
+ // console.log('下一步:', stepId);
+ let params = {
+ engine_id: "0019e3dac0b795b925b",
+ image_proportion: "1:1",
+ image_px: "2048x2048",
+ image_size: "2K"
+ }
+
+ removetwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
+ refreshTaskDetail();
+
+ }).catch((error: any) => {
+ });
+ }
+
+
+
+ // 下一步按钮点击处理
+ const handleNextStep = (stepId: number) => {
+ // 获取引擎 ID 和视频参数
+ const engineId = countType;
+ const videoParams = {
+ engine_id : countType,
+ duration: videoDuration,
+ aspect_ratio: videoAspectRatio,
+ resolution: videoResolution,
+ target_platform: "抖音",
+ };
+ // console.log('引擎 ID:', engineId);
+ removethree(taskDetail.id, stepId.toString(), videoParams).then((res: any) => {
+ // 重新获取任务详情以更新数据
+ refreshTaskDetail();
+ }).catch((error: any) => {
+ });
+ // 这里可以添加下一步的逻辑,比如调用接口等
+ };
+
+ const createvideo = (stepId: number,engineId: string) => {
+ let params = {
+ engine_id: engineId,
+ }
+
+
+ removefour(taskDetail.id, stepId.toString(), params).then((res: any) => {
+ // 重新获取任务详情以更新数据
+ refreshTaskDetail();
+ }).catch((error: any) => {
+ });
+ }
+ const agincreatevideo = () => {
+ let params = {
+ engine_id: steps[2].engineId,
+ }
+
+
+ removefour(taskDetail.id, steps[2].id.toString(), params).then((res: any) => {
+ // 重新获取任务详情以更新数据
+ refreshTaskDetail();
+ }).catch((error: any) => {
+ });
+ }
+
+
+
+ return (
+
+
+
+
+
+
+ }
+ onClick={() => navigate(-1)}
+ style={{ color: '#64748b' }}
+ >
+ 返回
+
+
+ 拆镜复刻 - 任务详情
+
+
+
+
+
+
+
视频
+
+ {taskDetail?.material?.materialVideoUrl ? (
+
+ ) : (
+
暂无视频
+ )}
+
+
+
+
产品图片
+
+ {taskDetail?.material?.materialImageUrl ? (
+

+ ) : (
+
暂无图片
+ )}
+
+
+
+ {taskDetail?.finalImageUrl && (
+
+
+
生成图片
+
+

+
+
+
+ )}
+ {taskDetail?.finalVideoUrl && (
+
+ )}
+
+
+
+
生成步骤
+
({
+ key: String(step.id),
+ label: (
+
+
+
+ {step.status === 'completed' ? (
+
+ ) : step.status === 'processing' ? (
+
+ ) : step.status === 'failed' ? (
+
+ ×
+
+ ) : (
+
+ )}
+
+
+ {step.title}
+
+ {(step.status === 'processing' || step.status === 'waiting_user') && (
+
生成中
+ )}
+ {step.status === 'failed' && (
+
失败
+ )}
+ {step.status === '' && (
+
待生成
+ )}
+
+
+ ),
+ children: (
+
+ {/* 步骤1: 原始素材 */}
+ {step.childId === 1 && (
+ <>
+
+
+
原视频
+
+ {taskDetail?.material?.materialVideoUrl ? (
+
+ ) : (
+
+ )}
+
+
+
+
产品图片
+
+ {taskDetail?.material?.materialImageUrl ? (
+

+ ) : (
+
+ )}
+
+
+
+
+
+ >
+ )}
+ {/* 步骤2: 生成提示词 */}
+ {step.childId === 2 && (
+ <>
+ {/*
+ {taskDetail?.generationPrompt || '暂无提示词'}
+ */}
+
+ {/* {taskDetail?.generationPrompt || '暂无提示词'} */}
+ {step?.output?.payload?.prompt || '暂无提示词'}
+
+
+ }
+ onClick={() => handleOpenModal(step?.output?.payload?.prompt)}
+ style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
+ disabled={step.status !== 'completed'}
+ >
+ 修改提示词
+
+
+
+ >
+ )}
+ {/* 步骤3: 生成产品融合图 */}
+ {step.childId === 3 && (
+ <>
+
+ {/* {step.output.result.result_image_url} */}
+
+
+ {taskDetail.finalImageUrl ? (
+

+ ) : (
+
暂无融合图
+ )}
+
+ {/* 引擎选择器 */}
+
+
+
+
+ {showEngineModal && (
+
e.stopPropagation()}
+ >
+ {/* 选择引擎 */}
+
+
+ 选择引擎
+
+
+ {enginesele.video?.map((engine: any) => (
+
+ ))}
+
+
+
+ )}
+
+
+
+ {/* 视频参数设置 */}
+
+
+
+
+ {showVideoSettingsModal && (
+
e.stopPropagation()}
+ >
+ {/* 选择比例 */}
+
+
+ 选择比例
+
+
+ {engineOptions.ratios.map((ratio) => (
+
+ ))}
+
+
+
+ {/* 选择时长 */}
+
+
+ 选择时长
+
+
+
+ {/* 背景轨道 */}
+
+ {/* 已滑动部分 */}
+
+ {/* 滑块 */}
+
setVideoDuration(Number(e.target.value))}
+ style={{
+ position: 'relative',
+ width: '100%',
+ height: 24,
+ borderRadius: 3,
+ background: 'transparent',
+ outline: 'none',
+ appearance: 'none',
+ cursor: 'pointer',
+ zIndex: 1,
+ }}
+ />
+
+
+
+ {videoDuration}
+
+ 秒
+
+
+
+
+ {/* 选择分辨率 */}
+
+
+ 选择分辨率
+
+
+ {engineOptions.resolutions.map((resolution) => (
+
+ ))}
+
+
+
+ )}
+
+
+
+ } style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }
+ } onClick={() => newcreateimage()} disabled={step.status !== 'completed'}>
+ 重新生成
+
+
+
+ >
+ )}
+ {/* 步骤4: 生成视频提示词 */}
+ {step.childId === 4 && (
+ <>
+
+ {/* {taskDetail?.videoPrompt || '暂无视频提示词'} */}
+ {step?.output?.payload?.finalPrompt || '暂无提示词'}
+
+
+
+ }
+ onClick={() => handleOpenModal(step?.output?.payload?.promptSchema,'video')}
+ style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
+ disabled={step.status !== 'completed'}
+ >
+ 修改提示词
+
+
+
+ >
+ )}
+ {/* 步骤5: 生成最终视频 */}
+ {step.childId === 5 && (
+ <>
+
+ {step.status === 'completed' && taskDetail?.finalVideoUrl ? (
+
+ ) : step.status === 'processing' ? (
+
+ ) : step.status === 'failed' ? (
+
视频生成失败
+ ) : (
+
暂无视频
+ )}
+
+
+
+ }
+ style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
+ disabled={step.status !== 'completed'}
+ onClick={() => {
+ const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
+ const link = document.createElement('a');
+ link.href = videoUrl;
+ link.download = `video_${Date.now()}.mp4`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }}
+ >
+ 下载视频
+
+
+ >
+ )}
+
+ ),
+ }))}
+ />
+
+
+
+
+ setModalVisible(false)}
+ footer={[
+ ,
+ ,
+ ]}
+ width={800}
+ >
+ {currentType === 'image' ? (
+
+ {/* 创作记录弹窗 */}
+ setIsModalOpen(false)}
+ width={800}
+ footer={null}
+ style={{ borderRadius: 12 }}
+ >
+ {/* 搜索区域 */}
+
+
+
+
+
+ {/* 表格 */}
+ {
+ const statusMap: Record = {
+ 'pending': '子任务待处理',
+ 'waiting_user': '等待用户确认或触发',
+ 'processing': '子任务处理中',
+ 'completed': '子任务完成',
+ 'failed': '子任务失败',
+ 'cancelled': '子任务取消',
+ };
+ return statusMap[text] || text || '-';
+ },
+ },
+ {
+ title: '创建时间',
+ dataIndex: 'createdAt',
+ key: 'createdAt',
+ render: (text: string) => {
+ if (!text) return '-';
+ const date = new Date(text);
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+ const seconds = String(date.getSeconds()).padStart(2, '0');
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+ },
+ },
+ {
+ title: '操作',
+ key: 'action',
+ render: (_, record) => (
+
+ ),
+ },
+ ]}
+ dataSource={tableData}
+ rowKey="id"
+ pagination={{
+ current: currentPage,
+ pageSize: pageSize,
+ total: total,
+ showSizeChanger: true,
+ showQuickJumper: true,
+ showTotal: (total) => `共 ${total} 条`,
+ onChange: handlePageChange,
+ }}
+ style={{ fontSize: 13 }}
+ />
+
+
+ );
+}
+
+const FormRenderer = ({ data, onChange }: { data: any; onChange: (data: any) => void }) => {
+ const handleFieldChange = (path: string[], value: any) => {
+ const newData = { ...data };
+ let current = newData;
+ for (let i = 0; i < path.length - 1; i++) {
+ current = current[path[i]];
+ }
+ current[path[path.length - 1]] = value;
+ onChange(newData);
+ };
+
+ const handleArrayItemChange = (path: string[], index: number, value: any) => {
+ const newData = { ...data };
+ let current = newData;
+ for (let i = 0; i < path.length; i++) {
+ if (i === path.length - 1) {
+ current[path[i]] = [...current[path[i]]];
+ current[path[i]][index] = value;
+ } else {
+ current = current[path[i]];
+ }
+ }
+ onChange(newData);
+ };
+
+ const handleArrayAdd = (path: string[]) => {
+ const newData = { ...data };
+ let current = newData;
+ for (let i = 0; i < path.length; i++) {
+ if (i === path.length - 1) {
+ current[path[i]] = [...current[path[i]], ''];
+ } else {
+ current = current[path[i]];
+ }
+ }
+ onChange(newData);
+ };
+
+ const handleArrayRemove = (path: string[], index: number) => {
+ const newData = { ...data };
+ let current = newData;
+ for (let i = 0; i < path.length; i++) {
+ if (i === path.length - 1) {
+ current[path[i]] = current[path[i]].filter((_: any, i: number) => i !== index);
+ } else {
+ current = current[path[i]];
+ }
+ }
+ onChange(newData);
+ };
+
+ const renderField = (key: string, value: any, path: string[]) => {
+ if (Array.isArray(value)) {
+ return (
+
+
+ {key}
+
+
+
+ {value.map((item: any, index: number) => (
+
+
{index + 1}.
+
+ {typeof item === 'object' ? (
+ handleArrayItemChange(path, index, newItem)}
+ />
+ ) : (
+ handleArrayItemChange(path, index, e.target.value)}
+ style={{ width: '100%', borderRadius: 6 }}
+ />
+ )}
+
+
+
+ ))}
+
+
+ );
+ }
+
+ if (typeof value === 'object' && value !== null) {
+ return (
+
+
+ {key}
+
+
+ handleFieldChange(path, newValue)} />
+
+
+ );
+ }
+
+ return (
+
+ {key}
+ handleFieldChange(path, e.target.value)}
+ style={{ width: '100%', borderRadius: 6 }}
+ />
+
+ );
+ };
+
+ return (
+
+ {Object.entries(data).map(([key, value]) => renderField(key, value, [key]))}
+
+ );
+};
+
+export default InitialInfo;