forked from 77media/video-flow
237 lines
6.2 KiB
TypeScript
237 lines
6.2 KiB
TypeScript
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: '120px',
|
|
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,
|
|
}); |