import { useEffect, useState } from 'react' import { Alert, Button, Empty, Select, Space, Statistic, Table, Tag, Tooltip, message, } from 'antd' import type { ColumnsType } from 'antd/es/table' import { ReloadOutlined } from '@ant-design/icons' import { intelligentEvalsApi, type TaskQueueItem, type TaskQueueStatus } from '../../api' import { usePolling } from '../../hooks/usePolling' import { colors, statusColors } from '../../tokens' import { formatDateTime } from '../../utils/date' const STATUS_META: Record = { pending: { label: '待处理', color: 'orange' }, assigned: { label: '执行中', color: 'blue' }, completed: { label: '已完成', color: 'green' }, failed: { label: '失败', color: 'red' }, } const STATUS_OPTIONS = (Object.keys(STATUS_META) as TaskQueueStatus[]).map((s) => ({ value: s, label: STATUS_META[s].label, })) /** * 任务队列监控(方案③可视化)。 * * 方案③的"定时触发"(scan loop 每 60s 扫描入队 + 触发 OpenClaw worker) * 此前只有 Worker 消费端 API,无可查看的列表。这里展示任务队列明细与 * 状态分布,5s 轮询,让平台侧的定时触发对用户可见。 */ export default function TaskQueueMonitor() { const [tasks, setTasks] = useState(null) const [stats, setStats] = useState<{ pending: number; assigned: number; completed: number; failed: number; unresolved: number } | null>(null) const [statusFilter, setStatusFilter] = useState('all') const [loading, setLoading] = useState(false) const loadData = async () => { setLoading(true) try { const res = await intelligentEvalsApi.listTasks( statusFilter === 'all' ? undefined : { status: statusFilter }, ) setTasks(res.data.tasks) setStats(res.data.stats) } catch { message.error('加载任务队列失败') } finally { setLoading(false) } } // Initial fetch (usePolling owns the 5s interval + visibility pause). useEffect(() => { void loadData() // eslint-disable-next-line react-hooks/exhaustive-deps }, [statusFilter]) usePolling(() => { void loadData() }, 5000, true) const columns: ColumnsType = [ { title: '入队时间', dataIndex: 'created_at', key: 'created_at', width: 170, render: (v: string | null) => (v ? formatDateTime(v) : '—'), }, { title: '评估', dataIndex: 'eval_name', key: 'eval_name', render: (name: string | null, t) => ( {name ?? t.eval_id.slice(0, 8)} {t.eval_status && ( {t.eval_status} )} ), }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (s: TaskQueueStatus) => { const meta = STATUS_META[s] return {meta.label} }, }, { title: '优先级', dataIndex: 'priority', key: 'priority', width: 80, render: (p: number) => {p}, }, { title: '原因', dataIndex: 'reason', key: 'reason', render: (r: string) => ( {r} ), }, { title: '认领方', dataIndex: 'assigned_cron_id', key: 'assigned_cron_id', width: 140, render: (v: string | null, t) => { if (!v) return return ( {v} ) }, }, { title: '完成时间', dataIndex: 'completed_at', key: 'completed_at', width: 170, render: (v: string | null, t) => { if (t.status === 'failed' && t.error) { return 失败 } return v ? formatDateTime(v) : '—' }, }, ] return (