- 新增完整的AI画布工具系统 - 可视化节点编辑器,支持拖拽连线 - 多种节点类型:文本输入、图片生成、视频生成等 - 智能连接验证和数据流转换 - 异步处理引擎,支持进度追踪和取消 - 批量处理系统,支持并发处理 - AI服务集成框架,支持多种AI API - 用户体验优化 - 智能弹框定位,防止被遮挡 - 节点删除功能(悬停删除按钮 + 键盘快捷键) - 通知系统和错误处理 - 快速开始模板 - 键盘快捷键支持 - 界面调整 - 暂时隐藏AI画布,保留代码 - 设置项目列表为首页 - 简化导航栏结构
249 lines
6.7 KiB
TypeScript
249 lines
6.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { CheckCircle, XCircle, AlertCircle, Info, X } from 'lucide-react';
|
|
|
|
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
|
|
|
export interface Notification {
|
|
id: string;
|
|
type: NotificationType;
|
|
title: string;
|
|
message?: string;
|
|
duration?: number;
|
|
action?: {
|
|
label: string;
|
|
onClick: () => void;
|
|
};
|
|
}
|
|
|
|
interface NotificationSystemProps {
|
|
notifications: Notification[];
|
|
onRemove: (id: string) => void;
|
|
}
|
|
|
|
const NotificationItem: React.FC<{
|
|
notification: Notification;
|
|
onRemove: (id: string) => void;
|
|
}> = ({ notification, onRemove }) => {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
const [isLeaving, setIsLeaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// 入场动画
|
|
setTimeout(() => setIsVisible(true), 50);
|
|
|
|
// 自动移除
|
|
if (notification.duration !== 0) {
|
|
const timer = setTimeout(() => {
|
|
handleRemove();
|
|
}, notification.duration || 5000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [notification.duration]);
|
|
|
|
const handleRemove = () => {
|
|
setIsLeaving(true);
|
|
setTimeout(() => {
|
|
onRemove(notification.id);
|
|
}, 300);
|
|
};
|
|
|
|
const getIcon = () => {
|
|
switch (notification.type) {
|
|
case 'success':
|
|
return <CheckCircle className="w-5 h-5 text-green-500" />;
|
|
case 'error':
|
|
return <XCircle className="w-5 h-5 text-red-500" />;
|
|
case 'warning':
|
|
return <AlertCircle className="w-5 h-5 text-yellow-500" />;
|
|
case 'info':
|
|
return <Info className="w-5 h-5 text-blue-500" />;
|
|
}
|
|
};
|
|
|
|
const getBackgroundColor = () => {
|
|
switch (notification.type) {
|
|
case 'success':
|
|
return 'bg-green-50 border-green-200';
|
|
case 'error':
|
|
return 'bg-red-50 border-red-200';
|
|
case 'warning':
|
|
return 'bg-yellow-50 border-yellow-200';
|
|
case 'info':
|
|
return 'bg-blue-50 border-blue-200';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
transform transition-all duration-300 ease-in-out
|
|
${isVisible && !isLeaving ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
|
|
${getBackgroundColor()}
|
|
border rounded-lg p-4 shadow-lg max-w-sm w-full
|
|
`}
|
|
>
|
|
<div className="flex items-start space-x-3">
|
|
<div className="flex-shrink-0">
|
|
{getIcon()}
|
|
</div>
|
|
|
|
<div className="flex-1 min-w-0">
|
|
<h4 className="text-sm font-medium text-gray-900">
|
|
{notification.title}
|
|
</h4>
|
|
{notification.message && (
|
|
<p className="text-sm text-gray-600 mt-1">
|
|
{notification.message}
|
|
</p>
|
|
)}
|
|
|
|
{notification.action && (
|
|
<button
|
|
onClick={notification.action.onClick}
|
|
className="text-sm font-medium text-blue-600 hover:text-blue-800 mt-2 transition-colors"
|
|
>
|
|
{notification.action.label}
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleRemove}
|
|
className="flex-shrink-0 text-gray-400 hover:text-gray-600 transition-colors"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const NotificationSystem: React.FC<NotificationSystemProps> = ({
|
|
notifications,
|
|
onRemove
|
|
}) => {
|
|
return (
|
|
<div className="fixed top-4 right-4 z-50 space-y-2">
|
|
{notifications.map(notification => (
|
|
<NotificationItem
|
|
key={notification.id}
|
|
notification={notification}
|
|
onRemove={onRemove}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Hook for managing notifications
|
|
export const useNotifications = () => {
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
|
|
const addNotification = (notification: Omit<Notification, 'id'>) => {
|
|
const id = `notification-${Date.now()}-${Math.random()}`;
|
|
setNotifications(prev => [...prev, { ...notification, id }]);
|
|
};
|
|
|
|
const removeNotification = (id: string) => {
|
|
setNotifications(prev => prev.filter(n => n.id !== id));
|
|
};
|
|
|
|
const clearAll = () => {
|
|
setNotifications([]);
|
|
};
|
|
|
|
// 便捷方法
|
|
const success = (title: string, message?: string, options?: Partial<Notification>) => {
|
|
addNotification({ type: 'success', title, message, ...options });
|
|
};
|
|
|
|
const error = (title: string, message?: string, options?: Partial<Notification>) => {
|
|
addNotification({ type: 'error', title, message, duration: 0, ...options });
|
|
};
|
|
|
|
const warning = (title: string, message?: string, options?: Partial<Notification>) => {
|
|
addNotification({ type: 'warning', title, message, ...options });
|
|
};
|
|
|
|
const info = (title: string, message?: string, options?: Partial<Notification>) => {
|
|
addNotification({ type: 'info', title, message, ...options });
|
|
};
|
|
|
|
return {
|
|
notifications,
|
|
addNotification,
|
|
removeNotification,
|
|
clearAll,
|
|
success,
|
|
error,
|
|
warning,
|
|
info
|
|
};
|
|
};
|
|
|
|
// 全局通知管理器
|
|
class GlobalNotificationManager {
|
|
private listeners: Array<(notifications: Notification[]) => void> = [];
|
|
private notifications: Notification[] = [];
|
|
|
|
subscribe(listener: (notifications: Notification[]) => void) {
|
|
this.listeners.push(listener);
|
|
return () => {
|
|
this.listeners = this.listeners.filter(l => l !== listener);
|
|
};
|
|
}
|
|
|
|
private notify() {
|
|
this.listeners.forEach(listener => listener([...this.notifications]));
|
|
}
|
|
|
|
add(notification: Omit<Notification, 'id'>) {
|
|
const id = `global-notification-${Date.now()}-${Math.random()}`;
|
|
this.notifications.push({ ...notification, id });
|
|
this.notify();
|
|
}
|
|
|
|
remove(id: string) {
|
|
this.notifications = this.notifications.filter(n => n.id !== id);
|
|
this.notify();
|
|
}
|
|
|
|
clear() {
|
|
this.notifications = [];
|
|
this.notify();
|
|
}
|
|
|
|
success(title: string, message?: string, options?: Partial<Notification>) {
|
|
this.add({ type: 'success', title, message, ...options });
|
|
}
|
|
|
|
error(title: string, message?: string, options?: Partial<Notification>) {
|
|
this.add({ type: 'error', title, message, duration: 0, ...options });
|
|
}
|
|
|
|
warning(title: string, message?: string, options?: Partial<Notification>) {
|
|
this.add({ type: 'warning', title, message, ...options });
|
|
}
|
|
|
|
info(title: string, message?: string, options?: Partial<Notification>) {
|
|
this.add({ type: 'info', title, message, ...options });
|
|
}
|
|
}
|
|
|
|
export const globalNotifications = new GlobalNotificationManager();
|
|
|
|
// Hook for global notifications
|
|
export const useGlobalNotifications = () => {
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
|
|
useEffect(() => {
|
|
return globalNotifications.subscribe(setNotifications);
|
|
}, []);
|
|
|
|
return {
|
|
notifications,
|
|
removeNotification: globalNotifications.remove.bind(globalNotifications)
|
|
};
|
|
};
|