爆款拆镜修改

This commit is contained in:
sjy
2026-07-27 17:22:59 +08:00
parent 4eb0360134
commit 1bdafa25cb
6 changed files with 262 additions and 109 deletions
File diff suppressed because one or more lines are too long
+2 -22
View File
@@ -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);
+114 -23
View File
@@ -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,12 +1605,26 @@ function InitialInfo() {
{/* 步骤4: 生成视频提示词 */}
{step.childId === 4 && (
<>
{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"
@@ -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={{
+13 -22
View File
@@ -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>
+113 -22
View File
@@ -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,12 +1511,26 @@ function InitialInfo() {
{/* 步骤4: 生成视频提示词 */}
{step.childId === 4 && (
<>
{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={{