refactor(core): 重构业务模块代码格式并清理冗余空行

统一前后端业务模块(热词组、提示词模板等)的代码换行符与导入语句格式,移除 Java 实体、DTO、VO 及 Service 实现类中的冗余空行,优化前端组件及 API 文件的 import 结构。
dev_na
chenhao 2026-07-31 17:07:57 +08:00
parent 49eaea32b2
commit a53d85bbec
14 changed files with 147 additions and 223 deletions

View File

@ -16,9 +16,6 @@ public class HotWordGroupDTO {
@Schema(description = "热词组名称") @Schema(description = "热词组名称")
private String groupName; private String groupName;
@Schema(description = "排序值,越小越靠前")
private Integer sortOrder;
@Schema(description = "状态1-启用0-禁用") @Schema(description = "状态1-启用0-禁用")
private Integer status; private Integer status;

View File

@ -24,9 +24,6 @@ public class HotWordGroupVO {
@Schema(description = "状态1-启用0-禁用") @Schema(description = "状态1-启用0-禁用")
private Integer status; private Integer status;
@Schema(description = "排序值,越小越靠前")
private Integer sortOrder;
@Schema(description = "组内热词数量") @Schema(description = "组内热词数量")
private Long hotWordCount; private Long hotWordCount;

View File

@ -31,9 +31,6 @@ public class PromptTemplateDTO {
@Schema(description = "绑定热词组 ID") @Schema(description = "绑定热词组 ID")
private Long hotWordGroupId; private Long hotWordGroupId;
@Schema(description = "鎺掑簭鍊硷紝瓒婂皬瓒婇潬鍓?")
private Integer sortOrder;
@Schema(description = "模板内容") @Schema(description = "模板内容")
private String promptContent; private String promptContent;

View File

@ -30,8 +30,6 @@ public class PromptTemplateVO {
private String hotWordGroupName; private String hotWordGroupName;
@Schema(description = "绑定热词列表") @Schema(description = "绑定热词列表")
private List<String> hotWords; private List<String> hotWords;
@Schema(description = "排序字段")
private Integer sortOrder;
@Schema(description = "使用次数") @Schema(description = "使用次数")
private Integer usageCount; private Integer usageCount;
@Schema(description = "提示词正文") @Schema(description = "提示词正文")

View File

@ -24,9 +24,6 @@ public class HotWordGroup extends BaseEntity {
@Schema(description = "创建者ID") @Schema(description = "创建者ID")
private Long creatorId; private Long creatorId;
@Schema(description = "排序值,越小越靠前")
private Integer sortOrder;
@Schema(description = "备注") @Schema(description = "备注")
private String remark; private String remark;
} }

View File

@ -39,9 +39,6 @@ public class PromptTemplate extends BaseEntity {
@Schema(description = "绑定热词组 ID") @Schema(description = "绑定热词组 ID")
private Long hotWordGroupId; private Long hotWordGroupId;
@Schema(description = "鎺掑簭鍊硷紝瓒婂皬瓒婇潬鍓?")
private Integer sortOrder;
@Schema(description = "使用次数") @Schema(description = "使用次数")
private Integer usageCount; private Integer usageCount;

View File

@ -27,8 +27,6 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, HotWordGroup> implements HotWordGroupService { public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, HotWordGroup> implements HotWordGroupService {
private static final int DEFAULT_SORT_ORDER = 0;
private final HotWordMapper hotWordMapper; private final HotWordMapper hotWordMapper;
private final PromptTemplateMapper promptTemplateMapper; private final PromptTemplateMapper promptTemplateMapper;
@ -86,7 +84,6 @@ public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, Hot
LambdaQueryWrapper<HotWordGroup> wrapper = new LambdaQueryWrapper<HotWordGroup>() LambdaQueryWrapper<HotWordGroup> wrapper = new LambdaQueryWrapper<HotWordGroup>()
.like(name != null && !name.isBlank(), HotWordGroup::getGroupName, name) .like(name != null && !name.isBlank(), HotWordGroup::getGroupName, name)
.eq(status != null, HotWordGroup::getStatus, status) .eq(status != null, HotWordGroup::getStatus, status)
.orderByAsc(HotWordGroup::getSortOrder)
.orderByDesc(HotWordGroup::getCreatedAt); .orderByDesc(HotWordGroup::getCreatedAt);
wrapper.eq(tenantId != null, HotWordGroup::getTenantId, tenantId); wrapper.eq(tenantId != null, HotWordGroup::getTenantId, tenantId);
Page<HotWordGroup> page = this.page(new Page<>(current, size), wrapper); Page<HotWordGroup> page = this.page(new Page<>(current, size), wrapper);
@ -104,7 +101,6 @@ public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, Hot
public List<HotWordGroupVO> listVisibleOptions(Long tenantId) { public List<HotWordGroupVO> listVisibleOptions(Long tenantId) {
LambdaQueryWrapper<HotWordGroup> wrapper = new LambdaQueryWrapper<HotWordGroup>() LambdaQueryWrapper<HotWordGroup> wrapper = new LambdaQueryWrapper<HotWordGroup>()
.eq(HotWordGroup::getStatus, 1) .eq(HotWordGroup::getStatus, 1)
.orderByAsc(HotWordGroup::getSortOrder)
.orderByDesc(HotWordGroup::getCreatedAt); .orderByDesc(HotWordGroup::getCreatedAt);
wrapper.eq(tenantId != null, HotWordGroup::getTenantId, tenantId); wrapper.eq(tenantId != null, HotWordGroup::getTenantId, tenantId);
List<HotWordGroup> groups = this.list(wrapper); List<HotWordGroup> groups = this.list(wrapper);
@ -133,7 +129,6 @@ public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, Hot
private void copyProperties(HotWordGroupDTO dto, HotWordGroup entity) { private void copyProperties(HotWordGroupDTO dto, HotWordGroup entity) {
entity.setGroupName(dto.getGroupName()); entity.setGroupName(dto.getGroupName());
entity.setSortOrder(normalizeSortOrder(dto.getSortOrder()));
entity.setStatus(dto.getStatus()); entity.setStatus(dto.getStatus());
entity.setRemark(dto.getRemark()); entity.setRemark(dto.getRemark());
} }
@ -145,7 +140,6 @@ public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, Hot
vo.setGroupName(entity.getGroupName()); vo.setGroupName(entity.getGroupName());
vo.setCreatorId(entity.getCreatorId()); vo.setCreatorId(entity.getCreatorId());
vo.setStatus(entity.getStatus()); vo.setStatus(entity.getStatus());
vo.setSortOrder(normalizeSortOrder(entity.getSortOrder()));
vo.setHotWordCount(hotWordCount); vo.setHotWordCount(hotWordCount);
vo.setRemark(entity.getRemark()); vo.setRemark(entity.getRemark());
vo.setCreatedAt(entity.getCreatedAt()); vo.setCreatedAt(entity.getCreatedAt());
@ -153,7 +147,4 @@ public class HotWordGroupServiceImpl extends ServiceImpl<HotWordGroupMapper, Hot
return vo; return vo;
} }
private Integer normalizeSortOrder(Integer sortOrder) {
return sortOrder == null ? DEFAULT_SORT_ORDER : sortOrder;
}
} }

View File

@ -209,7 +209,6 @@ public class MeetingRuntimeProfileResolverImpl implements MeetingRuntimeProfileR
.eq(PromptTemplate::getIsSystem, 1) .eq(PromptTemplate::getIsSystem, 1)
.and(wrapper -> wrapper.eq(PromptTemplate::getTenantId, tenantId).or().eq(PromptTemplate::getTenantId, 0L)) .and(wrapper -> wrapper.eq(PromptTemplate::getTenantId, tenantId).or().eq(PromptTemplate::getTenantId, 0L))
.orderByDesc(PromptTemplate::getTenantId) .orderByDesc(PromptTemplate::getTenantId)
.orderByAsc(PromptTemplate::getSortOrder)
.orderByDesc(PromptTemplate::getCreatedAt) .orderByDesc(PromptTemplate::getCreatedAt)
.last("LIMIT 1")); .last("LIMIT 1"));
if (template != null) { if (template != null) {
@ -221,7 +220,6 @@ public class MeetingRuntimeProfileResolverImpl implements MeetingRuntimeProfileR
.and(wrapper -> wrapper.eq(PromptTemplate::getTenantId, tenantId).or().eq(PromptTemplate::getTenantId, 0L)) .and(wrapper -> wrapper.eq(PromptTemplate::getTenantId, tenantId).or().eq(PromptTemplate::getTenantId, 0L))
.orderByDesc(PromptTemplate::getTenantId) .orderByDesc(PromptTemplate::getTenantId)
.orderByDesc(PromptTemplate::getIsSystem) .orderByDesc(PromptTemplate::getIsSystem)
.orderByAsc(PromptTemplate::getSortOrder)
.orderByDesc(PromptTemplate::getCreatedAt) .orderByDesc(PromptTemplate::getCreatedAt)
.last("LIMIT 1")); .last("LIMIT 1"));
if (template == null) { if (template == null) {

View File

@ -29,8 +29,6 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper, PromptTemplate> implements PromptTemplateService { public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper, PromptTemplate> implements PromptTemplateService {
private static final int DEFAULT_SORT_ORDER = 0;
private final PromptTemplateUserConfigMapper userConfigMapper; private final PromptTemplateUserConfigMapper userConfigMapper;
private final HotWordGroupMapper hotWordGroupMapper; private final HotWordGroupMapper hotWordGroupMapper;
private final HotWordService hotWordService; private final HotWordService hotWordService;
@ -72,8 +70,8 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
LambdaQueryWrapper<PromptTemplate> wrapper = buildVisibilityWrapper(tenantId, userId, isPlatformAdmin, isTenantAdmin); LambdaQueryWrapper<PromptTemplate> wrapper = buildVisibilityWrapper(tenantId, userId, isPlatformAdmin, isTenantAdmin);
wrapper.like(name != null && !name.isEmpty(), PromptTemplate::getTemplateName, name) wrapper.like(name != null && !name.isEmpty(), PromptTemplate::getTemplateName, name)
.eq(category != null && !category.isEmpty(), PromptTemplate::getCategory, category) .eq(category != null && !category.isEmpty(), PromptTemplate::getCategory, category)
.orderByDesc(PromptTemplate::getIsSystem) .orderByAsc(PromptTemplate::getIsSystem)
.orderByAsc(PromptTemplate::getSortOrder) .orderByDesc(PromptTemplate::getTenantId)
.orderByDesc(PromptTemplate::getCreatedAt); .orderByDesc(PromptTemplate::getCreatedAt);
Page<PromptTemplate> page = this.page(new Page<>(current, size), wrapper); Page<PromptTemplate> page = this.page(new Page<>(current, size), wrapper);
@ -259,16 +257,11 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
entity.setTenantId(dto.getTenantId()); entity.setTenantId(dto.getTenantId());
entity.setTags(dto.getTags()); entity.setTags(dto.getTags());
entity.setHotWordGroupId(dto.getHotWordGroupId()); entity.setHotWordGroupId(dto.getHotWordGroupId());
entity.setSortOrder(normalizeSortOrder(dto.getSortOrder()));
entity.setPromptContent(dto.getPromptContent()); entity.setPromptContent(dto.getPromptContent());
entity.setStatus(dto.getStatus()); entity.setStatus(dto.getStatus());
entity.setRemark(dto.getRemark()); entity.setRemark(dto.getRemark());
} }
private Integer normalizeSortOrder(Integer sortOrder) {
return sortOrder == null ? DEFAULT_SORT_ORDER : sortOrder;
}
private PromptTemplateVO toVO(PromptTemplate entity, Integer status, Map<Long, HotWordGroup> hotWordGroupMap) { private PromptTemplateVO toVO(PromptTemplate entity, Integer status, Map<Long, HotWordGroup> hotWordGroupMap) {
PromptTemplateVO vo = new PromptTemplateVO(); PromptTemplateVO vo = new PromptTemplateVO();
vo.setId(entity.getId()); vo.setId(entity.getId());
@ -283,7 +276,6 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
vo.setHotWordGroupId(hotWordGroupId); vo.setHotWordGroupId(hotWordGroupId);
HotWordGroup group = hotWordGroupId == null ? null : hotWordGroupMap.get(hotWordGroupId); HotWordGroup group = hotWordGroupId == null ? null : hotWordGroupMap.get(hotWordGroupId);
vo.setHotWordGroupName(group == null ? null : group.getGroupName()); vo.setHotWordGroupName(group == null ? null : group.getGroupName());
vo.setSortOrder(normalizeSortOrder(entity.getSortOrder()));
vo.setUsageCount(entity.getUsageCount()); vo.setUsageCount(entity.getUsageCount());
vo.setPromptContent(entity.getPromptContent()); vo.setPromptContent(entity.getPromptContent());
vo.setStatus(status); vo.setStatus(status);

View File

@ -6,7 +6,6 @@ export interface HotWordGroupVO {
groupName: string; groupName: string;
creatorId: number; creatorId: number;
status: number; status: number;
sortOrder?: number;
hotWordCount: number; hotWordCount: number;
remark?: string; remark?: string;
createdAt: string; createdAt: string;
@ -16,7 +15,6 @@ export interface HotWordGroupVO {
export interface HotWordGroupDTO { export interface HotWordGroupDTO {
id?: number; id?: number;
groupName: string; groupName: string;
sortOrder?: number;
status: number; status: number;
remark?: string; remark?: string;
} }

View File

@ -12,7 +12,6 @@ export interface PromptTemplateVO {
hotWordGroupId?: number; hotWordGroupId?: number;
hotWordGroupName?: string; hotWordGroupName?: string;
hotWords?: string[]; hotWords?: string[];
sortOrder?: number;
usageCount: number; usageCount: number;
promptContent: string; promptContent: string;
status: number; status: number;
@ -29,7 +28,6 @@ export interface PromptTemplateDTO {
isSystem: number; isSystem: number;
tags?: string[]; tags?: string[];
hotWordGroupId?: number; hotWordGroupId?: number;
sortOrder?: number;
promptContent: string; promptContent: string;
status: number; status: number;
remark?: string; remark?: string;

View File

@ -238,7 +238,7 @@ const AiModels: React.FC = () => {
const values = form.getFieldsValue(["provider", "baseUrl", "apiKey"]); const values = form.getFieldsValue(["provider", "baseUrl", "apiKey"]);
if (!values.provider || !values.baseUrl) { if (!values.provider || !values.baseUrl) {
message.warning("璇峰厛濉啓鎻愪緵鍟嗗拰 Base URL"); message.warning("请先填写提供商和 Base URL");
return; return;
} }
@ -248,7 +248,7 @@ const AiModels: React.FC = () => {
const rawModels = (res as any)?.data?.data ?? (Array.isArray(res) ? res : []); const rawModels = (res as any)?.data?.data ?? (Array.isArray(res) ? res : []);
const models = Array.isArray(rawModels) ? rawModels : []; const models = Array.isArray(rawModels) ? rawModels : [];
setRemoteModels(models); setRemoteModels(models);
message.success(`鑾峰彇鍒?${models.length} 涓ā鍨媊); message.success(`已获取 ${models.length} 个模型`);
} finally { } finally {
setFetchLoading(false); setFetchLoading(false);
} }
@ -292,7 +292,7 @@ const AiModels: React.FC = () => {
const handleSubmit = async () => { const handleSubmit = async () => {
const values = await form.validateFields(); const values = await form.validateFields();
if (values.isDefaultChecked && !values.statusChecked) { if (values.isDefaultChecked && !values.statusChecked) {
message.warning("????????????"); message.warning("默认模型必须保持启用状态");
return; return;
} }
@ -334,10 +334,10 @@ const AiModels: React.FC = () => {
try { try {
if (editingId) { if (editingId) {
await updateAiModel(payload); await updateAiModel(payload);
message.success("鏇存柊鎴愬姛"); message.success("更新成功");
} else { } else {
await saveAiModel(payload); await saveAiModel(payload);
message.success("鏂板鎴愬姛"); message.success("新增成功");
} }
setDrawerVisible(false); setDrawerVisible(false);
void fetchData(); void fetchData();
@ -363,7 +363,7 @@ const AiModels: React.FC = () => {
max_tokens: extraValues.max_tokens, max_tokens: extraValues.max_tokens,
testMessage: DEFAULT_LLM_TEST_MESSAGE, testMessage: DEFAULT_LLM_TEST_MESSAGE,
}); });
message.success("LLM ???????"); message.success("LLM 连通性测试成功");
} finally { } finally {
setConnectivityLoading(false); setConnectivityLoading(false);
} }
@ -372,7 +372,7 @@ const AiModels: React.FC = () => {
const values = await form.validateFields(["provider", "baseUrl"]); const values = await form.validateFields(["provider", "baseUrl"]);
if (String(values.provider || "").toLowerCase() !== "local") { if (String(values.provider || "").toLowerCase() !== "local") {
message.warning("???? ASR ????????"); message.warning("仅本地 ASR 模型支持连通性测试");
return; return;
} }
@ -385,7 +385,7 @@ const AiModels: React.FC = () => {
}); });
const profile = (res as any)?.data?.data ?? (res as any)?.data ?? (res as any); const profile = (res as any)?.data?.data ?? (res as any)?.data ?? (res as any);
applyLocalProfile(profile as AiLocalProfileVO, values.baseUrl); applyLocalProfile(profile as AiLocalProfileVO, values.baseUrl);
message.success("???????????"); message.success("本地模型连通性测试成功");
} finally { } finally {
setConnectivityLoading(false); setConnectivityLoading(false);
} }
@ -393,65 +393,63 @@ const AiModels: React.FC = () => {
const handleDelete = async (record: AiModelVO) => { const handleDelete = async (record: AiModelVO) => {
await deleteAiModelByType(record.id, record.modelType); await deleteAiModelByType(record.id, record.modelType);
message.success("鍒犻櫎鎴愬姛"); message.success("删除成功");
void fetchData(); void fetchData();
}; };
const handleTenantToggle = async (record: AiModelVO, checked: boolean) => { const handleTenantToggle = async (record: AiModelVO, checked: boolean) => {
if (checked) { if (checked) {
await tenantEnableModel(record.id, activeType); await tenantEnableModel(record.id, activeType);
message.success(activeType === "ASR" ? "宸插垏鎹㈠綋鍓?ASR" : "宸插惎鐢ㄥ綋鍓?LLM"); message.success(activeType === "ASR" ? "已启用当前 ASR 模型" : "已启用当前 LLM 模型");
} else { } else {
await tenantDisableModel(record.id, activeType); await tenantDisableModel(record.id, activeType);
message.success(activeType === "ASR" ? "宸插叧闂綋鍓?ASR" : "宸插叧闂綋鍓?LLM"); message.success(activeType === "ASR" ? "已停用当前 ASR 模型" : "已停用当前 LLM 模型");
} }
await fetchData(); await fetchData();
}; };
const handlePlatformStatusToggle = async (record: AiModelVO, checked: boolean) => { const handlePlatformStatusToggle = async (record: AiModelVO, checked: boolean) => {
await updatePlatformModelStatus(record.id, activeType, checked ? 1 : 0); await updatePlatformModelStatus(record.id, activeType, checked ? 1 : 0);
message.success(checked ? `骞冲彴绾?${activeType} 宸插惎鐢╜ : ` ? ${activeType} message.success(checked ? `平台级 ${activeType} 已启用` : `平台级 ${activeType} 已禁用`);
)
;
await fetchData(); await fetchData();
}; };
const handleSyncCurrentAsr = async () => { const handleSyncCurrentAsr = async () => {
await syncCurrentAsrSpeakers(); await syncCurrentAsrSpeakers();
message.success("?????????"); message.success("当前 ASR 声纹已同步");
}; };
const handleSetTenantDefault = async (record: AiModelVO) => { const handleSetTenantDefault = async (record: AiModelVO) => {
await setTenantDefaultModel(record.id, "LLM"); await setTenantDefaultModel(record.id, "LLM");
message.success("宸茶缃负榛樿 LLM"); message.success("已设置为默认 LLM");
await fetchData(); await fetchData();
}; };
const resolvedTableColumns = [ const resolvedTableColumns = [
{ {
title: "妯″瀷鍚嶇О", title: "模型名称",
dataIndex: "modelName", dataIndex: "modelName",
key: "modelName", key: "modelName",
render: (text: string, record: AiModelVO) => ( render: (text: string, record: AiModelVO) => (
<Space> <Space>
{text} {text}
{record.isDefault === 1 && <Tag color="gold">樿</Tag>} {record.isDefault === 1 && <Tag color="gold"></Tag>}
{record.tenantDefault === 1 && <Tag color="blue">樿</Tag>} {record.tenantDefault === 1 && <Tag color="blue"></Tag>}
{record.tenantId === 0 && ( {record.tenantId === 0 && (
<Tooltip title="骞冲彴閫忎紶妯″瀷"> <Tooltip title="平台透传模型">
<SafetyCertificateOutlined style={{ color: "#52c41a" }} /> <SafetyCertificateOutlined style={{ color: "#52c41a" }} />
</Tooltip> </Tooltip>
)} )}
{record.scope && ( {record.scope && (
<Tag bordered={false} color={record.scope === "PLATFORM" ? "geekblue" : "default"}> <Tag bordered={false} color={record.scope === "PLATFORM" ? "geekblue" : "default"}>
{record.scope === "PLATFORM" ? "骞冲彴绾? : " ?} {record.scope === "PLATFORM" ? "平台级" : "租户级"}
</Tag> </Tag>
)} )}
</Space> </Space>
), ),
}, },
{ {
title: "???", title: "提供商",
dataIndex: "provider", dataIndex: "provider",
key: "provider", key: "provider",
render: (value: string) => { render: (value: string) => {
@ -460,18 +458,18 @@ const AiModels: React.FC = () => {
}, },
}, },
{ {
title: "妯″瀷缂栫爜", title: "模型编码",
dataIndex: "modelCode", dataIndex: "modelCode",
key: "modelCode", key: "modelCode",
}, },
{ {
title: "鎺掑簭", title: "排序",
dataIndex: "sortOrder", dataIndex: "sortOrder",
key: "sortOrder", key: "sortOrder",
render: (value: number | undefined) => value ?? 0, render: (value: number | undefined) => value ?? 0,
}, },
{ {
title: "??", title: "状态",
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
render: (status: number, record: AiModelVO) => { render: (status: number, record: AiModelVO) => {
@ -479,8 +477,8 @@ const AiModels: React.FC = () => {
return ( return (
<Switch <Switch
checked={status === 1} checked={status === 1}
checkedChildren="鍚敤" checkedChildren="启用"
unCheckedChildren="绂佺敤" unCheckedChildren="禁用"
onChange={(checked) => void handlePlatformStatusToggle(record, checked)} onChange={(checked) => void handlePlatformStatusToggle(record, checked)}
/> />
); );
@ -488,8 +486,8 @@ const AiModels: React.FC = () => {
return ( return (
<Switch <Switch
checked={record.tenantEnabled === 1} checked={record.tenantEnabled === 1}
checkedChildren={activeType === "ASR" ? "????" : "???"} checkedChildren={activeType === "ASR" ? "已启用" : "已启用"}
unCheckedChildren={activeType === "ASR" ? "鏈惎鐢? : " ?} unCheckedChildren={activeType === "ASR" ? "未启用" : "已停用"}
disabled={status !== 1} disabled={status !== 1}
onChange={(checked) => void handleTenantToggle(record, checked)} onChange={(checked) => void handleTenantToggle(record, checked)}
/> />
@ -497,7 +495,7 @@ const AiModels: React.FC = () => {
}, },
}, },
{ {
title: "鎿嶄綔", title: "操作",
key: "action", key: "action",
render: (_: unknown, record: AiModelVO) => { render: (_: unknown, record: AiModelVO) => {
const canEdit = record.canEditConfig ?? (record.tenantId !== 0 || isPlatformAdmin); const canEdit = record.canEditConfig ?? (record.tenantId !== 0 || isPlatformAdmin);
@ -506,18 +504,18 @@ const AiModels: React.FC = () => {
<Space> <Space>
{canSetDefault && ( {canSetDefault && (
<Button type="link" onClick={() => void handleSetTenantDefault(record)}> <Button type="link" onClick={() => void handleSetTenantDefault(record)}>
{record.tenantDefault === 1 ? "榛樿 LLM" : "璁句负榛樿"} {record.tenantDefault === 1 ? "默认 LLM" : "设为默认"}
</Button> </Button>
)} )}
{canEdit && ( {canEdit && (
<Button type="link" icon={<EditOutlined />} onClick={() => openDrawer(record)}> <Button type="link" icon={<EditOutlined />} onClick={() => openDrawer(record)}>
</Button> </Button>
)} )}
{canEdit && ( {canEdit && (
<Popconfirm title="纭畾鍒犻櫎鍚楋紵" onConfirm={() => handleDelete(record)}> <Popconfirm title="确认删除吗?" onConfirm={() => handleDelete(record)}>
<Button type="link" danger icon={<DeleteOutlined />}> <Button type="link" danger icon={<DeleteOutlined />}>
</Button> </Button>
</Popconfirm> </Popconfirm>
)} )}
@ -530,11 +528,11 @@ const AiModels: React.FC = () => {
const leftActions = ( const leftActions = (
<Space wrap> <Space wrap>
<Button type="primary" icon={<PlusOutlined/>} onClick={() => openDrawer()}> <Button type="primary" icon={<PlusOutlined/>} onClick={() => openDrawer()}>
</Button> </Button>
{activeType === "ASR" && ( {activeType === "ASR" && (
<Button icon={<SyncOutlined/>} onClick={() => void handleSyncCurrentAsr()}> <Button icon={<SyncOutlined/>} onClick={() => void handleSyncCurrentAsr()}>
ASR ASR
</Button> </Button>
)} )}
</Space> </Space>
@ -543,8 +541,8 @@ const AiModels: React.FC = () => {
return ( return (
<PageContainer title={null} className="ai-models-page"> <PageContainer title={null} className="ai-models-page">
<SectionCard <SectionCard
title="AI 妯″瀷閰嶇疆" title="AI 模型配置"
description="?? ASR ????? LLM ??????" description="管理 ASR 语音识别模型和 LLM 大语言模型"
tabs={ tabs={
<Tabs <Tabs
activeKey={activeType} activeKey={activeType}
@ -553,8 +551,8 @@ const AiModels: React.FC = () => {
setCurrent(1); setCurrent(1);
}} }}
items={[ items={[
{key: "ASR", label: "ASR 妯″瀷"}, {key: "ASR", label: "ASR 模型"},
{key: "LLM", label: "LLM 妯″瀷"}, {key: "LLM", label: "LLM 模型"},
]} ]}
size="middle" size="middle"
type="card" type="card"
@ -568,7 +566,7 @@ const AiModels: React.FC = () => {
rightActions={ rightActions={
<Input.Search <Input.Search
allowClear allowClear
placeholder="鎼滅储妯″瀷鍚嶇О" placeholder="搜索模型名称"
prefix={<SearchOutlined />} prefix={<SearchOutlined />}
className="ai-models-search" className="ai-models-search"
onSearch={(value) => { onSearch={(value) => {
@ -605,13 +603,13 @@ const AiModels: React.FC = () => {
width={600} width={600}
open={drawerVisible} open={drawerVisible}
onClose={() => setDrawerVisible(false)} onClose={() => setDrawerVisible(false)}
title={<Title level={4} style={{margin: 0}}>{editingId ? "缂栬緫妯″瀷" : "鏂板妯″瀷"}</Title>} title={<Title level={4} style={{margin: 0}}>{editingId ? "编辑模型" : "新增模型"}</Title>}
forceRender forceRender
extra={ extra={
<Space> <Space>
<Button onClick={() => setDrawerVisible(false)}></Button> <Button onClick={() => setDrawerVisible(false)}></Button>
<Button type="primary" icon={<SaveOutlined />} loading={submitLoading} onClick={handleSubmit}> <Button type="primary" icon={<SaveOutlined />} loading={submitLoading} onClick={handleSubmit}>
</Button> </Button>
</Space> </Space>
} }
@ -621,9 +619,9 @@ const AiModels: React.FC = () => {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label="妯″瀷绫诲瀷"> <Form.Item label="模型类型">
<Tag color={activeType === "ASR" ? "blue" : "purple"}> <Tag color={activeType === "ASR" ? "blue" : "purple"}>
{activeType === "ASR" ? "璇煶璇嗗埆 (ASR)" : "澶ц瑷€妯″瀷 (LLM)"} {activeType === "ASR" ? "语音识别 (ASR)" : "大语言模型 (LLM)"}
</Tag> </Tag>
</Form.Item> </Form.Item>
@ -631,7 +629,7 @@ const AiModels: React.FC = () => {
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item <Form.Item
name="modelName" name="modelName"
label="鏄剧ず鍚嶇О" label="模型名称"
rules={[{required: true, message: "请输入显示名称"}, {max: 15, message: "模型名称不能超过15个字符"}]} rules={[{required: true, message: "请输入显示名称"}, {max: 15, message: "模型名称不能超过15个字符"}]}
> >
<Input onChange={() => { <Input onChange={() => {
@ -642,10 +640,10 @@ const AiModels: React.FC = () => {
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item <Form.Item
name="provider" name="provider"
label="???" label="提供商"
rules={[{required: true, message: "??????"}]} rules={[{required: true, message: "请选择提供商"}]}
> >
<Select allowClear placeholder="璇烽€夋嫨"> <Select allowClear placeholder="请选择">
{providers.map((item) => ( {providers.map((item) => (
<Option key={item.itemValue} value={item.itemValue}> <Option key={item.itemValue} value={item.itemValue}>
{item.itemLabel} {item.itemLabel}
@ -658,7 +656,7 @@ const AiModels: React.FC = () => {
<Row gutter={16} className="app-responsive-form-row"> <Row gutter={16} className="app-responsive-form-row">
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="sortOrder" label="???"> <Form.Item name="sortOrder" label="排序">
<InputNumber min={0} style={{ width: "100%" }} /> <InputNumber min={0} style={{ width: "100%" }} />
</Form.Item> </Form.Item>
</Col> </Col>
@ -666,7 +664,7 @@ const AiModels: React.FC = () => {
{!isTencentProvider && ( {!isTencentProvider && (
<> <>
<Form.Item name="baseUrl" label="Base URL" rules={[{required: true, message: "璇疯緭鍏?Base URL"}]}> <Form.Item name="baseUrl" label="Base URL" rules={[{required: true, message: "请输入 Base URL"}]}>
<Input placeholder="https://api.example.com"/> <Input placeholder="https://api.example.com"/>
</Form.Item> </Form.Item>
<Form.Item name="apiKey" label="API Key"> <Form.Item name="apiKey" label="API Key">
@ -676,28 +674,28 @@ const AiModels: React.FC = () => {
)} )}
{(activeType === "LLM" || isLocalProvider) && ( {(activeType === "LLM" || isLocalProvider) && (
<Form.Item label="?????"> <Form.Item label="连通性测试">
<Button icon={<WifiOutlined />} loading={connectivityLoading} onClick={handleTestConnectivity}> <Button icon={<WifiOutlined />} loading={connectivityLoading} onClick={handleTestConnectivity}>
</Button> </Button>
</Form.Item> </Form.Item>
)} )}
<Divider orientation="left" style={{ fontSize: 14, color: "#999" }}> <Divider orientation="left" style={{ fontSize: 14, color: "#999" }}>
</Divider> </Divider>
<Form.Item <Form.Item
label="妯″瀷缂栫爜" label="模型编码"
required={activeType === "LLM"} required={activeType === "LLM"}
hidden={activeType === "ASR" && isTencentProvider} hidden={activeType === "ASR" && isTencentProvider}
tooltip="鍙粠杩滅▼鍒楄〃閫夋嫨锛屼篃鍙墜鍔ㄨ緭鍏ワ紱璇ュ€间細浣滀负妯″瀷缂栫爜浼犵粰鍚庣" tooltip="可从远程列表选择,也可手动输入;该值会作为模型编码传给后端"
> >
<Space.Compact style={{ width: "100%" }}> <Space.Compact style={{ width: "100%" }}>
<Form.Item <Form.Item
name="modelCode" name="modelCode"
noStyle noStyle
rules={activeType === "LLM" ? [{required: true, message: "璇疯緭鍏ユ垨閫夋嫨妯″瀷缂栫爜"}] : []} rules={activeType === "LLM" ? [{required: true, message: "请输入或选择模型编码"}] : []}
> >
<AutoComplete <AutoComplete
style={{ width: "calc(100% - 100px)" }} style={{ width: "calc(100% - 100px)" }}
@ -711,18 +709,18 @@ const AiModels: React.FC = () => {
isLocalProvider || String(option?.value || "").toLowerCase().includes(inputValue.toLowerCase()) isLocalProvider || String(option?.value || "").toLowerCase().includes(inputValue.toLowerCase())
} }
> >
<Input allowClear placeholder="????????????"/> <Input allowClear placeholder="请输入或选择模型编码"/>
</AutoComplete> </AutoComplete>
</Form.Item> </Form.Item>
{!isTencentProvider && ( {!isTencentProvider && (
<Button icon={<SyncOutlined spin={fetchLoading}/>} onClick={handleFetchRemote} style={{width: 100}}> <Button icon={<SyncOutlined spin={fetchLoading}/>} onClick={handleFetchRemote} style={{width: 100}}>
</Button> </Button>
)} )}
</Space.Compact> </Space.Compact>
</Form.Item> </Form.Item>
<Form.Item name="wsUrl" label="WebSocket 鍦板潃" <Form.Item name="wsUrl" label="WebSocket 地址"
hidden={!(activeType === "ASR" && createConfig.realtimeEnabled)}> hidden={!(activeType === "ASR" && createConfig.realtimeEnabled)}>
<Input placeholder="wss://api.example.com/v1/ws" /> <Input placeholder="wss://api.example.com/v1/ws" />
</Form.Item> </Form.Item>
@ -730,7 +728,7 @@ const AiModels: React.FC = () => {
{activeType === "ASR" && isLocalProvider && ( {activeType === "ASR" && isLocalProvider && (
<Row gutter={16} hidden className="app-responsive-form-row"> <Row gutter={16} hidden className="app-responsive-form-row">
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="svThreshold" label="????"> <Form.Item name="svThreshold" label="声纹阈值">
<InputNumber min={0} max={1} step={0.01} style={{ width: "100%" }} /> <InputNumber min={0} max={1} step={0.01} style={{ width: "100%" }} />
</Form.Item> </Form.Item>
</Col> </Col>
@ -740,32 +738,32 @@ const AiModels: React.FC = () => {
{activeType === "ASR" && isTencentProvider && ( {activeType === "ASR" && isTencentProvider && (
<Row gutter={16} className="app-responsive-form-row"> <Row gutter={16} className="app-responsive-form-row">
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="tencentAppId" label="App ID" rules={[{required: true, message: "璇疯緭鍏?App ID"}]}> <Form.Item name="tencentAppId" label="App ID" rules={[{required: true, message: "请输入 App ID"}]}>
<Input/> <Input/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="tencentSecretId" label="Secret ID" <Form.Item name="tencentSecretId" label="Secret ID"
rules={[{required: true, message: "璇疯緭鍏?Secret ID"}]}> rules={[{required: true, message: "请输入 Secret ID"}]}>
<Input/> <Input/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={24}> <Col span={24}>
<Form.Item name="tencentSecretKey" label="Secret Key" <Form.Item name="tencentSecretKey" label="Secret Key"
rules={[{required: true, message: "璇疯緭鍏?Secret Key"}]}> rules={[{required: true, message: "请输入 Secret Key"}]}>
<Input.Password/> <Input.Password/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="tencentOfflineModelCode" label="绂荤嚎璇嗗埆妯″瀷" <Form.Item name="tencentOfflineModelCode" label="离线识别模型"
rules={[{required: true, message: "?????????"}]}> rules={[{required: true, message: "请输入离线识别模型"}]}>
<Input placeholder="渚嬪锛?6k_zh"/> <Input placeholder="例如16k_zh"/>
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} md={12}> <Col xs={24} md={12}>
<Form.Item name="tencentRealtimeModelCode" label="瀹炴椂璇嗗埆妯″瀷" <Form.Item name="tencentRealtimeModelCode" label="实时识别模型"
rules={[{required: true, message: "?????????"}]}> rules={[{required: true, message: "请输入实时识别模型"}]}>
<Input placeholder="渚嬪锛?6k_zh_realtime"/> <Input placeholder="例如16k_zh_realtime"/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@ -773,7 +771,7 @@ const AiModels: React.FC = () => {
{activeType === "LLM" && ( {activeType === "LLM" && (
<> <>
<Form.Item name="apiPath" label="API 璺緞" initialValue="/v1/chat/completions"> <Form.Item name="apiPath" label="API 路径" initialValue="/v1/chat/completions">
<Input /> <Input />
</Form.Item> </Form.Item>
<Row gutter={16} className="app-responsive-form-row"> <Row gutter={16} className="app-responsive-form-row">
@ -792,7 +790,7 @@ const AiModels: React.FC = () => {
name="max_tokens" name="max_tokens"
label="max_tokens" label="max_tokens"
rules={[ rules={[
{required: true, message: "璇疯緭鍏?max_tokens"}, {required: true, message: "请输入 max_tokens"},
{ {
validator: (_, value) => { validator: (_, value) => {
if (value === undefined || value === null || value === "") { if (value === undefined || value === null || value === "") {
@ -801,7 +799,7 @@ const AiModels: React.FC = () => {
if (Number.isInteger(value) && value > 0) { if (Number.isInteger(value) && value > 0) {
return Promise.resolve(); return Promise.resolve();
} }
return Promise.reject(new Error("max_tokens 蹇呴』涓烘鏁存暟")); return Promise.reject(new Error("max_tokens 必须为正整数"));
}, },
}, },
]} ]}
@ -815,10 +813,10 @@ const AiModels: React.FC = () => {
<Row gutter={16} className="app-responsive-form-row"> <Row gutter={16} className="app-responsive-form-row">
<Col xs={24} md={8}> <Col xs={24} md={8}>
<Form.Item name="isDefaultChecked" label="璁句负榛樿" valuePropName="checked"> <Form.Item name="isDefaultChecked" label="设为默认" valuePropName="checked">
<Switch <Switch
checkedChildren="?" checkedChildren=""
unCheckedChildren="?" unCheckedChildren=""
onChange={(checked) => { onChange={(checked) => {
if (checked) { if (checked) {
form.setFieldValue("statusChecked", true); form.setFieldValue("statusChecked", true);
@ -828,13 +826,13 @@ const AiModels: React.FC = () => {
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} md={8}> <Col xs={24} md={8}>
<Form.Item name="statusChecked" label="??" valuePropName="checked"> <Form.Item name="statusChecked" label="状态" valuePropName="checked">
<Switch checkedChildren="鍚敤" unCheckedChildren="绂佺敤" disabled={Boolean(isDefaultChecked)}/> <Switch checkedChildren="启用" unCheckedChildren="禁用" disabled={Boolean(isDefaultChecked)}/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Form.Item name="remark" label="澶囨敞"> <Form.Item name="remark" label="备注">
<Input.TextArea rows={2} /> <Input.TextArea rows={2} />
</Form.Item> </Form.Item>
</Form> </Form>

View File

@ -812,9 +812,6 @@ const HotWords: React.FC = () => {
}]}> }]}>
<Input placeholder="例如:项目术语、客户名单" maxLength={15} showCount/> <Input placeholder="例如:项目术语、客户名单" maxLength={15} showCount/>
</Form.Item> </Form.Item>
<Form.Item name="sortOrder" label="排序">
<InputNumber min={0} precision={0} className="hotwords-weight-input"/>
</Form.Item>
<Form.Item name="status" label="状态"> <Form.Item name="status" label="状态">
<Select> <Select>
<Option value={1}></Option> <Option value={1}></Option>

View File

@ -6,7 +6,6 @@ import {
Empty, Empty,
Form, Form,
Input, Input,
InputNumber,
Popconfirm, Popconfirm,
Row, Row,
Select, Select,
@ -133,7 +132,7 @@ const PromptTemplates: React.FC = () => {
setDrawerInitialValues({ setDrawerInitialValues({
...record, ...record,
tags: normalizePromptTags(record.tags), tags: normalizePromptTags(record.tags),
templateName: `${record.templateName} (鍓湰)`, templateName: `${record.templateName} (副本)`,
isSystem: 0, isSystem: 0,
id: undefined, id: undefined,
tenantId: undefined, tenantId: undefined,
@ -172,7 +171,6 @@ const PromptTemplates: React.FC = () => {
setDrawerInitialValues({ setDrawerInitialValues({
status: 1, status: 1,
isSystem: defaultLevel, isSystem: defaultLevel,
sortOrder: 0,
}); });
setTemplateLevel(defaultLevel); setTemplateLevel(defaultLevel);
setSelectedHotWordGroupId(undefined); setSelectedHotWordGroupId(undefined);
@ -199,8 +197,8 @@ const PromptTemplates: React.FC = () => {
) : null} ) : null}
<div className="prompt-template-detail__section"> <div className="prompt-template-detail__section">
<Space wrap> <Space wrap>
{detail.hotWordGroupName ? <Tag color="blue">{detail.hotWordGroupName}</Tag> : {detail.hotWordGroupName ? <Tag color="blue">{detail.hotWordGroupName}</Tag> :
<Tag></Tag>} <Tag></Tag>}
{normalizePromptTags(detail.tags).map((tag) => { {normalizePromptTags(detail.tags).map((tag) => {
const dictItem = dictTags.find((item) => item.itemValue === tag); const dictItem = dictTags.find((item) => item.itemValue === tag);
return <Tag key={tag}>{dictItem ? dictItem.itemLabel : tag}</Tag>; return <Tag key={tag}>{dictItem ? dictItem.itemLabel : tag}</Tag>;
@ -209,20 +207,20 @@ const PromptTemplates: React.FC = () => {
</div> </div>
{detail.hotWords && detail.hotWords.length > 0 ? ( {detail.hotWords && detail.hotWords.length > 0 ? (
<div className="prompt-template-detail__section"> <div className="prompt-template-detail__section">
<div className="prompt-template-detail__section-title"></div> <div className="prompt-template-detail__section-title"></div>
<Space wrap> <Space wrap>
{detail.hotWords.map((word) => <Tag key={word}>{word}</Tag>)} {detail.hotWords.map((word) => <Tag key={word}>{word}</Tag>)}
</Space> </Space>
</div> </div>
) : detail.hotWordGroupId ? ( ) : detail.hotWordGroupId ? (
<div className="prompt-template-detail__section"> <div className="prompt-template-detail__section">
<Text type="secondary"></Text> <Text type="secondary"></Text>
</div> </div>
) : null} ) : null}
<ReactMarkdown remarkPlugins={[remarkGfm]}>{detail.promptContent}</ReactMarkdown> <ReactMarkdown remarkPlugins={[remarkGfm]}>{detail.promptContent}</ReactMarkdown>
</div> </div>
), ),
okText: '鍏抽棴', okText: '关闭',
maskClosable: true, maskClosable: true,
}); });
})(); })();
@ -236,7 +234,7 @@ const PromptTemplates: React.FC = () => {
} }
if (editingId) { if (editingId) {
await updatePromptTemplate({ ...values, id: editingId }); await updatePromptTemplate({ ...values, id: editingId });
message.success('鏇存柊鎴愬姛'); message.success('更新成功');
} else { } else {
await savePromptTemplate(values); await savePromptTemplate(values);
message.success("模板创建成功"); message.success("模板创建成功");
@ -292,28 +290,25 @@ const PromptTemplates: React.FC = () => {
const tableColumns: ColumnsType<PromptTemplateVO> = [ const tableColumns: ColumnsType<PromptTemplateVO> = [
{ {
title: '妯℃澘鍚嶇О', title: '模板名称',
dataIndex: 'templateName', dataIndex: 'templateName',
width: 280, width: 280,
render: (_: unknown, item: PromptTemplateVO) => ( render: (_: unknown, item: PromptTemplateVO) => (
<div className="prompt-template-name-cell"> <div className="prompt-template-name-cell">
<Text strong ellipsis={{ tooltip: item.templateName }}>{item.templateName}</Text> <Text strong ellipsis={{ tooltip: item.templateName }}>{item.templateName}</Text>
{item.description ? ( {item.description ? <Text type="secondary" className="prompt-template-description"
<Text type="secondary" className="prompt-template-description" ellipsis={{ tooltip: item.description }}> ellipsis={{tooltip: item.description}}>{item.description}</Text> : null}
{item.description}
</Text>
) : null}
</div> </div>
), ),
}, },
{ {
title: '鍒嗙被', title: '分类',
dataIndex: 'category', dataIndex: 'category',
width: 140, width: 140,
render: (category: string) => categories.find((c) => c.itemValue === category)?.itemLabel || category || '-', render: (category: string) => categories.find((c) => c.itemValue === category)?.itemLabel || category || '-',
}, },
{ {
title: '灞傜骇', title: '层级',
dataIndex: 'isSystem', dataIndex: 'isSystem',
width: 110, width: 110,
render: (_: unknown, item: PromptTemplateVO) => { render: (_: unknown, item: PromptTemplateVO) => {
@ -322,36 +317,29 @@ const PromptTemplates: React.FC = () => {
}, },
}, },
{ {
title: "业务标签", title: '业务标签',
dataIndex: "tags", dataIndex: 'tags',
minWidth: 220, width: 220,
render: (tags: unknown) => {
render: (tags: unknown) => { render: (tags: unknown) => {
const tagList = normalizePromptTags(tags); const tagList = normalizePromptTags(tags);
if (!tagList.length) { if (!tagList.length) return <Text type="secondary"></Text>;
return <Text type="secondary"></Text>; return <Space size={[4, 4]} wrap className="prompt-template-tags-cell">
} {tagList.slice(0, 3).map((tag) => <Tag
return ( key={tag}>{dictTags.find((item) => item.itemValue === tag)?.itemLabel || tag}</Tag>)}
<Space size={[4, 4]} wrap className="prompt-template-tags-cell"> {tagList.length > 3 ? <Tag>+{tagList.length - 3}</Tag> : null}
{tagList.slice(0, 3).map((tag) => { </Space>;
const dictItem = dictTags.find((dt) => dt.itemValue === tag);
return <Tag key={tag}>{dictItem ? dictItem.itemLabel : tag}</Tag>;
})}
{tagList.length > 3 ? <Tag>+{tagList.length - 3}</Tag> : null}
</Space>
);
}, },
}, },
{ {
title: "状态", title: '状态',
dataIndex: "status", dataIndex: 'status',
width: 90, width: 90,
render: (_: unknown, item: PromptTemplateVO) => ( render: (_: unknown, item: PromptTemplateVO) => <Switch checked={item.status === 1}
/> onChange={(checked) => void handleStatusChange(item.id, checked)}
), onClick={(_, event) => event.stopPropagation()}/>,
}, },
{ {
title: '鎿嶄綔', title: '操作',
key: 'actions', key: 'actions',
width: 150, width: 150,
fixed: 'right' as const, fixed: 'right' as const,
@ -359,29 +347,29 @@ const PromptTemplates: React.FC = () => {
const canEdit = canManageTemplate(item); const canEdit = canManageTemplate(item);
return ( return (
<Space size={2} onClick={(e) => e.stopPropagation()}> <Space size={2} onClick={(e) => e.stopPropagation()}>
<Tooltip title="鏌ョ湅"> <Tooltip title="查看">
<Button type="text" size="small" icon={<EyeOutlined/>} onClick={() => showDetail(item)} <Button type="text" size="small" icon={<EyeOutlined/>} onClick={() => showDetail(item)}
aria-label="鏌ョ湅妯℃澘"/> aria-label="查看模板"/>
</Tooltip> </Tooltip>
{canEdit && ( {canEdit && (
<Tooltip title="缂栬緫"> <Tooltip title="编辑">
<Button type="text" size="small" icon={<EditOutlined/>} onClick={() => handleOpenDrawer(item)} <Button type="text" size="small" icon={<EditOutlined/>} onClick={() => handleOpenDrawer(item)}
aria-label="缂栬緫妯℃澘"/> aria-label="编辑模板"/>
</Tooltip> </Tooltip>
)} )}
<Tooltip title="浠ユ鍒涘缓"> <Tooltip title="以此创建">
<Button type="text" size="small" icon={<CopyOutlined/>} onClick={() => handleOpenDrawer(item, true)} <Button type="text" size="small" icon={<CopyOutlined/>} onClick={() => handleOpenDrawer(item, true)}
aria-label="浠ユ鍒涘缓妯℃澘"/> aria-label="以此创建模板"/>
</Tooltip> </Tooltip>
{canEdit && ( {canEdit && (
<Popconfirm <Popconfirm
title="?????" title="确认删除该模板吗?"
onConfirm={() => deletePromptTemplate(item.id).then(() => fetchData())} onConfirm={() => deletePromptTemplate(item.id).then(() => fetchData())}
okText={t('common.confirm')} okText={t('common.confirm')}
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
> >
<Tooltip title="鍒犻櫎"> <Tooltip title="删除">
<Button type="text" size="small" danger icon={<DeleteOutlined/>} aria-label="鍒犻櫎妯℃澘"/> <Button type="text" size="small" danger icon={<DeleteOutlined/>} aria-label="删除模板"/>
</Tooltip> </Tooltip>
</Popconfirm> </Popconfirm>
)} )}
@ -391,43 +379,32 @@ const PromptTemplates: React.FC = () => {
}, },
]; ];
const promptTableColumnsWithSort: ColumnsType<PromptTemplateVO> = [
...tableColumns.slice(0, 4),
{
title: '鎺掑簭',
dataIndex: 'sortOrder',
width: 100,
render: (value?: number) => value ?? 0,
},
...tableColumns.slice(4),
];
return ( return (
<PageContainer title={null} className="prompt-templates-page"> <PageContainer title={null} className="prompt-templates-page">
<SectionCard <SectionCard
title="?????" title="提示词模板"
description="?? AI ????????????" description="配置 AI 任务所需的提示词模板"
> >
<DataListPanel <DataListPanel
className="prompt-templates-list-panel" className="prompt-templates-list-panel"
leftActions={ leftActions={
<Button type="primary" icon={<PlusOutlined />} onClick={() => handleOpenDrawer()}> <Button type="primary" icon={<PlusOutlined />} onClick={() => handleOpenDrawer()}>
</Button> </Button>
} }
rightActions={ rightActions={
<Form layout="inline" onFinish={handleSearch} className="prompt-templates-search"> <Form layout="inline" onFinish={handleSearch} className="prompt-templates-search">
<Form.Item label="妯℃澘鍚嶇О"> <Form.Item label="模板名称">
<Input <Input
placeholder="璇疯緭鍏?.." placeholder="请输入模板名称"
className="prompt-templates-search__name" className="prompt-templates-search__name"
value={queryDraft.name} value={queryDraft.name}
onChange={(event) => setQueryDraft((currentDraft) => ({ ...currentDraft, name: event.target.value }))} onChange={(event) => setQueryDraft((currentDraft) => ({ ...currentDraft, name: event.target.value }))}
/> />
</Form.Item> </Form.Item>
<Form.Item label="鍒嗙被"> <Form.Item label="分类">
<Select <Select
placeholder="閫夋嫨鍒嗙被" placeholder="选择分类"
className="prompt-templates-search__category" className="prompt-templates-search__category"
allowClear allowClear
value={queryDraft.category} value={queryDraft.category}
@ -438,8 +415,8 @@ const PromptTemplates: React.FC = () => {
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Space> <Space>
<Button type="primary" htmlType="submit"></Button> <Button type="primary" htmlType="submit"></Button>
<Button onClick={handleResetSearch}></Button> <Button onClick={handleResetSearch}></Button>
</Space> </Space>
</Form.Item> </Form.Item>
</Form> </Form>
@ -458,14 +435,14 @@ const PromptTemplates: React.FC = () => {
> >
<Table<PromptTemplateVO> <Table<PromptTemplateVO>
className="prompt-templates-table" className="prompt-templates-table"
columns={promptTableColumnsWithSort} columns={tableColumns}
dataSource={data} dataSource={data}
rowKey="id" rowKey="id"
loading={loading} loading={loading}
pagination={false} pagination={false}
scroll={{ x: "max(100%, 1400px)", y: "100%" }} scroll={{ x: "max(100%, 1400px)", y: "100%" }}
onRow={(record) => ({ onClick: () => showDetail(record) })} onRow={(record) => ({ onClick: () => showDetail(record) })}
locale={{emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="鏆傛棤鍙敤妯℃澘"/>}} locale={{emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无可用模板"/>}}
/> />
</DataListPanel> </DataListPanel>
</SectionCard> </SectionCard>
@ -506,14 +483,14 @@ const PromptTemplates: React.FC = () => {
</Col> </Col>
{(isPlatformAdmin || isTenantAdmin) && ( {(isPlatformAdmin || isTenantAdmin) && (
<Col xs={24} md={12} xl={6}> <Col xs={24} md={12} xl={6}>
<Form.Item name="isSystem" label="????" rules={[{required: true}]}> <Form.Item name="isSystem" label="模板层级" rules={[{required: true}]}>
<Select placeholder="????"> <Select placeholder="请选择模板层级">
{promptLevels.length > 0 ? ( {promptLevels.length > 0 ? (
promptLevels.map((i) => <Option key={i.itemValue} value={Number(i.itemValue)}>{i.itemLabel}</Option>) promptLevels.map((i) => <Option key={i.itemValue} value={Number(i.itemValue)}>{i.itemLabel}</Option>)
) : ( ) : (
<> <>
<Option value={1}>{isPlatformAdmin ? '绯荤粺棰勭疆 (鍏ㄥ眬)' : '绉熸埛棰勭疆 (鍏ㄥ憳)'}</Option> <Option value={1}>{isPlatformAdmin ? '系统预置(全局)' : '租户预置(全员)'}</Option>
<Option value={0}></Option> <Option value={0}></Option>
</> </>
)} )}
</Select> </Select>
@ -521,35 +498,35 @@ const PromptTemplates: React.FC = () => {
</Col> </Col>
)} )}
<Col xs={24} md={12} xl={6}> <Col xs={24} md={12} xl={6}>
<Form.Item name="category" label="鍒嗙被" rules={[{required: true}]}> <Form.Item name="category" label="分类" rules={[{required: true}]}>
<Select loading={dictLoading}> <Select loading={dictLoading}>
{categories.map((i) => <Option key={i.itemValue} value={i.itemValue}>{i.itemLabel}</Option>)} {categories.map((i) => <Option key={i.itemValue} value={i.itemValue}>{i.itemLabel}</Option>)}
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
<Col xs={24} md={12} xl={6}> <Col xs={24} md={12} xl={6}>
<Form.Item name="status" label="??"> <Form.Item name="status" label="状态">
<Select> <Select>
<Option value={1}></Option> <Option value={1}></Option>
<Option value={0}></Option> <Option value={0}></Option>
</Select> </Select>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Form.Item name="description" label="妯℃澘鎻忚堪"> <Form.Item name="description" label="模板描述">
<Input.TextArea <Input.TextArea
maxLength={255} maxLength={255}
showCount showCount
autoSize={{ minRows: 2, maxRows: 4 }} autoSize={{ minRows: 2, maxRows: 4 }}
placeholder="???????" placeholder="请输入模板描述"
/> />
</Form.Item> </Form.Item>
<Row gutter={24}> <Row gutter={24}>
<Col xs={24} xl={12}> <Col xs={24} xl={12}>
<Form.Item name="tags" label="????" tooltip="??????????????????????"> <Form.Item name="tags" label="业务标签" tooltip="可选择已有标签,也可直接输入新标签">
<Select mode="tags" placeholder="閫夋嫨鎴栬緭鍏ユ柊鏍囩" allowClear tokenSeparators={[',', ' ', ';']}> <Select mode="tags" placeholder="选择或输入新标签" allowClear tokenSeparators={[',', ' ', ';']}>
{dictTags.map((item) => <Option key={item.itemValue} value={item.itemValue}>{item.itemLabel}</Option>)} {dictTags.map((item) => <Option key={item.itemValue} value={item.itemValue}>{item.itemLabel}</Option>)}
</Select> </Select>
</Form.Item> </Form.Item>
@ -557,11 +534,11 @@ const PromptTemplates: React.FC = () => {
<Col xs={24} xl={12}> <Col xs={24} xl={12}>
<Form.Item <Form.Item
name="hotWordGroupId" name="hotWordGroupId"
label="?????" label="热词组"
tooltip="鍙€夛紝鏈粦瀹氬垯淇濇寔鍏煎" tooltip="可选,未绑定则保持兼容"
> >
<Select <Select
placeholder="?????" placeholder="请选择热词组"
allowClear allowClear
options={groupOptions.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id }))} options={groupOptions.map((item) => ({ label: `${item.groupName} (${item.hotWordCount}/200)`, value: item.id }))}
/> />
@ -569,29 +546,21 @@ const PromptTemplates: React.FC = () => {
</Col> </Col>
</Row> </Row>
<Row gutter={24}>
<Col xs={24} md={12}>
<Form.Item name="sortOrder" label="鎺掑簭">
<InputNumber min={0} precision={0} style={{width: '100%'}}/>
</Form.Item>
</Col>
</Row>
<Row gutter={[12, 16]} className="prompt-template-editor-header"> <Row gutter={[12, 16]} className="prompt-template-editor-header">
<Col xs={24} xl={12} className="prompt-template-editor-header__col"> <Col xs={24} xl={12} className="prompt-template-editor-header__col">
<div className="prompt-template-editor-title"> <div className="prompt-template-editor-title">
(Markdown ) Markdown
</div> </div>
</Col> </Col>
<Col xs={24} xl={12} className="prompt-template-editor-header__col"> <Col xs={24} xl={12} className="prompt-template-editor-header__col">
<div className="prompt-template-editor__preview-meta"> <div className="prompt-template-editor__preview-meta">
{selectedHotWordGroupId ? ( {selectedHotWordGroupId ? (
<Tag color="blue"> <Tag color="blue">
{groupOptions.find((item) => item.id === selectedHotWordGroupId)?.groupName || '宸查€夋嫨'} {groupOptions.find((item) => item.id === selectedHotWordGroupId)?.groupName || '已选择'}
</Tag> </Tag>
) : ( ) : (
<Tag></Tag> <Tag></Tag>
)} )}
</div> </div>
</Col> </Col>
@ -602,7 +571,7 @@ const PromptTemplates: React.FC = () => {
<Input.TextArea <Input.TextArea
onChange={(e) => setPreviewContent(e.target.value)} onChange={(e) => setPreviewContent(e.target.value)}
className="prompt-template-editor__input" className="prompt-template-editor__input"
placeholder="鍦ㄦ杈撳叆 Markdown 鎸囦护..." placeholder="在此输入 Markdown 提示词..."
/> />
</Form.Item> </Form.Item>
</Col> </Col>