ai
This commit is contained in:
+95
-91
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-Bof1g4Ez.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-ByI-1GQy.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -113,6 +113,20 @@ export async function optimizePrompt(
|
||||
image_px: params.image_px || null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function uploadAudio(file: File): Promise<{ url: string; filename: string }> {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
const token = localStorage.getItem('auth_token');
|
||||
const res = await fetch(`${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}/api/generation-records/upload-audio`, {
|
||||
method: 'POST',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
body: form,
|
||||
});
|
||||
if (!res.ok) throw new Error('图片上传失败');
|
||||
const data = await res.json();
|
||||
return { url: data.url, filename: data.filename };
|
||||
}
|
||||
export async function uploadImage(file: File): Promise<{ url: string; filename: string }> {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
|
||||
@@ -62,7 +62,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuthStore } from '../../store/useAuthStore';
|
||||
import { getMenuConfigs, getRechargePackages, getPaymentMethods, createRechargeOrder, getPaymentOrder, cancelPaymentOrder, getSiteInfo, getUnreadCount, createContactRequest, getUser } from '../../api';
|
||||
import { getMenuConfigs, getRechargePackages, getPaymentMethods, createRechargeOrder, getPaymentOrder, cancelPaymentOrder, getSiteInfo, getUnreadCount, createContactRequest, getUser, changePassword } from '../../api';
|
||||
import NotificationPopup from '../NotificationPopup';
|
||||
import './AppLayout.css';
|
||||
|
||||
@@ -619,10 +619,18 @@ const AppLayout: React.FC = () => {
|
||||
|
||||
const handleChangePwd = async () => {
|
||||
try {
|
||||
await pwdForm.validateFields();
|
||||
message.success('密码修改成功(演示)');
|
||||
setPwdModalOpen(false); pwdForm.resetFields();
|
||||
} catch { }
|
||||
const values = await pwdForm.validateFields();
|
||||
await changePassword(values.oldPwd, values.newPwd);
|
||||
message.success('密码修改成功');
|
||||
setPwdModalOpen(false);
|
||||
pwdForm.resetFields();
|
||||
} catch (error: any) {
|
||||
if (error?.response?.data?.detail) {
|
||||
message.error(error.response.data.detail);
|
||||
} else if (error?.message) {
|
||||
message.error(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ import text from '../assets/testb.png';
|
||||
|
||||
|
||||
import {
|
||||
getParameters, createGenerationTask, getgen_list, getEngine, uploadImage,
|
||||
getParameters, createGenerationTask, getgen_list, getEngine, uploadImage,uploadAudio,
|
||||
uploadVideo, getCreditRatios, deleteHistory, calculateCredits
|
||||
} from '../api';
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
DownloadOutlined,
|
||||
ReloadOutlined,
|
||||
AudioOutlined,
|
||||
PauseOutlined,
|
||||
|
||||
} from '@ant-design/icons';
|
||||
|
||||
@@ -227,6 +228,38 @@ const AIChatPage: React.FC = () => {
|
||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (previewVisible && previewType === 'video') {
|
||||
const playVideo = () => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
if (videoRef.current) {
|
||||
if (videoRef.current.readyState >= 2) {
|
||||
playVideo();
|
||||
} else {
|
||||
videoRef.current.addEventListener('loadedmetadata', playVideo);
|
||||
}
|
||||
}
|
||||
|
||||
const timer = setTimeout(playVideo, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
if (videoRef.current) {
|
||||
videoRef.current.removeEventListener('loadedmetadata', playVideo);
|
||||
videoRef.current.pause();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}
|
||||
}, [previewVisible, previewType]);
|
||||
|
||||
// 提示词展开状态
|
||||
const [expandedPrompts, setExpandedPrompts] = useState<Set<string>>(new Set());
|
||||
|
||||
@@ -278,6 +311,8 @@ const AIChatPage: React.FC = () => {
|
||||
const [attachmentPreviewType, setAttachmentPreviewType] = useState<'image' | 'video' | 'audio'>('image');
|
||||
const [attachmentPreviewName, setAttachmentPreviewName] = useState<string>('');
|
||||
const attachmentPreviewVideoRef = useRef<HTMLVideoElement>(null);
|
||||
const [playingAudioUrl, setPlayingAudioUrl] = useState<string | null>(null);
|
||||
const [audioProgress, setAudioProgress] = useState(0);
|
||||
|
||||
// 附件详情悬浮窗状态
|
||||
const [attachmentPopupVisible, setAttachmentPopupVisible] = useState<boolean>(false);
|
||||
@@ -831,6 +866,9 @@ const AIChatPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(mediaReferences);
|
||||
|
||||
// 创建用户消息对象
|
||||
const newMessage: Message = {
|
||||
id: '',
|
||||
@@ -994,6 +1032,35 @@ const AIChatPage: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const getAudioDuration = (file: File): Promise<number> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const audio = document.createElement('audio');
|
||||
audio.preload = 'metadata';
|
||||
audio.onloadedmetadata = () => {
|
||||
URL.revokeObjectURL(audio.src);
|
||||
resolve(audio.duration);
|
||||
};
|
||||
audio.onerror = () => {
|
||||
URL.revokeObjectURL(audio.src);
|
||||
reject(new Error('无法获取音频时长'));
|
||||
};
|
||||
audio.src = URL.createObjectURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAudioPlay = (url: string) => {
|
||||
const audioUrl = url.startsWith('http') ? url : `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${url}`;
|
||||
if (playingAudioUrl === audioUrl) {
|
||||
const audio = document.getElementById('audio-player') as HTMLAudioElement;
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
}
|
||||
setPlayingAudioUrl(null);
|
||||
} else {
|
||||
setPlayingAudioUrl(audioUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
@@ -1096,16 +1163,24 @@ const AIChatPage: React.FC = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentEngineList = mediaType === 'image' ? enginesele.image : enginesele.video;
|
||||
const currentEngine = currentEngineList?.find((e: any) => e.id === countType);
|
||||
const maxImage = currentEngine?.maxImageCount ?? 4;
|
||||
const maxVideo = currentEngine?.maxVideoCount ?? 1;
|
||||
if (isAudio) {
|
||||
const audioExt = file.name.split('.').pop()?.toLowerCase();
|
||||
if (!['wav', 'mp3'].includes(audioExt || '')) {
|
||||
message.error('音频仅支持wav和mp3格式');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaType === 'image' && (isVideo || isAudio)) {
|
||||
message.error('图片模式仅支持上传图片');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAudio && mediaType !== 'video') {
|
||||
message.error('仅视频模式支持上传音频');
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxMB = isVideo ? 100 : (isAudio ? 50 : 10);
|
||||
const fileTypeText = isVideo ? '视频' : (isAudio ? '音频' : '图片');
|
||||
if (file.size / 1024 / 1024 > maxMB) {
|
||||
@@ -1140,7 +1215,14 @@ const AIChatPage: React.FC = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const audioCount = currentMedia.filter((m) => m.type === 'audio').length;
|
||||
if (isAudio && audioCount >= maxAudio) {
|
||||
message.error(`该引擎最多上传${maxAudio}个音频`);
|
||||
return false;
|
||||
}
|
||||
|
||||
let videoDuration = 0;
|
||||
let audioDuration = 0;
|
||||
if (isVideo) {
|
||||
try {
|
||||
videoDuration = await getVideoDuration(file);
|
||||
@@ -1168,10 +1250,31 @@ const AIChatPage: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isAudio) {
|
||||
try {
|
||||
audioDuration = await getAudioDuration(file);
|
||||
if (audioDuration < 2) {
|
||||
message.error('音频素材最短不能少于 2 秒');
|
||||
return false;
|
||||
}
|
||||
const existingAudioDuration = currentMedia
|
||||
.filter((m) => m.type === 'audio')
|
||||
.reduce((sum, m) => sum + (m.duration || 0), 0);
|
||||
if (existingAudioDuration + audioDuration > 15) {
|
||||
message.error(`所有音频素材总时长不能超过 15 秒,当前 ${(existingAudioDuration + audioDuration).toFixed(1)} 秒`);
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
message.error('无法获取音频信息,请检查文件是否损坏');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
const uploadFn = isImage ? uploadImage : (isAudio ? uploadImage : uploadVideo);
|
||||
const uploadFn = isImage ? uploadImage : (isAudio ? uploadAudio : uploadVideo);
|
||||
const res = await uploadFn(file);
|
||||
const mediaType: 'image' | 'video' | 'audio' = isImage ? 'image' : (isAudio ? 'audio' : 'video');
|
||||
const newList = [...currentMedia, {
|
||||
@@ -1179,7 +1282,8 @@ const AIChatPage: React.FC = () => {
|
||||
type: mediaType,
|
||||
url: res.url,
|
||||
label: '',
|
||||
...((isVideo || isAudio) && { duration: videoDuration }),
|
||||
...(isVideo && { duration: videoDuration }),
|
||||
...(isAudio && { duration: audioDuration }),
|
||||
}];
|
||||
const labels = generateMediaLabels(newList);
|
||||
setCurrentMedia(newList.map((m, i) => ({ ...m, label: labels[i] })));
|
||||
@@ -1287,10 +1391,11 @@ const AIChatPage: React.FC = () => {
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
const getAttachmentMediaType = (ref: any): 'image' | 'video' => {
|
||||
const getAttachmentMediaType = (ref: any): 'image' | 'video' | 'audio' => {
|
||||
const rawType = String(ref?.type || '').toLowerCase();
|
||||
const rawUrl = String(ref?.url || ref?.name || '').toLowerCase();
|
||||
if (rawType.includes('video') || /\.(mp4|mov|avi|webm|m4v)(\?|$)/.test(rawUrl)) return 'video';
|
||||
if (rawType.includes('audio') || /\.(mp3|wav|ogg|aac|m4a)(\?|$)/.test(rawUrl)) return 'audio';
|
||||
return 'image';
|
||||
};
|
||||
|
||||
@@ -1320,7 +1425,8 @@ const AIChatPage: React.FC = () => {
|
||||
if (!ref?.url) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = buildAttachmentDownloadUrl(ref.url);
|
||||
link.download = ref.name || (getAttachmentMediaType(ref) === 'image' ? 'image.png' : 'video.mp4');
|
||||
const refType = getAttachmentMediaType(ref);
|
||||
link.download = ref.name || (refType === 'image' ? 'image.png' : refType === 'video' ? 'video.mp4' : 'audio.mp3');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -1348,6 +1454,10 @@ const AIChatPage: React.FC = () => {
|
||||
? '首帧必传 · 尾帧可选 · 适合首尾画面连贯过渡'
|
||||
: '多素材参考 · 支持图片 / 视频 / 音频,输入 @ 可快速引用素材';
|
||||
|
||||
const maxImage = maxImageCount;
|
||||
const maxVideo = maxVideoCount;
|
||||
const maxAudio = currentEngine?.maxAudioCount ?? 1;
|
||||
|
||||
return (
|
||||
<Layout className="ai-create-page" style={{
|
||||
margin: '-24px -32px -32px',
|
||||
@@ -1357,6 +1467,14 @@ const AIChatPage: React.FC = () => {
|
||||
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
{/* 隐藏的音频播放器 */}
|
||||
<audio
|
||||
id="audio-player"
|
||||
src={playingAudioUrl || ''}
|
||||
autoPlay
|
||||
onEnded={() => setPlayingAudioUrl(null)}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
||||
{false && (
|
||||
<Sider
|
||||
@@ -1390,6 +1508,7 @@ const AIChatPage: React.FC = () => {
|
||||
</Button>
|
||||
)}
|
||||
|
||||
|
||||
{!collapsed && conversations.length > 0 && (
|
||||
<div style={{ maxHeight: 'calc(100vh - 120px)', overflowY: 'auto' }}>
|
||||
{conversations.map((conversation) => (
|
||||
@@ -1527,7 +1646,6 @@ const AIChatPage: React.FC = () => {
|
||||
paddingBottom: 18,
|
||||
// background: 'linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0))',
|
||||
background: '#fff',
|
||||
|
||||
// borderRadius: 22,
|
||||
}}
|
||||
>
|
||||
@@ -1961,7 +2079,7 @@ const AIChatPage: React.FC = () => {
|
||||
<div className="ai-reference-tray-scroll" style={{ display: 'flex', gap: 10, overflowX: 'auto', padding: '2px 2px 4px' }}>
|
||||
{attachmentRefs.map((ref: any, idx: number) => {
|
||||
const refType = getAttachmentMediaType(ref);
|
||||
const label = ref.label || (ref.role === 'first_frame' ? '首帧' : ref.role === 'last_frame' ? '尾帧' : `${refType === 'image' ? '图片' : '视频'}${idx + 1}`);
|
||||
const label = ref.label || (ref.role === 'first_frame' ? '首帧' : ref.role === 'last_frame' ? '尾帧' : `${refType === 'image' ? '图片' : refType === 'video' ? '视频' : '音频'}${idx + 1}`);
|
||||
return (
|
||||
<div
|
||||
key={`${ref.url || ref.name || idx}-${idx}`}
|
||||
@@ -1977,10 +2095,15 @@ const AIChatPage: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={() => {
|
||||
openAttachmentPreview(ref);
|
||||
setAttachmentPopupVisible(false);
|
||||
setAttachmentPopupMessageId(null);
|
||||
onClick={(e) => {
|
||||
if (refType === 'audio') {
|
||||
e.stopPropagation();
|
||||
handleAudioPlay(ref.url);
|
||||
} else {
|
||||
openAttachmentPreview(ref);
|
||||
setAttachmentPopupVisible(false);
|
||||
setAttachmentPopupMessageId(null);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
position: 'relative',
|
||||
@@ -1999,7 +2122,7 @@ const AIChatPage: React.FC = () => {
|
||||
alt={ref.name || label}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||
/>
|
||||
) : (
|
||||
) : refType === 'video' ? (
|
||||
<>
|
||||
<video
|
||||
src={buildAttachmentAssetUrl(ref.url)}
|
||||
@@ -2013,6 +2136,14 @@ const AIChatPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)' }}>
|
||||
{playingAudioUrl === (ref.url.startsWith('http') ? ref.url : `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${ref.url}`) ? (
|
||||
<PauseOutlined style={{ fontSize: 24, color: '#fff' }} />
|
||||
) : (
|
||||
<AudioOutlined style={{ fontSize: 24, color: '#fff' }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* <span style={{ position: 'absolute', left: 6, top: 6, padding: '2px 6px', borderRadius: 999, background: 'rgba(255,255,255,0.92)', color: '#8b5cf6', fontSize: 10, fontWeight: 800, boxShadow: '0 4px 10px rgba(31, 41, 55, 0.08)' }}>
|
||||
{label}
|
||||
@@ -2047,7 +2178,7 @@ const AIChatPage: React.FC = () => {
|
||||
>
|
||||
查看
|
||||
</button>
|
||||
<button
|
||||
{/* <button
|
||||
onClick={(e) => downloadAttachmentRef(ref, e)}
|
||||
title="下载"
|
||||
style={{
|
||||
@@ -2067,7 +2198,7 @@ const AIChatPage: React.FC = () => {
|
||||
onMouseLeave={(e) => { e.currentTarget.style.background = '#FFFFFF'; e.currentTarget.style.color = '#667085'; }}
|
||||
>
|
||||
<DownloadOutlined style={{ fontSize: 12 }} />
|
||||
</button>
|
||||
</button> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -2092,8 +2223,12 @@ const AIChatPage: React.FC = () => {
|
||||
backdropFilter: 'blur(24px)',
|
||||
}}
|
||||
>
|
||||
{/* <div>12312</div> */}
|
||||
|
||||
|
||||
{/* 上方输入布局 */}
|
||||
<div style={{ display: 'flex', gap: isFirstLastFrameComposer ? 18 : 18, alignItems: 'flex-end', marginBottom: 14 }}>
|
||||
|
||||
{/* 左侧附件区域 */}
|
||||
<div style={{ width: isFirstLastFrameComposer ? 220 : 70, minWidth: isFirstLastFrameComposer ? 220 : 70, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-start', paddingTop: 2, gap: 8 }}>
|
||||
|
||||
@@ -2223,7 +2358,9 @@ const AIChatPage: React.FC = () => {
|
||||
>
|
||||
<Tooltip title={mediaType === 'image'
|
||||
? `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount}`
|
||||
: `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount},视频${currentMedia.filter(m => m.type === 'video').length}/${maxVideoCount}`
|
||||
: `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount},
|
||||
视频${currentMedia.filter(m => m.type === 'video').length}/${maxVideoCount}${maxAudio > 0 ? `,
|
||||
音频${currentMedia.filter(m => m.type === 'audio').length}/${maxAudio}` : ''}`
|
||||
}>
|
||||
<div
|
||||
style={{
|
||||
@@ -2319,15 +2456,17 @@ const AIChatPage: React.FC = () => {
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
onClick={() => {
|
||||
setAttachmentPreviewUrl(media.url);
|
||||
setAttachmentPreviewType('audio');
|
||||
setAttachmentPreviewName(media.name);
|
||||
setAttachmentPreviewVisible(true);
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleAudioPlay(media.url);
|
||||
}}
|
||||
style={{ width: 52, height: 60, objectFit: 'cover', borderRadius: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.98)', boxShadow: '0 4px 12px rgba(31,41,55,0.15)', background: 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
<AudioOutlined style={{ fontSize: 20, color: '#fff' }} />
|
||||
{playingAudioUrl === (media.url.startsWith('http') ? media.url : `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${media.url}`) ? (
|
||||
<PauseOutlined style={{ fontSize: 20, color: '#fff' }} />
|
||||
) : (
|
||||
<AudioOutlined style={{ fontSize: 20, color: '#fff' }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* 右上角删除按钮 */}
|
||||
@@ -2378,7 +2517,9 @@ const AIChatPage: React.FC = () => {
|
||||
>
|
||||
<Tooltip title={mediaType === 'image'
|
||||
? `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount}`
|
||||
: `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount},视频${currentMedia.filter(m => m.type === 'video').length}/${maxVideoCount}`
|
||||
: `图片${currentMedia.filter(m => m.type === 'image').length}/${maxImageCount},
|
||||
视频${currentMedia.filter(m => m.type === 'video').length}/${maxVideoCount}${maxAudio > 0 ? `,
|
||||
音频${currentMedia.filter(m => m.type === 'audio').length}/${maxAudio}` : ''}`
|
||||
}>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -771,7 +771,8 @@ const HomePage: React.FC = () => {
|
||||
|
||||
|
||||
{/* ========== 素材案例区域 ========== */}
|
||||
<div className="animate-fadeInUp" style={{
|
||||
{caseAssets.length > 0 && (
|
||||
<div className="animate-fadeInUp" style={{
|
||||
padding: '24px 28px',
|
||||
borderRadius: 16,
|
||||
background: '#fff',
|
||||
@@ -901,6 +902,7 @@ const HomePage: React.FC = () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ========== 预览弹窗 ========== */}
|
||||
<Modal
|
||||
|
||||
@@ -62,6 +62,71 @@ function InitialInfo() {
|
||||
// 当前展开的步骤
|
||||
const [activeKey, setActiveKey] = useState<string[]>([]);
|
||||
|
||||
// 预览相关状态
|
||||
const [previewVisible, setPreviewVisible] = useState<boolean>(false);
|
||||
const [previewUrl, setPreviewUrl] = useState<string>('');
|
||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (previewVisible && previewType === 'video') {
|
||||
const playVideo = () => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
if (videoRef.current) {
|
||||
if (videoRef.current.readyState >= 2) {
|
||||
playVideo();
|
||||
} else {
|
||||
videoRef.current.addEventListener('loadedmetadata', playVideo);
|
||||
}
|
||||
}
|
||||
|
||||
const timer = setTimeout(playVideo, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
if (videoRef.current) {
|
||||
videoRef.current.removeEventListener('loadedmetadata', playVideo);
|
||||
videoRef.current.pause();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}
|
||||
}, [previewVisible, previewType]);
|
||||
|
||||
const openPreview = (url: string, type: 'image' | 'video') => {
|
||||
console.log(url, type);
|
||||
setPreviewUrl(url);
|
||||
setPreviewType(type);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
const handleClosePreview = () => {
|
||||
setPreviewVisible(false);
|
||||
setPreviewUrl('');
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!previewUrl) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewUrl}&download=1`;
|
||||
link.download = previewType === 'image' ? 'image.png' : 'video.mp4';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
//
|
||||
const baseSteps = [
|
||||
{ id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
|
||||
@@ -347,9 +412,12 @@ function InitialInfo() {
|
||||
image_size: "2K"
|
||||
}
|
||||
gettwo(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||
message.info('正在生成图片,请稍候...');
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
}
|
||||
const newcreateimage = () => {
|
||||
@@ -362,9 +430,13 @@ function InitialInfo() {
|
||||
}
|
||||
|
||||
gettwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
||||
message.info('正在生成图片,请稍候...');
|
||||
|
||||
refreshTaskDetail();
|
||||
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,7 +457,10 @@ function InitialInfo() {
|
||||
getthree(taskDetail.id, stepId.toString(), videoParams).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
message.info('正在生成视频提示词,请稍候...');
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
// 这里可以添加下一步的逻辑,比如调用接口等
|
||||
};
|
||||
@@ -398,8 +473,11 @@ function InitialInfo() {
|
||||
|
||||
getfour(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
message.info('正在生成视频,请稍候...');
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
}
|
||||
const agincreatevideo = () => {
|
||||
@@ -410,6 +488,8 @@ function InitialInfo() {
|
||||
|
||||
getfour(taskDetail.id, steps[3].id.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
message.info('正在生成视频,请稍候...');
|
||||
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
@@ -503,10 +583,10 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: taskDetail?.material?.materialVideoUrl ? 'pointer' : 'default' }} onClick={() => taskDetail?.material?.materialVideoUrl && openPreview(taskDetail.material.materialVideoUrl, 'video')}>
|
||||
{taskDetail?.material?.materialVideoUrl ? (
|
||||
<video
|
||||
controls
|
||||
|
||||
src={taskDetail.material.materialVideoUrl}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
@@ -520,7 +600,7 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
产品图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: taskDetail?.material?.materialImageUrl ? 'pointer' : 'default' }} onClick={() => taskDetail?.material?.materialImageUrl && openPreview(taskDetail.material.materialImageUrl, 'image')}>
|
||||
{taskDetail?.material?.materialImageUrl ? (
|
||||
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||
) : (
|
||||
@@ -537,7 +617,7 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer' }} onClick={() => openPreview(taskDetail.finalImageUrl, 'image')}>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||
alt=""
|
||||
@@ -554,9 +634,9 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer' }} onClick={() => openPreview(taskDetail.finalVideoUrl, 'video')}>
|
||||
<video
|
||||
controls
|
||||
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
@@ -700,7 +780,7 @@ function InitialInfo() {
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }}
|
||||
onClick={() => { createimage(step.id); }}
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
@@ -1106,7 +1186,7 @@ function InitialInfo() {
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: '100%', borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
|
||||
onClick={() => { handleNextStep(step.id); }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频提示词
|
||||
@@ -1132,7 +1212,7 @@ function InitialInfo() {
|
||||
查看/修改视频提词
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }}
|
||||
onClick={() => { createvideo(step.id, step.engineId); }}
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
@@ -1433,6 +1513,62 @@ function InitialInfo() {
|
||||
scroll={{ y: 350 }}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
{/* 图片/视频预览弹窗 */}
|
||||
<Modal
|
||||
open={previewVisible}
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ width: 4, height: 20, background: 'linear-gradient(180deg, #8b5cf6 0%, #ddd6fe 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 700, color: '#8b5cf6', letterSpacing: 0.4 }}>
|
||||
预览
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
onCancel={handleClosePreview}
|
||||
width={800}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: 'rgba(255,255,255,0.6)', borderTop: '1px solid rgba(139, 92, 246, 0.08)' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={handleDownload}
|
||||
style={{ borderRadius: 10, background: 'linear-gradient(135deg, #8b5cf6 0%, #ddd6fe 100%)', border: 'none', boxShadow: '0 8px 18px rgba(47, 52, 64, 0.15)' }}
|
||||
>
|
||||
下载
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
centered
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
body: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '400px',
|
||||
},
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(139, 92, 246, 0.08)', padding: '16px 24px' },
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%' }}>
|
||||
{previewType === 'image' ? (
|
||||
<img
|
||||
src={previewUrl.startsWith('http') ? previewUrl : `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewUrl}`}
|
||||
alt="预览"
|
||||
style={{ width: '100%', maxHeight: '400px', objectFit: 'contain' }}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={previewUrl.startsWith('http') ? previewUrl : `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewUrl}`}
|
||||
controls
|
||||
style={{ maxWidth: '100%', maxHeight: '400px' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,44 @@ function InitialInfo() {
|
||||
// 当前展开的步骤
|
||||
const [activeKey, setActiveKey] = useState<string[]>([]);
|
||||
|
||||
// 预览相关状态
|
||||
const [previewVisible, setPreviewVisible] = useState<boolean>(false);
|
||||
const [previewUrl, setPreviewUrl] = useState<string>('');
|
||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||
const videoRef = React.useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (previewVisible && previewType === 'video') {
|
||||
const playVideo = () => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.play().catch(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
if (videoRef.current) {
|
||||
if (videoRef.current.readyState >= 2) {
|
||||
playVideo();
|
||||
} else {
|
||||
videoRef.current.addEventListener('loadedmetadata', playVideo);
|
||||
}
|
||||
}
|
||||
|
||||
const timer = setTimeout(playVideo, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
if (videoRef.current) {
|
||||
videoRef.current.removeEventListener('loadedmetadata', playVideo);
|
||||
videoRef.current.pause();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}
|
||||
}, [previewVisible, previewType]);
|
||||
|
||||
//
|
||||
const baseSteps = [
|
||||
{ id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
|
||||
@@ -335,12 +373,42 @@ function InitialInfo() {
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const openPreview = (url: string, type: 'image' | 'video') => {
|
||||
setPreviewUrl(url);
|
||||
setPreviewType(type);
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
const handleClosePreview = () => {
|
||||
setPreviewVisible(false);
|
||||
setPreviewUrl('');
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!previewUrl) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewUrl}&download=1`;
|
||||
link.download = previewType === 'image' ? 'image.png' : 'video.mp4';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
const createone = (stepId: number) => {
|
||||
|
||||
removeone(taskDetail.id, stepId.toString()).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
message.info('正在生成图片提示词,请稍候...');
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -353,8 +421,11 @@ function InitialInfo() {
|
||||
}
|
||||
removetwo(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
message.info('正在生成图片,请稍候...');
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
}
|
||||
const newcreateimage = () => {
|
||||
@@ -367,9 +438,13 @@ function InitialInfo() {
|
||||
}
|
||||
|
||||
removetwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
||||
message.info('正在生成图片,请稍候...');
|
||||
|
||||
refreshTaskDetail();
|
||||
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -389,8 +464,11 @@ function InitialInfo() {
|
||||
// console.log('引擎 ID:', engineId);
|
||||
removethree(taskDetail.id, stepId.toString(), videoParams).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
message.info('正在生成视频提示词,请稍候...');
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
});
|
||||
// 这里可以添加下一步的逻辑,比如调用接口等
|
||||
};
|
||||
@@ -403,8 +481,12 @@ function InitialInfo() {
|
||||
|
||||
removefour(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
message.info('正在生成视频,请稍候...');
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||
message.error(errorMsg);
|
||||
|
||||
});
|
||||
}
|
||||
const agincreatevideo = () => {
|
||||
@@ -504,10 +586,10 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: taskDetail?.material?.materialVideoUrl ? 'pointer' : 'default' }} onClick={() => taskDetail?.material?.materialVideoUrl && openPreview(taskDetail.material.materialVideoUrl, 'video')}>
|
||||
{taskDetail?.material?.materialVideoUrl ? (
|
||||
<video
|
||||
controls
|
||||
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
@@ -521,7 +603,7 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
产品图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: taskDetail?.material?.materialImageUrl ? 'pointer' : 'default' }} onClick={() => taskDetail?.material?.materialImageUrl && openPreview(taskDetail.material.materialImageUrl, 'image')}>
|
||||
{taskDetail?.material?.materialImageUrl ? (
|
||||
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||
) : (
|
||||
@@ -537,7 +619,7 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer' }} onClick={() => openPreview(taskDetail.finalImageUrl, 'image')}>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||
alt=""
|
||||
@@ -554,9 +636,9 @@ function InitialInfo() {
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)', cursor: 'pointer' }} onClick={() => openPreview(taskDetail.finalVideoUrl, 'video')}>
|
||||
<video
|
||||
controls
|
||||
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
@@ -675,7 +757,7 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={() => { message.info('正在生成图片提示词,请稍候...'); createone(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => { createone(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成图片提示词
|
||||
</Button>
|
||||
</div>
|
||||
@@ -700,7 +782,7 @@ function InitialInfo() {
|
||||
>
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => { createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成图片
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -1102,7 +1184,7 @@ function InitialInfo() {
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: '100%', borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
|
||||
onClick={() => { handleNextStep(step.id); }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频提示词
|
||||
@@ -1127,7 +1209,7 @@ function InitialInfo() {
|
||||
>
|
||||
查看/修改视频提词
|
||||
</Button>
|
||||
<Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => { createvideo(step.id, step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成视频
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -1387,6 +1469,62 @@ function InitialInfo() {
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{/* 图片/视频预览弹窗 */}
|
||||
<Modal
|
||||
open={previewVisible}
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ width: 4, height: 20, background: 'linear-gradient(180deg, #8b5cf6 0%, #ddd6fe 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 700, color: '#8b5cf6', letterSpacing: 0.4 }}>
|
||||
预览
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
onCancel={handleClosePreview}
|
||||
width={800}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: 'rgba(255,255,255,0.6)', borderTop: '1px solid rgba(139, 92, 246, 0.08)' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={handleDownload}
|
||||
style={{ borderRadius: 10, background: 'linear-gradient(135deg, #8b5cf6 0%, #ddd6fe 100%)', border: 'none', boxShadow: '0 8px 18px rgba(47, 52, 64, 0.15)' }}
|
||||
>
|
||||
下载
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
centered
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
body: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '400px',
|
||||
},
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(139, 92, 246, 0.08)', padding: '16px 24px' },
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%' }}>
|
||||
{previewType === 'image' ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewUrl}`}
|
||||
alt="预览"
|
||||
style={{ width: '100%', maxHeight: '400px', objectFit: 'contain' }}
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewUrl}`}
|
||||
controls
|
||||
style={{ maxWidth: '100%', maxHeight: '400px' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user