P1 修复内容: - F7: health handler 连接池容量检查 (80%阈值返回503 degraded) - F9: SSE spawned task 并发限制 (Semaphore 16 permits) - F10: Key Pool 单次 JOIN 查询优化 (消除 N+1) - F12: CORS panic → 配置错误 - F14: 连接池使用率计算修正 (ratio = used*100/total) - F15: SQL 迁移解析器替换为状态机 (支持 $$, DO $body$, 存储过程) - Worker 重试机制: 失败任务通过 mpsc channel 重新入队 - DOMPurify XSS 防护 (PipelineResultPreview) - Admin V2: ErrorBoundary + SWR全局配置 + 请求优化
191 lines
7.2 KiB
TypeScript
191 lines
7.2 KiB
TypeScript
// ============================================================
|
|
// Agent 模板管理
|
|
// ============================================================
|
|
|
|
import { useState } from 'react'
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import { Button, message, Tag, Modal, Form, Input, Select, InputNumber, Space, Popconfirm, Descriptions } from 'antd'
|
|
import { PlusOutlined } from '@ant-design/icons'
|
|
import type { ProColumns } from '@ant-design/pro-components'
|
|
import { ProTable } from '@ant-design/pro-components'
|
|
import { agentTemplateService } from '@/services/agent-templates'
|
|
import type { AgentTemplate } from '@/types'
|
|
|
|
const { TextArea } = Input
|
|
|
|
const sourceLabels: Record<string, string> = { builtin: '内置', custom: '自定义' }
|
|
const visibilityLabels: Record<string, string> = { public: '公开', team: '团队', private: '私有' }
|
|
const statusLabels: Record<string, string> = { active: '活跃', archived: '已归档' }
|
|
const statusColors: Record<string, string> = { active: 'green', archived: 'default' }
|
|
|
|
export default function AgentTemplates() {
|
|
const queryClient = useQueryClient()
|
|
const [form] = Form.useForm()
|
|
const [modalOpen, setModalOpen] = useState(false)
|
|
const [detailRecord, setDetailRecord] = useState<AgentTemplate | null>(null)
|
|
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['agent-templates'],
|
|
queryFn: ({ signal }) => agentTemplateService.list(signal),
|
|
})
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: (data: Parameters<typeof agentTemplateService.create>[0]) =>
|
|
agentTemplateService.create(data),
|
|
onSuccess: () => {
|
|
message.success('创建成功')
|
|
queryClient.invalidateQueries({ queryKey: ['agent-templates'] })
|
|
setModalOpen(false)
|
|
form.resetFields()
|
|
},
|
|
onError: (err: Error) => message.error(err.message || '创建失败'),
|
|
})
|
|
|
|
const archiveMutation = useMutation({
|
|
mutationFn: (id: string) => agentTemplateService.archive(id),
|
|
onSuccess: () => {
|
|
message.success('已归档')
|
|
queryClient.invalidateQueries({ queryKey: ['agent-templates'] })
|
|
},
|
|
onError: (err: Error) => message.error(err.message || '归档失败'),
|
|
})
|
|
|
|
const columns: ProColumns<AgentTemplate>[] = [
|
|
{ title: '名称', dataIndex: 'name', width: 160 },
|
|
{ title: '分类', dataIndex: 'category', width: 100 },
|
|
{ title: '模型', dataIndex: 'model', width: 140, render: (_, r) => r.model || '-' },
|
|
{
|
|
title: '来源',
|
|
dataIndex: 'source',
|
|
width: 80,
|
|
render: (_, r) => <Tag>{sourceLabels[r.source] || r.source}</Tag>,
|
|
},
|
|
{
|
|
title: '可见性',
|
|
dataIndex: 'visibility',
|
|
width: 80,
|
|
render: (_, r) => <Tag color="blue">{visibilityLabels[r.visibility] || r.visibility}</Tag>,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 80,
|
|
render: (_, r) => <Tag color={statusColors[r.status]}>{statusLabels[r.status] || r.status}</Tag>,
|
|
},
|
|
{ title: '版本', dataIndex: 'current_version', width: 70 },
|
|
{
|
|
title: '操作',
|
|
width: 180,
|
|
render: (_, record) => (
|
|
<Space>
|
|
<Button size="small" onClick={() => setDetailRecord(record)}>详情</Button>
|
|
{record.status === 'active' && (
|
|
<Popconfirm title="确定归档此模板?" onConfirm={() => archiveMutation.mutate(record.id)}>
|
|
<Button size="small" danger>归档</Button>
|
|
</Popconfirm>
|
|
)}
|
|
</Space>
|
|
),
|
|
},
|
|
]
|
|
|
|
const handleCreate = async () => {
|
|
const values = await form.validateFields()
|
|
createMutation.mutate(values)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<ProTable<AgentTemplate>
|
|
columns={columns}
|
|
dataSource={data?.items ?? []}
|
|
loading={isLoading}
|
|
rowKey="id"
|
|
search={false}
|
|
toolBarRender={() => [
|
|
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => { form.resetFields(); setModalOpen(true) }}>
|
|
新建模板
|
|
</Button>,
|
|
]}
|
|
pagination={{
|
|
total: data?.total ?? 0,
|
|
pageSize: data?.page_size ?? 20,
|
|
current: data?.page ?? 1,
|
|
showSizeChanger: false,
|
|
}}
|
|
/>
|
|
|
|
<Modal
|
|
title="新建 Agent 模板"
|
|
open={modalOpen}
|
|
onOk={handleCreate}
|
|
onCancel={() => { setModalOpen(false); form.resetFields() }}
|
|
confirmLoading={createMutation.isPending}
|
|
width={640}
|
|
>
|
|
<Form form={form} layout="vertical">
|
|
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item name="description" label="描述">
|
|
<TextArea rows={2} />
|
|
</Form.Item>
|
|
<Form.Item name="category" label="分类">
|
|
<Input placeholder="如 assistant, tool" />
|
|
</Form.Item>
|
|
<Form.Item name="model" label="默认模型">
|
|
<Input placeholder="如 gpt-4o" />
|
|
</Form.Item>
|
|
<Form.Item name="system_prompt" label="系统提示词">
|
|
<TextArea rows={4} />
|
|
</Form.Item>
|
|
<Form.Item name="temperature" label="Temperature">
|
|
<InputNumber min={0} max={2} step={0.1} style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
<Form.Item name="max_tokens" label="最大 Token">
|
|
<InputNumber min={1} style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
<Form.Item name="visibility" label="可见性">
|
|
<Select options={[
|
|
{ value: 'public', label: '公开' },
|
|
{ value: 'team', label: '团队' },
|
|
{ value: 'private', label: '私有' },
|
|
]} />
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
<Modal
|
|
title="模板详情"
|
|
open={!!detailRecord}
|
|
onCancel={() => setDetailRecord(null)}
|
|
footer={null}
|
|
width={640}
|
|
>
|
|
{detailRecord && (
|
|
<Descriptions column={2} bordered size="small">
|
|
<Descriptions.Item label="名称">{detailRecord.name}</Descriptions.Item>
|
|
<Descriptions.Item label="分类">{detailRecord.category}</Descriptions.Item>
|
|
<Descriptions.Item label="模型">{detailRecord.model || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="来源">{sourceLabels[detailRecord.source]}</Descriptions.Item>
|
|
<Descriptions.Item label="可见性">{visibilityLabels[detailRecord.visibility]}</Descriptions.Item>
|
|
<Descriptions.Item label="状态">{statusLabels[detailRecord.status]}</Descriptions.Item>
|
|
<Descriptions.Item label="描述" span={2}>{detailRecord.description || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="系统提示词" span={2}>
|
|
<div style={{ whiteSpace: 'pre-wrap', maxHeight: 200, overflow: 'auto' }}>
|
|
{detailRecord.system_prompt || '-'}
|
|
</div>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="工具" span={2}>
|
|
{detailRecord.tools?.map((t) => <Tag key={t}>{t}</Tag>) || '-'}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="能力" span={2}>
|
|
{detailRecord.capabilities?.map((c) => <Tag key={c} color="blue">{c}</Tag>) || '-'}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
)}
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|