爆款拆镜修改
This commit is contained in:
+6
-6
File diff suppressed because one or more lines are too long
Vendored
+2
-22
@@ -2,33 +2,13 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" id="favicon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>民众智创</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName;
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-D_n_SkaH.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-_th8Je05.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-IrbjYl3V.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -130,7 +130,7 @@ const UploadResourceHistoryPicker: React.FC<UploadResourceHistoryPickerProps> =
|
||||
const [groupPage, setGroupPage] = useState(1);
|
||||
const [groupPageSize, setGroupPageSize] = useState(10);
|
||||
const [itemPage, setItemPage] = useState(1);
|
||||
const [itemPageSize, setItemPageSize] = useState(10);
|
||||
const [itemPageSize, setItemPageSize] = useState(8);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingGroups, setLoadingGroups] = useState(false);
|
||||
const [groups, setGroups] = useState<any[]>([]);
|
||||
@@ -473,7 +473,7 @@ const UploadResourceHistoryPicker: React.FC<UploadResourceHistoryPickerProps> =
|
||||
pageSize={itemPageSize}
|
||||
total={total}
|
||||
showSizeChanger
|
||||
// pageSizeOptions={[8, 16, 32, 64]}/
|
||||
pageSizeOptions={[8]}
|
||||
onChange={(nextPage, nextSize) => {
|
||||
setItemPage(nextPage);
|
||||
setItemPageSize(nextSize);
|
||||
|
||||
@@ -87,6 +87,7 @@ function InitialInfo() {
|
||||
const [previewVisible, setPreviewVisible] = useState<boolean>(false);
|
||||
const [previewUrl, setPreviewUrl] = useState<string>('');
|
||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||
const [showDownloadButton, setShowDownloadButton] = useState<boolean>(false);
|
||||
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -121,10 +122,11 @@ function InitialInfo() {
|
||||
}
|
||||
}, [previewVisible, previewType]);
|
||||
|
||||
const openPreview = (url: string, type: 'image' | 'video') => {
|
||||
const openPreview = (url: string, type: 'image' | 'video', showDownload = false) => {
|
||||
// console.log(url, type);
|
||||
setPreviewUrl(url);
|
||||
setPreviewType(type);
|
||||
setShowDownloadButton(showDownload);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
@@ -180,30 +182,46 @@ function InitialInfo() {
|
||||
}));
|
||||
}, [baseSteps, apiSteps]);
|
||||
|
||||
// 当steps更新时,默认只展开生成中的步骤
|
||||
const lastProcessingKeys = useRef<string>('');
|
||||
useEffect(() => {
|
||||
const processingKeys = steps
|
||||
.filter(step => step.status === 'processing')
|
||||
.map(step => String(step.childId));
|
||||
// 用户交互标志:点击下一步后设置为true,用于跳过一次自动展开逻辑
|
||||
const userClickedNextRef = useRef(false);
|
||||
|
||||
const processingKeysStr = JSON.stringify(processingKeys);
|
||||
if (processingKeysStr !== lastProcessingKeys.current) {
|
||||
lastProcessingKeys.current = processingKeysStr;
|
||||
setActiveKey(processingKeys);
|
||||
// 当steps更新时,管理展开状态(每次接口请求后都重新判断)
|
||||
useEffect(() => {
|
||||
// 当steps没有状态数据时跳过,避免在空状态时重置activeKey
|
||||
if (!steps.some(step => step.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从最后一步往前判断:优先展开生成中的
|
||||
const processingStep = [...steps].reverse().find(step => step.status === 'processing');
|
||||
if (processingStep) {
|
||||
// 有生成中的,只展开生成中的
|
||||
setActiveKey([String(processingStep.childId)]);
|
||||
userClickedNextRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有生成中的:如果用户刚点击了下一步,保持当前状态(不覆盖收缩)
|
||||
if (userClickedNextRef.current) {
|
||||
userClickedNextRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有生成中的且用户未操作:找最后一个完成的步骤(第一步不参与)
|
||||
const completedStep = [...steps].reverse().find(step => step.status === 'completed' && step.childId !== 1);
|
||||
if (completedStep) {
|
||||
setActiveKey([String(completedStep.childId)]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 都不满足时,不展开任何步骤
|
||||
setActiveKey([]);
|
||||
}, [steps]);
|
||||
|
||||
// console.log('steps', steps);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 获取列表数据的函数
|
||||
const fetchList = (page: number, size: number, keyword?: string) => {
|
||||
getReplicationList(page, size, keyword).then((res: any) => {
|
||||
@@ -677,6 +695,23 @@ function InitialInfo() {
|
||||
message.warning('当前没有可用的视频生成引擎');
|
||||
return;
|
||||
}
|
||||
// 从接口返回的数据中读取视频配置作为默认值
|
||||
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
|
||||
if (videoConfig) {
|
||||
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
|
||||
const engine = enginesele.video?.find((e: any) => String(e.id) === String(engineId));
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
setVideoAspectRatio(videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '16:9');
|
||||
setVideoResolution(videoConfig?.resolution || '480p');
|
||||
setVideoDuration(videoConfig?.duration || 5);
|
||||
}
|
||||
setRetryPromptStepId(String(stepId));
|
||||
setRetryPromptModalVisible(true);
|
||||
};
|
||||
@@ -944,7 +979,7 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成视频
|
||||
</span>
|
||||
<div className="medio_box video-container" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer', position: 'relative' }} onClick={() => openPreview(taskDetail.finalVideoUrl, 'video')}>
|
||||
<div className="medio_box video-container" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer', position: 'relative' }} onClick={() => openPreview(taskDetail.finalVideoUrl, 'video', true)}>
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
@@ -1107,7 +1142,11 @@ function InitialInfo() {
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => { createimage(step.id); }}
|
||||
onClick={() => {
|
||||
userClickedNextRef.current = true;
|
||||
setActiveKey(prev => prev.filter(k => k !== String(step.childId)));
|
||||
createimage(step.id);
|
||||
}}
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
@@ -1285,6 +1324,23 @@ function InitialInfo() {
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
// 从接口返回的数据中读取视频配置作为默认值
|
||||
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
|
||||
if (videoConfig) {
|
||||
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
|
||||
const engine = enginesele.video?.find((e: any) => String(e.id) === String(engineId));
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
setVideoAspectRatio(videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '16:9');
|
||||
setVideoResolution(videoConfig?.resolution || '480p');
|
||||
setVideoDuration(videoConfig?.duration || 5);
|
||||
}
|
||||
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||
setShowEngineModal(false);
|
||||
}}
|
||||
@@ -1531,6 +1587,8 @@ function InitialInfo() {
|
||||
message.warning('积分不足,请更换参数/充值积分');
|
||||
return;
|
||||
}
|
||||
userClickedNextRef.current = true;
|
||||
setActiveKey(prev => prev.filter(k => k !== String(step.childId)));
|
||||
handleNextStep(step.id);
|
||||
}}
|
||||
disabled={step.status !== 'completed' || ((user?.credits || 0) < estimatedCredits)}
|
||||
@@ -1547,11 +1605,25 @@ function InitialInfo() {
|
||||
{/* 步骤4: 生成视频提示词 */}
|
||||
{step.childId === 4 && (
|
||||
<>
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, lineHeight: 1.8 }}>
|
||||
视频提词已生成,可点击按钮查看/修改
|
||||
</Text>
|
||||
</div>
|
||||
{step.status === 'completed' ? (
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, lineHeight: 1.8 }}>
|
||||
视频提词已生成,可点击按钮查看/修改
|
||||
</Text>
|
||||
</div>)
|
||||
: step.status === 'failed' ? (
|
||||
<>
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#ef4444', fontSize: 14 }}>
|
||||
<p>提示词生成失败</p>
|
||||
<p>{step.errorMessage || '未知错误'}</p>
|
||||
</div>
|
||||
|
||||
</>
|
||||
|
||||
|
||||
): (
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}>暂无视频提示词生成</div>
|
||||
)}
|
||||
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px' }}>
|
||||
@@ -1605,6 +1677,23 @@ function InitialInfo() {
|
||||
message.warning(`积分不足${optimizeHoldCredits},请充值积分`);
|
||||
return;
|
||||
}
|
||||
// 从接口返回的数据中读取视频配置作为默认值
|
||||
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
|
||||
if (videoConfig) {
|
||||
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
|
||||
const engine = enginesele.video?.find((e: any) => String(e.id) === String(engineId));
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
setVideoAspectRatio(videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '16:9');
|
||||
setVideoResolution(videoConfig?.resolution || '480p');
|
||||
setVideoDuration(videoConfig?.duration || 5);
|
||||
}
|
||||
setRetryPromptStepId(String(step.id));
|
||||
setRetryPromptModalVisible(true);
|
||||
}}
|
||||
@@ -1626,6 +1715,8 @@ function InitialInfo() {
|
||||
message.warning(`积分不足${credits},请充值积分`);
|
||||
return;
|
||||
}
|
||||
userClickedNextRef.current = true;
|
||||
setActiveKey(prev => prev.filter(k => k !== String(step.childId)));
|
||||
createvideo(step.id, step.engineId);
|
||||
}}
|
||||
type="primary"
|
||||
@@ -1654,11 +1745,11 @@ function InitialInfo() {
|
||||
</div>
|
||||
) : step.status === 'failed' ? (
|
||||
<>
|
||||
<div style={{ width: '100%', height: 180, display: 'flex',flexDirection:'column', alignItems: 'center', justifyContent: 'center', color: '#ef4444', fontSize: 14 }}>
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#ef4444', fontSize: 14 }}>
|
||||
<p>视频生成失败</p>
|
||||
<p>{step.errorMessage || '未知错误'}</p>
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
|
||||
|
||||
@@ -1731,7 +1822,7 @@ function InitialInfo() {
|
||||
<Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving} style={{ borderRadius: 8 }}>取消</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}>确认</Button>,
|
||||
]}
|
||||
width={800}
|
||||
width="80%"
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
|
||||
@@ -1747,7 +1838,7 @@ function InitialInfo() {
|
||||
placeholder="请输入提示词"
|
||||
/>
|
||||
) : (
|
||||
<div style={{ maxHeight: 500, overflowY: 'auto', paddingRight: 10 }}>
|
||||
<div style={{ maxHeight: 600, overflowY: 'auto', paddingRight: 10 }}>
|
||||
<VideoPromptSchemaEditor value={formData} schemaConfigSnapshot={videoSchemaConfigSnapshot} onChange={setFormData} />
|
||||
</div>
|
||||
)}
|
||||
@@ -1969,7 +2060,7 @@ function InitialInfo() {
|
||||
}
|
||||
onCancel={handleClosePreview}
|
||||
width={800}
|
||||
footer={
|
||||
footer={showDownloadButton ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: 'rgba(255,255,255,0.6)', borderTop: '1px solid rgba(139, 92, 246, 0.08)' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -1980,7 +2071,7 @@ function InitialInfo() {
|
||||
下载
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
) : null}
|
||||
centered
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
|
||||
@@ -66,7 +66,7 @@ const GenerateConver: React.FC = () => {
|
||||
// 卡片列表专用状态
|
||||
const [cardData, setCardData] = useState<any[]>([]);
|
||||
const [cardCurrentPage, setCardCurrentPage] = useState(1);
|
||||
const [cardPageSize, setCardPageSize] = useState(8);
|
||||
const [cardPageSize, setCardPageSize] = useState(20);
|
||||
const [cardTotal, setCardTotal] = useState(0);
|
||||
|
||||
// 表单数据
|
||||
@@ -315,6 +315,13 @@ const GenerateConver: React.FC = () => {
|
||||
setVideoUrl(res.url);
|
||||
setVideoResourceId(res.resource_id || '');
|
||||
setVideoDurationSeconds(video.duration);
|
||||
// 根据上传视频的时长取整,设置最接近的可选时长
|
||||
const roundedDuration = Math.round(video.duration);
|
||||
const durations = engineOptions.durations;
|
||||
const closestDuration = durations.reduce((prev, curr) => {
|
||||
return Math.abs(curr - roundedDuration) < Math.abs(prev - roundedDuration) ? curr : prev;
|
||||
});
|
||||
setVideoDuration(closestDuration);
|
||||
message.success('视频上传成功');
|
||||
resolve(false);
|
||||
} catch (error) {
|
||||
@@ -468,7 +475,7 @@ const GenerateConver: React.FC = () => {
|
||||
setOriginalProductName('');
|
||||
setOwnProductName('');
|
||||
setProductSellingPoints('');
|
||||
fetchCardList(1, 8);
|
||||
fetchCardList(1, 20);
|
||||
message.success('项目已创建,视频提词正在生成');
|
||||
if (projectId) navigate(`/initial/${projectId}/initialinfo?flow_version=v2`);
|
||||
}).catch((error: any) => {
|
||||
@@ -931,15 +938,7 @@ const GenerateConver: React.FC = () => {
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<VideoCameraOutlined style={{ color: '#6366f1', fontSize: 14 }} />
|
||||
<span style={{ fontSize: 12, color: '#475569' }}>
|
||||
{videoFile?.name}
|
||||
</span>
|
||||
<span style={{ fontSize: 11, color: '#94a3b8' }}>
|
||||
({(videoFile?.size ? (videoFile.size / 1024 / 1024).toFixed(2) : 0)}MB)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<Upload
|
||||
@@ -1013,7 +1012,7 @@ const GenerateConver: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* 上传产品图片 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<p style={{ margin: 0, fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10 }}>
|
||||
上传产品图片
|
||||
<span>(可选)</span>
|
||||
@@ -1048,15 +1047,7 @@ const GenerateConver: React.FC = () => {
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<PictureOutlined style={{ color: '#6366f1', fontSize: 14 }} />
|
||||
<span style={{ fontSize: 12, color: '#475569' }}>
|
||||
{imageFile?.name}
|
||||
</span>
|
||||
<span style={{ fontSize: 11, color: '#94a3b8' }}>
|
||||
({(imageFile?.size ? (imageFile.size / 1024 / 1024).toFixed(2) : 0)}MB)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
<UploadSelector
|
||||
@@ -1212,7 +1203,7 @@ const GenerateConver: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 16, display: 'grid', gap: 10 }}>
|
||||
<div style={{ marginBottom: 12, display: 'grid', gap: 10 }}>
|
||||
<p style={{ margin: 0, fontSize: 13, fontWeight: 600, color: '#475569' }}>视频生成参数
|
||||
<span style={{ color: '#f94444' }}>(必选)</span>
|
||||
</p>
|
||||
|
||||
@@ -82,6 +82,7 @@ function InitialInfo() {
|
||||
const [previewVisible, setPreviewVisible] = useState<boolean>(false);
|
||||
const [previewUrl, setPreviewUrl] = useState<string>('');
|
||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||
const [showDownloadButton, setShowDownloadButton] = useState<boolean>(false);
|
||||
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -149,26 +150,42 @@ function InitialInfo() {
|
||||
}));
|
||||
}, [baseSteps, apiSteps]);
|
||||
|
||||
// 当steps更新时,默认只展开生成中的步骤
|
||||
const lastProcessingKeys = useRef<string>('');
|
||||
// 用户交互标志:点击下一步后设置为true,用于跳过一次自动展开逻辑
|
||||
const userClickedNextRef = useRef(false);
|
||||
|
||||
// 当steps更新时,管理展开状态(每次接口请求后都重新判断)
|
||||
useEffect(() => {
|
||||
const processingKeys = steps
|
||||
.filter(step => step.status === 'processing')
|
||||
.map(step => String(step.childId));
|
||||
|
||||
const processingKeysStr = JSON.stringify(processingKeys);
|
||||
if (processingKeysStr !== lastProcessingKeys.current) {
|
||||
lastProcessingKeys.current = processingKeysStr;
|
||||
setActiveKey(processingKeys);
|
||||
// 当steps没有状态数据时跳过,避免在空状态时重置activeKey
|
||||
if (!steps.some(step => step.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从最后一步往前判断:优先展开生成中的
|
||||
const processingStep = [...steps].reverse().find(step => step.status === 'processing');
|
||||
if (processingStep) {
|
||||
// 有生成中的,只展开生成中的
|
||||
setActiveKey([String(processingStep.childId)]);
|
||||
userClickedNextRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有生成中的:如果用户刚点击了下一步,保持当前状态(不覆盖收缩)
|
||||
if (userClickedNextRef.current) {
|
||||
userClickedNextRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 没有生成中的且用户未操作:找最后一个完成的步骤(第一步不参与)
|
||||
const completedStep = [...steps].reverse().find(step => step.status === 'completed' && step.childId !== 1);
|
||||
if (completedStep) {
|
||||
setActiveKey([String(completedStep.childId)]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 都不满足时,不展开任何步骤
|
||||
setActiveKey([]);
|
||||
}, [steps]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 获取列表数据的函数
|
||||
const fetchList = (page: number, size: number, keyword?: string) => {
|
||||
getShotReplicationList(page, size, keyword).then((res: any) => {
|
||||
@@ -212,7 +229,7 @@ function InitialInfo() {
|
||||
// 默认选中第一个视频引擎
|
||||
if (filteredVideoEngines.length > 0) {
|
||||
setCountType(filteredVideoEngines[0].id);
|
||||
// 设置默认引擎参数
|
||||
//
|
||||
const firstEngine = filteredVideoEngines[0];
|
||||
setEngineOptions({
|
||||
ratios: firstEngine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
@@ -496,9 +513,10 @@ function InitialInfo() {
|
||||
});
|
||||
};
|
||||
|
||||
const openPreview = (url: string, type: 'image' | 'video') => {
|
||||
const openPreview = (url: string, type: 'image' | 'video', showDownload = false) => {
|
||||
setPreviewUrl(url);
|
||||
setPreviewType(type);
|
||||
setShowDownloadButton(showDownload);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
@@ -672,6 +690,23 @@ function InitialInfo() {
|
||||
message.warning('当前没有可用的视频生成引擎');
|
||||
return;
|
||||
}
|
||||
// 从接口返回的数据中读取视频配置作为默认值
|
||||
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
|
||||
if (videoConfig) {
|
||||
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
|
||||
const engine = enginesele.video?.find((e: any) => String(e.id) === String(engineId));
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
setVideoAspectRatio(videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '16:9');
|
||||
setVideoResolution(videoConfig?.resolution || '480p');
|
||||
setVideoDuration(videoConfig?.duration || 5);
|
||||
}
|
||||
setRetryPromptStepId(String(stepId));
|
||||
setRetryPromptModalVisible(true);
|
||||
};
|
||||
@@ -835,7 +870,7 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成视频
|
||||
</span>
|
||||
<div className="medio_box video-container" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer', position: 'relative' }} onClick={() => openPreview(taskDetail.finalVideoUrl, 'video')}>
|
||||
<div className="medio_box video-container" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer', position: 'relative' }} onClick={() => openPreview(taskDetail.finalVideoUrl, 'video', true)}>
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
@@ -1019,7 +1054,11 @@ function InitialInfo() {
|
||||
>
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button onClick={() => { createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => {
|
||||
userClickedNextRef.current = true;
|
||||
setActiveKey(prev => prev.filter(k => k !== String(step.childId)));
|
||||
createimage(step.id);
|
||||
}} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成图片
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -1192,6 +1231,23 @@ function InitialInfo() {
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
// 从接口返回的数据中读取视频配置作为默认值
|
||||
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
|
||||
if (videoConfig) {
|
||||
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
|
||||
const engine = enginesele.video?.find((e: any) => String(e.id) === String(engineId));
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
setVideoAspectRatio(videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '16:9');
|
||||
setVideoResolution(videoConfig?.resolution || '480p');
|
||||
setVideoDuration(videoConfig?.duration || 5);
|
||||
}
|
||||
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||
setShowEngineModal(false);
|
||||
}}
|
||||
@@ -1438,6 +1494,8 @@ function InitialInfo() {
|
||||
message.warning('积分不足,请更换参数/充值积分');
|
||||
return;
|
||||
}
|
||||
userClickedNextRef.current = true;
|
||||
setActiveKey(prev => prev.filter(k => k !== String(step.childId)));
|
||||
handleNextStep(step.id);
|
||||
}}
|
||||
disabled={step.status !== 'completed' || ((user?.credits || 0) < estimatedCredits)}
|
||||
@@ -1453,11 +1511,25 @@ function InitialInfo() {
|
||||
{/* 步骤4: 生成视频提示词 */}
|
||||
{step.childId === 4 && (
|
||||
<>
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, lineHeight: 1.8 }}>
|
||||
视频提词已生成,可点击按钮查看/修改
|
||||
</Text>
|
||||
</div>
|
||||
{step.status === 'completed' ? (
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, lineHeight: 1.8 }}>
|
||||
视频提词已生成,可点击按钮查看/修改
|
||||
</Text>
|
||||
</div>)
|
||||
: step.status === 'failed' ? (
|
||||
<>
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#ef4444', fontSize: 14 }}>
|
||||
<p>提示词生成失败</p>
|
||||
<p>{step.errorMessage || '未知错误'}</p>
|
||||
</div>
|
||||
|
||||
</>
|
||||
|
||||
|
||||
): (
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}>暂无视频提示词生成</div>
|
||||
)}
|
||||
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px' }}>
|
||||
@@ -1512,6 +1584,23 @@ function InitialInfo() {
|
||||
message.warning(`积分不足${optimizeHoldCredits},请充值积分`);
|
||||
return;
|
||||
}
|
||||
// 从接口返回的数据中读取视频配置作为默认值
|
||||
const videoConfig = steps[1]?.input?.payload?.videoConfig || steps[1]?.input?.payload?.video_config;
|
||||
if (videoConfig) {
|
||||
const engineId = videoConfig?.engineId || videoConfig?.engine_id;
|
||||
const engine = enginesele.video?.find((e: any) => String(e.id) === String(engineId));
|
||||
if (engine) {
|
||||
setCountType(engine.id);
|
||||
setEngineOptions({
|
||||
ratios: engine.supportedRatios || ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'],
|
||||
resolutions: engine.supportedResolutions || ['480p', '720p', '1080p'],
|
||||
durations: engine.supportedDurations || [5, 8, 10, 12, 15],
|
||||
});
|
||||
}
|
||||
setVideoAspectRatio(videoConfig?.aspectRatio || videoConfig?.aspect_ratio || '16:9');
|
||||
setVideoResolution(videoConfig?.resolution || '480p');
|
||||
setVideoDuration(videoConfig?.duration || 5);
|
||||
}
|
||||
setRetryPromptStepId(String(step.id));
|
||||
setRetryPromptModalVisible(true);
|
||||
}}
|
||||
@@ -1533,6 +1622,8 @@ function InitialInfo() {
|
||||
message.warning(`积分不足${credits},请充值积分`);
|
||||
return;
|
||||
}
|
||||
userClickedNextRef.current = true;
|
||||
setActiveKey(prev => prev.filter(k => k !== String(step.childId)));
|
||||
createvideo(step.id, step.engineId);
|
||||
}}
|
||||
type="primary"
|
||||
@@ -2096,7 +2187,7 @@ function InitialInfo() {
|
||||
}
|
||||
onCancel={handleClosePreview}
|
||||
width={800}
|
||||
footer={
|
||||
footer={showDownloadButton ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: 'rgba(255,255,255,0.6)', borderTop: '1px solid rgba(139, 92, 246, 0.08)' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -2107,7 +2198,7 @@ function InitialInfo() {
|
||||
下载
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
) : null}
|
||||
centered
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
|
||||
Reference in New Issue
Block a user