forked from 77media/video-flow
review修改
This commit is contained in:
parent
01e2514650
commit
56f6fa0ef1
@ -1,16 +1,16 @@
|
||||
|
||||
# 测试
|
||||
NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
|
||||
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
||||
NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com
|
||||
NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback
|
||||
NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video
|
||||
# NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
|
||||
# NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
||||
# NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com
|
||||
# NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback
|
||||
# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video
|
||||
# 生产
|
||||
# NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai
|
||||
# NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai
|
||||
# NEXT_PUBLIC_CUT_URL = https://smartcut.api.movieflow.ai
|
||||
# NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback
|
||||
# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai
|
||||
NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai
|
||||
NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai
|
||||
NEXT_PUBLIC_CUT_URL = https://smartcut.api.movieflow.ai
|
||||
NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback
|
||||
NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai
|
||||
# 通用
|
||||
# 当前域名配置
|
||||
NEXT_PUBLIC_FRONTEND_URL = https://www.movieflow.ai
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { ApiResponse } from './common';
|
||||
import { showH5QueueNotification } from '../components/QueueBox/H5QueueNotication';
|
||||
import { notification } from 'antd';
|
||||
import { showQueueNotification } from '../components/QueueBox/QueueNotication';
|
||||
|
||||
/** 队列状态枚举 */
|
||||
export enum QueueStatus {
|
||||
@ -75,7 +74,6 @@ export async function withQueuePolling<T>(
|
||||
const cancel = () => {
|
||||
isCancelled = true;
|
||||
try { closeModal?.(); } catch {}
|
||||
notification.destroy(); // 兼容旧弹层
|
||||
onCancel?.();
|
||||
cancelTokens.delete(pollId);
|
||||
};
|
||||
@ -104,14 +102,14 @@ export async function withQueuePolling<T>(
|
||||
position !== undefined && waiting !== undefined) {
|
||||
// 打开或更新 H5 弹窗(仅允许 Cancel 关闭,Refresh 触发刷新)
|
||||
try { closeModal?.(); } catch {}
|
||||
closeModal = showH5QueueNotification(
|
||||
closeModal = showQueueNotification(
|
||||
position,
|
||||
waiting,
|
||||
status,
|
||||
cancel,
|
||||
async () => {
|
||||
// 触发一次立刻刷新:重置 attempts 的等待,直接递归调用 poll()
|
||||
// 不关闭弹窗,由 showH5QueueNotification 保持打开
|
||||
// 不关闭弹窗,由 showQueueNotification 保持打开
|
||||
attempts = Math.max(0, attempts - 1);
|
||||
}
|
||||
);
|
||||
@ -123,7 +121,6 @@ export async function withQueuePolling<T>(
|
||||
|
||||
// 检查是否达到最大尝试次数
|
||||
if (attempts >= maxAttempts) {
|
||||
notification.destroy(); // 关闭通知
|
||||
throw new Error('Exceeded the maximum polling limit');
|
||||
}
|
||||
|
||||
@ -135,17 +132,14 @@ export async function withQueuePolling<T>(
|
||||
// 如果状态为ready,结束轮询
|
||||
if (response.code !== 202 && response.data) {
|
||||
try { closeModal?.(); } catch {}
|
||||
notification.destroy(); // 兼容旧弹层
|
||||
onSuccess?.(response.data);
|
||||
return response;
|
||||
}
|
||||
|
||||
try { closeModal?.(); } catch {}
|
||||
notification.destroy(); // 兼容旧弹层
|
||||
return response;
|
||||
} catch (error) {
|
||||
try { closeModal?.(); } catch {}
|
||||
notification.destroy(); // 兼容旧弹层
|
||||
if (error instanceof Error) {
|
||||
onError?.(error);
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ interface H5QueueNotificationProps {
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function H5QueueNotificationModal(props: H5QueueNotificationProps) {
|
||||
function QueueNotificationModal(props: H5QueueNotificationProps) {
|
||||
const { position, estimatedMinutes, status, onCancel, onClose } = props;
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@ -39,11 +39,11 @@ function H5QueueNotificationModal(props: H5QueueNotificationProps) {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
data-alt="h5-queue-overlay"
|
||||
data-alt="queue-overlay"
|
||||
className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/60"
|
||||
>
|
||||
<div
|
||||
data-alt="h5-queue-modal"
|
||||
data-alt="queue-modal"
|
||||
className="relative w-11/12 max-w-sm rounded-2xl bg-white/5 border border-white/10 backdrop-blur-xl shadow-2xl p-6 text-white"
|
||||
>
|
||||
{/* 去除右上角关闭按钮,避免除取消以外的关闭路径 */}
|
||||
@ -116,7 +116,7 @@ function H5QueueNotificationModal(props: H5QueueNotificationProps) {
|
||||
* @param {() => void} onCancel - Callback when user confirms cancel.
|
||||
* @returns {() => void} - Close function to dismiss the modal programmatically.
|
||||
*/
|
||||
export function showH5QueueNotification(
|
||||
export function showQueueNotification(
|
||||
position: number,
|
||||
estimatedMinutes: number,
|
||||
status: QueueStatus,
|
||||
@ -128,7 +128,7 @@ export function showH5QueueNotification(
|
||||
}
|
||||
|
||||
const mount = document.createElement('div');
|
||||
mount.setAttribute('data-alt', 'h5-queue-root');
|
||||
mount.setAttribute('data-alt', 'queue-root');
|
||||
document.body.appendChild(mount);
|
||||
|
||||
let root: Root | null = null;
|
||||
@ -151,7 +151,7 @@ export function showH5QueueNotification(
|
||||
};
|
||||
|
||||
root.render(
|
||||
<H5QueueNotificationModal
|
||||
<QueueNotificationModal
|
||||
position={position}
|
||||
estimatedMinutes={estimatedMinutes}
|
||||
status={status}
|
||||
@ -1,237 +0,0 @@
|
||||
import { notification } from 'antd';
|
||||
|
||||
const darkGlassStyle = {
|
||||
background: 'rgba(30, 32, 40, 0.95)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
WebkitBackdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.4)',
|
||||
padding: '12px 16px',
|
||||
};
|
||||
|
||||
/** AI导演工作室容器样式 */
|
||||
const studioContainerStyle = {
|
||||
position: 'relative' as const,
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
marginBottom: '16px',
|
||||
background: 'rgba(26, 27, 30, 0.6)',
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
/** AI导演组件 */
|
||||
const AIDirector = () => (
|
||||
<div className="ai-director">
|
||||
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* AI导演的圆形头部 */}
|
||||
<circle cx="50" cy="40" r="25" fill="#F6B266"/>
|
||||
{/* 眼睛 */}
|
||||
<circle cx="40" cy="35" r="5" fill="#2A2B2E"/>
|
||||
<circle cx="60" cy="35" r="5" fill="#2A2B2E"/>
|
||||
{/* 笑容 */}
|
||||
<path d="M40 45 Q50 55 60 45" stroke="#2A2B2E" strokeWidth="3" strokeLinecap="round"/>
|
||||
{/* 导演帽 */}
|
||||
<path d="M25 30 H75 V25 H25" fill="#2A2B2E"/>
|
||||
{/* 身体 */}
|
||||
<rect x="35" y="65" width="30" height="25" fill="#F6B266"/>
|
||||
{/* 手臂 - 动画中会移动 */}
|
||||
<rect className="director-arm" x="25" y="70" width="15" height="5" fill="#F6B266"/>
|
||||
<rect className="director-arm" x="60" y="70" width="15" height="5" fill="#F6B266"/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
/** 工作进度条组件 */
|
||||
const ProgressTimeline = () => (
|
||||
<div className="progress-timeline" style={{
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
left: '20px',
|
||||
right: '20px',
|
||||
height: '4px',
|
||||
background: 'rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '2px',
|
||||
}}>
|
||||
<div className="progress-indicator" style={{
|
||||
width: '30%',
|
||||
height: '100%',
|
||||
background: '#F6B266',
|
||||
borderRadius: '2px',
|
||||
animation: 'progress 2s ease-in-out infinite',
|
||||
}}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
/** 工作台元素组件 */
|
||||
const Workstation = () => (
|
||||
<div className="workstation" style={{
|
||||
position: 'absolute',
|
||||
bottom: '20px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
}}>
|
||||
{/* 小型场景图标,会在动画中浮动 */}
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<div key={i} className={`scene-icon scene-${i}`} style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
background: 'rgba(246, 178, 102, 0.3)',
|
||||
borderRadius: '4px',
|
||||
animation: `float ${1 + i * 0.5}s ease-in-out infinite alternate`,
|
||||
}}/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
/**
|
||||
* 显示队列等待通知
|
||||
* @param position - 当前队列位置
|
||||
* @param estimatedMinutes - 预计等待分钟数
|
||||
*/
|
||||
export const showQueueNotification = (
|
||||
position: number,
|
||||
estimatedMinutes: number,
|
||||
status: string,
|
||||
onCancel: () => void
|
||||
) => {
|
||||
const notificationKey = 'queueNotification';
|
||||
|
||||
// 创建或更新通知内容
|
||||
const notificationContent = (
|
||||
<div data-alt="queue-notification" style={{ minWidth: '320px' }}>
|
||||
{/* AI导演工作室场景 */}
|
||||
<div style={studioContainerStyle}>
|
||||
<AIDirector />
|
||||
<Workstation />
|
||||
<ProgressTimeline />
|
||||
</div>
|
||||
|
||||
{/* 队列信息 */}
|
||||
<div style={{
|
||||
fontSize: '13px',
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
marginBottom: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
background: 'rgba(246, 178, 102, 0.1)',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '6px',
|
||||
}}>
|
||||
<span style={{ marginRight: '8px' }}>🎬</span>
|
||||
{status === 'process' ? `Your work is being produced. Please wait until it is completed before creating a new work.` : `Your work is waiting for production at the ${position} position`}
|
||||
</div>
|
||||
|
||||
{/* 预计等待时间 */}
|
||||
<div style={{
|
||||
fontSize: '12px',
|
||||
color: 'rgba(255, 255, 255, 0.65)',
|
||||
marginBottom: '12px',
|
||||
}}>
|
||||
{status !== 'process' && `Estimated waiting time: about ${estimatedMinutes} minutes`}
|
||||
</div>
|
||||
|
||||
{/* 取消按钮 */}
|
||||
<button
|
||||
onClick={() => {
|
||||
onCancel?.();
|
||||
notification.destroy(notificationKey);
|
||||
}}
|
||||
style={{
|
||||
color: 'rgb(250 173 20 / 90%)',
|
||||
background: 'transparent',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: 0,
|
||||
fontSize: '12px',
|
||||
fontWeight: 500,
|
||||
textDecoration: 'underline',
|
||||
textUnderlineOffset: '2px',
|
||||
textDecorationColor: 'rgb(250 173 20 / 60%)',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
data-alt="cancel-queue-button"
|
||||
>
|
||||
Cancel queue →
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
// 打开或更新通知
|
||||
notification.open({
|
||||
key: notificationKey,
|
||||
message: null,
|
||||
description: notificationContent,
|
||||
duration: 0,
|
||||
placement: 'topRight',
|
||||
style: {
|
||||
...darkGlassStyle,
|
||||
border: '1px solid rgba(246, 178, 102, 0.2)',
|
||||
},
|
||||
className: 'director-studio-notification',
|
||||
closeIcon: null,
|
||||
});
|
||||
};
|
||||
|
||||
// 添加必要的CSS动画
|
||||
const styles = `
|
||||
.ai-director {
|
||||
animation: bounce 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.director-arm {
|
||||
transform-origin: center;
|
||||
animation: wave 1s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0% { transform: rotate(-5deg); }
|
||||
100% { transform: rotate(5deg); }
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0% { transform: translateY(0); }
|
||||
100% { transform: translateY(-10px); }
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
0% { width: 0%; }
|
||||
50% { width: 60%; }
|
||||
100% { width: 30%; }
|
||||
}
|
||||
|
||||
.director-studio-notification {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
.scene-0 { animation-delay: 0s; }
|
||||
.scene-1 { animation-delay: 0.2s; }
|
||||
.scene-2 { animation-delay: 0.4s; }
|
||||
`;
|
||||
|
||||
// 将样式注入到页面
|
||||
if (typeof document !== 'undefined') {
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.textContent = styles;
|
||||
document.head.appendChild(styleSheet);
|
||||
}
|
||||
|
||||
// 配置通知
|
||||
notification.config({
|
||||
maxCount: 3,
|
||||
});
|
||||
@ -369,9 +369,6 @@ export function H5MediaViewer({
|
||||
<video
|
||||
ref={(el) => (videoRefs.current[idx] = el)}
|
||||
className="w-full h-full object-contain [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black cursor-pointer"
|
||||
style={{
|
||||
maxHeight: '100%',
|
||||
}}
|
||||
src={url}
|
||||
preload="metadata"
|
||||
playsInline
|
||||
@ -453,9 +450,7 @@ export function H5MediaViewer({
|
||||
return (
|
||||
<div key={`h5-image-${idx}`} data-alt="image-slide" className="relative w-full h-full flex justify-center">
|
||||
{showImage ? (
|
||||
<img src={url} alt="scene" className="w-full h-full object-contain" style={{
|
||||
maxHeight: '100%',
|
||||
}} />
|
||||
<img src={url} alt="scene" className="w-full h-full object-contain" />
|
||||
) : (
|
||||
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="image-status" style={{
|
||||
height: imageWrapperHeight,
|
||||
@ -632,7 +627,6 @@ export function H5MediaViewer({
|
||||
if (hasFinalVideo) {
|
||||
all.push(taskObject.final.url);
|
||||
}
|
||||
console.log('h5-media-viewer:all', all);
|
||||
await downloadAllVideos(all);
|
||||
},
|
||||
});
|
||||
|
||||
@ -164,7 +164,6 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
|
||||
segmentProgress
|
||||
}
|
||||
})
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
currentStage,
|
||||
scriptData,
|
||||
|
||||
@ -312,6 +312,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
|
||||
|
||||
const all_task_data = response.data;
|
||||
const { current: taskCurrent } = tempTaskObject;
|
||||
let combinerVideoUrl = '';
|
||||
|
||||
console.log('---look-all_task_data', all_task_data);
|
||||
console.log('---look-tempTaskObject', taskCurrent);
|
||||
@ -442,10 +443,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
|
||||
// 合成视频
|
||||
if (task.task_name === 'combiner_videos') {
|
||||
if (task.task_status === 'COMPLETED') {
|
||||
taskCurrent.currentStage = 'final_video';
|
||||
taskCurrent.final.url = task.task_result.video_url;
|
||||
taskCurrent.final.note = 'combiner';
|
||||
taskCurrent.status = 'COMPLETED';
|
||||
combinerVideoUrl = task.task_result.video_url;
|
||||
}
|
||||
if (task.task_status === 'FAILED' || task.task_status === 'ERROR') {
|
||||
taskCurrent.status = 'FAILED';
|
||||
@ -470,6 +468,11 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
|
||||
setNeedStreamData(false);
|
||||
}
|
||||
if (task.task_status === 'FAILED' || task.task_status === 'ERROR') {
|
||||
// 使用合成视频地址
|
||||
taskCurrent.currentStage = 'final_video';
|
||||
taskCurrent.final.url = combinerVideoUrl;
|
||||
taskCurrent.final.note = 'combiner';
|
||||
taskCurrent.status = 'COMPLETED';
|
||||
// 停止轮询
|
||||
setNeedStreamData(false);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user