Merge branch 'main' of https://gitee.com/wg123/video-gen
This commit is contained in:
+7
-7
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-5mDsoYNB.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CVSmzRXM.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Bsz_Xon-.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -119,16 +119,7 @@ const NotificationPopup: React.FC = () => {
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
right: -50,
|
||||
top: -50,
|
||||
width: 200,
|
||||
height: 200,
|
||||
borderRadius: '50%',
|
||||
background: 'radial-gradient(circle, rgba(255,255,255,0.15) 0%, transparent 60%)',
|
||||
filter: 'blur(30px)',
|
||||
}} />
|
||||
|
||||
|
||||
{/* Top row: type badge + close */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
|
||||
@@ -136,7 +127,7 @@ const NotificationPopup: React.FC = () => {
|
||||
{tc.icon} {tc.label}
|
||||
</Tag>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
onClick={(e) => { e.stopPropagation(); handleClose(); }}
|
||||
style={{
|
||||
width: 28, height: 28, borderRadius: '50%',
|
||||
background: 'rgba(255,255,255,0.1)',
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
CloseCircleOutlined,
|
||||
EditOutlined,
|
||||
RocketOutlined,
|
||||
LayoutOutlined,
|
||||
CaretRightOutlined,
|
||||
CaretDownOutlined,
|
||||
VideoCameraOutlined,
|
||||
@@ -288,16 +289,20 @@ const PortalOptionGroup: React.FC<PortalOptionGroupProps> = ({ group, groupName,
|
||||
borderRadius: 8,
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
transition: "all 0.2s",
|
||||
background: selected ? "rgba(99,102,241,0.08)" : "#f8f9fc",
|
||||
border: expanded ? "1px solid #6366f1" : "1px solid #e2e8f0",
|
||||
transition: "background 0.2s, border-color 0.2s",
|
||||
background: "#ffffffff",
|
||||
border: "1px solid #e2e8f0",
|
||||
minWidth: 70,
|
||||
whiteSpace: "nowrap",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Typography.Text
|
||||
style={{
|
||||
fontSize: 12,
|
||||
color: selected ? "#6366f1" : "#64748b",
|
||||
fontWeight: selected ? 500 : 400,
|
||||
fontSize: 13,
|
||||
color: "#374151",
|
||||
fontWeight: 500,
|
||||
lineHeight: "18px",
|
||||
}}
|
||||
>
|
||||
{selected || group.name}
|
||||
@@ -642,13 +647,14 @@ const GeneratePage: React.FC = () => {
|
||||
|
||||
// Video params for step 2
|
||||
const [videoDuration, setVideoDuration] = useState(5);
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState<AspectRatio>("16:9");
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState<AspectRatio>("9:16");
|
||||
const [videoResolution, setVideoResolution] = useState<Resolution>("720p");
|
||||
const [videoEngines, setVideoEngines] = useState<any[]>([]);
|
||||
const [imageEngines, setImageEngines] = useState<any[]>([]);
|
||||
const [selectedEngineId, setSelectedEngineId] = useState("");
|
||||
const [includeMediaReferences, setIncludeMediaReferences] = useState(false);
|
||||
const [expandedEngine, setExpandedEngine] = useState<string | null>(null);
|
||||
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
||||
const [engineOptions, setEngineOptions] = useState<{
|
||||
ratios: string[];
|
||||
resolutions: string[];
|
||||
@@ -856,7 +862,7 @@ const GeneratePage: React.FC = () => {
|
||||
calculateCredits().then((data: any) => {
|
||||
setCreditCalculationData(data);
|
||||
})
|
||||
if (!showImageSettingsModal) return;
|
||||
if (!showImageSettingsModal && !showVideoSettingsModal) return;
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
@@ -864,11 +870,12 @@ const GeneratePage: React.FC = () => {
|
||||
!target.closest(".image-settings-trigger")
|
||||
) {
|
||||
setShowImageSettingsModal(false);
|
||||
setShowVideoSettingsModal(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
return () => document.removeEventListener("click", handleClickOutside);
|
||||
}, [showImageSettingsModal]);
|
||||
}, [showImageSettingsModal, showVideoSettingsModal]);
|
||||
|
||||
|
||||
const getReferenceUsage = (items?: MediaReference[]) => {
|
||||
@@ -1141,6 +1148,13 @@ const GeneratePage: React.FC = () => {
|
||||
// console.log(numericValue);
|
||||
// 更新状态(使用转换后的数字值)
|
||||
setMediaType(numericValue);
|
||||
|
||||
// 切换媒体类型后,默认选择第一个可用引擎
|
||||
const engines = numericValue === "image" ? imageEngines : videoEngines;
|
||||
const filteredEngines = engines.filter((item: any) => item.supportsUniversalReference !== false);
|
||||
if (filteredEngines.length > 0) {
|
||||
handleGenerationEngineChange(filteredEngines[0].id);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1271,8 +1285,23 @@ const GeneratePage: React.FC = () => {
|
||||
if (mediaType === 'image') {
|
||||
const sizes = engine.supportedSizes || {};
|
||||
setSupportedSizes(sizes);
|
||||
const resolution = Object.keys(sizes)[0] || '2K';
|
||||
const ratio = Object.keys(sizes[resolution] || {})[0] || '1:1';
|
||||
|
||||
const resolutionKeys = Object.keys(sizes);
|
||||
const resolutionOpts = resolutionKeys.map((key) => ({
|
||||
value: key,
|
||||
label: key,
|
||||
}));
|
||||
setResolutionOptions(resolutionOpts);
|
||||
|
||||
const resolution = resolutionKeys.includes(selectedResolution) ? selectedResolution : resolutionKeys[0] || '2K';
|
||||
const ratioKeys = Object.keys(sizes[resolution] || {});
|
||||
const ratioOpts = ratioKeys.map((key) => ({
|
||||
value: key,
|
||||
label: key,
|
||||
}));
|
||||
setRatioOptions(ratioOpts);
|
||||
|
||||
const ratio = ratioKeys.includes(selectedRatio) ? selectedRatio : ratioKeys[0] || '1:1';
|
||||
setSelectedResolution(resolution);
|
||||
setSelectedRatio(ratio);
|
||||
const pixelSize = sizes[resolution]?.[ratio] || '';
|
||||
@@ -2417,7 +2446,7 @@ const GeneratePage: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
rows={3}
|
||||
placeholder="上传参考素材(只用做模型理解,不参与生成)、输入文字,自由组合图、文多元素。输入 @ 可引用参考内容..."
|
||||
placeholder="上传参考素材后(手动开启是否参与最终生成,如不开启则只用做模型理解)、输入文字,自由组合图、文多元素。输入 @ 可引用参考内容..."
|
||||
maxLength={500}
|
||||
variant="borderless"
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
@@ -2560,12 +2589,12 @@ const GeneratePage: React.FC = () => {
|
||||
>
|
||||
<PortalDropdown
|
||||
label="引擎"
|
||||
value={(mediaType === "image" ? imageEngines : videoEngines).find((item: any) => item.id === selectedEngineId)?.name || "选择引擎"}
|
||||
options={(mediaType === "image" ? imageEngines : videoEngines).map((item: any) => item.name)}
|
||||
value={(mediaType === "image" ? imageEngines : videoEngines).filter((item: any) => item.supportsUniversalReference !== false).find((item: any) => item.id === selectedEngineId)?.name || "选择引擎"}
|
||||
options={(mediaType === "image" ? imageEngines : videoEngines).filter((item: any) => item.supportsUniversalReference !== false).map((item: any) => item.name)}
|
||||
expanded={expandedEngine === "engine"}
|
||||
onToggle={() => setExpandedEngine(expandedEngine === "engine" ? null : "engine")}
|
||||
onSelect={(value) => {
|
||||
const engine = (mediaType === "image" ? imageEngines : videoEngines).find((item: any) => item.name === value);
|
||||
const engine = (mediaType === "image" ? imageEngines : videoEngines).filter((item: any) => item.supportsUniversalReference !== false).find((item: any) => item.name === value);
|
||||
if (engine) {
|
||||
handleGenerationEngineChange(engine.id);
|
||||
}
|
||||
@@ -2575,34 +2604,228 @@ const GeneratePage: React.FC = () => {
|
||||
/>
|
||||
{mediaType !== "image" && (
|
||||
<>
|
||||
<PortalDropdown
|
||||
label="时长"
|
||||
value={`${videoDuration}`}
|
||||
options={durationOptions}
|
||||
suffix="秒"
|
||||
expanded={expandedEngine === "duration"}
|
||||
onToggle={handleDurationToggle}
|
||||
onSelect={handleDurationSelect}
|
||||
onClose={handleDurationClose}
|
||||
/>
|
||||
<PortalDropdown
|
||||
label="比例"
|
||||
value={videoAspectRatio as string}
|
||||
options={engineOptions.ratios}
|
||||
expanded={expandedEngine === "aspectRatio"}
|
||||
onToggle={() => setExpandedEngine(expandedEngine === "aspectRatio" ? null : "aspectRatio")}
|
||||
onSelect={(value) => { setVideoAspectRatio(value as AspectRatio); setExpandedEngine(null); }}
|
||||
onClose={() => setExpandedEngine(null)}
|
||||
/>
|
||||
<PortalDropdown
|
||||
label="分辨率"
|
||||
value={videoResolution as string}
|
||||
options={engineOptions.resolutions}
|
||||
expanded={expandedEngine === "resolution"}
|
||||
onToggle={() => setExpandedEngine(expandedEngine === "resolution" ? null : "resolution")}
|
||||
onSelect={(value) => { setVideoResolution(value as Resolution); setExpandedEngine(null); }}
|
||||
onClose={() => setExpandedEngine(null)}
|
||||
/>
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||
setExpandedEngine(null);
|
||||
}}
|
||||
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 as AspectRatio)}
|
||||
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 as 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>
|
||||
{currentOptionGroups.map((group, gi) => {
|
||||
const isExpanded = expandedGroup === group.name;
|
||||
const selected = selectedOptions[group.name];
|
||||
@@ -2622,63 +2845,50 @@ const GeneratePage: React.FC = () => {
|
||||
</>
|
||||
)}
|
||||
{mediaType == "image" && (
|
||||
<div
|
||||
style={{ position: "relative", display: "inline-block" }}
|
||||
>
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() =>
|
||||
setShowImageSettingsModal(!showImageSettingsModal)
|
||||
}
|
||||
onClick={() => {
|
||||
setShowImageSettingsModal(!showImageSettingsModal);
|
||||
setExpandedEngine(null);
|
||||
}}
|
||||
className="image-settings-trigger"
|
||||
style={{
|
||||
minWidth: 200,
|
||||
padding: "0px 14px",
|
||||
height: 32,
|
||||
borderRadius: 8,
|
||||
border: "1px solid #e2e8f0",
|
||||
backgroundColor: "#fff",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 10,
|
||||
transition: "all 0.2s",
|
||||
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',
|
||||
}}
|
||||
>
|
||||
<div style={{ textAlign: "left" }}>
|
||||
<Typography.Text
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
color: "#000000ff",
|
||||
}}
|
||||
>
|
||||
{selectedRatio === "auto" ? "智能" : selectedRatio}
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
style={{ fontSize: 11, color: "#000000ff" }}
|
||||
>
|
||||
{selectedResolution === "1K" ? "标清" : selectedResolution === "2K" ? "高清" : `${selectedResolution}`} | {width}×{height}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<CaretDownOutlined
|
||||
style={{ fontSize: 14, color: "#000000ff" }}
|
||||
/>
|
||||
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||
<span style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
color: '#64748b',
|
||||
}}>
|
||||
{selectedRatio === "auto" ? "智能" : selectedRatio} · {selectedResolution} · {width}×{height}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{showImageSettingsModal && (
|
||||
<div
|
||||
className="image-settings-popover"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: "calc(100% + 8px)",
|
||||
left: 0,
|
||||
width: 520,
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 12,
|
||||
boxShadow: "0 8px 32px rgba(0,0,0,0.12)",
|
||||
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: "1px solid rgba(231, 234, 240, 0.8)",
|
||||
border: 'none',
|
||||
zIndex: 9999,
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
|
||||
@@ -87,7 +87,7 @@ const GenerateConver: React.FC = () => {
|
||||
const [videoEngines, setVideoEngines] = useState<any[]>([]);
|
||||
const [engineId, setEngineId] = useState('');
|
||||
const [videoDuration, setVideoDuration] = useState(5);
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState('9:16');
|
||||
const [videoResolution, setVideoResolution] = useState('480p');
|
||||
const [showEngineModal, setShowEngineModal] = useState(false);
|
||||
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
||||
@@ -120,7 +120,7 @@ const GenerateConver: React.FC = () => {
|
||||
if (engines.length > 0) {
|
||||
const first = engines[0];
|
||||
setEngineId(first.id);
|
||||
setVideoAspectRatio(first.supportedRatios?.[0] || '16:9');
|
||||
setVideoAspectRatio(first.supportedRatios?.includes('9:16') ? '9:16' : first.supportedRatios?.[0] || '9:16');
|
||||
setVideoResolution(first.supportedResolutions?.[0] || '480p');
|
||||
setVideoDuration(first.supportedDurations?.[0] || 5);
|
||||
}
|
||||
@@ -128,6 +128,18 @@ const GenerateConver: React.FC = () => {
|
||||
calculateCredits().then((rules: any) => setCreditRules(Array.isArray(rules) ? rules : [])).catch(() => setCreditRules([]));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
||||
setShowEngineModal(false);
|
||||
setShowVideoSettingsModal(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
return () => document.removeEventListener('click', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const selectedEngine = videoEngines.find((item: any) => item.id === engineId);
|
||||
const selectedEngineSupportsImage = !imageUrl || supportsReferenceImage(selectedEngine);
|
||||
const estimatedCredits = (() => {
|
||||
@@ -1129,12 +1141,12 @@ const GenerateConver: React.FC = () => {
|
||||
{/* 原视频产品名称 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<p style={{ margin: 0, fontSize: 13, fontWeight: 500, color: '#475569', marginBottom: 8 }}>
|
||||
原视频产品名称
|
||||
原产品名称
|
||||
</p>
|
||||
<Input
|
||||
value={originalProductName}
|
||||
onChange={(e) => setOriginalProductName(e.target.value)}
|
||||
placeholder="请输入原视频产品名称"
|
||||
placeholder="请输入原产品名称"
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
height: 40,
|
||||
@@ -1195,7 +1207,7 @@ const GenerateConver: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom:16, display: 'grid', gap: 10 }}>
|
||||
<div style={{ marginBottom: 16, display: 'grid', gap: 10 }}>
|
||||
<p style={{ margin: 0, fontSize: 13, fontWeight: 600, color: '#475569' }}>视频生成参数(必选)</p>
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
@@ -1580,24 +1592,14 @@ const GenerateConver: React.FC = () => {
|
||||
>
|
||||
立即生成
|
||||
|
||||
|
||||
<span style={{ color: '#ffffffff', marginLeft: 8, fontSize: 13 }}>
|
||||
预估积分:{(estimatedCredits + optimizeHoldCredits)}
|
||||
</span>
|
||||
|
||||
</Button>
|
||||
|
||||
|
||||
</Tooltip>
|
||||
<div style={{textAlign:'center'}}>
|
||||
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
|
||||
视频积分:{estimatedCredits}
|
||||
</span>
|
||||
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
|
||||
+
|
||||
</span>
|
||||
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
|
||||
提词暂扣:{optimizeHoldCredits}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ function RemoveInfo() {
|
||||
const [videoEngines, setVideoEngines] = useState<any[]>([]);
|
||||
const [engineId, setEngineId] = useState('');
|
||||
const [videoDuration, setVideoDuration] = useState(5);
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState('16:9');
|
||||
const [videoAspectRatio, setVideoAspectRatio] = useState('9:16');
|
||||
const [videoResolution, setVideoResolution] = useState('480p');
|
||||
const [showEngineModal, setShowEngineModal] = useState(false);
|
||||
const [showVideoSettingsModal, setShowVideoSettingsModal] = useState(false);
|
||||
@@ -79,7 +79,7 @@ function RemoveInfo() {
|
||||
if (engines.length > 0) {
|
||||
const first = engines[0];
|
||||
setEngineId(first.id);
|
||||
setVideoAspectRatio(first.supportedRatios?.[0] || '16:9');
|
||||
setVideoAspectRatio(first.supportedRatios?.includes('9:16') ? '9:16' : first.supportedRatios?.[0] || '9:16');
|
||||
setVideoResolution(first.supportedResolutions?.[0] || '480p');
|
||||
setVideoDuration(first.supportedDurations?.[0] || 5);
|
||||
}
|
||||
@@ -195,6 +195,18 @@ function RemoveInfo() {
|
||||
};
|
||||
}, [refreshPageData]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.closest('.image-settings-popover') && !target.closest('.image-settings-trigger')) {
|
||||
setShowEngineModal(false);
|
||||
setShowVideoSettingsModal(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
return () => document.removeEventListener('click', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const processing = taskDetail?.analysisStatus === 'processing';
|
||||
if (!processing || !creatID) {
|
||||
@@ -207,8 +219,6 @@ function RemoveInfo() {
|
||||
}
|
||||
|
||||
if (!analysisStartedAtRef.current) analysisStartedAtRef.current = Date.now();
|
||||
const elapsed = Date.now() - analysisStartedAtRef.current;
|
||||
const delay = elapsed < 60_000 ? 3_000 : (elapsed < 600_000 ? 10_000 : 30_000);
|
||||
|
||||
const poll = async () => {
|
||||
if (document.visibilityState !== 'visible') return;
|
||||
@@ -220,11 +230,26 @@ function RemoveInfo() {
|
||||
}
|
||||
};
|
||||
|
||||
analysisPollingRef.current = window.setTimeout(() => void poll(), delay);
|
||||
const scheduleNextPoll = () => {
|
||||
if (!analysisPollingRef.current) return;
|
||||
const elapsed = Date.now() - (analysisStartedAtRef.current || Date.now());
|
||||
const delay = elapsed < 60_000 ? 3_000 : (elapsed < 600_000 ? 10_000 : 30_000);
|
||||
analysisPollingRef.current = window.setTimeout(async () => {
|
||||
await poll();
|
||||
scheduleNextPoll();
|
||||
}, delay);
|
||||
};
|
||||
|
||||
analysisPollingRef.current = window.setTimeout(async () => {
|
||||
await poll();
|
||||
scheduleNextPoll();
|
||||
}, 3_000);
|
||||
|
||||
const handleVisibility = () => {
|
||||
if (document.visibilityState === 'visible') void poll();
|
||||
};
|
||||
document.addEventListener('visibilitychange', handleVisibility);
|
||||
|
||||
return () => {
|
||||
if (analysisPollingRef.current) {
|
||||
window.clearTimeout(analysisPollingRef.current);
|
||||
@@ -356,7 +381,16 @@ function RemoveInfo() {
|
||||
//
|
||||
// }
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '创建失败,请重试');
|
||||
let errorMsg = '';
|
||||
if (err instanceof Error) {
|
||||
try {
|
||||
const errorData = JSON.parse(err.message);
|
||||
errorMsg = errorData.detail || err.message;
|
||||
} catch {
|
||||
errorMsg = err.message;
|
||||
}
|
||||
}
|
||||
message.error(errorMsg || '创建失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -878,7 +912,7 @@ function RemoveInfo() {
|
||||
<Button
|
||||
type="default"
|
||||
onClick={handleOpenTrimModal}
|
||||
disabled={!videoUrl || splitLoading}
|
||||
disabled={!videoUrl || splitLoading || taskDetail?.analysisStatus === 'processing'}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 52,
|
||||
@@ -911,7 +945,7 @@ function RemoveInfo() {
|
||||
<Button
|
||||
type="primary"
|
||||
loading={autoSplitLoading}
|
||||
disabled={autoSplitLoading}
|
||||
disabled={autoSplitLoading || taskDetail?.analysisStatus === 'processing'}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 52,
|
||||
@@ -1551,21 +1585,13 @@ function RemoveInfo() {
|
||||
>
|
||||
{loading ? '生成中...' : '手动生成'}
|
||||
|
||||
<span style={{ color: '#ffffffff', marginLeft: 8, fontSize: 13 }}>
|
||||
预估积分:{(estimatedCredits + optimizeHoldCredits)}
|
||||
</span>
|
||||
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
|
||||
视频积分:{estimatedCredits}
|
||||
</span>
|
||||
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
|
||||
+
|
||||
</span>
|
||||
<span style={{ color: '#000000ff', marginLeft: 8, fontSize: 13 }}>
|
||||
提词暂扣:{optimizeHoldCredits}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { Button, Modal, Input, Table, Upload, Popconfirm, Tag, Space, message } from 'antd';
|
||||
import { Button, Modal, Input, Table, Upload, Popconfirm, Tag, Space, App } from 'antd';
|
||||
import { FileTextOutlined, CloudUploadOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -13,6 +13,7 @@ const renderStatus = (status: string) => {
|
||||
};
|
||||
|
||||
export default function VideoFrameExtractor() {
|
||||
const { message } = App.useApp();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [videoUrl, setVideoUrl] = useState<string>('');
|
||||
@@ -32,7 +33,7 @@ export default function VideoFrameExtractor() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
const cleanupResources = useCallback(() => {
|
||||
if (videoUrl) {
|
||||
if (videoUrl && videoUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(videoUrl);
|
||||
}
|
||||
setVideoUrl('');
|
||||
@@ -112,24 +113,23 @@ export default function VideoFrameExtractor() {
|
||||
idempotency_key: `shot_${Date.now()}`,
|
||||
};
|
||||
|
||||
createShotReplication(params).then((res) => {
|
||||
// message.success('任务创建成功');
|
||||
getShotReplicationList(1, 20, searchKeyword).then((res) => {
|
||||
// console.log('获取列表成功', res.items[0].id);
|
||||
message.loading('创建中...', 3);
|
||||
|
||||
setTimeout(() => {
|
||||
navigate(`/removelens/${res.items[0].id}/removeinfo`);
|
||||
}, 3000);
|
||||
|
||||
// setTableData(res.items || []);
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
const createRes = await createShotReplication(params);
|
||||
const listRes = await getShotReplicationList(1, 20, searchKeyword);
|
||||
|
||||
message.loading('创建中...', 3);
|
||||
setTimeout(() => {
|
||||
navigate(`/removelens/${listRes.items[0].id}/removeinfo`);
|
||||
}, 3000);
|
||||
|
||||
cleanupResources();
|
||||
} catch (err) {
|
||||
message.error('任务创建失败,请重试');
|
||||
} catch (err: any) {
|
||||
let errorMsg = '';
|
||||
if (err instanceof Error) {
|
||||
errorMsg = err.message;
|
||||
} else if (typeof err === 'string') {
|
||||
errorMsg = err;
|
||||
}
|
||||
message.error(errorMsg || '任务创建失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user