import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Card, List, Button, Space, Badge, Tag, Progress, Popconfirm, Empty, theme, Tooltip, message } from 'antd'; import { ClockCircleOutlined, LoadingOutlined, CheckCircleOutlined, CloseCircleOutlined, ReloadOutlined, DeleteOutlined, UpOutlined, DownOutlined, ClearOutlined, } from '@ant-design/icons'; import { getProjectTasks, cancelTask, cancelBatchTask, deleteTask, clearProjectTasks, type TaskStatus } from '../services/backgroundTaskService'; import { eventBus } from '../store/eventBus'; interface FloatingTaskPanelProps { projectId: string; autoRefreshInterval?: number; // 自动刷新间隔(毫秒),默认3000 } /** * 悬浮任务框组件 * 显示在页面右下角,支持收起/展开 */ export const FloatingTaskPanel: React.FC = ({ projectId, autoRefreshInterval = 3000, }) => { const [taskList, setTaskList] = useState([]); const [loading, setLoading] = useState(false); const [collapsed, setCollapsed] = useState(true); // 默认收起 const userCollapsedRef = useRef(false); // 用户手动收起标记 const { token } = theme.useToken(); // 加载任务列表 const loadTasks = useCallback(async () => { if (!projectId) return; setLoading(true); try { const result = await getProjectTasks(projectId); setTaskList(result.items || []); } catch (error) { console.error('加载任务列表失败:', error); } finally { setLoading(false); } }, [projectId]); // 初始加载 useEffect(() => { loadTasks(); }, [loadTasks]); // 监听后台任务创建事件,立即刷新列表并展开浮窗 useEffect(() => { const handleTaskCreated = () => { loadTasks(); // 创建新任务时自动展开(重置用户手动收起标记) userCollapsedRef.current = false; setCollapsed(false); }; eventBus.on('background-task-created', handleTaskCreated); return () => { eventBus.off('background-task-created', handleTaskCreated); }; }, [loadTasks]); // 有活跃任务时自动展开(仅当用户没有手动收起时) useEffect(() => { const hasActiveTasks = taskList.some( (t) => t.status === 'running' || t.status === 'pending' ); if (hasActiveTasks && !userCollapsedRef.current) { setCollapsed(false); } }, [taskList]); // 自动刷新(仅当有运行中或等待中的任务时) useEffect(() => { const hasActiveTasks = taskList.some( (t) => t.status === 'running' || t.status === 'pending' ); if (!hasActiveTasks) return; const timer = setInterval(loadTasks, autoRefreshInterval); return () => clearInterval(timer); }, [taskList, autoRefreshInterval, loadTasks]); // 取消任务 const handleCancelTask = async (task: TaskStatus) => { try { if (task.task_type === 'chapter_batch') { await cancelBatchTask(task.id); } else { await cancelTask(task.id); } loadTasks(); } catch (error) { console.error('取消任务失败:', error); } }; // 删除任务记录 const handleDeleteTask = async (taskId: string) => { try { await deleteTask(taskId); loadTasks(); } catch (error) { console.error('删除任务记录失败:', error); } }; // 一键清理已结束的任务记录 const handleClearTasks = async () => { try { const result = await clearProjectTasks(projectId); message.success(`已清理 ${result.deleted_count} 条任务记录`); loadTasks(); } catch (error) { console.error('清理任务记录失败:', error); message.error('清理任务记录失败'); } }; // 获取任务状态标签 const getTaskStatusTag = (status: TaskStatus['status']) => { switch (status) { case 'pending': return } color="default">等待中; case 'running': return } color="processing">运行中; case 'completed': return } color="success">已完成; case 'failed': return } color="error">失败; case 'cancelled': return } color="default">已取消; default: return {status}; } }; // 获取任务类型标签 const getTaskTypeLabel = (taskType: string) => { switch (taskType) { case 'outline_new': return '大纲生成'; case 'outline_continue': return '大纲续写'; case 'outline_expand': return '大纲展开'; case 'outline_batch_expand': return '批量大纲展开'; case 'chapter_generate': return '章节生成'; case 'chapter_batch': return '批量章节生成'; case 'wizard': return '向导创建'; default: return taskType; } }; const activeTasks = taskList.filter((t) => t.status === 'running' || t.status === 'pending'); const hasActiveTasks = activeTasks.length > 0; // 没有任务时不显示浮窗 if (taskList.length === 0) return null; return (
后台任务 {hasActiveTasks && } } extra={ )} {(task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled') && ( handleDeleteTask(task.id)} okText="确认" cancelText="取消" > )}
)} /> )} )} ); }; export default FloatingTaskPanel;