feat: V1 - DAG编排/告警系统/Agent循环/知识库RAG/Webhook + 审核管理富上下文修复
This commit is contained in:
+214
-72
@@ -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';
|
||||
Reference in New Issue
Block a user