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全局配置 + 请求优化
229 lines
8.2 KiB
TypeScript
229 lines
8.2 KiB
TypeScript
// ============================================================
|
||
// 提示词管理
|
||
// ============================================================
|
||
|
||
import { useState } from 'react'
|
||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||
import { Button, message, Tag, Modal, Form, Input, Select, Space, Popconfirm, Descriptions, Tabs, Typography } from 'antd'
|
||
import { PlusOutlined } from '@ant-design/icons'
|
||
import type { ProColumns } from '@ant-design/pro-components'
|
||
import { ProTable } from '@ant-design/pro-components'
|
||
import { promptService } from '@/services/prompts'
|
||
import type { PromptTemplate, PromptVersion } from '@/types'
|
||
|
||
const { TextArea } = Input
|
||
const { Text } = Typography
|
||
|
||
const sourceLabels: Record<string, string> = { builtin: '内置', custom: '自定义' }
|
||
const statusLabels: Record<string, string> = { active: '活跃', deprecated: '已废弃', archived: '已归档' }
|
||
const statusColors: Record<string, string> = { active: 'green', deprecated: 'orange', archived: 'default' }
|
||
|
||
export default function Prompts() {
|
||
const queryClient = useQueryClient()
|
||
const [form] = Form.useForm()
|
||
const [createOpen, setCreateOpen] = useState(false)
|
||
const [detailName, setDetailName] = useState<string | null>(null)
|
||
|
||
const { data, isLoading } = useQuery({
|
||
queryKey: ['prompts'],
|
||
queryFn: ({ signal }) => promptService.list(signal),
|
||
})
|
||
|
||
const { data: detailData } = useQuery({
|
||
queryKey: ['prompt-detail', detailName],
|
||
queryFn: ({ signal }) => promptService.get(detailName!, signal),
|
||
enabled: !!detailName,
|
||
})
|
||
|
||
const { data: versionsData } = useQuery({
|
||
queryKey: ['prompt-versions', detailName],
|
||
queryFn: ({ signal }) => promptService.listVersions(detailName!, signal),
|
||
enabled: !!detailName,
|
||
})
|
||
|
||
const createMutation = useMutation({
|
||
mutationFn: (data: Parameters<typeof promptService.create>[0]) => promptService.create(data),
|
||
onSuccess: () => {
|
||
message.success('创建成功')
|
||
queryClient.invalidateQueries({ queryKey: ['prompts'] })
|
||
setCreateOpen(false)
|
||
form.resetFields()
|
||
},
|
||
onError: (err: Error) => message.error(err.message || '创建失败'),
|
||
})
|
||
|
||
const archiveMutation = useMutation({
|
||
mutationFn: (name: string) => promptService.archive(name),
|
||
onSuccess: () => {
|
||
message.success('已归档')
|
||
queryClient.invalidateQueries({ queryKey: ['prompts'] })
|
||
},
|
||
onError: (err: Error) => message.error(err.message || '归档失败'),
|
||
})
|
||
|
||
const rollbackMutation = useMutation({
|
||
mutationFn: ({ name, version }: { name: string; version: number }) =>
|
||
promptService.rollback(name, version),
|
||
onSuccess: () => {
|
||
message.success('回滚成功')
|
||
queryClient.invalidateQueries({ queryKey: ['prompts'] })
|
||
queryClient.invalidateQueries({ queryKey: ['prompt-detail', detailName] })
|
||
queryClient.invalidateQueries({ queryKey: ['prompt-versions', detailName] })
|
||
},
|
||
onError: (err: Error) => message.error(err.message || '回滚失败'),
|
||
})
|
||
|
||
const columns: ProColumns<PromptTemplate>[] = [
|
||
{ title: '名称', dataIndex: 'name', width: 200, render: (_, r) => <Text code>{r.name}</Text> },
|
||
{ title: '分类', dataIndex: 'category', width: 100 },
|
||
{ title: '描述', dataIndex: 'description', width: 200, ellipsis: true },
|
||
{
|
||
title: '来源',
|
||
dataIndex: 'source',
|
||
width: 80,
|
||
render: (_, r) => <Tag>{sourceLabels[r.source]}</Tag>,
|
||
},
|
||
{ title: '版本', dataIndex: 'current_version', width: 70 },
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
width: 90,
|
||
render: (_, r) => <Tag color={statusColors[r.status]}>{statusLabels[r.status]}</Tag>,
|
||
},
|
||
{
|
||
title: '操作',
|
||
width: 180,
|
||
render: (_, record) => (
|
||
<Space>
|
||
<Button size="small" onClick={() => setDetailName(record.name)}>详情</Button>
|
||
{record.status === 'active' && (
|
||
<Popconfirm title="确定归档此提示词?" onConfirm={() => archiveMutation.mutate(record.name)}>
|
||
<Button size="small" danger>归档</Button>
|
||
</Popconfirm>
|
||
)}
|
||
</Space>
|
||
),
|
||
},
|
||
]
|
||
|
||
const handleCreate = async () => {
|
||
const values = await form.validateFields()
|
||
createMutation.mutate(values)
|
||
}
|
||
|
||
const versionColumns: ProColumns<PromptVersion>[] = [
|
||
{ title: '版本', dataIndex: 'version', width: 60 },
|
||
{ title: '更新说明', dataIndex: 'changelog', width: 200, ellipsis: true },
|
||
{ title: '最低版本', dataIndex: 'min_app_version', width: 100, render: (_, r) => r.min_app_version || '-' },
|
||
{
|
||
title: '创建时间',
|
||
dataIndex: 'created_at',
|
||
width: 180,
|
||
render: (_, r) => new Date(r.created_at).toLocaleString('zh-CN'),
|
||
},
|
||
{
|
||
title: '操作',
|
||
width: 80,
|
||
render: (_, record) => (
|
||
<Popconfirm
|
||
title={`确定回滚到版本 ${record.version}?`}
|
||
onConfirm={() => detailName && rollbackMutation.mutate({ name: detailName, version: record.version })}
|
||
>
|
||
<Button size="small">回滚</Button>
|
||
</Popconfirm>
|
||
),
|
||
},
|
||
]
|
||
|
||
return (
|
||
<div>
|
||
<ProTable<PromptTemplate>
|
||
columns={columns}
|
||
dataSource={data?.items ?? []}
|
||
loading={isLoading}
|
||
rowKey="id"
|
||
search={false}
|
||
toolBarRender={() => [
|
||
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => { form.resetFields(); setCreateOpen(true) }}>
|
||
新建提示词
|
||
</Button>,
|
||
]}
|
||
pagination={{
|
||
total: data?.total ?? 0,
|
||
pageSize: data?.page_size ?? 20,
|
||
current: data?.page ?? 1,
|
||
showSizeChanger: false,
|
||
}}
|
||
/>
|
||
|
||
<Modal
|
||
title="新建提示词"
|
||
open={createOpen}
|
||
onOk={handleCreate}
|
||
onCancel={() => { setCreateOpen(false); form.resetFields() }}
|
||
confirmLoading={createMutation.isPending}
|
||
width={640}
|
||
>
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item name="name" label="名称" rules={[{ required: true }]}>
|
||
<Input placeholder="唯一标识" />
|
||
</Form.Item>
|
||
<Form.Item name="category" label="分类" rules={[{ required: true }]}>
|
||
<Input placeholder="如 system, tool" />
|
||
</Form.Item>
|
||
<Form.Item name="description" label="描述">
|
||
<TextArea rows={2} />
|
||
</Form.Item>
|
||
<Form.Item name="system_prompt" label="系统提示词" rules={[{ required: true }]}>
|
||
<TextArea rows={6} />
|
||
</Form.Item>
|
||
<Form.Item name="user_prompt_template" label="用户提示词模板">
|
||
<TextArea rows={4} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
<Modal
|
||
title={`提示词详情: ${detailName || ''}`}
|
||
open={!!detailName}
|
||
onCancel={() => setDetailName(null)}
|
||
footer={null}
|
||
width={800}
|
||
>
|
||
<Tabs items={[
|
||
{
|
||
key: 'info',
|
||
label: '基本信息',
|
||
children: detailData ? (
|
||
<Descriptions column={2} bordered size="small">
|
||
<Descriptions.Item label="名称">{detailData.name}</Descriptions.Item>
|
||
<Descriptions.Item label="分类">{detailData.category}</Descriptions.Item>
|
||
<Descriptions.Item label="来源">{sourceLabels[detailData.source]}</Descriptions.Item>
|
||
<Descriptions.Item label="状态">{statusLabels[detailData.status]}</Descriptions.Item>
|
||
<Descriptions.Item label="当前版本">{detailData.current_version}</Descriptions.Item>
|
||
<Descriptions.Item label="描述" span={2}>{detailData.description || '-'}</Descriptions.Item>
|
||
</Descriptions>
|
||
) : null,
|
||
},
|
||
{
|
||
key: 'versions',
|
||
label: '版本历史',
|
||
children: (
|
||
<ProTable<PromptVersion>
|
||
columns={versionColumns}
|
||
dataSource={versionsData ?? []}
|
||
rowKey="id"
|
||
search={false}
|
||
toolBarRender={false}
|
||
pagination={false}
|
||
size="small"
|
||
loading={!versionsData}
|
||
/>
|
||
),
|
||
},
|
||
]} />
|
||
</Modal>
|
||
</div>
|
||
)
|
||
}
|