import React, { useEffect, useState } from 'react'; import { App, Button, Col, Divider, Empty, Form, Input, Popconfirm, Row, Select, Space, Switch, Table, Tag, Tooltip, Typography, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import PageContainer from "@/components/shared/PageContainer"; import DataListPanel from "@/components/shared/DataListPanel"; import FormDrawer from "@/components/shared/FormDrawer"; import SectionCard from "@/components/shared/SectionCard"; import { CopyOutlined, DeleteOutlined, EditOutlined, EyeOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons'; import ReactMarkdown from 'react-markdown'; import { useTranslation } from 'react-i18next'; import { useDict } from '../../hooks/useDict'; import { deletePromptTemplate, getPromptDetail, getPromptPage, savePromptTemplate, updatePromptStatus, updatePromptTemplate, type PromptTemplateVO, } from '../../api/business/prompt'; import { getHotWordGroupOptions, type HotWordGroupVO } from '../../api/business/hotwordGroup'; import AppPagination from '../../components/shared/AppPagination'; import './PromptTemplates.css'; const { Option } = Select; const { Text } = Typography; const normalizePromptTags = (tags: unknown) => { if (Array.isArray(tags)) { return tags.map((tag) => String(tag).trim()).filter(Boolean); } if (typeof tags === 'string') { return tags.split(',').map((tag) => tag.trim()).filter(Boolean); } return []; }; const PromptTemplates: React.FC = () => { const { message, modal } = App.useApp(); const { t } = useTranslation(); const { items: categories, loading: dictLoading } = useDict('biz_prompt_category'); const { items: dictTags } = useDict('biz_prompt_tag'); const { items: promptLevels } = useDict('biz_prompt_level'); const [loading, setLoading] = useState(false); const [data, setData] = useState([]); const [total, setTotal] = useState(0); const [current, setCurrent] = useState(1); const [pageSize, setPageSize] = useState(8); const [queryDraft, setQueryDraft] = useState<{ name?: string; category?: string }>({}); const [query, setQuery] = useState<{ name?: string; category?: string }>({}); const [drawerVisible, setDrawerVisible] = useState(false); const [editingId, setEditingId] = useState(null); const [submitLoading, setSubmitLoading] = useState(false); const [previewContent, setPreviewContent] = useState(''); const [groupOptions, setGroupOptions] = useState([]); const [templateLevel, setTemplateLevel] = useState(); const [selectedHotWordGroupId, setSelectedHotWordGroupId] = useState(); const [drawerInitialValues, setDrawerInitialValues] = useState>({}); const [drawerFormKey, setDrawerFormKey] = useState(0); const userProfile = React.useMemo(() => { const profileStr = sessionStorage.getItem("userProfile"); return profileStr ? JSON.parse(profileStr) : {}; }, []); const activeTenantId = React.useMemo(() => Number(localStorage.getItem("activeTenantId") || 0), []); const isPlatformAdmin = userProfile.isPlatformAdmin === true; const isTenantAdmin = userProfile.isTenantAdmin === true; useEffect(() => { void fetchData(); }, [current, pageSize, query]); useEffect(() => { void loadGroupOptions(); }, [isPlatformAdmin, templateLevel, activeTenantId]); const loadGroupOptions = async () => { const targetTenantId = isPlatformAdmin && Number(templateLevel) === 1 ? 0 : undefined; const res = await getHotWordGroupOptions(targetTenantId ?? (isPlatformAdmin && activeTenantId === 0 ? 0 : undefined)); setGroupOptions(res.data?.data || []); }; const fetchData = async () => { setLoading(true); try { const res = await getPromptPage({ current, size: pageSize, name: query.name, category: query.category, }); if (res.data?.data) { setData(res.data.data.records || []); setTotal(res.data.data.total || 0); } } finally { setLoading(false); } }; const handleStatusChange = async (id: number, checked: boolean) => { await updatePromptStatus(id, checked ? 1 : 0); message.success(checked ? '模板已启用' : '模板已停用'); await fetchData(); }; const handleOpenDrawer = (record?: PromptTemplateVO, isClone = false) => { if (record) { if (isClone) { setEditingId(null); setTemplateLevel(0); setSelectedHotWordGroupId(record.hotWordGroupId); setDrawerInitialValues({ ...record, tags: normalizePromptTags(record.tags), templateName: `${record.templateName} (副本)`, isSystem: 0, id: undefined, tenantId: undefined, }); setPreviewContent(record.promptContent); } else { const isPlatformLevel = Number(record.tenantId) === 0 && Number(record.isSystem) === 1; const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1; let canEdit = false; if (Number(record.isSystem) === 0) { canEdit = Number(record.creatorId) === currentUserId; } else if (isPlatformAdmin) { canEdit = isPlatformLevel; } else if (isTenantAdmin) { canEdit = Number(record.tenantId) === activeTenantId; } if (!canEdit) { message.warning('您无权修改此层级的模板'); return; } setEditingId(record.id); setTemplateLevel(Number(record.isSystem)); setSelectedHotWordGroupId(record.hotWordGroupId); setDrawerInitialValues({ ...record, tags: normalizePromptTags(record.tags), }); setPreviewContent(record.promptContent); } } else { const defaultLevel = (isTenantAdmin || isPlatformAdmin) ? 1 : 0; setEditingId(null); setDrawerInitialValues({ status: 1, isSystem: defaultLevel, }); setTemplateLevel(defaultLevel); setSelectedHotWordGroupId(undefined); setPreviewContent(''); } setDrawerFormKey((key) => key + 1); setDrawerVisible(true); }; const showDetail = (record: PromptTemplateVO) => { void (async () => { const detailRes = await getPromptDetail(record.id); const detail = detailRes.data?.data || record; modal.info({ title: record.templateName, width: 800, icon: null, content: (
{detail.description ? (
{detail.description}
) : null}
{detail.hotWordGroupName ? 热词组:{detail.hotWordGroupName} : 未绑定热词组} {normalizePromptTags(detail.tags).map((tag) => { const dictItem = dictTags.find((item) => item.itemValue === tag); return {dictItem ? dictItem.itemLabel : tag}; })}
{detail.hotWords && detail.hotWords.length > 0 ? (
绑定热词
{detail.hotWords.map((word) => {word})}
) : detail.hotWordGroupId ? (
该热词组当前没有热词
) : null} {detail.promptContent}
), okText: '关闭', maskClosable: true, }); })(); }; const handleSubmit = async (values: any) => { try { setSubmitLoading(true); if (!editingId && isPlatformAdmin && values.isSystem === 1) { values.tenantId = 0; } if (editingId) { await updatePromptTemplate({ ...values, id: editingId }); message.success('更新成功'); } else { await savePromptTemplate(values); message.success('模板已创建'); } setDrawerVisible(false); await fetchData(); } finally { setSubmitLoading(false); } }; const getTemplateLevelMeta = (item: PromptTemplateVO) => { const isSystem = item.isSystem === 1; const isPlatformLevel = Number(item.tenantId) === 0 && isSystem; const isTenantLevel = Number(item.tenantId) > 0 && isSystem; if (isPlatformLevel) { return { label: '平台级', color: 'gold' as const }; } if (isTenantLevel) { return { label: '租户级', color: 'blue' as const }; } return { label: '个人级', color: 'cyan' as const }; }; const handleSearch = () => { setCurrent(1); setQuery(queryDraft); }; const handleResetSearch = () => { setQueryDraft({}); setCurrent(1); setQuery({}); }; const canManageTemplate = (item: PromptTemplateVO) => { const isSystem = item.isSystem === 1; const isPlatformLevel = Number(item.tenantId) === 0 && isSystem; const isPersonalLevel = !isSystem; const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1; if (isPersonalLevel) { return Number(item.creatorId) === currentUserId; } if (isPlatformAdmin) { return isPlatformLevel; } if (isTenantAdmin) { return Number(item.tenantId) === activeTenantId; } return false; }; const tableColumns: ColumnsType = [ { title: '模板名称', dataIndex: 'templateName', width: 280, render: (_: unknown, item: PromptTemplateVO) => (
{item.templateName} {item.description ? ( {item.description} ) : null}
), }, { title: '分类', dataIndex: 'category', width: 140, render: (category: string) => categories.find((c) => c.itemValue === category)?.itemLabel || category || '-', }, { title: '层级', dataIndex: 'isSystem', width: 110, render: (_: unknown, item: PromptTemplateVO) => { const level = getTemplateLevelMeta(item); return {level.label}; }, }, { title: '热词组', dataIndex: 'hotWordGroupName', width: 180, render: (name: string) => name ? {name} : 未绑定, }, { title: '业务标签', dataIndex: 'tags', minWidth: 220, render: (tags: unknown) => { const tagList = normalizePromptTags(tags); if (!tagList.length) { return 未配置; } return ( {tagList.slice(0, 3).map((tag) => { const dictItem = dictTags.find((dt) => dt.itemValue === tag); return {dictItem ? dictItem.itemLabel : tag}; })} {tagList.length > 3 ? +{tagList.length - 3} : null} ); }, }, { title: '状态', dataIndex: 'status', width: 90, render: (_: unknown, item: PromptTemplateVO) => ( event.stopPropagation()} onChange={(checked) => void handleStatusChange(item.id, checked)} /> ), }, { title: '操作', key: 'actions', width: 150, fixed: 'right' as const, render: (_: unknown, item: PromptTemplateVO) => { const canEdit = canManageTemplate(item); return ( e.stopPropagation()}> } rightActions={
setQueryDraft((currentDraft) => ({ ...currentDraft, name: event.target.value }))} />
} footer={ { setCurrent(page); setPageSize(size); }} /> } > className="prompt-templates-table" columns={tableColumns} dataSource={data} rowKey="id" loading={loading} pagination={false} scroll={{ x: "max-content", y: "100%" }} onRow={(record) => ({ onClick: () => showDetail(record) })} locale={{ emptyText: }} /> setDrawerVisible(false)} open={drawerVisible} okIcon={} okLoading={submitLoading} okButtonProps={{ htmlType: "submit", form: "prompt-template-form" }} >
void handleSubmit(values)} onValuesChange={(changedValues) => { if (Object.prototype.hasOwnProperty.call(changedValues, 'isSystem')) { setTemplateLevel(Number(changedValues.isSystem)); } if (Object.prototype.hasOwnProperty.call(changedValues, 'hotWordGroupId')) { setSelectedHotWordGroupId(changedValues.hotWordGroupId); } }} > {(isPlatformAdmin || isTenantAdmin) && ( )}