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