feat: M4-07 — Hermes agent tasks + artifacts tabs with approval buttons
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s

- Replace iframe-only HermesSettings with 3-tab page
- Agent tasks table with approve/reject buttons
- Agent artifacts table with type labels
- Keep Hermes panel iframe tab

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
WangDL 2026-05-24 18:11:04 +08:00
parent cb83928d7a
commit 8b6c957a30

View File

@ -1,11 +1,85 @@
import { Tabs, Table, Tag, Button, Space, Typography } from 'antd'
import { CheckOutlined, CloseOutlined, RobotOutlined } from '@ant-design/icons'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { api } from '@/services/http-client'
import { message } from 'antd'
const { Title } = Typography
const statusColors: Record<string, string> = { pending: 'blue', approved: 'green', rejected: 'red', completed: 'green' }
const artifactTypeLabels: Record<string, string> = { issue_draft: 'Issue 草稿', milestone_summary: '里程碑总结', release_notes: '发布说明', decision_record: '决策记录' }
export default function HermesSettings() { export default function HermesSettings() {
const qc = useQueryClient()
const { data: tasks, isLoading: tLoading } = useQuery({
queryKey: ['hermes', 'tasks'],
queryFn: () => api.get<any[]>('/admin-api/hermes/tasks').then(d => d ?? []),
})
const { data: artifacts, isLoading: aLoading } = useQuery({
queryKey: ['hermes', 'artifacts'],
queryFn: () => api.get<any[]>('/admin-api/hermes/artifacts').then(d => d ?? []),
})
const approveTask = useMutation({
mutationFn: (id: string) => api.post(`/admin-api/hermes/tasks/${id}/approve`),
onSuccess: () => { message.success('已通过'); qc.invalidateQueries({ queryKey: ['hermes', 'tasks'] }) },
})
const rejectTask = useMutation({
mutationFn: (id: string) => api.post(`/admin-api/hermes/tasks/${id}/reject`),
onSuccess: () => { message.success('已驳回'); qc.invalidateQueries({ queryKey: ['hermes', 'tasks'] }) },
})
const taskColumns = [
{ title: 'ID', dataIndex: 'id', width: 80, ellipsis: true },
{ title: '类型', dataIndex: 'type', width: 90 },
{ title: '输入', dataIndex: 'input', width: 180, ellipsis: true, render: (i: string) => i ? i.slice(0, 100) : '-' },
{ title: '输出', dataIndex: 'output', width: 200, ellipsis: true, render: (o: string) => o ? o.slice(0, 120) : '-' },
{ title: '状态', dataIndex: 'status', width: 70, render: (s: string) => <Tag color={statusColors[s] || 'default'}>{s}</Tag> },
{ title: '创建时间', dataIndex: 'createdAt', width: 110, render: (d: string) => new Date(d).toLocaleString() },
{
title: '操作', width: 120,
render: (_: any, r: any) => r.status === 'pending' ? (
<Space>
<Button size="small" type="primary" icon={<CheckOutlined />} onClick={() => approveTask.mutate(r.id)}></Button>
<Button size="small" danger icon={<CloseOutlined />} onClick={() => rejectTask.mutate(r.id)}></Button>
</Space>
) : null,
},
]
const artifactColumns = [
{ title: 'ID', dataIndex: 'id', width: 80, ellipsis: true },
{ title: '类型', dataIndex: 'type', width: 100, render: (t: string) => artifactTypeLabels[t] || t },
{ title: '标题', dataIndex: 'title', width: 200, ellipsis: true },
{ title: '内容', dataIndex: 'content', width: 250, ellipsis: true, render: (c: string) => c ? c.slice(0, 150) : '-' },
{ title: '状态', dataIndex: 'status', width: 70, render: (s: string) => <Tag color={s === 'draft' ? 'orange' : 'green'}>{s}</Tag> },
{ title: '创建时间', dataIndex: 'createdAt', width: 110, render: (d: string) => new Date(d).toLocaleString() },
]
return ( return (
<div style={{ width: '100%', height: 'calc(100vh - 112px)', overflow: 'hidden' }}> <div>
<iframe <Title level={4}><RobotOutlined /> Hermes Agent</Title>
src="https://hermes.admin.longde.cloud" <Tabs defaultActiveKey="tasks" items={[
style={{ width: '100%', height: '100%', border: 'none' }} {
title="Hermes Agent" key: 'tasks', label: 'Agent 任务',
/> children: <Table dataSource={tasks || []} columns={taskColumns} rowKey="id" loading={tLoading} size="small" scroll={{ x: 950 }} />,
},
{
key: 'artifacts', label: '产出物',
children: <Table dataSource={artifacts || []} columns={artifactColumns} rowKey="id" loading={aLoading} size="small" scroll={{ x: 900 }} />,
},
{
key: 'chat', label: 'Hermes 面板',
children: (
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<iframe src="https://hermes.admin.longde.cloud" style={{ width: '100%', height: '100%', border: 'none' }} title="Hermes Agent" />
</div>
),
},
]} />
</div> </div>
) )
} }