拆镜复刻
This commit is contained in:
+110
-110
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-DTCz7Wbv.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BwL6llN9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
@@ -29,7 +29,7 @@ function isRecord(value: unknown): value is Record<string, JsonValue> {
|
||||
}
|
||||
|
||||
function ReadonlyBlock({ value }: { value: JsonValue }) {
|
||||
const text = stringifySchemaValue(value);
|
||||
const text = value === 'true' ? '是' : value === 'false' ? '否' : stringifySchemaValue(value);
|
||||
return shouldUseSchemaTextArea(value) ? (
|
||||
<TextArea value={text} rows={Math.min(6, Math.max(2, Math.ceil(text.length / 42)))} disabled style={{ borderRadius: 8, color: '#64748b' }} />
|
||||
) : (
|
||||
|
||||
@@ -466,7 +466,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
title={title}
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
width={980}
|
||||
width={Math.min(window.innerWidth - 40, 1400)}
|
||||
destroyOnHidden
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel} disabled={loading} style={{ borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)', color: '#6366f1' }}>
|
||||
@@ -538,12 +538,13 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(${Math.max(frames.length, 1)}, minmax(42px, 1fr))`,
|
||||
gridTemplateColumns: `repeat(${Math.max(frames.length, 1)}, minmax(0, 1fr))`,
|
||||
height: 86,
|
||||
overflow: 'hidden',
|
||||
borderRadius: 12,
|
||||
background: '#fff',
|
||||
position: 'relative',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
{frameLoading && frames.length === 0 ? (
|
||||
@@ -588,10 +589,10 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
<span
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 4,
|
||||
left: 0,
|
||||
bottom: 3,
|
||||
color: '#fff',
|
||||
fontSize: 11,
|
||||
color: '#ffffffff',
|
||||
fontSize: 10,
|
||||
textShadow: '0 1px 3px rgba(0,0,0,.7)',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
|
||||
import bg1 from '../assets/bg1.png';
|
||||
import bg2 from '../assets/bg2.png';
|
||||
import text from '../assets/testb.png';
|
||||
|
||||
|
||||
|
||||
import {
|
||||
@@ -540,7 +542,7 @@ const AIChatPage: React.FC = () => {
|
||||
setHeight(width);
|
||||
};
|
||||
|
||||
// ==================== 事件处理函数 ====================
|
||||
// ==================== 事处理函数 ====================
|
||||
|
||||
const handleNewChat = () => {
|
||||
// 生成唯一ID(使用时间戳)
|
||||
@@ -1006,7 +1008,7 @@ const AIChatPage: React.FC = () => {
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
paddingBottom: 24,
|
||||
// paddingBottom: 24,
|
||||
paddingRight: 8,
|
||||
// backgroundImage: `url(${bg1})`,
|
||||
// backgroundRepeat: 'no-repeat',
|
||||
@@ -1084,11 +1086,22 @@ const AIChatPage: React.FC = () => {
|
||||
boxShadow: '0 4px 20px rgba(99, 102, 241, 0.08), 0 1px 3px rgba(0,0,0,0.04)',
|
||||
position: 'relative',
|
||||
border: '1px solid rgba(99, 102, 241, 0.06)',
|
||||
|
||||
}}
|
||||
>
|
||||
<div style={{ margin: 4, fontSize: 11, color: '#999', textAlign: 'left', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
|
||||
<span>{msg.createdAt?.replace('T', ' ').split('.')[0]}</span>
|
||||
|
||||
<span style={{
|
||||
background: msg.genType === 'image' ? 'rgba(34, 197, 94, 0.12)' : 'rgba(249, 115, 22, 0.12)',
|
||||
color: msg.genType === 'image' ? '#22c55e' : '#f97316',
|
||||
padding: '2px 8px',
|
||||
borderRadius: 12,
|
||||
fontWeight: 500,
|
||||
fontSize: 11,
|
||||
marginLeft: 8
|
||||
}}>
|
||||
{msg.genType === 'image' ? '图片生成' : '视频生成'}
|
||||
</span>
|
||||
</div>
|
||||
{/* 删除按钮 - 右上角 */}
|
||||
<div style={{ position: 'absolute', top: 8, right: 8, zIndex: 100 }}>
|
||||
@@ -1197,174 +1210,78 @@ const AIChatPage: React.FC = () => {
|
||||
</div> */}
|
||||
|
||||
{/* 根据 status 显示不同内容 */}
|
||||
{/* 生成中 - 显示加载动画 */}
|
||||
{msg.status === 'generating' && (
|
||||
<div style={{
|
||||
height: 200,
|
||||
marginBottom: 10,
|
||||
marginTop: 12,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
background: 'linear-gradient(135deg, #e0e7ff 0%, #f3e8ff 50%, #e0e7ff 100%)',
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 12,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}>
|
||||
<div style={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
border: '2px solid #c7d2fe',
|
||||
borderTopColor: '#6366f1',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 1s linear infinite'
|
||||
}} />
|
||||
<span style={{ fontSize: 13, color: '#6366f1' }}>生成中...</span>
|
||||
</div>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.3) 50%, transparent 100%)',
|
||||
animation: 'shimmer 2s infinite'
|
||||
}} />
|
||||
</div>
|
||||
)}
|
||||
{/* 生成失败 - 显示失败提示 */}
|
||||
{msg.status === 'failed' && (
|
||||
<div style={{
|
||||
height: 200,
|
||||
marginBottom: 10,
|
||||
marginTop: 12,
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
backgroundColor: '#fafafa',
|
||||
border: '1px dashed #e2e8f0',
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 12
|
||||
}}>
|
||||
<div style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: '50%',
|
||||
background: '#fff2f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<WarningOutlined style={{ color: '#ff4d4f', fontSize: 22 }} />
|
||||
</div>
|
||||
<span style={{ fontSize: 14, color: '#94a3b8' }}>生成失败</span>
|
||||
{/* <span style={{ fontSize: 12, color: '#cbd5e1' }}>请重试</span> */}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* 已完成 - 显示媒体内容 */}
|
||||
{msg.status === 'completed' && (
|
||||
<div style={{ display: 'flex', gridTemplateColumns: 'repeat(auto-fill, minmax(150px, 1fr))', gap: 8, marginBottom: 10, marginTop: 12, width: '100%', }}>
|
||||
<div
|
||||
onClick={() => {
|
||||
setPreviewUrl(msg.genType === 'image' ? msg.imageUrl : msg.videoUrl);
|
||||
setPreviewType(msg.genType === 'image' ? 'image' : 'video');
|
||||
setPreviewVisible(true);
|
||||
}}
|
||||
style={{ width: '50%', cursor: 'pointer', overflow: 'hidden', borderRadius: 8, position: 'relative', height: 200 }}
|
||||
>
|
||||
{msg.genType === 'image' ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${msg.imageUrl}&w=300&p=50`}
|
||||
alt={msg.name}
|
||||
style={{ width: '100%', height: '100%', borderRadius: 8, objectFit: 'contain', backgroundColor: '#f1f5f9', transition: 'transform 0.2s' }}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
|
||||
/>
|
||||
{(msg.status === 'generating' || msg.status === 'failed' || msg.status === 'completed') && (
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 16, marginTop: 16, width: '100%', alignItems: 'flex-start',
|
||||
|
||||
}}>
|
||||
<div style={{ width: '50%', overflow: 'hidden', borderRadius: 12, position: 'relative', height: 220, border: '1px solid rgba(99, 102, 241, 0.1)', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.08)', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 100%)' }}>
|
||||
{msg.status === 'generating' ? (
|
||||
<>
|
||||
<div style={{ position: 'absolute', top: 12, left: 12, display: 'flex', alignItems: 'center', gap: 8, zIndex: 2 }}>
|
||||
{/* <div style={{ width: 20, height: 20, border: '2px solid #c7d2fe', borderTopColor: '#6366f1', borderRadius: '50%', animation: 'spin 1s linear infinite' }} /> */}
|
||||
</div>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.6) 50%, transparent 100%)', animation: 'shimmer 2s infinite' }} />
|
||||
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 64, height: 64, borderRadius: '50%', background: 'rgba(99, 102, 241, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 0 30px rgba(99, 102, 241, 0.2)' }}>
|
||||
<div style={{ width: 48, height: 48, border: '3px solid #c7d2fe', borderTopColor: '#6366f1', borderRadius: '50%', animation: 'spin 1s linear infinite' }} />
|
||||
</div>
|
||||
<span style={{ fontSize: 12, color: '#6366f1', fontWeight: 500 }}>生成中...</span>
|
||||
|
||||
{/* <div style={{ display: 'flex', gap: 4 }}>
|
||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#6366f1', animation: 'pulse 1.5s ease-in-out infinite' }} />
|
||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#818cf8', animation: 'pulse 1.5s ease-in-out 0.2s infinite' }} />
|
||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#a5b4fc', animation: 'pulse 1.5s ease-in-out 0.4s infinite' }} />
|
||||
</div> */}
|
||||
</div>
|
||||
</>
|
||||
) : msg.status === 'failed' ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12 }}>
|
||||
<div style={{ width: 48, height: 48, borderRadius: '50%', background: 'rgba(254, 226, 226, 0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<WarningOutlined style={{ color: '#ef4444', fontSize: 22 }} />
|
||||
</div>
|
||||
<span style={{ fontSize: 14, color: '#94a3b8' }}>生成失败</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${msg.videoCoverUrl}&w=300&p=50`}
|
||||
alt={msg.name}
|
||||
style={{ width: '100%', height: '100%', borderRadius: 8, objectFit: 'contain', backgroundColor: '#f1f5f9', transition: 'transform 0.2s' }}
|
||||
onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }}
|
||||
onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
|
||||
/>
|
||||
{/* 视频播放按钮 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 56,
|
||||
height: 56,
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'none',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
||||
}}>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff">
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
{msg.genType === 'image' ? (
|
||||
<img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${msg.imageUrl}&w=300&p=50`} alt={msg.name} style={{ width: '100%', height: '100%', borderRadius: 12, objectFit: 'contain', cursor: 'pointer', transition: 'transform 0.3s ease' }} onClick={() => { setPreviewUrl(msg.imageUrl); setPreviewType('image'); setPreviewVisible(true); }} onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }} />
|
||||
) : (
|
||||
<>
|
||||
<img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${msg.videoCoverUrl}&w=300&p=50`} alt={msg.name} style={{ width: '100%', height: '100%', borderRadius: 12, objectFit: 'contain', cursor: 'pointer', transition: 'transform 0.3s ease' }} onClick={() => { setPreviewUrl(msg.videoUrl); setPreviewType('video'); setPreviewVisible(true); }} onMouseEnter={(e) => { e.currentTarget.style.transform = 'scale(1.05)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }} />
|
||||
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 56, height: 56, background: 'rgba(99, 102, 241, 0.85)', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'none', boxShadow: '0 4px 20px rgba(99, 102, 241, 0.4)' }}>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff"><path d="M8 5v14l11-7z" /></svg>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ width: '50%', }}>
|
||||
<p style={{ marginBottom: 20 ,height: 100, overflow: 'auto', padding: 0, margin: 0 }}>
|
||||
{msg.originalPrompt}
|
||||
</p>
|
||||
<div style={{fontSize: 11, color: '#999', textAlign: 'left', display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
|
||||
{/* 引擎标签 */}
|
||||
<span >
|
||||
{/* <SettingsOutlined style={{ fontSize: 12 }} /> */}
|
||||
{msg.engineSnapshot.name}
|
||||
</span>
|
||||
{/* 参数标签 */}
|
||||
<span >
|
||||
{/* <LayoutGridOutlined style={{ fontSize: 12 }} /> */}
|
||||
{msg.genType === 'image'
|
||||
? `${msg.imageProportion || ''} · ${msg.imagePx || ''} · ${msg.imageSize || ''}`
|
||||
: `${msg.duration || ''}s · ${msg.aspectRatio || ''} · ${msg.resolution || ''}`
|
||||
}
|
||||
</span>
|
||||
<span style={{ marginLeft: 8 }}>消耗积分:{msg.creditsCost}</span>
|
||||
{msg.mediaReferences && msg.mediaReferences.length > 0 && (
|
||||
<span
|
||||
style={{ marginLeft: 20, color: '#6366f1', cursor: 'pointer' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
setAttachmentPopupPosition({
|
||||
x: rect.left,
|
||||
y: rect.top - 10
|
||||
});
|
||||
setAttachmentPopupMessageId(msg.id);
|
||||
setAttachmentPopupVisible(true);
|
||||
}}
|
||||
>
|
||||
附件详情
|
||||
</span>
|
||||
|
||||
)}
|
||||
<div style={{ width: '50%', display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
<div style={{ background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 12, padding: 12, border: '1px solid rgba(99, 102, 241, 0.08)', flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<p style={{width: '100%' ,height: 150, overflow: 'auto', padding: 0, marginTop: 0, marginBottom: 0, lineHeight: 2, color: '#475569', fontSize: 13 }}>{msg.originalPrompt}</p>
|
||||
</div>
|
||||
<div style={{ fontSize: 12, color: '#64748b', textAlign: 'left', display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center', justifyContent: 'space-between',marginBottom: 12 }}>
|
||||
<span style={{
|
||||
// background: 'rgba(99, 102, 241, 0.08)',
|
||||
borderRadius: 16, color: '#6366f1', fontWeight: 500 }}>{msg.engineSnapshot.name}</span>
|
||||
<span style={{
|
||||
// background: 'rgba(99, 102, 241, 0.08)',
|
||||
borderRadius: 16, color: '#64748b' }}>{msg.genType === 'image' ? `${msg.imageProportion || ''} · ${msg.imagePx || ''} · ${msg.imageSize || ''}` : `${msg.duration || ''}s · ${msg.aspectRatio || ''} · ${msg.resolution || ''}`}</span>
|
||||
<span style={{
|
||||
// background: 'rgba(239, 68, 68, 0.08)',
|
||||
borderRadius: 16, color: '#ef4444', fontWeight: 500 }}>消耗积分:{msg.creditsCost}</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)}
|
||||
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
{msg.mediaReferences && msg.mediaReferences.length > 0 && (
|
||||
<span style={{ padding: '4px 12px', borderRadius: 16, color: '#6366f1', cursor: 'pointer', fontWeight: 500, border: '1px solid rgba(99, 102, 241, 0.2)', background: 'rgba(99, 102, 241, 0.04)' }} onClick={(e) => { e.stopPropagation(); const target = e.currentTarget as HTMLElement; const rect = target.getBoundingClientRect(); setAttachmentPopupPosition({ x: rect.left, y: rect.top - 10 }); setAttachmentPopupMessageId(msg.id); setAttachmentPopupVisible(true); }}>附件详情</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -80,13 +80,27 @@ function InitialInfo() {
|
||||
|
||||
// 当apiSteps更新时,逆向遍历找到第一个已完成或失败的步骤并展开
|
||||
useEffect(() => {
|
||||
const completedFailedKeys = new Set<string>();
|
||||
|
||||
activeKey.forEach(key => {
|
||||
const step = steps.find(s => String(s.childId) === key);
|
||||
if (step && (step.status === 'completed' || step.status === 'failed')) {
|
||||
completedFailedKeys.add(key);
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = steps.length - 1; i >= 0; i--) {
|
||||
if (steps[i].status === 'completed' || steps[i].status === 'failed') {
|
||||
setActiveKey([String(steps[i].childId)]);
|
||||
return;
|
||||
completedFailedKeys.add(String(steps[i].childId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
setActiveKey([]);
|
||||
|
||||
if (completedFailedKeys.size > 0) {
|
||||
setActiveKey(Array.from(completedFailedKeys));
|
||||
} else {
|
||||
setActiveKey([]);
|
||||
}
|
||||
}, [apiSteps]);
|
||||
|
||||
// console.log('steps', steps);
|
||||
@@ -198,7 +212,7 @@ function InitialInfo() {
|
||||
if (fifthStep && fifthStep.status !== 'completed' && fifthStep.status !== 'failed') {
|
||||
// 第五步未完成,启动轮询
|
||||
if (!pollingTimer) {
|
||||
const timer = setInterval(pollTaskDetail, 30000);
|
||||
const timer = setInterval(pollTaskDetail, 15000);
|
||||
setPollingTimer(timer);
|
||||
} else {
|
||||
}
|
||||
@@ -551,7 +565,7 @@ function InitialInfo() {
|
||||
className="replication-form"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: '400px',
|
||||
minWidth: '450px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
|
||||
@@ -422,7 +422,7 @@ const GenerateConver: React.FC = () => {
|
||||
className="replication-container"
|
||||
style={{
|
||||
margin: '-24px -32px -32px',
|
||||
height: 'calc(100vh - 32px)',
|
||||
height: 'calc(100vh - 34px)',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'stretch',
|
||||
@@ -431,6 +431,7 @@ const GenerateConver: React.FC = () => {
|
||||
position: 'relative',
|
||||
// padding: '20px 32px',
|
||||
boxSizing: 'border-box',
|
||||
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1411,7 +1412,7 @@ const GenerateConver: React.FC = () => {
|
||||
},
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
scroll={{ y: 380 }}
|
||||
scroll={{ y: 350 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -138,7 +138,6 @@ function RemoveInfo() {
|
||||
useEffect(() => {
|
||||
refreshPageData();
|
||||
|
||||
// 组件卸载时清除轮询
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
@@ -147,6 +146,34 @@ function RemoveInfo() {
|
||||
};
|
||||
}, [refreshPageData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!taskDetail) return;
|
||||
|
||||
if (taskDetail.analysisStatus === 'processing') {
|
||||
console.log("开始轮询");
|
||||
|
||||
pollingIntervalRef.current = window.setInterval(async () => {
|
||||
try {
|
||||
const res = await getShotReplicationDetail(creatID);
|
||||
setTaskDetail(res);
|
||||
} catch {
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [taskDetail?.analysisStatus, creatID]);
|
||||
|
||||
const handleGenerate = (segmentId: string, segmentName: string) => {
|
||||
setCurrentSegment(segmentId);
|
||||
setCurrentSegmentName(segmentName);
|
||||
@@ -197,19 +224,45 @@ function RemoveInfo() {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const params = {
|
||||
|
||||
const params = {
|
||||
target_project_name: productName.trim(),
|
||||
core_content_point: productSellingPoint.trim(),
|
||||
material_image_url: buildAssetUrl(productImage),
|
||||
idempotency_key: `replication_${Date.now()}`,
|
||||
};
|
||||
console.log(currentSegment,params);
|
||||
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await removeCreate(currentSegment, params);
|
||||
message.success('视频生成任务创建成功');
|
||||
handleCloseDrawer();
|
||||
await fetchSegments();
|
||||
Removelist(creatID).then((res: any) => {
|
||||
console.log('123123123123',res);
|
||||
|
||||
const targetItem = res.items.find((item: any) => item.id === currentSegment);
|
||||
console.log(targetItem);
|
||||
message.loading('创建中...', 3);
|
||||
|
||||
setTimeout(() => {
|
||||
navigate(`/removelens/${targetItem.moduleProjectId}/removefenbu`);
|
||||
}, 3000);
|
||||
});
|
||||
// fetchSegments().then((res: any) => {
|
||||
// console.log('123123123123',res);
|
||||
|
||||
// // const targetItem = res.items.find((item: any) => item.id === currentSegment);
|
||||
// // console.log(targetItem);
|
||||
// });
|
||||
|
||||
|
||||
|
||||
// if (targetItem?.moduleProjectId) {
|
||||
// message.loading('跳转中...', 1.5);
|
||||
//
|
||||
// }
|
||||
} catch (err: any) {
|
||||
message.error(err?.message || '创建失败,请重试');
|
||||
} finally {
|
||||
@@ -518,7 +571,7 @@ function RemoveInfo() {
|
||||
style={{
|
||||
margin: '-24px -32px -32px',
|
||||
borderRadius: 20,
|
||||
minHeight: 'calc(100vh - 32px)',
|
||||
minHeight: 'calc(100vh - 34px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
@@ -851,7 +904,7 @@ function RemoveInfo() {
|
||||
styles={{
|
||||
body: { padding: '24px', background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
|
||||
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
|
||||
content: { borderRadius: '20px 0 0 20px', overflow: 'hidden' },
|
||||
section: { borderRadius: '20px 0 0 20px', overflow: 'hidden' },
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, Modal, Input, Table, Upload, Popconfirm, message } from 'antd';
|
||||
import { FileTextOutlined, CloudUploadOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { uploadVideo, createShotReplication, getShotReplicationList } from '../api';
|
||||
import bg1 from '../assets/bg1.png';
|
||||
|
||||
export default function VideoFrameExtractor() {
|
||||
const navigate = useNavigate();
|
||||
@@ -43,6 +44,24 @@ export default function VideoFrameExtractor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const duration = await new Promise<number>((resolve) => {
|
||||
const video = document.createElement('video');
|
||||
video.preload = 'metadata';
|
||||
video.onloadedmetadata = () => {
|
||||
resolve(video.duration);
|
||||
URL.revokeObjectURL(video.src);
|
||||
};
|
||||
video.onerror = () => {
|
||||
resolve(0);
|
||||
};
|
||||
video.src = URL.createObjectURL(file);
|
||||
});
|
||||
|
||||
if (duration > 60) {
|
||||
setError('视频时长不能超过 1分钟');
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
setLoading(true);
|
||||
|
||||
@@ -135,11 +154,15 @@ export default function VideoFrameExtractor() {
|
||||
style={{
|
||||
margin: '-24px -32px -32px',
|
||||
borderRadius: 20,
|
||||
minHeight: 'calc(100vh - 32px)',
|
||||
minHeight: 'calc(100vh - 34px)',
|
||||
overflow: 'auto',
|
||||
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
|
||||
position: 'relative',
|
||||
padding: '20px 32px',
|
||||
backgroundImage: `url(${bg1})`,
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: '100% 100%',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -285,7 +308,7 @@ export default function VideoFrameExtractor() {
|
||||
点击或拖拽到此上传视频文件
|
||||
</p>
|
||||
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
|
||||
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频
|
||||
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 1 分钟内的视频
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -80,13 +80,27 @@ function InitialInfo() {
|
||||
|
||||
// 当apiSteps更新时,逆向遍历找到第一个已完成或失败的步骤并展开
|
||||
useEffect(() => {
|
||||
const completedFailedKeys = new Set<string>();
|
||||
|
||||
activeKey.forEach(key => {
|
||||
const step = steps.find(s => String(s.childId) === key);
|
||||
if (step && (step.status === 'completed' || step.status === 'failed')) {
|
||||
completedFailedKeys.add(key);
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = steps.length - 1; i >= 0; i--) {
|
||||
if (steps[i].status === 'completed' || steps[i].status === 'failed') {
|
||||
setActiveKey([String(steps[i].childId)]);
|
||||
return;
|
||||
completedFailedKeys.add(String(steps[i].childId));
|
||||
break;
|
||||
}
|
||||
}
|
||||
setActiveKey([]);
|
||||
|
||||
if (completedFailedKeys.size > 0) {
|
||||
setActiveKey(Array.from(completedFailedKeys));
|
||||
} else {
|
||||
setActiveKey([]);
|
||||
}
|
||||
}, [apiSteps]);
|
||||
|
||||
|
||||
@@ -441,7 +455,7 @@ function InitialInfo() {
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/removelens')}
|
||||
onClick={() => window.history.back()}
|
||||
style={{
|
||||
color: '#64748b',
|
||||
borderRadius: 10,
|
||||
@@ -552,7 +566,7 @@ function InitialInfo() {
|
||||
className="replication-form"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: '400px',
|
||||
minWidth: '450px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
|
||||
Reference in New Issue
Block a user