feat: V1 - DAG编排/告警系统/Agent循环/知识库RAG/Webhook + 审核管理富上下文修复

This commit is contained in:
2026-08-13 00:47:29 +08:00
parent 081c37ed08
commit caf871ecad
+214 -72
View File
@@ -1,13 +1,14 @@
import { useState, useEffect } from 'react';
import {
Card, Table, Button, Modal, Tag, Space, message, Input,
Typography, Empty, Badge, Tooltip,
Typography, Empty, Badge, Tooltip, Descriptions,
} from 'antd';
import {
CheckOutlined, CloseOutlined, EyeOutlined, FileSearchOutlined,
} from '@ant-design/icons';
import { reviewApi } from '@/api';
import type { Review, ReviewDecisionPayload } from '@/types';
import type { Review, ReviewDecisionPayload, TaskPriority } from '@/types';
import { PriorityTag } from '@/components/constants';
const { TextArea } = Input;
const { Text, Paragraph } = Typography;
@@ -26,6 +27,26 @@ const reviewTypeConfig: Record<string, string> = {
quality_check: '质量检查',
};
/** 格式化成本(分 -> 元) */
const formatCost = (cents?: number) => {
if (cents === undefined || cents === null) return '¥0.00';
return `¥${(cents / 100).toFixed(2)}`;
};
/** 尝试从任务输入数据中解析出 prompt 字段,否则返回原始内容 */
const parseTaskInput = (raw?: string): string => {
if (!raw) return '-';
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object' && 'prompt' in parsed) {
return String(parsed.prompt);
}
return JSON.stringify(parsed, null, 2);
} catch {
return raw;
}
};
export default function Reviews() {
const [reviews, setReviews] = useState<Review[]>([]);
const [loading, setLoading] = useState(false);
@@ -85,71 +106,128 @@ export default function Reviews() {
width: 60,
},
{
title: '任务',
title: '任务信息',
key: 'task',
render: (_: any, record: Review) => (
<Space direction="vertical" size={0}>
<Text strong> #{record.task_id}</Text>
<Text type="secondary" style={{ fontSize: 12 }}>
#{record.project_id}
width: 240,
render: (_: unknown, record: Review) => (
<Space direction="vertical" size={0} style={{ width: '100%' }}>
<Text strong ellipsis style={{ maxWidth: 220 }}>
{record.task_title || `任务 #${record.task_id}`}
</Text>
{record.task_description && (
<Tooltip title={record.task_description}>
<Text
type="secondary"
ellipsis
style={{ fontSize: 12, maxWidth: 220 }}
>
{record.task_description}
</Text>
</Tooltip>
)}
</Space>
),
},
{
title: '所属项目',
key: 'project',
width: 140,
render: (_: unknown, record: Review) => (
<Text ellipsis style={{ maxWidth: 130 }}>
{record.project_name || `项目 #${record.project_id}`}
</Text>
),
},
{
title: '执行Worker',
key: 'worker',
width: 200,
render: (_: unknown, record: Review) => {
if (!record.worker_name && !record.worker_model_name) {
return <Text type="secondary">-</Text>;
}
return (
<Tooltip
title={
record.worker_provider
? `${record.worker_provider || '-'} / ${record.worker_model_name || '-'}`
: record.worker_model_name || ''
}
>
<Text style={{ fontSize: 12 }}>
{record.worker_name || '-'}{' / '}{record.worker_model_name || ''}
</Text>
</Tooltip>
);
},
},
{
title: '审核类型',
dataIndex: 'review_type',
key: 'review_type',
width: 100,
render: (type: string) => (
<Tag>{reviewTypeConfig[type] || type}</Tag>
),
},
{
title: '优先级',
dataIndex: 'task_priority',
key: 'task_priority',
width: 80,
render: (priority: string) =>
priority ? <PriorityTag priority={priority as TaskPriority} /> : <Text type="secondary">-</Text>,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => {
const cfg = reviewStatusConfig[status] || { label: status, color: 'default' };
return <Badge status={cfg.color as any} text={cfg.label} />;
return <Badge status={cfg.color as BadgeStatus} text={cfg.label} />;
},
},
{
title: '成本',
key: 'cost',
width: 140,
render: (_: unknown, record: Review) => (
<Space direction="vertical" size={0}>
<Text style={{ fontSize: 12 }}>
{record.token_usage != null ? `${record.token_usage} tokens` : '-'}
</Text>
<Text type="secondary" style={{ fontSize: 12 }}>
{formatCost(record.cost_cents)}
</Text>
</Space>
),
},
{
title: '提交时间',
dataIndex: 'submitted_at',
key: 'submitted_at',
width: 160,
render: (time: string) => (
<Text style={{ fontSize: 12 }}>
{time ? new Date(time).toLocaleString('zh-CN') : '-'}
</Text>
),
},
{
title: '审核人',
key: 'reviewer',
render: (_: any, record: Review) => (
record.reviewer_id ? (
<Text style={{ fontSize: 12 }}>
#{record.reviewer_id}
{record.reviewer_role ? ` (${record.reviewer_role})` : ''}
</Text>
) : (
<Text type="secondary">-</Text>
)
),
},
{
title: '操作',
key: 'action',
width: 220,
render: (_: any, record: Review) => (
fixed: 'right' as const,
render: (_: unknown, record: Review) => (
<Space>
<Tooltip title="查看详情">
<Button
size="small"
icon={<EyeOutlined />}
onClick={() => setDetailModal(record)}
/>
</Tooltip>
<Button
size="small"
icon={<EyeOutlined />}
onClick={() => setDetailModal(record)}
>
</Button>
{record.status === 'pending' && (
<>
<Button
@@ -211,70 +289,131 @@ export default function Reviews() {
rowKey="id"
loading={loading}
pagination={{ pageSize: 10 }}
scroll={{ x: 1400 }}
/>
)}
</Card>
{/* 审核详情弹窗 */}
<Modal
title={`审核详情 #${detailModal?.id || ''}`}
title={
detailModal ? (
<Space>
<Tag>{reviewTypeConfig[detailModal.review_type] || detailModal.review_type}</Tag>
<Badge
status={reviewStatusConfig[detailModal.status]?.color as BadgeStatus}
text={reviewStatusConfig[detailModal.status]?.label || detailModal.status}
/>
{detailModal.task_priority && (
<PriorityTag priority={detailModal.task_priority as TaskPriority} />
)}
</Space>
) : (
'审核详情'
)
}
open={!!detailModal}
onCancel={() => setDetailModal(null)}
footer={<Button onClick={() => setDetailModal(null)}></Button>}
width={640}
width={800}
>
{detailModal && (
<div>
<Space style={{ marginBottom: 12 }}>
<Tag>{reviewTypeConfig[detailModal.review_type] || detailModal.review_type}</Tag>
<Badge
status={reviewStatusConfig[detailModal.status]?.color as any}
text={reviewStatusConfig[detailModal.status]?.label || detailModal.status}
/>
</Space>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
{/* 1. 任务信息 */}
<Card size="small" title="任务信息">
<Descriptions size="small" column={1} colon>
<Descriptions.Item label="任务标题">
<Text strong>{detailModal.task_title || `任务 #${detailModal.task_id}`}</Text>
</Descriptions.Item>
{detailModal.task_description && (
<Descriptions.Item label="任务描述">
<Text>{detailModal.task_description}</Text>
</Descriptions.Item>
)}
<Descriptions.Item label="任务类型">
<Tag>{detailModal.task_type || '-'}</Tag>
</Descriptions.Item>
<Descriptions.Item label="任务输入">
<Paragraph
style={{ whiteSpace: 'pre-wrap', marginBottom: 0, maxHeight: 180, overflow: 'auto' }}
>
{parseTaskInput(detailModal.task_input_data)}
</Paragraph>
</Descriptions.Item>
</Descriptions>
</Card>
<div style={{ marginBottom: 12 }}>
<Text type="secondary"> ID: </Text>
<Text strong>{detailModal.task_id}</Text>
<Text type="secondary"> | ID: </Text>
<Text strong>{detailModal.project_id}</Text>
</div>
{/* 2. 所属项目 */}
<Card size="small" title="所属项目">
<Descriptions size="small" column={1} colon>
<Descriptions.Item label="项目名称">
<Text strong>{detailModal.project_name || `项目 #${detailModal.project_id}`}</Text>
</Descriptions.Item>
{detailModal.project_description && (
<Descriptions.Item label="项目描述">
<Text>{detailModal.project_description}</Text>
</Descriptions.Item>
)}
</Descriptions>
</Card>
<div style={{ marginBottom: 12 }}>
<Text type="secondary">: </Text>
<Text>
{detailModal.submitted_at
? new Date(detailModal.submitted_at).toLocaleString('zh-CN')
: '-'}
</Text>
</div>
{/* 3. 执行Worker */}
<Card size="small" title="执行Worker">
<Descriptions size="small" column={1} colon>
<Descriptions.Item label="Worker名称">
<Text>{detailModal.worker_name || '-'}</Text>
</Descriptions.Item>
<Descriptions.Item label="模型">
<Text>
{detailModal.worker_provider || '-'}{' / '}{detailModal.worker_model_name || '-'}
</Text>
</Descriptions.Item>
{detailModal.worker_system_prompt && (
<Descriptions.Item label="系统提示词">
<Paragraph
ellipsis={{ rows: 3, expandable: true, symbol: '展开' }}
style={{ marginBottom: 0, whiteSpace: 'pre-wrap' }}
>
{detailModal.worker_system_prompt}
</Paragraph>
</Descriptions.Item>
)}
</Descriptions>
</Card>
{detailModal.reviewed_at && (
<div style={{ marginBottom: 12 }}>
<Text type="secondary">: </Text>
<Text>{new Date(detailModal.reviewed_at).toLocaleString('zh-CN')}</Text>
</div>
)}
<Text type="secondary">AI :</Text>
{/* 4. AI产出内容 */}
<Card
size="small"
style={{ marginTop: 8, marginBottom: 12, maxHeight: 300, overflow: 'auto' }}
title="AI产出内容"
style={{ maxHeight: 320, overflow: 'auto' }}
>
<Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>
{detailModal.review_content}
{detailModal.review_content || '-'}
</Paragraph>
</Card>
{/* 5. 执行消耗 */}
<Card size="small" title="执行消耗">
<Descriptions size="small" column={3} colon>
<Descriptions.Item label="Token用量">
{detailModal.token_usage != null ? detailModal.token_usage : '-'}
</Descriptions.Item>
<Descriptions.Item label="执行耗时">
{detailModal.duration_ms != null ? `${detailModal.duration_ms} ms` : '-'}
</Descriptions.Item>
<Descriptions.Item label="成本">
<Text strong>{formatCost(detailModal.cost_cents)}</Text>
</Descriptions.Item>
</Descriptions>
</Card>
{/* 6. 审核意见 */}
{detailModal.reviewer_comment && (
<div>
<Text type="secondary">:</Text>
<Card size="small" style={{ marginTop: 8 }}>
<Text>{detailModal.reviewer_comment}</Text>
</Card>
</div>
<Card size="small" title="审核意见">
<Text>{detailModal.reviewer_comment}</Text>
</Card>
)}
</div>
</Space>
)}
</Modal>
@@ -309,3 +448,6 @@ export default function Reviews() {
</div>
);
}
/** antd Badge `status` prop accepted values. */
type BadgeStatus = 'success' | 'processing' | 'default' | 'error' | 'warning';