import { useEffect, useState } from 'react' import { Button, Empty, Space, 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' import { EVAL_STATUS } from './status' const STATUS_META: Record = { pending: { label: '待认领', color: 'orange' }, assigned: { label: '执行中', color: 'blue' }, completed: { label: '已完成', color: 'green' }, failed: { label: '失败', color: 'red' }, } type FilterKey = 'all' | 'unresolved' | TaskQueueStatus /** * 任务队列监控(方案③可视化,与评估列表页统一 stat-chip + 单行表格)。 * * 平台每 60s 扫描 executing 评估入队 + 触发 OpenClaw worker 执行。本组件 * 展示任务明细与状态分布,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 [filter, setFilter] = useState('all') const [loading, setLoading] = useState(false) const loadData = async () => { setLoading(true) try { const res = await intelligentEvalsApi.listTasks( filter === 'all' || filter === 'unresolved' ? undefined : { status: filter }, ) setTasks(res.data.tasks) setStats(res.data.stats) } catch { message.error('加载任务队列失败') } finally { setLoading(false) } } // Initial fetch + reload on filter change (usePolling owns the 5s interval). useEffect(() => { void loadData() // eslint-disable-next-line react-hooks/exhaustive-deps }, [filter]) usePolling(() => { void loadData() }, 5000, true) const chipMeta: { key: FilterKey; label: string; color?: string; get: () => number }[] = [ { key: 'all', label: '全部任务', get: () => (stats ? Object.values(stats).reduce((a, b) => a + b, 0) : (tasks?.length ?? 0)) }, { key: 'unresolved', label: '待处理', color: colors.warning, get: () => stats?.unresolved ?? 0 }, { key: 'pending', label: '待认领', get: () => stats?.pending ?? 0 }, { key: 'assigned', label: '执行中', color: colors.primary, get: () => stats?.assigned ?? 0 }, { key: 'completed', label: '已完成', color: statusColors.completed, get: () => stats?.completed ?? 0 }, { key: 'failed', label: '失败', color: statusColors.failed, get: () => stats?.failed ?? 0 }, ] const columns: ColumnsType = [ { title: '入队时间', dataIndex: 'created_at', key: 'created_at', width: 170, render: (v: string | null) => (v ? formatDateTime(v) : '—'), }, { title: '评估', key: 'eval', width: 300, ellipsis: true, render: (_, t) => ( {t.eval_name ?? t.eval_id.slice(0, 8)} {t.eval_status && EVAL_STATUS[t.eval_status as keyof typeof EVAL_STATUS] && ( {EVAL_STATUS[t.eval_status as keyof typeof EVAL_STATUS].label} )} ), }, { 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', width: 240, ellipsis: true, render: (r: string) => {r}, }, { 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 (
{chipMeta.map((c) => (
setFilter(c.key)} >
{c.get()}
{c.label}
))}
`共 ${t} 条`, }} locale={{ emptyText: }} /> ) }