爆款,拆镜流程修改
This commit is contained in:
-613
File diff suppressed because one or more lines are too long
+613
File diff suppressed because one or more lines are too long
Vendored
+36
-36
@@ -1,37 +1,37 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<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" />
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
<title>民众智创</title>
|
<title>民众智创</title>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var cached = localStorage.getItem('siteInfo');
|
var cached = localStorage.getItem('siteInfo');
|
||||||
if (cached) {
|
if (cached) {
|
||||||
try {
|
try {
|
||||||
var info = JSON.parse(cached);
|
var info = JSON.parse(cached);
|
||||||
if (info.siteName) {
|
if (info.siteName) {
|
||||||
document.title = info.siteName;
|
document.title = info.siteName;
|
||||||
}
|
}
|
||||||
if (info.siteLogo) {
|
if (info.siteLogo) {
|
||||||
var link = document.querySelector('link[rel="icon"]');
|
var link = document.querySelector('link[rel="icon"]');
|
||||||
if (link) {
|
if (link) {
|
||||||
link.href = info.siteLogo;
|
link.href = info.siteLogo;
|
||||||
link.type = 'image/png';
|
link.type = 'image/png';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-DZg1zviY.js"></script>
|
<script type="module" crossorigin src="/assets/index-Dej_BHGv.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>␍
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ const HomePage: React.FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content_box">
|
<div className="content_box" style={{backgroundColor: '#fff', borderRadius: 16, }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
<h3>首页工作台 </h3>
|
<h3>首页工作台 </h3>
|
||||||
<p
|
<p
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Button, Typography, Collapse, Space, Modal, Input, Table, message, Tooltip } from 'antd';
|
import { Button, Typography, Collapse, Space, Modal, Input, Table, message, Tooltip, Select } from 'antd';
|
||||||
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined, WarningOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined, WarningOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { getReplicationList, getReplicationDetail, gettwo, getthree, getfour, getEngine, updateHotOpeningVideoPromptSchema, updateImagePrompt, calculateCredits, retryHotOpeningVideoPromptV2, updateHotOpeningVideoPromptSchemaV2, generateHotOpeningVideoV2 } from '../api/index';
|
import { getReplicationList, getReplicationDetail, gettwo, getthree, getfour, getEngine, updateHotOpeningVideoPromptSchema, updateImagePrompt, calculateCredits, retryHotOpeningVideoPromptV2, updateHotOpeningVideoPromptSchemaV2, generateHotOpeningVideoV2 } from '../api/index';
|
||||||
@@ -49,6 +49,7 @@ function InitialInfo() {
|
|||||||
const [imageEngineId, setImageEngineId] = useState<string>('');
|
const [imageEngineId, setImageEngineId] = useState<string>('');
|
||||||
const [showEngineModal, setShowEngineModal] = useState(false);
|
const [showEngineModal, setShowEngineModal] = useState(false);
|
||||||
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
||||||
|
const [showVideoSettingsModalStep4, setShowVideoSettingsModalStep4] = useState(false);
|
||||||
const [videoDuration, setVideoDuration] = useState(5);
|
const [videoDuration, setVideoDuration] = useState(5);
|
||||||
const [videoAspectRatio, setVideoAspectRatio] = useState<string>('16:9');
|
const [videoAspectRatio, setVideoAspectRatio] = useState<string>('16:9');
|
||||||
const [videoResolution, setVideoResolution] = useState<string>('480p');
|
const [videoResolution, setVideoResolution] = useState<string>('480p');
|
||||||
@@ -74,6 +75,7 @@ function InitialInfo() {
|
|||||||
const [searchKeyword, setSearchKeyword] = useState('');
|
const [searchKeyword, setSearchKeyword] = useState('');
|
||||||
// 任务详情数据
|
// 任务详情数据
|
||||||
const [taskDetail, setTaskDetail] = useState<any>({});
|
const [taskDetail, setTaskDetail] = useState<any>({});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
// API 返回的步骤数据
|
// API 返回的步骤数据
|
||||||
const [apiSteps, setApiSteps] = useState<any[]>([]);
|
const [apiSteps, setApiSteps] = useState<any[]>([]);
|
||||||
// 当前展开的步骤
|
// 当前展开的步骤
|
||||||
@@ -352,6 +354,7 @@ function InitialInfo() {
|
|||||||
// 获取任务详情数据
|
// 获取任务详情数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (creatID) {
|
if (creatID) {
|
||||||
|
setIsLoading(true);
|
||||||
getReplicationDetail(creatID, flowVersion).then((res: any) => {
|
getReplicationDetail(creatID, flowVersion).then((res: any) => {
|
||||||
setTaskDetail(res);
|
setTaskDetail(res);
|
||||||
if (res.steps) {
|
if (res.steps) {
|
||||||
@@ -359,6 +362,8 @@ function InitialInfo() {
|
|||||||
}
|
}
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
message.error(error?.message || '加载任务详情失败');
|
message.error(error?.message || '加载任务详情失败');
|
||||||
|
}).finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [creatID, flowVersion]);
|
}, [creatID, flowVersion]);
|
||||||
@@ -628,11 +633,11 @@ function InitialInfo() {
|
|||||||
setRetryPromptModalVisible(true);
|
setRetryPromptModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitRegenerateVideoPrompt = async () => {
|
const submitRegenerateVideoPrompt = async (stepId: string) => {
|
||||||
if (!isV2 || !taskDetail?.id || !retryPromptStepId || !countType) return;
|
if (!isV2 || !taskDetail?.id || !stepId || !countType) return;
|
||||||
setRetryPromptSubmitting(true);
|
// setRetryPromptSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await retryHotOpeningVideoPromptV2(taskDetail.id, retryPromptStepId, {
|
await retryHotOpeningVideoPromptV2(taskDetail.id, stepId, {
|
||||||
video_config: {
|
video_config: {
|
||||||
engine_id: countType,
|
engine_id: countType,
|
||||||
duration: videoDuration,
|
duration: videoDuration,
|
||||||
@@ -641,13 +646,11 @@ function InitialInfo() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
message.info('正在按新视频参数重新生成视频提示词,请稍候...');
|
message.info('正在按新视频参数重新生成视频提示词,请稍候...');
|
||||||
setRetryPromptModalVisible(false);
|
|
||||||
setRetryPromptStepId('');
|
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '重新生成视频提示词失败');
|
message.error(error?.message || '重新生成视频提示词失败');
|
||||||
} finally {
|
} finally {
|
||||||
setRetryPromptSubmitting(false);
|
// setRetryPromptSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -670,7 +673,106 @@ function InitialInfo() {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{isLoading && (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'linear-gradient(135deg, rgba(250, 248, 255, 0.95) 0%, rgba(245, 243, 255, 0.95) 50%, rgba(240, 249, 255, 0.95) 100%)',
|
||||||
|
backdropFilter: 'blur(12px)',
|
||||||
|
zIndex: 9999,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 20,
|
||||||
|
opacity: 1,
|
||||||
|
transition: 'opacity 0.3s ease',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
border: '2px solid rgba(99, 102, 241, 0.08)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
border: '2px solid transparent',
|
||||||
|
borderTopColor: '#6366f1',
|
||||||
|
borderRightColor: '#8b5cf6',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: 'spinSlow 0.8s linear infinite',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
border: '2px solid transparent',
|
||||||
|
borderBottomColor: '#a855f7',
|
||||||
|
borderLeftColor: '#c084fc',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: 'spinSlow 1.2s linear infinite reverse',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
boxShadow: '0 0 12px rgba(99, 102, 241, 0.6)',
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#4f46e5',
|
||||||
|
fontWeight: 600,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
}}>加载中</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#94a3b8',
|
||||||
|
fontWeight: 400,
|
||||||
|
}}>正在获取任务详情...</span>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 4,
|
||||||
|
marginTop: 8,
|
||||||
|
}}>
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
width: 4,
|
||||||
|
height: 4,
|
||||||
|
background: '#a5b4fc',
|
||||||
|
borderRadius: '50%',
|
||||||
|
animation: `spinSlow ${0.8 + i * 0.2}s ease-in-out infinite`,
|
||||||
|
animationDelay: `${i * 0.15}s`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', gap: '2%', position: 'relative', zIndex: 1, minHeight: 0 }}>
|
<div style={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', gap: '2%', position: 'relative', zIndex: 1, minHeight: 0 }}>
|
||||||
|
|
||||||
@@ -1355,7 +1457,7 @@ function InitialInfo() {
|
|||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)'
|
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)'
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if ((user?.credits || 0) < estimatedCredits) {
|
if ((user?.credits || 0) < estimatedCredits) {
|
||||||
message.warning('积分不足,请更换参数/充值积分');
|
message.warning('积分不足,请更换参数/充值积分');
|
||||||
return;
|
return;
|
||||||
@@ -1366,7 +1468,7 @@ function InitialInfo() {
|
|||||||
>
|
>
|
||||||
下一步:生成视频提示词
|
下一步:生成视频提示词
|
||||||
<span style={{ color: '#fff', marginLeft: 8 }}>
|
<span style={{ color: '#fff', marginLeft: 8 }}>
|
||||||
预估积分:{estimatedCredits}
|
视频生成预估积分:{estimatedCredits}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -1381,12 +1483,13 @@ function InitialInfo() {
|
|||||||
视频提词已生成,可点击按钮查看/修改
|
视频提词已生成,可点击按钮查看/修改
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
|
|
||||||
|
<Space style={{ marginTop: 16, gap: 12, width: '100%', flexWrap: 'wrap' }}>
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => handleOpenModal(taskDetail?.videoGeneration?.promptSchema, 'video', step.id, taskDetail?.videoGeneration?.schemaConfigSnapshot)}
|
onClick={() => handleOpenModal(taskDetail?.videoGeneration?.promptSchema, 'video', step.id, taskDetail?.videoGeneration?.schemaConfigSnapshot)}
|
||||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
style={{ flex: '0 0 auto', minWidth: 160, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||||
disabled={step.status !== 'completed' || !taskDetail?.videoGeneration?.promptSchema || !taskDetail?.videoGeneration?.schemaConfigSnapshot}
|
disabled={step.status !== 'completed' || !taskDetail?.videoGeneration?.promptSchema || !taskDetail?.videoGeneration?.schemaConfigSnapshot}
|
||||||
>
|
>
|
||||||
查看/修改视频提词
|
查看/修改视频提词
|
||||||
@@ -1394,21 +1497,24 @@ function InitialInfo() {
|
|||||||
{isV2 && (
|
{isV2 && (
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
onClick={() => openRegenerateVideoPrompt(step.id)}
|
onClick={() => {
|
||||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
|
setRetryPromptStepId(String(step.id));
|
||||||
|
setRetryPromptModalVisible(true);
|
||||||
|
}}
|
||||||
|
style={{ flex: '0 0 auto', minWidth: 160, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
|
||||||
disabled={step.status === 'processing' || steps[2]?.status === 'processing'}
|
disabled={step.status === 'processing' || steps[2]?.status === 'processing'}
|
||||||
>
|
>
|
||||||
重新生成视频提词
|
重新生成视频提词
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => { createvideo(step.id, step.engineId); }}
|
onClick={() => createvideo(step.id, step.engineId)}
|
||||||
type="primary"
|
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)' }}
|
style={{ flex: '1 0 auto', minWidth: 140, 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'}
|
disabled={step.status !== 'completed'}
|
||||||
>
|
>
|
||||||
下一步:生成视频
|
下一步:生成视频
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -1467,101 +1573,6 @@ function InitialInfo() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
|
||||||
title="重新生成视频提词"
|
|
||||||
open={retryPromptModalVisible}
|
|
||||||
onCancel={() => {
|
|
||||||
if (!retryPromptSubmitting) {
|
|
||||||
setRetryPromptModalVisible(false);
|
|
||||||
setRetryPromptStepId('');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onOk={submitRegenerateVideoPrompt}
|
|
||||||
confirmLoading={retryPromptSubmitting}
|
|
||||||
okText="按新参数生成提词"
|
|
||||||
cancelText="取消"
|
|
||||||
width={720}
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
|
||||||
<div>
|
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>视频引擎</Text>
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }}>
|
|
||||||
{(enginesele.video || []).map((engine: any) => (
|
|
||||||
<button
|
|
||||||
key={engine.id}
|
|
||||||
type="button"
|
|
||||||
onClick={() => applyRetryVideoEngine(String(engine.id), {
|
|
||||||
duration: videoDuration,
|
|
||||||
aspectRatio: videoAspectRatio,
|
|
||||||
resolution: videoResolution,
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
minHeight: 46,
|
|
||||||
padding: '8px 12px',
|
|
||||||
borderRadius: 8,
|
|
||||||
border: countType === String(engine.id) ? '2px solid #6366f1' : '1px solid #e5e7eb',
|
|
||||||
background: countType === String(engine.id) ? 'rgba(99,102,241,0.08)' : '#fff',
|
|
||||||
color: countType === String(engine.id) ? '#4f46e5' : '#374151',
|
|
||||||
cursor: 'pointer',
|
|
||||||
textAlign: 'left',
|
|
||||||
fontWeight: countType === String(engine.id) ? 600 : 400,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{engine.name || engine.id}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>视频时长</Text>
|
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
||||||
{engineOptions.durations.map((duration) => (
|
|
||||||
<Button
|
|
||||||
key={duration}
|
|
||||||
type={videoDuration === duration ? 'primary' : 'default'}
|
|
||||||
onClick={() => setVideoDuration(duration)}
|
|
||||||
>
|
|
||||||
{duration} 秒
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>画面比例</Text>
|
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
||||||
{engineOptions.ratios.map((ratio) => (
|
|
||||||
<Button
|
|
||||||
key={ratio}
|
|
||||||
type={videoAspectRatio === ratio ? 'primary' : 'default'}
|
|
||||||
onClick={() => setVideoAspectRatio(ratio)}
|
|
||||||
>
|
|
||||||
{ratio}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>分辨率</Text>
|
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
||||||
{engineOptions.resolutions.map((resolution) => (
|
|
||||||
<Button
|
|
||||||
key={resolution}
|
|
||||||
type={videoResolution === resolution ? 'primary' : 'default'}
|
|
||||||
onClick={() => setVideoResolution(resolution)}
|
|
||||||
>
|
|
||||||
{resolution}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ padding: '10px 12px', borderRadius: 8, background: '#f8fafc', color: '#64748b' }}>
|
|
||||||
当前参数:{enginesele.video?.find((engine: any) => String(engine.id) === countType)?.name || countType}
|
|
||||||
{' · '}{videoDuration} 秒 · {videoAspectRatio} · {videoResolution}
|
|
||||||
<span style={{ marginLeft: 12 }}>最终视频预估积分:{estimatedCredits}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||||
@@ -1775,7 +1786,10 @@ function InitialInfo() {
|
|||||||
|
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/initial/${record.id}/initialinfo?flow_version=${record.flowVersion === 'v2' ? 'v2' : 'v1'}`)}
|
onClick={() => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
navigate(`/initial/${record.id}/initialinfo?flow_version=${record.flowVersion === 'v2' ? 'v2' : 'v1'}`);
|
||||||
|
}}
|
||||||
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'rgba(99, 102, 241, 0.08)', padding: '4px 12px', borderRadius: 8, cursor: 'pointer', transition: 'all 0.2s', fontWeight: 500 }}
|
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'rgba(99, 102, 241, 0.08)', padding: '4px 12px', borderRadius: 8, cursor: 'pointer', transition: 'all 0.2s', fontWeight: 500 }}
|
||||||
>
|
>
|
||||||
查看详情
|
查看详情
|
||||||
@@ -1854,6 +1868,265 @@ function InitialInfo() {
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={retryPromptModalVisible}
|
||||||
|
title={
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{ width: 4, height: 20, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||||
|
<span style={{ fontSize: 16, fontWeight: 700, color: '#6366f1', letterSpacing: 0.4 }}>
|
||||||
|
重新生成视频提词
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onCancel={() => setRetryPromptModalVisible(false)}
|
||||||
|
width={400}
|
||||||
|
footer={null}
|
||||||
|
centered
|
||||||
|
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' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '16px 0' }}>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择引擎
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={countType}
|
||||||
|
onChange={(value) => {
|
||||||
|
const engine = enginesele.video?.find((e: any) => e.id === value);
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
if (!engine.supportedRatios?.includes(videoAspectRatio)) {
|
||||||
|
setVideoAspectRatio(engine.supportedRatios?.[0] || '16:9');
|
||||||
|
}
|
||||||
|
if (!engine.supportedResolutions?.includes(videoResolution)) {
|
||||||
|
setVideoResolution(engine.supportedResolutions?.[0] || '720p');
|
||||||
|
}
|
||||||
|
if (!engine.supportedDurations?.includes(videoDuration)) {
|
||||||
|
setVideoDuration(engine.supportedDurations?.[0] || 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ width: '100%', borderRadius: 8 }}
|
||||||
|
options={enginesele.video?.map((engine: any) => ({
|
||||||
|
value: engine.id,
|
||||||
|
label: engine.name,
|
||||||
|
}))}
|
||||||
|
placeholder="请选择引擎"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择比例
|
||||||
|
</span>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{engineOptions.ratios.map((ratio) => (
|
||||||
|
<button
|
||||||
|
key={ratio}
|
||||||
|
onClick={() => setVideoAspectRatio(ratio)}
|
||||||
|
style={{
|
||||||
|
flex: '0 0 calc(14.28% - 4px)',
|
||||||
|
minWidth: 44,
|
||||||
|
height: 52,
|
||||||
|
borderRadius: 6,
|
||||||
|
border: videoAspectRatio === ratio
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoAspectRatio === ratio
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
border: `2px solid ${videoAspectRatio === ratio ? '#6366f1' : '#9ca3af'}`,
|
||||||
|
borderRadius: 2,
|
||||||
|
marginBottom: 2,
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 9,
|
||||||
|
color: videoAspectRatio === ratio
|
||||||
|
? '#6366f1'
|
||||||
|
: '#6b7280',
|
||||||
|
fontWeight: videoAspectRatio === ratio ? 600 : 400,
|
||||||
|
}}>
|
||||||
|
{ratio}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择时长
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{ flex: 1, position: 'relative', height: 24, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#e5e7eb',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#6366f1',
|
||||||
|
width: `${((videoDuration - Math.min(...engineOptions.durations)) / (Math.max(...engineOptions.durations) - Math.min(...engineOptions.durations))) * 100}%`,
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={Math.min(...engineOptions.durations)}
|
||||||
|
max={Math.max(...engineOptions.durations)}
|
||||||
|
value={videoDuration}
|
||||||
|
onChange={(e) => setVideoDuration(Number(e.target.value))}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: 'transparent',
|
||||||
|
outline: 'none',
|
||||||
|
appearance: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
padding: '4px 12px',
|
||||||
|
backgroundColor: '#f1f5f9',
|
||||||
|
borderRadius: 6,
|
||||||
|
}}>
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 600, color: '#64748b' }}>
|
||||||
|
{videoDuration}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: '#9ca3af' }}>秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择分辨率
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
|
{engineOptions.resolutions.map((resolution) => (
|
||||||
|
<button
|
||||||
|
key={resolution}
|
||||||
|
onClick={() => setVideoResolution(resolution)}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 42,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: videoResolution === resolution
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoResolution === resolution
|
||||||
|
? '#6366f1'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: videoResolution === resolution
|
||||||
|
? '#fff'
|
||||||
|
: '#4b5563',
|
||||||
|
}}>
|
||||||
|
{resolution}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: 12, marginTop: 16 }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setRetryPromptModalVisible(false)}
|
||||||
|
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (!countType) {
|
||||||
|
message.warning('请选择视频引擎');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitRegenerateVideoPrompt(retryPromptStepId);
|
||||||
|
setRetryPromptModalVisible(false);
|
||||||
|
}}
|
||||||
|
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={!countType}
|
||||||
|
>
|
||||||
|
确认生成
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,21 @@ import {
|
|||||||
Pagination,
|
Pagination,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Select,
|
Select,
|
||||||
|
Tooltip
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
VideoCameraOutlined,
|
VideoCameraOutlined,
|
||||||
PictureOutlined,
|
PictureOutlined,
|
||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
|
SettingOutlined,
|
||||||
|
LayoutOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { uploadHotOpeningVideo, uploadHotOpeningImage, generateReplication, getReplicationList, deleteHotOpeningReplicationTask, getEngine, calculateCredits } from '../api';
|
import { uploadHotOpeningVideo, uploadHotOpeningImage, generateReplication, getReplicationList, deleteHotOpeningReplicationTask, getEngine, calculateCredits } from '../api';
|
||||||
import bg1 from '../assets/bg1.png';
|
import bg1 from '../assets/bg1.png';
|
||||||
import UploadSelector from '../components/UploadSelector';
|
import UploadSelector from '../components/UploadSelector';
|
||||||
|
import { useAuthStore } from '../store/useAuthStore';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +51,7 @@ const buildAssetUrl = (url?: string): string => {
|
|||||||
|
|
||||||
const GenerateConver: React.FC = () => {
|
const GenerateConver: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { user } = useAuthStore();
|
||||||
|
|
||||||
const [tableData, setTableData] = useState<any[]>(
|
const [tableData, setTableData] = useState<any[]>(
|
||||||
[]
|
[]
|
||||||
@@ -84,6 +89,17 @@ const GenerateConver: React.FC = () => {
|
|||||||
const [videoDuration, setVideoDuration] = useState(5);
|
const [videoDuration, setVideoDuration] = useState(5);
|
||||||
const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
|
const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
|
||||||
const [videoResolution, setVideoResolution] = useState('480p');
|
const [videoResolution, setVideoResolution] = useState('480p');
|
||||||
|
const [showEngineModal, setShowEngineModal] = useState(false);
|
||||||
|
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
||||||
|
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 [creditRules, setCreditRules] = useState<any[]>([]);
|
const [creditRules, setCreditRules] = useState<any[]>([]);
|
||||||
|
|
||||||
// 弹窗状态
|
// 弹窗状态
|
||||||
@@ -465,12 +481,13 @@ const GenerateConver: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className="replication-preview" style={{ width: '70%', background: '#f1f2f3', backdropFilter: 'blur(20px)', display: 'flex', flexDirection: 'column', borderRadius: 20, overflow: 'hidden',
|
<div className="replication-preview" style={{
|
||||||
// border: '1px solid rgba(99, 102, 241, 0.1)',
|
width: '68%', background: '#f1f2f3', backdropFilter: 'blur(20px)', display: 'flex', flexDirection: 'column', borderRadius: 20, overflow: 'hidden',
|
||||||
|
// border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||||
// boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
// boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||||
position: 'relative', zIndex: 10,
|
position: 'relative', zIndex: 10,
|
||||||
|
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -531,7 +548,7 @@ const GenerateConver: React.FC = () => {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
borderRight: '1px solid rgba(99, 102, 241, 0.08)', overflowY: 'auto',
|
borderRight: '1px solid rgba(99, 102, 241, 0.08)', overflowY: 'auto',
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
|
|
||||||
}}>
|
}}>
|
||||||
{cardData.length > 0 ? (
|
{cardData.length > 0 ? (
|
||||||
@@ -852,12 +869,13 @@ const GenerateConver: React.FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
<div className="replication-form" style={{
|
<div className="replication-form" style={{
|
||||||
flex: 1, overflow: 'auto', width: '28%',
|
overflow: 'auto', width: '30%',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
backdropFilter: 'blur(20px)', borderRadius: 20,
|
backdropFilter: 'blur(20px)', borderRadius: 20,
|
||||||
// border: '1px solid rgba(99, 102, 241, 0.1)',
|
// border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||||
// boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
// boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||||
position: 'relative', zIndex: 10
|
position: 'relative', zIndex: 10,
|
||||||
|
scrollbarWidth: 'none',
|
||||||
}}>
|
}}>
|
||||||
{/* 右侧表单区域 */}
|
{/* 右侧表单区域 */}
|
||||||
<div className="replication-form-content" style={{
|
<div className="replication-form-content" style={{
|
||||||
@@ -1179,35 +1197,375 @@ const GenerateConver: React.FC = () => {
|
|||||||
|
|
||||||
<div style={{ marginBottom: 20, display: 'grid', gap: 10 }}>
|
<div style={{ marginBottom: 20, display: 'grid', gap: 10 }}>
|
||||||
<p style={{ margin: 0, fontSize: 13, fontWeight: 600, color: '#475569' }}>视频生成参数(必选)</p>
|
<p style={{ margin: 0, fontSize: 13, fontWeight: 600, color: '#475569' }}>视频生成参数(必选)</p>
|
||||||
<Select value={engineId || undefined} placeholder="选择视频引擎" onChange={(value) => {
|
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
setEngineId(value);
|
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||||
const engine = videoEngines.find((item: any) => item.id === value);
|
<button
|
||||||
setVideoAspectRatio(engine?.supportedRatios?.[0] || '16:9');
|
onClick={() => {
|
||||||
setVideoResolution(engine?.supportedResolutions?.[0] || '480p');
|
setShowEngineModal(!showEngineModal);
|
||||||
setVideoDuration(engine?.supportedDurations?.[0] || 5);
|
setShowVideoSettingsModal(false);
|
||||||
}} options={videoEngines.map((item: any) => ({
|
}}
|
||||||
value: item.id,
|
className="image-settings-trigger"
|
||||||
label: imageUrl && !supportsReferenceImage(item) ? `${item.name}(不支持参考图)` : item.name,
|
style={{
|
||||||
disabled: Boolean(imageUrl) && !supportsReferenceImage(item),
|
width: '100%',
|
||||||
}))} />
|
padding: '4px 12px',
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8 }}>
|
height: 34,
|
||||||
<Select value={videoDuration} onChange={setVideoDuration} options={(selectedEngine?.supportedDurations || [5]).map((value: number) => ({ value, label: `${value}秒` }))} />
|
borderRadius: 10,
|
||||||
<Select value={videoAspectRatio} onChange={setVideoAspectRatio} options={(selectedEngine?.supportedRatios || ['16:9']).map((value: string) => ({ value, label: value }))} />
|
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||||
<Select value={videoResolution} onChange={setVideoResolution} options={(selectedEngine?.supportedResolutions || ['480p']).map((value: string) => ({ value, label: value }))} />
|
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#64748b',
|
||||||
|
}}>
|
||||||
|
{videoEngines.find((e: any) => e.id === engineId)?.name || '选择引擎'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showEngineModal && (
|
||||||
|
<div
|
||||||
|
className="image-settings-popover"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 'calc(100% + 8px)',
|
||||||
|
left: -10,
|
||||||
|
width: 350,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
|
||||||
|
padding: 16,
|
||||||
|
border: 'none',
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择引擎
|
||||||
|
</span>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{videoEngines.filter((item: any) => item.supportsUniversalReference !== false).map((engine: any) => (
|
||||||
|
<button
|
||||||
|
key={engine.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setEngineId(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],
|
||||||
|
});
|
||||||
|
if (!engine.supportedRatios?.includes(videoAspectRatio)) {
|
||||||
|
setVideoAspectRatio(engine.supportedRatios?.[0] || '16:9');
|
||||||
|
}
|
||||||
|
if (!engine.supportedResolutions?.includes(videoResolution)) {
|
||||||
|
setVideoResolution(engine.supportedResolutions?.[0] || '720p');
|
||||||
|
}
|
||||||
|
if (!engine.supportedDurations?.includes(videoDuration)) {
|
||||||
|
setVideoDuration(engine.supportedDurations?.[0] || 5);
|
||||||
|
}
|
||||||
|
setShowEngineModal(false);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 48,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: engineId === engine.id
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: engineId === engine.id
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
padding: '8px 12px',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: engineId === engine.id ? 600 : 500,
|
||||||
|
color: engineId === engine.id ? '#6366f1' : '#4b5563',
|
||||||
|
marginBottom: 2,
|
||||||
|
}}>
|
||||||
|
{engine.name}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#9ca3af',
|
||||||
|
}}>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||||
|
setShowEngineModal(false);
|
||||||
|
}}
|
||||||
|
className="image-settings-trigger"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '4px 12px',
|
||||||
|
height: 34,
|
||||||
|
borderRadius: 10,
|
||||||
|
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#64748b',
|
||||||
|
}}>
|
||||||
|
{videoAspectRatio} · {videoDuration}s · {videoResolution}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showVideoSettingsModal && (
|
||||||
|
<div
|
||||||
|
className="image-settings-popover"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 'calc(100% + 8px)',
|
||||||
|
left: -160,
|
||||||
|
width: 350,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
|
||||||
|
padding: 16,
|
||||||
|
border: 'none',
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择比例
|
||||||
|
</span>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{engineOptions.ratios.map((ratio) => (
|
||||||
|
<button
|
||||||
|
key={ratio}
|
||||||
|
onClick={() => setVideoAspectRatio(ratio)}
|
||||||
|
style={{
|
||||||
|
flex: '0 0 calc(14.28% - 4px)',
|
||||||
|
minWidth: 44,
|
||||||
|
height: 52,
|
||||||
|
borderRadius: 6,
|
||||||
|
border: videoAspectRatio === ratio
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoAspectRatio === ratio
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
border: `2px solid ${videoAspectRatio === ratio ? '#6366f1' : '#9ca3af'}`,
|
||||||
|
borderRadius: 2,
|
||||||
|
marginBottom: 2,
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 9,
|
||||||
|
color: videoAspectRatio === ratio
|
||||||
|
? '#6366f1'
|
||||||
|
: '#6b7280',
|
||||||
|
fontWeight: videoAspectRatio === ratio ? 600 : 400,
|
||||||
|
}}>
|
||||||
|
{ratio}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择时长
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{ flex: 1, position: 'relative', height: 24, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#e5e7eb',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#6366f1',
|
||||||
|
width: `${((videoDuration - Math.min(...engineOptions.durations)) / (Math.max(...engineOptions.durations) - Math.min(...engineOptions.durations))) * 100}%`,
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={Math.min(...engineOptions.durations)}
|
||||||
|
max={Math.max(...engineOptions.durations)}
|
||||||
|
value={videoDuration}
|
||||||
|
onChange={(e) => setVideoDuration(Number(e.target.value))}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: 'transparent',
|
||||||
|
outline: 'none',
|
||||||
|
appearance: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
padding: '4px 12px',
|
||||||
|
backgroundColor: '#f1f5f9',
|
||||||
|
borderRadius: 6,
|
||||||
|
}}>
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 600, color: '#64748b' }}>
|
||||||
|
{videoDuration}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: '#9ca3af' }}>秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择分辨率
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
|
{engineOptions.resolutions.map((resolution) => (
|
||||||
|
<button
|
||||||
|
key={resolution}
|
||||||
|
onClick={() => setVideoResolution(resolution)}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 42,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: videoResolution === resolution
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoResolution === resolution
|
||||||
|
? '#6366f1'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: videoResolution === resolution
|
||||||
|
? '#fff'
|
||||||
|
: '#4b5563',
|
||||||
|
}}>
|
||||||
|
{resolution}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: 12, color: selectedEngineSupportsImage ? '#8b5cf6' : '#ef4444' }}>
|
|
||||||
{selectedEngineSupportsImage
|
|
||||||
? `预估视频积分:${estimatedCredits || '-'};素材图片可不传,最终生成只会携带图片,不会携带参考视频。`
|
|
||||||
: '当前引擎不支持参考图片,请移除素材图片或切换引擎。'}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 立即生成按钮 */}
|
{/* 立即生成按钮 */}
|
||||||
<Button
|
<Tooltip
|
||||||
|
title={((user?.credits || 0) < estimatedCredits) ? '积分不足,请更换参数/充值积分' : ''}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
block
|
block
|
||||||
size="large"
|
size="large"
|
||||||
onClick={handleGenerate}
|
onClick={() => {
|
||||||
|
if ((user?.credits || 0) < estimatedCredits) {
|
||||||
|
message.warning('积分不足,请更换参数/充值积分');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleGenerate();
|
||||||
|
}}
|
||||||
disabled={!selectedEngineSupportsImage}
|
disabled={!selectedEngineSupportsImage}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
@@ -1221,7 +1579,13 @@ const GenerateConver: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
立即生成+
|
立即生成+
|
||||||
|
<span style={{ color: '#fff', marginLeft: 8, fontSize: 13 }}>
|
||||||
|
预估积分:{estimatedCredits}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Button, Drawer, Input, Modal, Popconfirm, Select, Spin, Table, Tag, Tooltip, message } from 'antd';
|
import { Button, Drawer, Input, Modal, Popconfirm, Select, Spin, Table, Tag, Tooltip, message } from 'antd';
|
||||||
import { ArrowLeftOutlined, DeleteOutlined, PlusOutlined, XOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, DeleteOutlined, PlusOutlined, XOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
import { uploadShotReplicateImage, createRemoveLens, deleteSegment, getShotReplicationDetail, reanalyzeSegment, retrySplit, Removelist, removeCreate, reanalyzeShotReplication, splitCustom, getEngine, calculateCredits } from '../api';
|
import { uploadShotReplicateImage, createRemoveLens, deleteSegment, getShotReplicationDetail, reanalyzeSegment, retrySplit, Removelist, removeCreate, reanalyzeShotReplication, splitCustom, getEngine, calculateCredits } from '../api';
|
||||||
|
|
||||||
@@ -48,6 +48,17 @@ function RemoveInfo() {
|
|||||||
const [videoDuration, setVideoDuration] = useState(5);
|
const [videoDuration, setVideoDuration] = useState(5);
|
||||||
const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
|
const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
|
||||||
const [videoResolution, setVideoResolution] = useState('480p');
|
const [videoResolution, setVideoResolution] = useState('480p');
|
||||||
|
const [showEngineModal, setShowEngineModal] = useState(false);
|
||||||
|
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
||||||
|
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 [creditRules, setCreditRules] = useState<any[]>([]);
|
const [creditRules, setCreditRules] = useState<any[]>([]);
|
||||||
const [taskDetail, setTaskDetail] = useState<any>(null);
|
const [taskDetail, setTaskDetail] = useState<any>(null);
|
||||||
const [tableData, setTableData] = useState<any[]>([]);
|
const [tableData, setTableData] = useState<any[]>([]);
|
||||||
@@ -1058,7 +1069,9 @@ function RemoveInfo() {
|
|||||||
}
|
}
|
||||||
placement="top"
|
placement="top"
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
overlayInnerStyle={{ backgroundColor: '#fff', height: 300, overflowY: 'auto', border: '1px solid rgba(99, 102, 241, 0.1)', borderRadius: 12, boxShadow: '0 8px 24px rgba(99, 102, 241, 0.12)' }}
|
styles={{
|
||||||
|
container: { backgroundColor: '#fff', height: 300, overflowY: 'auto', border: '1px solid rgba(99, 102, 241, 0.1)', borderRadius: 12, boxShadow: '0 8px 24px rgba(99, 102, 241, 0.12)' }
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ marginLeft: 8, fontSize: 13, opacity: 0.9 }}>(AI 镜头拆分方案)</span>
|
<span style={{ marginLeft: 8, fontSize: 13, opacity: 0.9 }}>(AI 镜头拆分方案)</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -1251,25 +1264,359 @@ function RemoveInfo() {
|
|||||||
|
|
||||||
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
|
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
|
||||||
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>视频参数</label>
|
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>视频参数</label>
|
||||||
<Select
|
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
value={engineId || undefined}
|
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||||
onChange={handleEngineChange}
|
<button
|
||||||
placeholder="选择视频引擎"
|
onClick={() => {
|
||||||
style={{ width: '100%', marginBottom: 12 }}
|
setShowEngineModal(!showEngineModal);
|
||||||
options={videoEngines.map((item: any) => ({
|
setShowVideoSettingsModal(false);
|
||||||
value: item.id,
|
}}
|
||||||
label: productImage && !supportsReferenceImage(item) ? `${item.name}(不支持参考图)` : item.name,
|
className="image-settings-trigger"
|
||||||
disabled: Boolean(productImage) && !supportsReferenceImage(item),
|
style={{
|
||||||
}))}
|
width: '100%',
|
||||||
/>
|
padding: '4px 12px',
|
||||||
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
height: 34,
|
||||||
<Select value={videoDuration} onChange={setVideoDuration} style={{ flex: 1 }} options={(selectedEngine?.supportedDurations || [5]).map((item: number) => ({ value: item, label: `${item}秒` }))} />
|
borderRadius: 10,
|
||||||
<Select value={videoAspectRatio} onChange={setVideoAspectRatio} style={{ flex: 1 }} options={(selectedEngine?.supportedRatios || ['16:9']).map((item: string) => ({ value: item, label: item }))} />
|
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||||
<Select value={videoResolution} onChange={setVideoResolution} style={{ flex: 1 }} options={(selectedEngine?.supportedResolutions || ['480p']).map((item: string) => ({ value: item, label: item }))} />
|
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#64748b',
|
||||||
|
}}>
|
||||||
|
{videoEngines.find((e: any) => e.id === engineId)?.name || '选择引擎'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showEngineModal && (
|
||||||
|
<div
|
||||||
|
className="image-settings-popover"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 'calc(100% + 8px)',
|
||||||
|
left: -10,
|
||||||
|
width: 350,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
|
||||||
|
padding: 16,
|
||||||
|
border: 'none',
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择引擎
|
||||||
|
</span>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{videoEngines.filter((item: any) => item.supportsUniversalReference !== false).map((engine: any) => (
|
||||||
|
<button
|
||||||
|
key={engine.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setEngineId(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],
|
||||||
|
});
|
||||||
|
if (!engine.supportedRatios?.includes(videoAspectRatio)) {
|
||||||
|
setVideoAspectRatio(engine.supportedRatios?.[0] || '16:9');
|
||||||
|
}
|
||||||
|
if (!engine.supportedResolutions?.includes(videoResolution)) {
|
||||||
|
setVideoResolution(engine.supportedResolutions?.[0] || '720p');
|
||||||
|
}
|
||||||
|
if (!engine.supportedDurations?.includes(videoDuration)) {
|
||||||
|
setVideoDuration(engine.supportedDurations?.[0] || 5);
|
||||||
|
}
|
||||||
|
setShowEngineModal(false);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 48,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: engineId === engine.id
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: engineId === engine.id
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
padding: '8px 12px',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: engineId === engine.id ? 600 : 500,
|
||||||
|
color: engineId === engine.id ? '#6366f1' : '#4b5563',
|
||||||
|
marginBottom: 2,
|
||||||
|
}}>
|
||||||
|
{engine.name}
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#9ca3af',
|
||||||
|
}}>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||||
|
setShowEngineModal(false);
|
||||||
|
}}
|
||||||
|
className="image-settings-trigger"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '4px 12px',
|
||||||
|
height: 34,
|
||||||
|
borderRadius: 10,
|
||||||
|
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#64748b',
|
||||||
|
}}>
|
||||||
|
{videoAspectRatio} · {videoDuration}s · {videoResolution}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showVideoSettingsModal && (
|
||||||
|
<div
|
||||||
|
className="image-settings-popover"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 'calc(100% + 8px)',
|
||||||
|
left: -160,
|
||||||
|
width: 350,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
boxShadow: '0 10px 40px rgba(0,0,0,0.15)',
|
||||||
|
padding: 16,
|
||||||
|
border: 'none',
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择比例
|
||||||
|
</span>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{engineOptions.ratios.map((ratio) => (
|
||||||
|
<button
|
||||||
|
key={ratio}
|
||||||
|
onClick={() => setVideoAspectRatio(ratio)}
|
||||||
|
style={{
|
||||||
|
flex: '0 0 calc(14.28% - 4px)',
|
||||||
|
minWidth: 44,
|
||||||
|
height: 52,
|
||||||
|
borderRadius: 6,
|
||||||
|
border: videoAspectRatio === ratio
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoAspectRatio === ratio
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
border: `2px solid ${videoAspectRatio === ratio ? '#6366f1' : '#9ca3af'}`,
|
||||||
|
borderRadius: 2,
|
||||||
|
marginBottom: 2,
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 9,
|
||||||
|
color: videoAspectRatio === ratio
|
||||||
|
? '#6366f1'
|
||||||
|
: '#6b7280',
|
||||||
|
fontWeight: videoAspectRatio === ratio ? 600 : 400,
|
||||||
|
}}>
|
||||||
|
{ratio}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择时长
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{ flex: 1, position: 'relative', height: 24, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#e5e7eb',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#6366f1',
|
||||||
|
width: `${((videoDuration - Math.min(...engineOptions.durations)) / (Math.max(...engineOptions.durations) - Math.min(...engineOptions.durations))) * 100}%`,
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={Math.min(...engineOptions.durations)}
|
||||||
|
max={Math.max(...engineOptions.durations)}
|
||||||
|
value={videoDuration}
|
||||||
|
onChange={(e) => setVideoDuration(Number(e.target.value))}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: 'transparent',
|
||||||
|
outline: 'none',
|
||||||
|
appearance: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
padding: '4px 12px',
|
||||||
|
backgroundColor: '#f1f5f9',
|
||||||
|
borderRadius: 6,
|
||||||
|
}}>
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 600, color: '#64748b' }}>
|
||||||
|
{videoDuration}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: '#9ca3af' }}>秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择分辨率
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
|
{engineOptions.resolutions.map((resolution) => (
|
||||||
|
<button
|
||||||
|
key={resolution}
|
||||||
|
onClick={() => setVideoResolution(resolution)}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 42,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: videoResolution === resolution
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoResolution === resolution
|
||||||
|
? '#6366f1'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: videoResolution === resolution
|
||||||
|
? '#fff'
|
||||||
|
: '#4b5563',
|
||||||
|
}}>
|
||||||
|
{resolution}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: creditsInsufficient || !selectedEngineSupportsImage ? '#ef4444' : '#6366f1', fontSize: 13 }}>
|
<div style={{ color: !selectedEngineSupportsImage ? '#ef4444' : '#6366f1', fontSize: 13 }}>
|
||||||
{selectedEngineSupportsImage
|
{selectedEngineSupportsImage
|
||||||
? `预估积分:${estimatedCredits},当前积分:${Number(user?.credits || 0).toFixed(2)}`
|
? ''
|
||||||
: '当前引擎不支持参考图片,请移除素材图片或切换引擎。'}
|
: '当前引擎不支持参考图片,请移除素材图片或切换引擎。'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1277,30 +1624,31 @@ function RemoveInfo() {
|
|||||||
<div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
|
<div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleManualGenerate}
|
onClick={() => {
|
||||||
|
if ((user?.credits || 0) < estimatedCredits) {
|
||||||
|
message.warning('积分不足,请更换参数/充值积分');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleManualGenerate();
|
||||||
|
}}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading || creditsInsufficient || !engineId || !selectedEngineSupportsImage}
|
disabled={loading || !engineId || !selectedEngineSupportsImage || ((user?.credits || 0) < estimatedCredits)}
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: 48,
|
height: 44,
|
||||||
borderRadius: 10,
|
borderRadius: 12,
|
||||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
boxShadow: '0 4px 15px rgba(99, 102, 241, 0.3)',
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
|
||||||
transition: 'all 0.2s ease',
|
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.3)',
|
||||||
}}
|
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.4)';
|
|
||||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.boxShadow = '0 4px 15px rgba(99, 102, 241, 0.3)';
|
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loading ? '生成中...' : '手动生成'}
|
{loading ? '生成中...' : '手动生成'}
|
||||||
|
<span style={{ color: '#fff', marginLeft: 8, fontSize: 13 }}>
|
||||||
|
预估积分:{estimatedCredits}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ export default function VideoFrameExtractor() {
|
|||||||
video.src = URL.createObjectURL(file);
|
video.src = URL.createObjectURL(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (duration > 60) {
|
if (duration > 190) {
|
||||||
setError('视频时长不能超过 1分钟');
|
setError('视频时长不能超过 3分钟');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ export default function VideoFrameExtractor() {
|
|||||||
点击或拖拽到此上传视频文件
|
点击或拖拽到此上传视频文件
|
||||||
</p>
|
</p>
|
||||||
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
|
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
|
||||||
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 1 分钟内的视频
|
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -479,7 +479,7 @@ export default function VideoFrameExtractor() {
|
|||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
if (videoUrl && productName.trim() && !loading) {
|
if (videoUrl && productName.trim() && !loading) {
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
e.currentTarget.style.transform = 'traslnateY(0)';
|
||||||
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.3)';
|
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.3)';
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Button, Typography, Collapse, Space, Modal, Input, Table, message, Tooltip } from 'antd';
|
import { Button, Typography, Collapse, Space, Modal, Input, Table, message, Tooltip, Select } from 'antd';
|
||||||
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine, updateShotImagePrompt, updateShotVideoPromptSchema, calculateCredits, retryShotVideoPromptV2, updateShotVideoPromptSchemaV2, generateShotVideoV2 } from '../api/index';
|
import { getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine, updateShotImagePrompt, updateShotVideoPromptSchema, calculateCredits, retryShotVideoPromptV2, updateShotVideoPromptSchemaV2, generateShotVideoV2 } from '../api/index';
|
||||||
@@ -629,11 +629,11 @@ function InitialInfo() {
|
|||||||
setRetryPromptModalVisible(true);
|
setRetryPromptModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitRegenerateVideoPrompt = async () => {
|
const submitRegenerateVideoPrompt = async (stepId: string) => {
|
||||||
if (!isV2 || !taskDetail?.id || !retryPromptStepId || !countType) return;
|
if (!isV2 || !taskDetail?.id || !stepId || !countType) return;
|
||||||
setRetryPromptSubmitting(true);
|
setRetryPromptSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await retryShotVideoPromptV2(taskDetail.id, retryPromptStepId, {
|
await retryShotVideoPromptV2(taskDetail.id, stepId, {
|
||||||
video_config: {
|
video_config: {
|
||||||
engine_id: countType,
|
engine_id: countType,
|
||||||
duration: videoDuration,
|
duration: videoDuration,
|
||||||
@@ -642,8 +642,6 @@ function InitialInfo() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
message.info('正在按新视频参数重新生成视频提示词,请稍候...');
|
message.info('正在按新视频参数重新生成视频提示词,请稍候...');
|
||||||
setRetryPromptModalVisible(false);
|
|
||||||
setRetryPromptStepId('');
|
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || '重新生成视频提示词失败');
|
message.error(error?.message || '重新生成视频提示词失败');
|
||||||
@@ -1357,7 +1355,7 @@ function InitialInfo() {
|
|||||||
>
|
>
|
||||||
下一步:生成视频提示词
|
下一步:生成视频提示词
|
||||||
<span style={{ color: '#fff', marginLeft: 8 }}>
|
<span style={{ color: '#fff', marginLeft: 8 }}>
|
||||||
预估积分:{estimatedCredits}
|
视频生成预估积分:{estimatedCredits}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -1371,24 +1369,40 @@ function InitialInfo() {
|
|||||||
视频提词已生成,可点击按钮查看/修改
|
视频提词已生成,可点击按钮查看/修改
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
|
|
||||||
|
|
||||||
|
|
||||||
|
<Space style={{ marginTop: 16, gap: 12, width: '100%', flexWrap: 'wrap' }}>
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => handleOpenModal(taskDetail?.videoGeneration?.promptSchema, 'video', step.id, taskDetail?.videoGeneration?.schemaConfigSnapshot)}
|
onClick={() => handleOpenModal(taskDetail?.videoGeneration?.promptSchema, 'video', step.id, taskDetail?.videoGeneration?.schemaConfigSnapshot)}
|
||||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
style={{ flex: '0 0 auto', minWidth: 160, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||||
disabled={step.status !== 'completed' || !taskDetail?.videoGeneration?.promptSchema || !taskDetail?.videoGeneration?.schemaConfigSnapshot}
|
disabled={step.status !== 'completed' || !taskDetail?.videoGeneration?.promptSchema || !taskDetail?.videoGeneration?.schemaConfigSnapshot}
|
||||||
>
|
>
|
||||||
查看/修改视频提词
|
查看/修改视频提词
|
||||||
</Button>
|
</Button>
|
||||||
{isV2 && (
|
{isV2 && (
|
||||||
<Button type="default" onClick={() => openRegenerateVideoPrompt(step.id)} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }} disabled={step.status === 'processing' || steps[2]?.status === 'processing'}>
|
<Button
|
||||||
|
type="default"
|
||||||
|
onClick={() => {
|
||||||
|
setRetryPromptStepId(String(step.id));
|
||||||
|
setRetryPromptModalVisible(true);
|
||||||
|
}}
|
||||||
|
style={{ flex: '0 0 auto', minWidth: 160, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
|
||||||
|
disabled={step.status === 'processing' || steps[2]?.status === 'processing'}
|
||||||
|
>
|
||||||
重新生成视频提词
|
重新生成视频提词
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button onClick={() => { createvideo(step.id, step.engineId); }} 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={() => createvideo(step.id, step.engineId)}
|
||||||
</Button>
|
type="primary"
|
||||||
|
style={{ flex: '1 0 auto', minWidth: 140, 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>
|
</Space>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -1442,85 +1456,260 @@ function InitialInfo() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Modal
|
<Modal
|
||||||
title="重新生成视频提词"
|
|
||||||
open={retryPromptModalVisible}
|
open={retryPromptModalVisible}
|
||||||
onCancel={() => {
|
title={
|
||||||
if (!retryPromptSubmitting) {
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
setRetryPromptModalVisible(false);
|
<div style={{ width: 4, height: 20, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||||
setRetryPromptStepId('');
|
<span style={{ fontSize: 16, fontWeight: 700, color: '#6366f1', letterSpacing: 0.4 }}>
|
||||||
}
|
重新生成视频提词
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onCancel={() => setRetryPromptModalVisible(false)}
|
||||||
|
width={400}
|
||||||
|
footer={null}
|
||||||
|
centered
|
||||||
|
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' },
|
||||||
}}
|
}}
|
||||||
onOk={submitRegenerateVideoPrompt}
|
|
||||||
confirmLoading={retryPromptSubmitting}
|
|
||||||
okText="按新参数生成提词"
|
|
||||||
cancelText="取消"
|
|
||||||
width={720}
|
|
||||||
destroyOnClose
|
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
<div style={{ padding: '16px 0' }}>
|
||||||
<div>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>视频引擎</Text>
|
<span style={{
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }}>
|
display: 'block',
|
||||||
{(enginesele.video || []).map((engine: any) => (
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择引擎
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={countType}
|
||||||
|
onChange={(value) => {
|
||||||
|
const engine = enginesele.video?.find((e: any) => e.id === value);
|
||||||
|
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],
|
||||||
|
});
|
||||||
|
if (!engine.supportedRatios?.includes(videoAspectRatio)) {
|
||||||
|
setVideoAspectRatio(engine.supportedRatios?.[0] || '16:9');
|
||||||
|
}
|
||||||
|
if (!engine.supportedResolutions?.includes(videoResolution)) {
|
||||||
|
setVideoResolution(engine.supportedResolutions?.[0] || '720p');
|
||||||
|
}
|
||||||
|
if (!engine.supportedDurations?.includes(videoDuration)) {
|
||||||
|
setVideoDuration(engine.supportedDurations?.[0] || 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ width: '100%', borderRadius: 8 }}
|
||||||
|
options={enginesele.video?.map((engine: any) => ({
|
||||||
|
value: engine.id,
|
||||||
|
label: engine.name,
|
||||||
|
}))}
|
||||||
|
placeholder="请选择引擎"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<span style={{
|
||||||
|
display: 'block',
|
||||||
|
marginBottom: 8,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择比例
|
||||||
|
</span>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 4,
|
||||||
|
}}>
|
||||||
|
{engineOptions.ratios.map((ratio) => (
|
||||||
<button
|
<button
|
||||||
key={engine.id}
|
key={ratio}
|
||||||
type="button"
|
onClick={() => setVideoAspectRatio(ratio)}
|
||||||
onClick={() => applyRetryVideoEngine(String(engine.id), {
|
|
||||||
duration: videoDuration,
|
|
||||||
aspectRatio: videoAspectRatio,
|
|
||||||
resolution: videoResolution,
|
|
||||||
})}
|
|
||||||
style={{
|
style={{
|
||||||
minHeight: 46,
|
flex: '0 0 calc(14.28% - 4px)',
|
||||||
padding: '8px 12px',
|
minWidth: 44,
|
||||||
borderRadius: 8,
|
height: 52,
|
||||||
border: countType === String(engine.id) ? '2px solid #6366f1' : '1px solid #e5e7eb',
|
borderRadius: 6,
|
||||||
background: countType === String(engine.id) ? 'rgba(99,102,241,0.08)' : '#fff',
|
border: videoAspectRatio === ratio
|
||||||
color: countType === String(engine.id) ? '#4f46e5' : '#374151',
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoAspectRatio === ratio
|
||||||
|
? '#fff'
|
||||||
|
: '#f9fafb',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
textAlign: 'left',
|
display: 'flex',
|
||||||
fontWeight: countType === String(engine.id) ? 600 : 400,
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{engine.name || engine.id}
|
<div style={{
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
border: `2px solid ${videoAspectRatio === ratio ? '#6366f1' : '#9ca3af'}`,
|
||||||
|
borderRadius: 2,
|
||||||
|
marginBottom: 2,
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: 9,
|
||||||
|
color: videoAspectRatio === ratio
|
||||||
|
? '#6366f1'
|
||||||
|
: '#6b7280',
|
||||||
|
fontWeight: videoAspectRatio === ratio ? 600 : 400,
|
||||||
|
}}>
|
||||||
|
{ratio}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>视频时长</Text>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
<span style={{
|
||||||
{engineOptions.durations.map((duration) => (
|
display: 'block',
|
||||||
<Button key={duration} type={videoDuration === duration ? 'primary' : 'default'} onClick={() => setVideoDuration(duration)}>
|
marginBottom: 8,
|
||||||
{duration} 秒
|
fontSize: 12,
|
||||||
</Button>
|
fontWeight: 500,
|
||||||
))}
|
color: '#666666',
|
||||||
|
}}>
|
||||||
|
选择时长
|
||||||
|
</span>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
|
<div style={{ flex: 1, position: 'relative', height: 24, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#e5e7eb',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: 0,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: '#6366f1',
|
||||||
|
width: `${((videoDuration - Math.min(...engineOptions.durations)) / (Math.max(...engineOptions.durations) - Math.min(...engineOptions.durations))) * 100}%`,
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
}} />
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={Math.min(...engineOptions.durations)}
|
||||||
|
max={Math.max(...engineOptions.durations)}
|
||||||
|
value={videoDuration}
|
||||||
|
onChange={(e) => setVideoDuration(Number(e.target.value))}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 3,
|
||||||
|
background: 'transparent',
|
||||||
|
outline: 'none',
|
||||||
|
appearance: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 4,
|
||||||
|
padding: '4px 12px',
|
||||||
|
backgroundColor: '#f1f5f9',
|
||||||
|
borderRadius: 6,
|
||||||
|
}}>
|
||||||
|
<span style={{ fontSize: 14, fontWeight: 600, color: '#64748b' }}>
|
||||||
|
{videoDuration}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: 12, color: '#9ca3af' }}>秒</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>画面比例</Text>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
<span style={{
|
||||||
{engineOptions.ratios.map((ratio) => (
|
display: 'block',
|
||||||
<Button key={ratio} type={videoAspectRatio === ratio ? 'primary' : 'default'} onClick={() => setVideoAspectRatio(ratio)}>
|
marginBottom: 8,
|
||||||
{ratio}
|
fontSize: 12,
|
||||||
</Button>
|
fontWeight: 500,
|
||||||
))}
|
color: '#666666',
|
||||||
</div>
|
}}>
|
||||||
</div>
|
选择分辨率
|
||||||
<div>
|
</span>
|
||||||
<Text strong style={{ display: 'block', marginBottom: 10 }}>分辨率</Text>
|
<div style={{ display: 'flex', gap: 6 }}>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
|
||||||
{engineOptions.resolutions.map((resolution) => (
|
{engineOptions.resolutions.map((resolution) => (
|
||||||
<Button key={resolution} type={videoResolution === resolution ? 'primary' : 'default'} onClick={() => setVideoResolution(resolution)}>
|
<button
|
||||||
{resolution}
|
key={resolution}
|
||||||
</Button>
|
onClick={() => setVideoResolution(resolution)}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
height: 42,
|
||||||
|
borderRadius: 8,
|
||||||
|
border: videoResolution === resolution
|
||||||
|
? '2px solid #6366f1'
|
||||||
|
: '1px solid #e5e7eb',
|
||||||
|
backgroundColor: videoResolution === resolution
|
||||||
|
? '#6366f1'
|
||||||
|
: '#f9fafb',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
transition: 'all 0.2s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: videoResolution === resolution
|
||||||
|
? '#fff'
|
||||||
|
: '#4b5563',
|
||||||
|
}}>
|
||||||
|
{resolution}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ padding: '10px 12px', borderRadius: 8, background: '#f8fafc', color: '#64748b' }}>
|
|
||||||
当前参数:{enginesele.video?.find((engine: any) => String(engine.id) === countType)?.name || countType}
|
<div style={{ display: 'flex', gap: 12, marginTop: 16 }}>
|
||||||
{' · '}{videoDuration} 秒 · {videoAspectRatio} · {videoResolution}
|
<Button
|
||||||
<span style={{ marginLeft: 12 }}>最终视频预估积分:{estimatedCredits}</span>
|
onClick={() => setRetryPromptModalVisible(false)}
|
||||||
|
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500 }}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (!countType) {
|
||||||
|
message.warning('请选择视频引擎');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitRegenerateVideoPrompt(retryPromptStepId);
|
||||||
|
setRetryPromptModalVisible(false);
|
||||||
|
}}
|
||||||
|
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={!countType}
|
||||||
|
>
|
||||||
|
确认生成
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user