Merge branch 'main' of gitee.com:wg123/video-gen
This commit is contained in:
@@ -56,7 +56,7 @@ class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|||||||
"method": request.method,
|
"method": request.method,
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"query_params": dict(request.query_params),
|
"query_params": dict(request.query_params),
|
||||||
"request_body": encrypt_data(request_body) if request_body else "",
|
"request_body": encrypt_data(request_body, True) if request_body else "",
|
||||||
"status": response.status_code,
|
"status": response.status_code,
|
||||||
"duration_ms": duration_ms,
|
"duration_ms": duration_ms,
|
||||||
"ip": request.client.host if request.client else "-",
|
"ip": request.client.host if request.client else "-",
|
||||||
|
|||||||
@@ -159,4 +159,4 @@ def extract_error_message(exc: Exception, service_type: str = "video") -> str:
|
|||||||
return f"{service_type}生成失败: {code}"
|
return f"{service_type}生成失败: {code}"
|
||||||
except (json.JSONDecodeError, TypeError, ValueError):
|
except (json.JSONDecodeError, TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
return raw[:200] if len(raw) > 200 else raw
|
return raw[:5000] if len(raw) > 5000 else raw
|
||||||
@@ -30,7 +30,7 @@ def _log_image_request(engine: ProviderImageEngineLike, record_id: str, request_
|
|||||||
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
||||||
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
||||||
request_str = json.dumps(request_data, ensure_ascii=False)
|
request_str = json.dumps(request_data, ensure_ascii=False)
|
||||||
request_encrypted = encrypt_data(request_data)
|
request_encrypted = encrypt_data(request_data, True)
|
||||||
entry = {
|
entry = {
|
||||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"type": "image_gen_request",
|
"type": "image_gen_request",
|
||||||
@@ -54,7 +54,7 @@ def _log_image_response(record_id: str, response_data: dict, error: str | None =
|
|||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
||||||
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
||||||
response_encrypted = encrypt_data(response_data) if response_data else ""
|
response_encrypted = encrypt_data(response_data, True) if response_data else ""
|
||||||
entry = {
|
entry = {
|
||||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"type": "image_gen_response",
|
"type": "image_gen_response",
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ def _log_ai_request_response(config, request_data: dict, response_data: dict | N
|
|||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
||||||
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
||||||
request_encrypted = encrypt_data(_sanitize_for_log(request_data))
|
request_encrypted = encrypt_data(_sanitize_for_log(request_data), True)
|
||||||
response_encrypted = encrypt_data(_sanitize_for_log(response_data)) if response_data else ""
|
response_encrypted = encrypt_data(_sanitize_for_log(response_data), True) if response_data else ""
|
||||||
entry = {
|
entry = {
|
||||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"model_name": config.name,
|
"model_name": config.name,
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ ENCRYPTION_KEY = b'videogen@202605!'
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def encrypt_data(data: dict) -> str:
|
def encrypt_data(data: dict, is_encrypt: bool = False) -> str:
|
||||||
data_str = json.dumps(data, ensure_ascii=False, sort_keys=True)
|
data_str = json.dumps(data, ensure_ascii=False, sort_keys=True)
|
||||||
|
if is_encrypt:
|
||||||
|
return data_str
|
||||||
|
|
||||||
data_bytes = data_str.encode("utf-8")
|
data_bytes = data_str.encode("utf-8")
|
||||||
|
|
||||||
padder = padding.PKCS7(128).padder()
|
padder = padding.PKCS7(128).padder()
|
||||||
padded_data = padder.update(data_bytes) + padder.finalize()
|
padded_data = padder.update(data_bytes) + padder.finalize()
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def _log_video_request(engine: ProviderVideoEngineLike, record_id: str, request_
|
|||||||
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
||||||
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
||||||
request_str = json.dumps(request_data, ensure_ascii=False)
|
request_str = json.dumps(request_data, ensure_ascii=False)
|
||||||
request_encrypted = encrypt_data(request_data)
|
request_encrypted = encrypt_data(request_data, True)
|
||||||
entry = {
|
entry = {
|
||||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"type": "video_gen_request",
|
"type": "video_gen_request",
|
||||||
@@ -54,7 +54,7 @@ def _log_video_response(record_id: str, response_data: dict, error: str | None =
|
|||||||
os.makedirs(LOG_DIR, exist_ok=True)
|
os.makedirs(LOG_DIR, exist_ok=True)
|
||||||
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
||||||
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
log_file = os.path.join(LOG_DIR, f"{today}.log")
|
||||||
response_encrypted = encrypt_data(response_data) if response_data else ""
|
response_encrypted = encrypt_data(response_data, True) if response_data else ""
|
||||||
|
|
||||||
entry = {
|
entry = {
|
||||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
|||||||
+95
-91
File diff suppressed because one or more lines are too long
Vendored
+36
-36
@@ -1,37 +1,37 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
<title>民众智创</title>
|
<title>民众智创</title>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var cached = localStorage.getItem('siteInfo');
|
var cached = localStorage.getItem('siteInfo');
|
||||||
if (cached) {
|
if (cached) {
|
||||||
try {
|
try {
|
||||||
var info = JSON.parse(cached);
|
var info = JSON.parse(cached);
|
||||||
if (info.siteName) {
|
if (info.siteName) {
|
||||||
document.title = info.siteName;
|
document.title = info.siteName;
|
||||||
}
|
}
|
||||||
if (info.siteLogo) {
|
if (info.siteLogo) {
|
||||||
var link = document.querySelector('link[rel="icon"]');
|
var link = document.querySelector('link[rel="icon"]');
|
||||||
if (link) {
|
if (link) {
|
||||||
link.href = info.siteLogo;
|
link.href = info.siteLogo;
|
||||||
link.type = 'image/png';
|
link.type = 'image/png';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-Bof1g4Ez.js"></script>
|
<script type="module" crossorigin src="/assets/index-ByI-1GQy.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>␍
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -113,6 +113,20 @@ export async function optimizePrompt(
|
|||||||
image_px: params.image_px || null,
|
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 }> {
|
export async function uploadImage(file: File): Promise<{ url: string; filename: string }> {
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.append('file', file);
|
form.append('file', file);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useAuthStore } from '../../store/useAuthStore';
|
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 NotificationPopup from '../NotificationPopup';
|
||||||
import './AppLayout.css';
|
import './AppLayout.css';
|
||||||
|
|
||||||
@@ -619,10 +619,18 @@ const AppLayout: React.FC = () => {
|
|||||||
|
|
||||||
const handleChangePwd = async () => {
|
const handleChangePwd = async () => {
|
||||||
try {
|
try {
|
||||||
await pwdForm.validateFields();
|
const values = await pwdForm.validateFields();
|
||||||
message.success('密码修改成功(演示)');
|
await changePassword(values.oldPwd, values.newPwd);
|
||||||
setPwdModalOpen(false); pwdForm.resetFields();
|
message.success('密码修改成功');
|
||||||
} catch { }
|
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 = () => {
|
const handleLogout = () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import text from '../assets/testb.png';
|
|||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getParameters, createGenerationTask, getgen_list, getEngine, uploadImage,
|
getParameters, createGenerationTask, getgen_list, getEngine, uploadImage,uploadAudio,
|
||||||
uploadVideo, getCreditRatios, deleteHistory, calculateCredits
|
uploadVideo, getCreditRatios, deleteHistory, calculateCredits
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
|
||||||
@@ -51,6 +51,7 @@ import {
|
|||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
AudioOutlined,
|
AudioOutlined,
|
||||||
|
PauseOutlined,
|
||||||
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
@@ -227,6 +228,38 @@ const AIChatPage: React.FC = () => {
|
|||||||
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
const [previewType, setPreviewType] = useState<'image' | 'video'>('image');
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
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());
|
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 [attachmentPreviewType, setAttachmentPreviewType] = useState<'image' | 'video' | 'audio'>('image');
|
||||||
const [attachmentPreviewName, setAttachmentPreviewName] = useState<string>('');
|
const [attachmentPreviewName, setAttachmentPreviewName] = useState<string>('');
|
||||||
const attachmentPreviewVideoRef = useRef<HTMLVideoElement>(null);
|
const attachmentPreviewVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
const [playingAudioUrl, setPlayingAudioUrl] = useState<string | null>(null);
|
||||||
|
const [audioProgress, setAudioProgress] = useState(0);
|
||||||
|
|
||||||
// 附件详情悬浮窗状态
|
// 附件详情悬浮窗状态
|
||||||
const [attachmentPopupVisible, setAttachmentPopupVisible] = useState<boolean>(false);
|
const [attachmentPopupVisible, setAttachmentPopupVisible] = useState<boolean>(false);
|
||||||
@@ -831,6 +866,9 @@ const AIChatPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log(mediaReferences);
|
||||||
|
|
||||||
// 创建用户消息对象
|
// 创建用户消息对象
|
||||||
const newMessage: Message = {
|
const newMessage: Message = {
|
||||||
id: '',
|
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 }> => {
|
const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@@ -1096,16 +1163,24 @@ const AIChatPage: React.FC = () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentEngineList = mediaType === 'image' ? enginesele.image : enginesele.video;
|
if (isAudio) {
|
||||||
const currentEngine = currentEngineList?.find((e: any) => e.id === countType);
|
const audioExt = file.name.split('.').pop()?.toLowerCase();
|
||||||
const maxImage = currentEngine?.maxImageCount ?? 4;
|
if (!['wav', 'mp3'].includes(audioExt || '')) {
|
||||||
const maxVideo = currentEngine?.maxVideoCount ?? 1;
|
message.error('音频仅支持wav和mp3格式');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mediaType === 'image' && (isVideo || isAudio)) {
|
if (mediaType === 'image' && (isVideo || isAudio)) {
|
||||||
message.error('图片模式仅支持上传图片');
|
message.error('图片模式仅支持上传图片');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAudio && mediaType !== 'video') {
|
||||||
|
message.error('仅视频模式支持上传音频');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const maxMB = isVideo ? 100 : (isAudio ? 50 : 10);
|
const maxMB = isVideo ? 100 : (isAudio ? 50 : 10);
|
||||||
const fileTypeText = isVideo ? '视频' : (isAudio ? '音频' : '图片');
|
const fileTypeText = isVideo ? '视频' : (isAudio ? '音频' : '图片');
|
||||||
if (file.size / 1024 / 1024 > maxMB) {
|
if (file.size / 1024 / 1024 > maxMB) {
|
||||||
@@ -1140,7 +1215,14 @@ const AIChatPage: React.FC = () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const audioCount = currentMedia.filter((m) => m.type === 'audio').length;
|
||||||
|
if (isAudio && audioCount >= maxAudio) {
|
||||||
|
message.error(`该引擎最多上传${maxAudio}个音频`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let videoDuration = 0;
|
let videoDuration = 0;
|
||||||
|
let audioDuration = 0;
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
try {
|
try {
|
||||||
videoDuration = await getVideoDuration(file);
|
videoDuration = await getVideoDuration(file);
|
||||||
@@ -1167,11 +1249,32 @@ const AIChatPage: React.FC = () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
setUploading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uploadFn = isImage ? uploadImage : (isAudio ? uploadImage : uploadVideo);
|
const uploadFn = isImage ? uploadImage : (isAudio ? uploadAudio : uploadVideo);
|
||||||
const res = await uploadFn(file);
|
const res = await uploadFn(file);
|
||||||
const mediaType: 'image' | 'video' | 'audio' = isImage ? 'image' : (isAudio ? 'audio' : 'video');
|
const mediaType: 'image' | 'video' | 'audio' = isImage ? 'image' : (isAudio ? 'audio' : 'video');
|
||||||
const newList = [...currentMedia, {
|
const newList = [...currentMedia, {
|
||||||
@@ -1179,7 +1282,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
type: mediaType,
|
type: mediaType,
|
||||||
url: res.url,
|
url: res.url,
|
||||||
label: '',
|
label: '',
|
||||||
...((isVideo || isAudio) && { duration: videoDuration }),
|
...(isVideo && { duration: videoDuration }),
|
||||||
|
...(isAudio && { duration: audioDuration }),
|
||||||
}];
|
}];
|
||||||
const labels = generateMediaLabels(newList);
|
const labels = generateMediaLabels(newList);
|
||||||
setCurrentMedia(newList.map((m, i) => ({ ...m, label: labels[i] })));
|
setCurrentMedia(newList.map((m, i) => ({ ...m, label: labels[i] })));
|
||||||
@@ -1287,10 +1391,11 @@ const AIChatPage: React.FC = () => {
|
|||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAttachmentMediaType = (ref: any): 'image' | 'video' => {
|
const getAttachmentMediaType = (ref: any): 'image' | 'video' | 'audio' => {
|
||||||
const rawType = String(ref?.type || '').toLowerCase();
|
const rawType = String(ref?.type || '').toLowerCase();
|
||||||
const rawUrl = String(ref?.url || ref?.name || '').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('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';
|
return 'image';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1320,7 +1425,8 @@ const AIChatPage: React.FC = () => {
|
|||||||
if (!ref?.url) return;
|
if (!ref?.url) return;
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = buildAttachmentDownloadUrl(ref.url);
|
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);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
@@ -1348,6 +1454,10 @@ const AIChatPage: React.FC = () => {
|
|||||||
? '首帧必传 · 尾帧可选 · 适合首尾画面连贯过渡'
|
? '首帧必传 · 尾帧可选 · 适合首尾画面连贯过渡'
|
||||||
: '多素材参考 · 支持图片 / 视频 / 音频,输入 @ 可快速引用素材';
|
: '多素材参考 · 支持图片 / 视频 / 音频,输入 @ 可快速引用素材';
|
||||||
|
|
||||||
|
const maxImage = maxImageCount;
|
||||||
|
const maxVideo = maxVideoCount;
|
||||||
|
const maxAudio = currentEngine?.maxAudioCount ?? 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout className="ai-create-page" style={{
|
<Layout className="ai-create-page" style={{
|
||||||
margin: '-24px -32px -32px',
|
margin: '-24px -32px -32px',
|
||||||
@@ -1357,6 +1467,14 @@ const AIChatPage: React.FC = () => {
|
|||||||
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
|
{/* 隐藏的音频播放器 */}
|
||||||
|
<audio
|
||||||
|
id="audio-player"
|
||||||
|
src={playingAudioUrl || ''}
|
||||||
|
autoPlay
|
||||||
|
onEnded={() => setPlayingAudioUrl(null)}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
/>
|
||||||
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
||||||
{false && (
|
{false && (
|
||||||
<Sider
|
<Sider
|
||||||
@@ -1380,7 +1498,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<Button
|
<Button
|
||||||
block
|
block
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
onClick={handleNewChat}
|
onClick={handleNewChat}
|
||||||
@@ -1389,6 +1507,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
新对话
|
新对话
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{!collapsed && conversations.length > 0 && (
|
{!collapsed && conversations.length > 0 && (
|
||||||
<div style={{ maxHeight: 'calc(100vh - 120px)', overflowY: 'auto' }}>
|
<div style={{ maxHeight: 'calc(100vh - 120px)', overflowY: 'auto' }}>
|
||||||
@@ -1527,7 +1646,6 @@ const AIChatPage: React.FC = () => {
|
|||||||
paddingBottom: 18,
|
paddingBottom: 18,
|
||||||
// background: 'linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0))',
|
// background: 'linear-gradient(180deg, rgba(255,255,255,0.72), rgba(255,255,255,0))',
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
|
|
||||||
// borderRadius: 22,
|
// 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' }}>
|
<div className="ai-reference-tray-scroll" style={{ display: 'flex', gap: 10, overflowX: 'auto', padding: '2px 2px 4px' }}>
|
||||||
{attachmentRefs.map((ref: any, idx: number) => {
|
{attachmentRefs.map((ref: any, idx: number) => {
|
||||||
const refType = getAttachmentMediaType(ref);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${ref.url || ref.name || idx}-${idx}`}
|
key={`${ref.url || ref.name || idx}-${idx}`}
|
||||||
@@ -1977,10 +2095,15 @@ const AIChatPage: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
openAttachmentPreview(ref);
|
if (refType === 'audio') {
|
||||||
setAttachmentPopupVisible(false);
|
e.stopPropagation();
|
||||||
setAttachmentPopupMessageId(null);
|
handleAudioPlay(ref.url);
|
||||||
|
} else {
|
||||||
|
openAttachmentPreview(ref);
|
||||||
|
setAttachmentPopupVisible(false);
|
||||||
|
setAttachmentPopupMessageId(null);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -1999,7 +2122,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
alt={ref.name || label}
|
alt={ref.name || label}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : refType === 'video' ? (
|
||||||
<>
|
<>
|
||||||
<video
|
<video
|
||||||
src={buildAttachmentAssetUrl(ref.url)}
|
src={buildAttachmentAssetUrl(ref.url)}
|
||||||
@@ -2013,6 +2136,14 @@ const AIChatPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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)' }}>
|
{/* <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}
|
{label}
|
||||||
@@ -2047,7 +2178,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
查看
|
查看
|
||||||
</button>
|
</button>
|
||||||
<button
|
{/* <button
|
||||||
onClick={(e) => downloadAttachmentRef(ref, e)}
|
onClick={(e) => downloadAttachmentRef(ref, e)}
|
||||||
title="下载"
|
title="下载"
|
||||||
style={{
|
style={{
|
||||||
@@ -2067,7 +2198,7 @@ const AIChatPage: React.FC = () => {
|
|||||||
onMouseLeave={(e) => { e.currentTarget.style.background = '#FFFFFF'; e.currentTarget.style.color = '#667085'; }}
|
onMouseLeave={(e) => { e.currentTarget.style.background = '#FFFFFF'; e.currentTarget.style.color = '#667085'; }}
|
||||||
>
|
>
|
||||||
<DownloadOutlined style={{ fontSize: 12 }} />
|
<DownloadOutlined style={{ fontSize: 12 }} />
|
||||||
</button>
|
</button> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -2092,8 +2223,12 @@ const AIChatPage: React.FC = () => {
|
|||||||
backdropFilter: 'blur(24px)',
|
backdropFilter: 'blur(24px)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* <div>12312</div> */}
|
||||||
|
|
||||||
|
|
||||||
{/* 上方输入布局 */}
|
{/* 上方输入布局 */}
|
||||||
<div style={{ display: 'flex', gap: isFirstLastFrameComposer ? 18 : 18, alignItems: 'flex-end', marginBottom: 14 }}>
|
<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 }}>
|
<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'
|
<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 === '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
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -2319,15 +2456,17 @@ const AIChatPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
setAttachmentPreviewUrl(media.url);
|
e.stopPropagation();
|
||||||
setAttachmentPreviewType('audio');
|
handleAudioPlay(media.url);
|
||||||
setAttachmentPreviewName(media.name);
|
|
||||||
setAttachmentPreviewVisible(true);
|
|
||||||
}}
|
}}
|
||||||
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' }}
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 右上角删除按钮 */}
|
{/* 右上角删除按钮 */}
|
||||||
@@ -2378,7 +2517,9 @@ const AIChatPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Tooltip title={mediaType === 'image'
|
<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 === '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
|
<div
|
||||||
style={{
|
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',
|
padding: '24px 28px',
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
@@ -901,6 +902,7 @@ const HomePage: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ========== 预览弹窗 ========== */}
|
{/* ========== 预览弹窗 ========== */}
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -62,6 +62,71 @@ function InitialInfo() {
|
|||||||
// 当前展开的步骤
|
// 当前展开的步骤
|
||||||
const [activeKey, setActiveKey] = useState<string[]>([]);
|
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 = [
|
const baseSteps = [
|
||||||
{ id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
|
{ id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
|
||||||
@@ -347,9 +412,12 @@ function InitialInfo() {
|
|||||||
image_size: "2K"
|
image_size: "2K"
|
||||||
}
|
}
|
||||||
gettwo(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
gettwo(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||||
|
message.info('正在生成图片,请稍候...');
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
|
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||||
|
message.error(errorMsg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newcreateimage = () => {
|
const newcreateimage = () => {
|
||||||
@@ -362,9 +430,13 @@ function InitialInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gettwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
gettwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
||||||
|
message.info('正在生成图片,请稍候...');
|
||||||
|
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
|
|
||||||
}).catch((error: any) => {
|
}).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) => {
|
getthree(taskDetail.id, stepId.toString(), videoParams).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
|
message.info('正在生成视频提示词,请稍候...');
|
||||||
}).catch((error: any) => {
|
}).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) => {
|
getfour(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
|
message.info('正在生成视频,请稍候...');
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
|
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||||
|
message.error(errorMsg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const agincreatevideo = () => {
|
const agincreatevideo = () => {
|
||||||
@@ -410,6 +488,8 @@ function InitialInfo() {
|
|||||||
|
|
||||||
getfour(taskDetail.id, steps[3].id.toString(), params).then((res: any) => {
|
getfour(taskDetail.id, steps[3].id.toString(), params).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
|
message.info('正在生成视频,请稍候...');
|
||||||
|
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
视频
|
视频
|
||||||
</span>
|
</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 ? (
|
{taskDetail?.material?.materialVideoUrl ? (
|
||||||
<video
|
<video
|
||||||
controls
|
|
||||||
src={taskDetail.material.materialVideoUrl}
|
src={taskDetail.material.materialVideoUrl}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
产品图片
|
产品图片
|
||||||
</span>
|
</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 ? (
|
{taskDetail?.material?.materialImageUrl ? (
|
||||||
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
<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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
生成图片
|
生成图片
|
||||||
</span>
|
</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
|
<img
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||||
alt=""
|
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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
生成视频
|
生成视频
|
||||||
</span>
|
</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
|
<video
|
||||||
controls
|
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||||
/>
|
/>
|
||||||
@@ -700,7 +780,7 @@ function InitialInfo() {
|
|||||||
修改提示词
|
修改提示词
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }}
|
onClick={() => { createimage(step.id); }}
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||||
disabled={step.status !== 'completed'}
|
disabled={step.status !== 'completed'}
|
||||||
@@ -1106,7 +1186,7 @@ function InitialInfo() {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
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)' }}
|
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'}
|
disabled={step.status !== 'completed'}
|
||||||
>
|
>
|
||||||
下一步:生成视频提示词
|
下一步:生成视频提示词
|
||||||
@@ -1132,7 +1212,7 @@ function InitialInfo() {
|
|||||||
查看/修改视频提词
|
查看/修改视频提词
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }}
|
onClick={() => { createvideo(step.id, step.engineId); }}
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||||
disabled={step.status !== 'completed'}
|
disabled={step.status !== 'completed'}
|
||||||
@@ -1433,6 +1513,62 @@ function InitialInfo() {
|
|||||||
scroll={{ y: 350 }}
|
scroll={{ y: 350 }}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</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>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,44 @@ function InitialInfo() {
|
|||||||
// 当前展开的步骤
|
// 当前展开的步骤
|
||||||
const [activeKey, setActiveKey] = useState<string[]>([]);
|
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 = [
|
const baseSteps = [
|
||||||
{ id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
|
{ id: 1, title: '原始素材', description: '上传原始视频素材', childId: 1 },
|
||||||
@@ -83,7 +121,7 @@ function InitialInfo() {
|
|||||||
// 当apiSteps更新时,逆向遍历找到第一个已完成或失败的步骤并展开
|
// 当apiSteps更新时,逆向遍历找到第一个已完成或失败的步骤并展开
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const completedFailedKeys = new Set<string>();
|
const completedFailedKeys = new Set<string>();
|
||||||
|
|
||||||
activeKey.forEach(key => {
|
activeKey.forEach(key => {
|
||||||
const step = steps.find(s => String(s.childId) === key);
|
const step = steps.find(s => String(s.childId) === key);
|
||||||
if (step && (step.status === 'completed' || step.status === 'failed')) {
|
if (step && (step.status === 'completed' || step.status === 'failed')) {
|
||||||
@@ -335,12 +373,42 @@ function InitialInfo() {
|
|||||||
}).catch((error: any) => {
|
}).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) => {
|
const createone = (stepId: number) => {
|
||||||
|
|
||||||
removeone(taskDetail.id, stepId.toString()).then((res: any) => {
|
removeone(taskDetail.id, stepId.toString()).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
|
message.info('正在生成图片提示词,请稍候...');
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).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) => {
|
removetwo(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
|
message.info('正在生成图片,请稍候...');
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
|
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||||
|
message.error(errorMsg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newcreateimage = () => {
|
const newcreateimage = () => {
|
||||||
@@ -367,9 +438,13 @@ function InitialInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removetwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
removetwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
||||||
|
message.info('正在生成图片,请稍候...');
|
||||||
|
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
|
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
|
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||||
|
message.error(errorMsg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,8 +464,11 @@ function InitialInfo() {
|
|||||||
// console.log('引擎 ID:', engineId);
|
// console.log('引擎 ID:', engineId);
|
||||||
removethree(taskDetail.id, stepId.toString(), videoParams).then((res: any) => {
|
removethree(taskDetail.id, stepId.toString(), videoParams).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
|
message.info('正在生成视频提示词,请稍候...');
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).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) => {
|
removefour(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||||
// 重新获取任务详情以更新数据
|
// 重新获取任务详情以更新数据
|
||||||
|
message.info('正在生成视频,请稍候...');
|
||||||
refreshTaskDetail();
|
refreshTaskDetail();
|
||||||
}).catch((error: any) => {
|
}).catch((error: any) => {
|
||||||
|
const errorMsg = error?.message?.split(': ')?.[1] || error?.message || '生成失败';
|
||||||
|
message.error(errorMsg);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const agincreatevideo = () => {
|
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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
视频
|
视频
|
||||||
</span>
|
</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 ? (
|
{taskDetail?.material?.materialVideoUrl ? (
|
||||||
<video
|
<video
|
||||||
controls
|
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
产品图片
|
产品图片
|
||||||
</span>
|
</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 ? (
|
{taskDetail?.material?.materialImageUrl ? (
|
||||||
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
<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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
生成图片
|
生成图片
|
||||||
</span>
|
</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
|
<img
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||||
alt=""
|
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 style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||||
生成视频
|
生成视频
|
||||||
</span>
|
</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
|
<video
|
||||||
controls
|
|
||||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||||
/>
|
/>
|
||||||
@@ -675,7 +757,7 @@ function InitialInfo() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -700,7 +782,7 @@ function InitialInfo() {
|
|||||||
>
|
>
|
||||||
修改提示词
|
修改提示词
|
||||||
</Button>
|
</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>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -1102,7 +1184,7 @@ function InitialInfo() {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
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)' }}
|
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'}
|
disabled={step.status !== 'completed'}
|
||||||
>
|
>
|
||||||
下一步:生成视频提示词
|
下一步:生成视频提示词
|
||||||
@@ -1127,7 +1209,7 @@ function InitialInfo() {
|
|||||||
>
|
>
|
||||||
查看/修改视频提词
|
查看/修改视频提词
|
||||||
</Button>
|
</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>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -1387,6 +1469,62 @@ function InitialInfo() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</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>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user