2026-03-02 11:59:47 +00:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
2026-03-04 12:49:32 +00:00
|
|
|
|
import { Card, Button, Input, Space, Drawer, Form, Select, Tag, message, Popconfirm, Typography, Divider, Tooltip, Row, Col, List, Empty, Skeleton, Switch, Modal, Pagination } from 'antd';
|
2026-03-02 11:59:47 +00:00
|
|
|
|
import { PlusOutlined, EditOutlined, DeleteOutlined, CopyOutlined, SearchOutlined, SaveOutlined, StarFilled } from '@ant-design/icons';
|
|
|
|
|
|
import ReactMarkdown from 'react-markdown';
|
|
|
|
|
|
import { useDict } from '../../hooks/useDict';
|
|
|
|
|
|
import {
|
|
|
|
|
|
getPromptPage,
|
|
|
|
|
|
savePromptTemplate,
|
|
|
|
|
|
updatePromptTemplate,
|
|
|
|
|
|
deletePromptTemplate,
|
|
|
|
|
|
updatePromptStatus,
|
|
|
|
|
|
PromptTemplateVO,
|
|
|
|
|
|
PromptTemplateDTO
|
|
|
|
|
|
} from '../../api/business/prompt';
|
|
|
|
|
|
|
2026-03-04 12:49:32 +00:00
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
|
|
|
2026-03-02 11:59:47 +00:00
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
const { Text, Title } = Typography;
|
|
|
|
|
|
|
|
|
|
|
|
const PromptTemplates: React.FC = () => {
|
2026-03-04 12:49:32 +00:00
|
|
|
|
const { t } = useTranslation();
|
2026-03-02 11:59:47 +00:00
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
|
|
const [searchForm] = Form.useForm();
|
|
|
|
|
|
const { items: categories, loading: dictLoading } = useDict('biz_prompt_category');
|
|
|
|
|
|
const { items: dictTags } = useDict('biz_prompt_tag');
|
2026-03-04 07:19:40 +00:00
|
|
|
|
const { items: promptLevels } = useDict('biz_prompt_level');
|
2026-03-04 12:49:32 +00:00
|
|
|
|
|
2026-03-02 11:59:47 +00:00
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [data, setData] = useState<PromptTemplateVO[]>([]);
|
2026-03-04 12:49:32 +00:00
|
|
|
|
const [total, setTotal] = useState(0);
|
|
|
|
|
|
const [current, setCurrent] = useState(1);
|
|
|
|
|
|
const [pageSize, setPageSize] = useState(12);
|
2026-03-02 11:59:47 +00:00
|
|
|
|
|
|
|
|
|
|
const [drawerVisible, setDrawerVisible] = useState(false);
|
|
|
|
|
|
const [editingId, setEditingId] = useState<number | null>(null);
|
|
|
|
|
|
const [submitLoading, setSubmitLoading] = useState(false);
|
|
|
|
|
|
const [previewContent, setPreviewContent] = useState('');
|
|
|
|
|
|
|
|
|
|
|
|
const userProfile = React.useMemo(() => {
|
|
|
|
|
|
const profileStr = sessionStorage.getItem("userProfile");
|
|
|
|
|
|
return profileStr ? JSON.parse(profileStr) : {};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-03-04 07:19:40 +00:00
|
|
|
|
const activeTenantId = React.useMemo(() => Number(localStorage.getItem("activeTenantId") || 0), []);
|
|
|
|
|
|
|
2026-03-02 11:59:47 +00:00
|
|
|
|
const isPlatformAdmin = userProfile.isPlatformAdmin === true;
|
2026-03-04 07:19:40 +00:00
|
|
|
|
const isTenantAdmin = userProfile.isTenantAdmin === true;
|
2026-03-02 11:59:47 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
fetchData();
|
2026-03-04 12:49:32 +00:00
|
|
|
|
}, [current, pageSize]);
|
2026-03-02 11:59:47 +00:00
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
|
const values = searchForm.getFieldsValue();
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getPromptPage({
|
2026-03-04 12:49:32 +00:00
|
|
|
|
current,
|
|
|
|
|
|
size: pageSize,
|
2026-03-02 11:59:47 +00:00
|
|
|
|
name: values.name,
|
|
|
|
|
|
category: values.category
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res.data && res.data.data) {
|
|
|
|
|
|
setData(res.data.data.records);
|
2026-03-04 12:49:32 +00:00
|
|
|
|
setTotal(res.data.data.total);
|
2026-03-02 11:59:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleStatusChange = async (id: number, checked: boolean) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await updatePromptStatus(id, checked ? 1 : 0);
|
|
|
|
|
|
message.success(checked ? '模板已启用' : '模板已停用');
|
|
|
|
|
|
fetchData();
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleOpenDrawer = (record?: PromptTemplateVO, isClone = false) => {
|
|
|
|
|
|
if (record) {
|
|
|
|
|
|
if (isClone) {
|
|
|
|
|
|
setEditingId(null);
|
|
|
|
|
|
form.setFieldsValue({
|
|
|
|
|
|
...record,
|
|
|
|
|
|
templateName: `${record.templateName} (副本)`,
|
2026-03-04 07:19:40 +00:00
|
|
|
|
isSystem: 0, // 副本强制设为普通模板
|
|
|
|
|
|
id: undefined,
|
|
|
|
|
|
tenantId: undefined
|
2026-03-02 11:59:47 +00:00
|
|
|
|
});
|
|
|
|
|
|
setPreviewContent(record.promptContent);
|
|
|
|
|
|
} else {
|
2026-03-09 06:44:24 +00:00
|
|
|
|
const isPlatformLevel = Number(record.tenantId) === 0 && Number(record.isSystem) === 1;
|
2026-03-04 07:19:40 +00:00
|
|
|
|
const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1;
|
|
|
|
|
|
|
|
|
|
|
|
// 权限判定逻辑
|
|
|
|
|
|
let canEdit = false;
|
2026-03-09 06:44:24 +00:00
|
|
|
|
if (Number(record.isSystem) === 0) {
|
|
|
|
|
|
canEdit = Number(record.creatorId) === currentUserId;
|
|
|
|
|
|
} else if (isPlatformAdmin) {
|
2026-03-04 07:19:40 +00:00
|
|
|
|
canEdit = isPlatformLevel;
|
|
|
|
|
|
} else if (isTenantAdmin) {
|
|
|
|
|
|
canEdit = Number(record.tenantId) === activeTenantId;
|
|
|
|
|
|
} else {
|
2026-03-09 06:44:24 +00:00
|
|
|
|
canEdit = false;
|
2026-03-02 11:59:47 +00:00
|
|
|
|
}
|
2026-03-04 07:19:40 +00:00
|
|
|
|
|
|
|
|
|
|
if (!canEdit) {
|
|
|
|
|
|
message.warning('您无权修改此层级的模板');
|
2026-03-02 11:59:47 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-04 07:19:40 +00:00
|
|
|
|
|
2026-03-02 11:59:47 +00:00
|
|
|
|
setEditingId(record.id);
|
|
|
|
|
|
form.setFieldsValue(record);
|
|
|
|
|
|
setPreviewContent(record.promptContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setEditingId(null);
|
|
|
|
|
|
form.resetFields();
|
2026-03-04 07:19:40 +00:00
|
|
|
|
// 租户管理员或平台管理员新增默认选系统/租户预置
|
|
|
|
|
|
form.setFieldsValue({
|
|
|
|
|
|
status: 1,
|
|
|
|
|
|
isSystem: (isTenantAdmin || isPlatformAdmin) ? 1 : 0
|
|
|
|
|
|
});
|
2026-03-02 11:59:47 +00:00
|
|
|
|
setPreviewContent('');
|
|
|
|
|
|
}
|
|
|
|
|
|
setDrawerVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const showDetail = (record: PromptTemplateVO) => {
|
|
|
|
|
|
Modal.info({
|
|
|
|
|
|
title: record.templateName,
|
|
|
|
|
|
width: 800,
|
|
|
|
|
|
icon: null,
|
|
|
|
|
|
content: (
|
|
|
|
|
|
<div style={{ maxHeight: '65vh', overflowY: 'auto', padding: '12px 0' }}>
|
|
|
|
|
|
<ReactMarkdown>{record.promptContent}</ReactMarkdown>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
),
|
|
|
|
|
|
okText: '关闭',
|
|
|
|
|
|
maskClosable: true
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const values = await form.validateFields();
|
|
|
|
|
|
setSubmitLoading(true);
|
2026-03-04 07:19:40 +00:00
|
|
|
|
|
|
|
|
|
|
// 处理 tenantId,如果是新增且是平台管理员设为系统模板,手动设置 tenantId 为 0
|
|
|
|
|
|
if (!editingId && isPlatformAdmin && values.isSystem === 1) {
|
|
|
|
|
|
values.tenantId = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 11:59:47 +00:00
|
|
|
|
if (editingId) {
|
|
|
|
|
|
await updatePromptTemplate({ ...values, id: editingId });
|
|
|
|
|
|
message.success('更新成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await savePromptTemplate(values);
|
|
|
|
|
|
message.success('模板已创建');
|
|
|
|
|
|
}
|
|
|
|
|
|
setDrawerVisible(false);
|
|
|
|
|
|
fetchData();
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSubmitLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const groupedData = React.useMemo(() => {
|
|
|
|
|
|
const groups: Record<string, PromptTemplateVO[]> = {};
|
|
|
|
|
|
data.forEach(item => {
|
|
|
|
|
|
const cat = item.category || 'default';
|
|
|
|
|
|
if (!groups[cat]) groups[cat] = [];
|
|
|
|
|
|
groups[cat].push(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
return groups;
|
|
|
|
|
|
}, [data]);
|
|
|
|
|
|
|
|
|
|
|
|
const renderCard = (item: PromptTemplateVO) => {
|
|
|
|
|
|
const isSystem = item.isSystem === 1;
|
2026-03-04 12:49:32 +00:00
|
|
|
|
const isPlatformLevel = Number(item.tenantId) === 0 && isSystem;
|
2026-03-04 07:19:40 +00:00
|
|
|
|
const isTenantLevel = Number(item.tenantId) > 0 && isSystem;
|
2026-03-04 12:49:32 +00:00
|
|
|
|
const isPersonalLevel = !isSystem;
|
2026-03-04 07:19:40 +00:00
|
|
|
|
|
|
|
|
|
|
// 权限判定逻辑 (使用 Number 强制转换防止类型不匹配)
|
|
|
|
|
|
let canEdit = false;
|
|
|
|
|
|
const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1;
|
|
|
|
|
|
|
2026-03-09 06:44:24 +00:00
|
|
|
|
if (isPersonalLevel) {
|
|
|
|
|
|
// 个人模板仅本人可编辑
|
|
|
|
|
|
canEdit = Number(item.creatorId) === currentUserId;
|
|
|
|
|
|
} else if (isPlatformAdmin) {
|
|
|
|
|
|
// 平台管理员管理平台公开模板 (tenantId = 0)
|
2026-03-04 12:49:32 +00:00
|
|
|
|
canEdit = Number(item.tenantId) === 0;
|
2026-03-04 07:19:40 +00:00
|
|
|
|
} else if (isTenantAdmin) {
|
2026-03-09 06:44:24 +00:00
|
|
|
|
// 租户管理员管理本租户公开模板
|
2026-03-04 07:19:40 +00:00
|
|
|
|
canEdit = Number(item.tenantId) === activeTenantId;
|
|
|
|
|
|
} else {
|
2026-03-09 06:44:24 +00:00
|
|
|
|
// 普通用户不可编辑公开模板
|
|
|
|
|
|
canEdit = false;
|
2026-03-04 07:19:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标签颜色与文字
|
|
|
|
|
|
const levelTag = isPlatformLevel ? (
|
|
|
|
|
|
<Tag color="gold" style={{ borderRadius: 4 }}>平台级</Tag>
|
|
|
|
|
|
) : isTenantLevel ? (
|
|
|
|
|
|
<Tag color="blue" style={{ borderRadius: 4 }}>租户级</Tag>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Tag color="cyan" style={{ borderRadius: 4 }}>个人级</Tag>
|
|
|
|
|
|
);
|
2026-03-02 11:59:47 +00:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Card
|
|
|
|
|
|
key={item.id}
|
|
|
|
|
|
hoverable
|
|
|
|
|
|
onClick={() => showDetail(item)}
|
|
|
|
|
|
style={{ width: 320, borderRadius: 12, border: '1px solid #f0f0f0', position: 'relative', overflow: 'hidden' }}
|
|
|
|
|
|
bodyStyle={{ padding: '24px' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
2026-03-04 07:19:40 +00:00
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
|
|
|
|
<div style={{
|
2026-03-04 12:49:32 +00:00
|
|
|
|
width: 40, height: 40, borderRadius: 10,
|
|
|
|
|
|
backgroundColor: isPlatformLevel ? '#fffbe6' : (isTenantLevel ? '#e6f7ff' : '#e6fffb'),
|
2026-03-04 07:19:40 +00:00
|
|
|
|
display: 'flex', justifyContent: 'center', alignItems: 'center'
|
|
|
|
|
|
}}>
|
2026-03-04 12:49:32 +00:00
|
|
|
|
<StarFilled style={{ fontSize: 20, color: isPlatformLevel ? '#faad14' : (isTenantLevel ? '#1890ff' : '#13c2c2') }} />
|
2026-03-04 07:19:40 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
{levelTag}
|
2026-03-02 11:59:47 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<Space onClick={e => e.stopPropagation()}>
|
|
|
|
|
|
{canEdit && <EditOutlined style={{ fontSize: 18, color: '#bfbfbf', cursor: 'pointer' }} onClick={() => handleOpenDrawer(item)} />}
|
|
|
|
|
|
<Switch
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
checked={item.status === 1}
|
|
|
|
|
|
onChange={(checked) => handleStatusChange(item.id, checked)}
|
2026-03-09 06:44:24 +00:00
|
|
|
|
disabled={false}
|
2026-03-02 11:59:47 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-09 06:44:24 +00:00
|
|
|
|
<div style={{ marginBottom: 12 }}>
|
|
|
|
|
|
<Text strong style={{ fontSize: 16, display: 'block' }} ellipsis={{ tooltip: item.templateName }}>{item.templateName}</Text>
|
|
|
|
|
|
{/*<Text type="secondary" style={{ fontSize: 12 }}>使用次数: {item.usageCount || 0}</Text>*/}
|
|
|
|
|
|
</div>
|
2026-03-02 11:59:47 +00:00
|
|
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 20, height: 22, overflow: 'hidden' }}>
|
|
|
|
|
|
{item.tags?.map(tag => {
|
|
|
|
|
|
const dictItem = dictTags.find(dt => dt.itemValue === tag);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Tag key={tag} style={{ margin: 0, border: 'none', backgroundColor: '#f0f2f5', color: '#595959', borderRadius: 4, fontSize: 10 }}>
|
|
|
|
|
|
{dictItem ? dictItem.itemLabel : tag}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', paddingTop: 12, borderTop: '1px solid #f5f5f5' }}>
|
|
|
|
|
|
<Space onClick={e => e.stopPropagation()}>
|
|
|
|
|
|
<Tooltip title="以此创建">
|
|
|
|
|
|
<CopyOutlined style={{ color: '#bfbfbf', cursor: 'pointer', fontSize: 16 }} onClick={() => handleOpenDrawer(item, true)} />
|
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
{canEdit && (
|
2026-03-04 12:49:32 +00:00
|
|
|
|
<Popconfirm
|
|
|
|
|
|
title="确定删除?"
|
|
|
|
|
|
onConfirm={() => deletePromptTemplate(item.id).then(fetchData)}
|
|
|
|
|
|
okText={t('common.confirm')}
|
|
|
|
|
|
cancelText={t('common.cancel')}
|
|
|
|
|
|
>
|
2026-03-02 11:59:47 +00:00
|
|
|
|
<Tooltip title="删除">
|
|
|
|
|
|
<DeleteOutlined style={{ color: '#bfbfbf', cursor: 'pointer', fontSize: 16 }} />
|
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</Popconfirm>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ padding: '32px', backgroundColor: '#fff', minHeight: '100%', overflowY: 'auto' }}>
|
|
|
|
|
|
<div style={{ maxWidth: 1400, margin: '0 auto' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 32 }}>
|
|
|
|
|
|
<Title level={3} style={{ margin: 0 }}>提示词模板</Title>
|
|
|
|
|
|
<Button type="primary" icon={<PlusOutlined />} size="large" onClick={() => handleOpenDrawer()} style={{ borderRadius: 6 }}>
|
|
|
|
|
|
新增模板
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Card bordered={false} bodyStyle={{ padding: '20px 24px', backgroundColor: '#f9f9f9', borderRadius: 12, marginBottom: 32 }}>
|
|
|
|
|
|
<Form form={searchForm} layout="inline" onFinish={fetchData}>
|
|
|
|
|
|
<Form.Item name="name" label="模板名称"><Input placeholder="请输入..." style={{ width: 180 }} /></Form.Item>
|
|
|
|
|
|
<Form.Item name="category" label="分类">
|
|
|
|
|
|
<Select placeholder="选择分类" style={{ width: 160 }} allowClear>
|
|
|
|
|
|
{categories.map(c => <Option key={c.itemValue} value={c.itemValue}>{c.itemLabel}</Option>)}
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button type="primary" htmlType="submit">查询数据</Button>
|
|
|
|
|
|
<Button onClick={() => { searchForm.resetFields(); fetchData(); }}>重置</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Skeleton loading={loading} active>
|
|
|
|
|
|
{Object.keys(groupedData).length === 0 ? (
|
|
|
|
|
|
<Empty description="暂无可用模板" />
|
|
|
|
|
|
) : (
|
2026-03-04 12:49:32 +00:00
|
|
|
|
<>
|
|
|
|
|
|
{Object.keys(groupedData).map(catKey => {
|
|
|
|
|
|
const catLabel = categories.find(c => c.itemValue === catKey)?.itemLabel || catKey;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={catKey} style={{ marginBottom: 40 }}>
|
|
|
|
|
|
<Title level={4} style={{ marginBottom: 24, paddingLeft: 8, borderLeft: '4px solid #1890ff' }}>{catLabel}</Title>
|
|
|
|
|
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24 }}>
|
|
|
|
|
|
{groupedData[catKey].map(renderCard)}
|
|
|
|
|
|
</div>
|
2026-03-02 11:59:47 +00:00
|
|
|
|
</div>
|
2026-03-04 12:49:32 +00:00
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 40, paddingBottom: 20 }}>
|
|
|
|
|
|
<Pagination
|
|
|
|
|
|
current={current}
|
|
|
|
|
|
pageSize={pageSize}
|
|
|
|
|
|
total={total}
|
|
|
|
|
|
showSizeChanger
|
|
|
|
|
|
showQuickJumper
|
|
|
|
|
|
onChange={(page, size) => {
|
|
|
|
|
|
setCurrent(page);
|
|
|
|
|
|
setPageSize(size);
|
|
|
|
|
|
}}
|
|
|
|
|
|
showTotal={(total) => `共 ${total} 条模板`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
2026-03-02 11:59:47 +00:00
|
|
|
|
)}
|
|
|
|
|
|
</Skeleton>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Drawer
|
|
|
|
|
|
title={<Title level={4} style={{ margin: 0 }}>{editingId ? '编辑模板' : '创建新模板'}</Title>}
|
|
|
|
|
|
width="80%"
|
|
|
|
|
|
onClose={() => setDrawerVisible(false)}
|
|
|
|
|
|
open={drawerVisible}
|
|
|
|
|
|
extra={
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={() => setDrawerVisible(false)}>取消</Button>
|
|
|
|
|
|
<Button type="primary" icon={<SaveOutlined />} loading={submitLoading} onClick={handleSubmit}>保存</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={form} layout="vertical">
|
|
|
|
|
|
<Row gutter={24}>
|
2026-03-04 07:19:40 +00:00
|
|
|
|
<Col span={(isPlatformAdmin || isTenantAdmin) ? 8 : 12}>
|
|
|
|
|
|
<Form.Item name="templateName" label="模板名称" rules={[{ required: true }]}><Input /></Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
{(isPlatformAdmin || isTenantAdmin) && (
|
|
|
|
|
|
<Col span={6}>
|
|
|
|
|
|
<Form.Item name="isSystem" label="模板属性" rules={[{ required: true }]}>
|
|
|
|
|
|
<Select placeholder="选择属性">
|
|
|
|
|
|
{promptLevels.length > 0 ? (
|
|
|
|
|
|
promptLevels.map(i => <Option key={i.itemValue} value={Number(i.itemValue)}>{i.itemLabel}</Option>)
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Option value={1}>{isPlatformAdmin ? '系统预置 (全局)' : '租户预置 (全员)'}</Option>
|
|
|
|
|
|
<Option value={0}>个人模板</Option>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Col span={(isPlatformAdmin || isTenantAdmin) ? 5 : 6}>
|
|
|
|
|
|
<Form.Item name="category" label="分类" rules={[{ required: true }]}>
|
|
|
|
|
|
<Select loading={dictLoading}>{categories.map(i => <Option key={i.itemValue} value={i.itemValue}>{i.itemLabel}</Option>)}</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={isPlatformAdmin ? 5 : 6}>
|
|
|
|
|
|
<Form.Item name="status" label="状态">
|
|
|
|
|
|
<Select>
|
|
|
|
|
|
<Option value={1}>启用</Option>
|
|
|
|
|
|
<Option value={0}>禁用</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
2026-03-02 11:59:47 +00:00
|
|
|
|
</Row>
|
|
|
|
|
|
<Form.Item name="tags" label="业务标签" tooltip="可从现有标签中选择,也可输入新内容按回车保存">
|
|
|
|
|
|
<Select
|
|
|
|
|
|
mode="tags"
|
|
|
|
|
|
placeholder="选择或输入新标签"
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
tokenSeparators={[',', ' ', ';']}
|
|
|
|
|
|
>
|
|
|
|
|
|
{dictTags.map(t => <Option key={t.itemValue} value={t.itemValue}>{t.itemLabel}</Option>)}
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
|
|
<Divider orientation="left">提示词编辑器 (Markdown 实时预览)</Divider>
|
|
|
|
|
|
<Row gutter={24} style={{ height: 'calc(100vh - 400px)' }}>
|
|
|
|
|
|
<Col span={12} style={{ height: '100%' }}>
|
|
|
|
|
|
<Form.Item name="promptContent" noStyle rules={[{ required: true }]}>
|
|
|
|
|
|
<Input.TextArea
|
|
|
|
|
|
onChange={e => setPreviewContent(e.target.value)}
|
|
|
|
|
|
style={{ height: '100%', fontFamily: 'monospace', resize: 'none', border: '1px solid #d9d9d9', borderRadius: 8, padding: 12 }}
|
|
|
|
|
|
placeholder="在此输入 Markdown 指令..."
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12} style={{ height: '100%', overflowY: 'auto', background: '#fafafa', border: '1px solid #f0f0f0', borderRadius: 8, padding: '16px 24px' }}>
|
|
|
|
|
|
<div className="markdown-preview"><ReactMarkdown>{previewContent}</ReactMarkdown></div>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Drawer>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default PromptTemplates;
|