新增调整角色模版的功能

This commit is contained in:
LiJinHui
2025-09-17 15:38:23 +08:00
parent 83b88eeaa0
commit c331b8322c
9 changed files with 2242 additions and 5 deletions
@@ -0,0 +1,223 @@
package xiaozhi.modules.agent.controller;
import java.util.List;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result;
import xiaozhi.common.utils.ResultUtils;
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.agent.vo.AgentTemplateVO;
@Tag(name = "智能体模板管理")
@AllArgsConstructor
@RestController
@RequestMapping("/agent/template")
public class AgentTemplateController {
private final AgentTemplateService agentTemplateService;
@GetMapping("/all")
@Operation(summary = "获取所有模板列表")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentTemplateVO>> getAgentTemplates() {
List<AgentTemplateEntity> templates = agentTemplateService.list();
// 转换为VO列表
List<AgentTemplateVO> voList = templates.stream().map(template -> {
AgentTemplateVO vo = new AgentTemplateVO();
// 复制属性
vo.setId(template.getId());
vo.setAgentCode(template.getAgentCode());
vo.setAgentName(template.getAgentName());
vo.setAsrModelId(template.getAsrModelId());
vo.setVadModelId(template.getVadModelId());
vo.setLlmModelId(template.getLlmModelId());
vo.setVllmModelId(template.getVllmModelId());
vo.setTtsModelId(template.getTtsModelId());
vo.setTtsVoiceId(template.getTtsVoiceId());
vo.setMemModelId(template.getMemModelId());
vo.setIntentModelId(template.getIntentModelId());
vo.setChatHistoryConf(template.getChatHistoryConf());
vo.setSystemPrompt(template.getSystemPrompt());
vo.setSummaryMemory(template.getSummaryMemory());
vo.setLangCode(template.getLangCode());
vo.setLanguage(template.getLanguage());
vo.setSort(template.getSort());
vo.setCreator(template.getCreator());
vo.setCreatedAt(template.getCreatedAt());
vo.setUpdater(template.getUpdater());
vo.setUpdatedAt(template.getUpdatedAt());
return vo;
}).toList();
return new Result<List<AgentTemplateVO>>().ok(voList);
}
@GetMapping("/page")
@Operation(summary = "获取模板分页列表")
@RequiresPermissions("sys:role:normal")
@Parameters({
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
@Parameter(name = "agentName", description = "模板名称,模糊查询")
})
public Result<PageData<AgentTemplateVO>> getAgentTemplatesPage(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
// 创建分页对象
int page = Integer.parseInt(params.getOrDefault(Constant.PAGE, "1").toString());
int limit = Integer.parseInt(params.getOrDefault(Constant.LIMIT, "10").toString());
Page<AgentTemplateEntity> pageInfo = new Page<>(page, limit);
// 创建查询条件
QueryWrapper<AgentTemplateEntity> wrapper = new QueryWrapper<>();
String agentName = (String) params.get("agentName");
if (agentName != null && !agentName.isEmpty()) {
wrapper.like("agent_name", agentName);
}
wrapper.orderByAsc("sort");
// 执行分页查询
IPage<AgentTemplateEntity> pageResult = agentTemplateService.page(pageInfo, wrapper);
// 转换为PageData对象
List<AgentTemplateVO> voList = pageResult.getRecords().stream().map(template -> {
AgentTemplateVO vo = new AgentTemplateVO();
// 复制属性
vo.setId(template.getId());
vo.setAgentCode(template.getAgentCode());
vo.setAgentName(template.getAgentName());
vo.setAsrModelId(template.getAsrModelId());
vo.setVadModelId(template.getVadModelId());
vo.setLlmModelId(template.getLlmModelId());
vo.setVllmModelId(template.getVllmModelId());
vo.setTtsModelId(template.getTtsModelId());
vo.setTtsVoiceId(template.getTtsVoiceId());
vo.setMemModelId(template.getMemModelId());
vo.setIntentModelId(template.getIntentModelId());
vo.setChatHistoryConf(template.getChatHistoryConf());
vo.setSystemPrompt(template.getSystemPrompt());
vo.setSummaryMemory(template.getSummaryMemory());
vo.setLangCode(template.getLangCode());
vo.setLanguage(template.getLanguage());
vo.setSort(template.getSort());
vo.setCreator(template.getCreator());
vo.setCreatedAt(template.getCreatedAt());
vo.setUpdater(template.getUpdater());
vo.setUpdatedAt(template.getUpdatedAt());
return vo;
}).toList();
// 修复:使用构造函数创建PageData对象,而不是无参构造+setter
PageData<AgentTemplateVO> pageData = new PageData<>(voList, pageResult.getTotal());
return new Result<PageData<AgentTemplateVO>>().ok(pageData);
}
@GetMapping("/{id}")
@Operation(summary = "获取模板详情")
@RequiresPermissions("sys:role:normal")
public Result<AgentTemplateVO> getAgentTemplateById(@PathVariable("id") String id) {
AgentTemplateEntity template = agentTemplateService.getById(id);
if (template == null) {
return ResultUtils.error("模板不存在");
}
// 转换为VO
AgentTemplateVO vo = new AgentTemplateVO();
// 复制属性
vo.setId(template.getId());
vo.setAgentCode(template.getAgentCode());
vo.setAgentName(template.getAgentName());
vo.setAsrModelId(template.getAsrModelId());
vo.setVadModelId(template.getVadModelId());
vo.setLlmModelId(template.getLlmModelId());
vo.setVllmModelId(template.getVllmModelId());
vo.setTtsModelId(template.getTtsModelId());
vo.setTtsVoiceId(template.getTtsVoiceId());
vo.setMemModelId(template.getMemModelId());
vo.setIntentModelId(template.getIntentModelId());
vo.setChatHistoryConf(template.getChatHistoryConf());
vo.setSystemPrompt(template.getSystemPrompt());
vo.setSummaryMemory(template.getSummaryMemory());
vo.setLangCode(template.getLangCode());
vo.setLanguage(template.getLanguage());
vo.setSort(template.getSort());
vo.setCreator(template.getCreator());
vo.setCreatedAt(template.getCreatedAt());
vo.setUpdater(template.getUpdater());
vo.setUpdatedAt(template.getUpdatedAt());
return ResultUtils.success(vo);
}
@PostMapping
@Operation(summary = "创建模板")
@RequiresPermissions("sys:role:normal")
public Result<AgentTemplateEntity> createAgentTemplate(@Valid @RequestBody AgentTemplateEntity template) {
boolean saved = agentTemplateService.save(template);
if (saved) {
return ResultUtils.success(template);
} else {
return ResultUtils.error("创建模板失败");
}
}
@PutMapping
@Operation(summary = "更新模板")
@RequiresPermissions("sys:role:normal")
public Result<AgentTemplateEntity> updateAgentTemplate(@Valid @RequestBody AgentTemplateEntity template) {
boolean updated = agentTemplateService.updateById(template);
if (updated) {
return ResultUtils.success(template);
} else {
return ResultUtils.error("更新模板失败");
}
}
@DeleteMapping("/{id}")
@Operation(summary = "删除模板")
@RequiresPermissions("sys:role:normal")
public Result<String> deleteAgentTemplate(@PathVariable("id") String id) {
boolean deleted = agentTemplateService.removeById(id);
if (deleted) {
return ResultUtils.success("删除成功");
} else {
return ResultUtils.error("删除模板失败");
}
}
@DeleteMapping("/batch-delete")
@Operation(summary = "批量删除模板")
@RequiresPermissions("sys:role:normal")
public Result<String> batchDeleteAgentTemplates(@RequestBody List<String> ids) {
boolean deleted = agentTemplateService.removeByIds(ids);
if (deleted) {
return ResultUtils.success("批量删除成功");
} else {
return ResultUtils.error("批量删除模板失败");
}
}
}
+86 -1
View File
@@ -84,7 +84,7 @@ export default {
// 新增方法:获取智能体模板
getAgentTemplate(callback) { // 移除templateName参数
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/template`)
.url(`${getServiceUrl()}/agent/template/all`)
.method('GET')
.success((res) => {
RequestService.clearRequestTime();
@@ -97,6 +97,24 @@ export default {
});
}).send();
},
// 新增:获取智能体模板分页列表
getAgentTemplatesPage(params, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/template/page`)
.method('GET')
.data(params)
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail((err) => {
console.error('获取模板分页列表失败:', err);
RequestService.reAjaxFun(() => {
this.getAgentTemplatesPage(params, callback);
});
}).send();
},
// 获取智能体会话列表
getAgentSessions(agentId, params, callback) {
RequestService.sendRequest()
@@ -265,4 +283,71 @@ export default {
});
}).send();
},
// 在文件末尾(在最后一个方法后,大括号前)添加以下方法:
// 新增智能体模板
addAgentTemplate(templateData, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/template`)
.method('POST')
.data(templateData)
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.addAgentTemplate(templateData, callback);
});
}).send();
},
// 更新智能体模板
updateAgentTemplate(templateData, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/template`)
.method('PUT')
.data(templateData)
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.updateAgentTemplate(templateData, callback);
});
}).send();
},
// 删除智能体模板
deleteAgentTemplate(id, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/template/${id}`)
.method('DELETE')
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.deleteAgentTemplate(id, callback);
});
}).send();
},
// 批量删除智能体模板
batchDeleteAgentTemplate(ids, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/agent/template/batch-delete`)
.method('DELETE')
.data(ids)
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.networkFail(() => {
RequestService.reAjaxFun(() => {
this.batchDeleteAgentTemplate(ids, callback);
});
}).send();
},
}
@@ -0,0 +1,106 @@
<template>
<el-dialog :title="title" :visible.sync="visible" width="60%" @close="handleClose">
<el-form ref="templateForm" :model="templateData" label-width="120px" :rules="rules">
<el-form-item label="模板名称" prop="agentName">
<el-input v-model="templateData.agentName" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="语言编码" prop="langCode">
<el-input v-model="templateData.langCode" placeholder="请输入语言编码,如zh-CN" />
</el-form-item>
<el-form-item label="交互语种" prop="language">
<el-input v-model="templateData.language" placeholder="请输入交互语种,如中文" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="templateData.sort" :min="0" placeholder="请输入排序值" />
</el-form-item>
<el-form-item label="聊天记录配置">
<el-select v-model="templateData.chatHistoryConf" placeholder="请选择聊天记录配置">
<el-option label="不记录" :value="0" />
<el-option label="仅记录文本" :value="1" />
<el-option label="记录文本和语音" :value="2" />
</el-select>
</el-form-item>
<el-form-item label="角色设定参数" prop="systemPrompt">
<el-input v-model="templateData.systemPrompt" type="textarea" placeholder="请输入角色设定参数" :rows="4" />
<div class="form-tip">角色设定参数将作为智能体的系统提示定义智能体的行为和回答风格</div>
</el-form-item>
<el-form-item label="总结记忆" prop="summaryMemory">
<el-input v-model="templateData.summaryMemory" type="textarea" placeholder="请输入总结记忆" :rows="4" />
<div class="form-tip">总结记忆将作为智能体的初始记忆帮助智能体理解上下文</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSave">确定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'AgentTemplateDialog',
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
templateData: {
type: Object,
default: () => ({})
}
},
data() {
return {
// 表单验证规则
rules: {
agentName: [
{ required: true, message: '请输入模板名称', trigger: 'blur' },
{ min: 2, max: 50, message: '模板名称长度在 2 到 50 个字符', trigger: 'blur' }
],
langCode: [
{ required: true, message: '请输入语言编码', trigger: 'blur' }
],
language: [
{ required: true, message: '请输入交互语种', trigger: 'blur' }
],
systemPrompt: [
{ required: true, message: '请输入角色设定参数', trigger: 'blur' },
{ min: 10, message: '角色设定参数至少需要 10 个字符', trigger: 'blur' }
]
}
}
},
methods: {
// 处理关闭对话框
handleClose() {
this.$emit('update:visible', false)
},
// 处理保存模板
handleSave() {
this.$refs.templateForm.validate((valid) => {
if (valid) {
this.$emit('save', this.templateData)
}
})
}
}
}
</script>
<style scoped>
.form-tip {
color: #909399;
font-size: 12px;
margin-top: 5px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
</style>
@@ -0,0 +1,322 @@
<template>
<el-dialog :title="title"
:visible.sync="visible"
width="520px"
class="param-dialog-wrapper"
:append-to-body="true"
:close-on-click-modal="false"
:key="dialogKey"
custom-class="custom-param-dialog"
:show-close="false"
>
<div class="dialog-container">
<div class="dialog-header">
<h2 class="dialog-title">{{ title }}</h2>
<button class="custom-close-btn" @click="cancel">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 1L1 13M1 1L13 13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button>
</div>
<el-form :model="form" :rules="rules" ref="form" label-width="110px" label-position="left" class="param-form">
<el-form-item label="角色名称" prop="roleName" class="form-item">
<el-input v-model="form.roleName" placeholder="请输入角色名称" class="custom-input"></el-input>
</el-form-item>
<el-form-item label="角色编码" prop="roleCode" class="form-item">
<el-input v-model="form.roleCode" placeholder="请输入角色编码" class="custom-input"></el-input>
</el-form-item>
<el-form-item label="角色描述" prop="description" class="form-item remark-item">
<el-input type="textarea" v-model="form.description" placeholder="请输入角色描述" :rows="3" class="custom-textarea"></el-input>
</el-form-item>
</el-form>
<div class="dialog-footer">
<el-button
type="primary"
@click="submit"
class="save-btn"
:loading="saving"
:disabled="saving">
保存
</el-button>
<el-button @click="cancel" class="cancel-btn">
取消
</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
title: {
type: String,
default: '新增角色'
},
visible: {
type: Boolean,
default: false
},
form: {
type: Object,
default: () => ({
id: null,
roleName: '',
roleCode: '',
description: '',
permissions: []
})
}
},
data() {
return {
dialogKey: Date.now(),
saving: false,
rules: {
roleName: [
{ required: true, message: "请输入角色名称", trigger: "blur" }
],
roleCode: [
{ required: true, message: "请输入角色编码", trigger: "blur" }
]
}
};
},
methods: {
submit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.saving = true; // 开始加载
this.$emit('submit', {
form: this.form,
done: () => {
this.saving = false; // 加载完成
}
});
setTimeout(() => {
this.saving = false;
}, 3000);
}
});
},
cancel() {
this.saving = false; // 取消时重置状态
this.$emit('cancel');
}
},
watch: {
visible(newVal) {
if (newVal) {
this.dialogKey = Date.now();
}
}
}
};
</script>
<style>
.custom-param-dialog {
border-radius: 16px !important;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15) !important;
border: none !important;
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0 !important;
border-radius: 16px;
}
}
</style>
<style scoped lang="scss">
.param-dialog-wrapper {
.dialog-container {
padding: 24px 32px;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
}
.dialog-header {
position: relative;
margin-bottom: 24px;
text-align: center;
}
.dialog-title {
font-size: 20px;
color: #1e293b;
margin: 0;
padding: 0;
font-weight: 600;
letter-spacing: 0.5px;
}
.custom-close-btn {
position: absolute;
top: -8px;
right: -8px;
width: 32px;
height: 32px;
border-radius: 50%;
border: none;
background: #f1f5f9;
color: #64748b;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
outline: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
&:hover {
color: #ffffff;
background: #ef4444;
transform: rotate(90deg);
box-shadow: 0 4px 6px rgba(239, 68, 68, 0.2);
}
svg {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
}
.param-form {
.form-item {
margin-bottom: 20px;
:deep(.el-form-item__label) {
color: #475569;
font-weight: 500;
padding-right: 12px;
text-align: right;
font-size: 14px;
letter-spacing: 0.2px;
}
}
.custom-input {
:deep(.el-input__inner) {
background-color: #ffffff;
border-radius: 8px;
border: 1px solid #e2e8f0;
height: 42px;
padding: 0 14px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 14px;
color: #334155;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
background-color: #ffffff;
}
&::placeholder {
color: #94a3b8;
font-weight: 400;
}
}
}
.custom-textarea {
:deep(.el-textarea__inner) {
background-color: #ffffff;
border-radius: 8px;
border: 1px solid #e2e8f0;
padding: 12px 14px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 14px;
color: #334155;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
line-height: 1.5;
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
background-color: #ffffff;
}
&::placeholder {
color: #94a3b8;
font-weight: 400;
}
}
}
.remark-item :deep(.el-form-item__label) {
margin-top: -4px;
}
}
.dialog-footer {
display: flex;
justify-content: center;
padding: 16px 0 0;
margin-top: 16px;
.save-btn {
width: 120px;
height: 42px;
font-size: 14px;
font-weight: 500;
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: #3b82f6;
color: white;
border: none;
letter-spacing: 0.5px;
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2);
&:hover {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(59, 130, 246, 0.3);
}
&:active {
transform: translateY(0);
box-shadow: 0 2px 3px rgba(59, 130, 246, 0.2);
}
}
.cancel-btn {
width: 120px;
height: 42px;
font-size: 14px;
font-weight: 500;
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: #ffffff;
color: #64748b;
border: 1px solid #e2e8f0;
margin-left: 16px;
letter-spacing: 0.5px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
&:hover {
background: #f8fafc;
color: #475569;
border-color: #cbd5e1;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
&:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
}
}
}
</style>
+11 -3
View File
@@ -35,11 +35,11 @@
<span class="nav-text">{{ $t('header.otaManagement') }}</span>
</div>
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown"
:class="{ 'active-tab': $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' }"
:class="{ 'active-tab': $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' || $route.path === '/agent-template-management' }"
@visible-change="handleParamDropdownVisibleChange">
<span class="el-dropdown-link">
<img loading="lazy" alt="" src="@/assets/header/param_management.png"
:style="{ filter: $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' ? 'brightness(0) invert(1)' : 'None' }" />
:style="{ filter: $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' || $route.path === '/agent-template-management' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.paramDictionary') }}</span>
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': paramDropdownVisible }"></i>
</span>
@@ -56,10 +56,14 @@
<el-dropdown-item @click.native="goServerSideManagement">
{{ $t('header.serverSideManagement') }}
</el-dropdown-item>
<!-- 添加默认角色模板管理到参数字典下拉菜单 -->
<el-dropdown-item @click.native="goAgentTemplateManagement">
{{ $t('header.agentTemplate') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<!-- 右侧元素 -->
<div class="header-right">
<div class="search-container" v-if="$route.path === '/home' && !(isSuperAdmin && isSmallScreen)">
@@ -193,6 +197,10 @@ export default {
goServerSideManagement() {
this.$router.push('/server-side-management')
},
// 添加默认角色模板管理导航方法
goAgentTemplateManagement() {
this.$router.push('/agent-template-management')
},
// 获取用户信息
fetchUserInfo() {
userApi.getUserInfo(({ data }) => {
+1
View File
@@ -7,6 +7,7 @@ export default {
'header.paramDictionary': '参数字典',
'header.paramManagement': '参数管理',
'header.dictManagement': '字典管理',
'header.agentTemplate': '默认角色模板',
// 字典数据对话框相关
'dictDataDialog.addDictData': '新增字典数据',
+24 -1
View File
@@ -124,6 +124,29 @@ const routes = [
return import('../views/ProviderManagement.vue')
}
},
// 添加默认角色管理路由
{
path: '/agent-template-management',
name: 'AgentTemplateManagement',
component: function () {
return import('../views/AgentTemplateManagement.vue')
}
},
// 添加模板快速配置路由
{
path: '/template-quick-config',
name: 'TemplateQuickConfig',
component: function () {
return import('../views/TemplateQuickConfig.vue')
}
},
{
path: '/provider-management',
name: 'ProviderManagement',
component: function () {
return import('../views/ProviderManagement.vue')
}
},
]
const router = new VueRouter({
base: process.env.VUE_APP_PUBLIC_PATH || '/',
@@ -145,7 +168,7 @@ VueRouter.prototype.push = function push(location) {
}
// 需要登录才能访问的路由
const protectedRoutes = ['home', 'RoleConfig', 'DeviceManagement', 'UserManagement', 'ModelConfig']
const protectedRoutes = ['home', 'RoleConfig', 'DeviceManagement', 'UserManagement', 'ModelConfig', 'TemplateQuickConfig']
// 路由守卫
router.beforeEach((to, from, next) => {
@@ -0,0 +1,731 @@
<template>
<div class="welcome">
<HeaderBar />
<div class="operation-bar">
<h2 class="page-title">默认角色管理</h2>
<div class="right-operations">
<el-input placeholder="请输入模板名称查询" v-model="search" class="search-input" clearable
@keyup.enter.native="handleSearch" style="width: 240px" />
<el-button class="btn-search" @click="handleSearch">
搜索
</el-button>
</div>
</div>
<!-- 主体内容 -->
<div class="main-wrapper">
<div class="content-panel">
<div class="content-area">
<el-card class="template-card" shadow="never">
<el-table ref="templateTable" :data="templateList" style="width: 100%" v-loading="templateLoading"
element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.7)"
class="transparent-table" @row-click="handleRowClick">
<!-- 自定义选择列实现表头是"选择"文字数据行是小方框 -->
<el-table-column label="选择" align="center" width="80">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.selected" @change="handleRowSelectionChange(scope.row)"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="模板名称" prop="agentName" align="center"></el-table-column>
<el-table-column label="语言编码" prop="langCode" align="center"></el-table-column>
<el-table-column label="交互语种" prop="language" align="center"></el-table-column>
<el-table-column label="排序" prop="sort" align="center"></el-table-column>
<!-- 将原来的操作列合并为一列显示编辑和删除按钮 -->
<el-table-column label="操作" width="160" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click.stop="editTemplate(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click.stop="deleteTemplate(scope.row)" class="delete-btn">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 修改table_bottom结构添加按钮组容器 -->
<div class="table_bottom">
<!-- 左侧按钮组 -->
<div class="ctrl_btn">
<!-- 将checkbox改为按钮样式 -->
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
{{ isAllSelected ? '取消全选' : '全选' }}
</el-button>
<el-button size="mini" type="success" @click="showAddTemplateDialog">新增模板</el-button>
<el-button size="mini" type="danger" @click="batchDeleteTemplate" :disabled="selectedTemplates.length === 0">
批量删除模板
</el-button>
</div>
<!-- 右侧分页控件 -->
<div class="custom-pagination">
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
<el-option v-for="item in pageSizeOptions" :key="item" :label="`${item}条/页`" :value="item">
</el-option>
</el-select>
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
首页
</button>
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">
上一页
</button>
<button v-for="page in visiblePages" :key="page" class="pagination-btn"
:class="{ active: page === currentPage }" @click="goToPage(page)">
{{ page }}
</button>
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
下一页
</button>
<span class="total-text">{{ total }}条记录</span>
</div>
</div>
</el-card>
</div>
</div>
</div>
<!-- 使用模板编辑弹框组件 -->
<AgentTemplateDialog :visible.sync="templateDialogVisible" :title="templateDialogTitle" :templateData="templateForm"
@save="saveTemplate" />
<el-footer style="flex-shrink:unset;">
<version-footer />
</el-footer>
</div>
</template>
<script>
import agentApi from '@/apis/module/agent'
import AgentTemplateDialog from '@/components/AgentTemplateDialog.vue'
import HeaderBar from '@/components/HeaderBar.vue'
import VersionFooter from '@/components/VersionFooter.vue'
export default {
name: 'AgentTemplateManagement',
components: {
HeaderBar,
AgentTemplateDialog,
VersionFooter
},
// 1. 首先确保在 data 部分添加了 isAllSelected 状态
data() {
return {
// 模板相关
templateList: [],
templateLoading: false,
selectedTemplates: [],
isAllSelected: false, // 添加全选状态
templateDialogVisible: false,
templateDialogTitle: '新增模板',
templateForm: {
id: null,
agentCode: '',
agentName: '',
asrModelId: '',
vadModelId: '',
llmModelId: '',
vllmModelId: '',
ttsModelId: '',
ttsVoiceId: '',
memModelId: '',
intentModelId: '',
chatHistoryConf: 0,
systemPrompt: '',
summaryMemory: '',
langCode: '',
language: '',
sort: 0
},
search: '',
// 分页相关数据
pageSizeOptions: [10, 20, 50, 100],
currentPage: 1,
pageSize: 10,
total: 0
}
},
created() {
this.loadTemplateList()
},
computed: {
pageCount() {
return Math.ceil(this.total / this.pageSize)
},
visiblePages() {
return this.getVisiblePages()
}
},
methods: {
// 加载模板列表
// 改进loadTemplateList方法的错误处理逻辑
loadTemplateList() {
this.templateLoading = true
const params = {
page: this.currentPage,
limit: this.pageSize
}
if (this.search) {
params.agentName = this.search
}
try {
agentApi.getAgentTemplatesPage(params, (res) => {
// 更健壮的响应处理逻辑
if (res && typeof res === 'object') {
if (res.data && res.data.code === 0) {
const responseData = res.data.data || {}
// 为每个模板添加selected属性
this.templateList = Array.isArray(responseData.list) ?
responseData.list.map(item => ({ ...item, selected: false })) : []
this.total = typeof responseData.total === 'number' ? responseData.total : 0
} else {
console.error('获取模板列表失败:', res)
this.templateList = []
this.total = 0
this.$message.error(res?.data?.msg || '获取模板列表失败')
}
} else {
console.error('无效的响应对象:', res)
this.templateList = []
this.total = 0
this.$message.error('获取模板列表失败')
}
this.templateLoading = false
}, (error) => {
console.error('API调用失败:', error)
this.templateList = []
this.total = 0
this.templateLoading = false
this.$message.error('网络请求失败')
})
} catch (error) {
console.error('调用API时发生异常:', error)
this.templateList = []
this.total = 0
this.templateLoading = false
this.$message.error('加载模板列表时发生错误')
}
},
// 搜索模板
handleSearch() {
if (this.search) {
const searchValue = this.search.toLowerCase()
const filteredList = this.templateList.filter(template =>
template.agentName.toLowerCase().includes(searchValue)
)
this.templateList = filteredList
this.total = filteredList.length
} else {
this.loadTemplateList()
}
},
// 修改showAddTemplateDialog方法,使其跳转到与编辑页面相同的页面
// 显示新增模板弹窗
showAddTemplateDialog() {
// 跳转到模板快速配置页面,不传递templateId参数表示新增
this.$router.push({
path: '/template-quick-config'
})
},
// 保留原有的editTemplate方法
// 编辑模板 - 完全替换这个方法
editTemplate(row) {
// 跳转到模板快速配置页面,并传递模板ID参数
this.$router.push({
path: '/template-quick-config',
query: { templateId: row.id }
})
},
// 保存模板
saveTemplate(data) {
// 这里需要调用保存模板的API
if (data.id) {
// 更新模板
agentApi.updateAgentTemplate(data, (res) => {
if (res.code === 0) {
this.$message.success('模板更新成功')
this.templateDialogVisible = false
this.loadTemplateList()
} else {
this.$message.error('模板更新失败')
}
})
} else {
// 新增模板
agentApi.addAgentTemplate(data, (res) => {
if (res.code === 0) {
this.$message.success('模板新增成功')
this.templateDialogVisible = false
this.loadTemplateList()
} else {
this.$message.error('模板新增失败')
}
})
}
},
// 删除模板
deleteTemplate(row) {
this.$confirm('确定要删除这个模板吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
agentApi.deleteAgentTemplate(row.id, (res) => {
// 添加调试日志
console.log('删除模板响应:', res);
if (res && typeof res === 'object') {
// 检查res.data是否存在且包含code=0
if (res.data && res.data.code === 0) {
this.$message.success('模板删除成功')
this.loadTemplateList()
} else {
this.$message.error(res?.data?.msg || '模板删除失败')
}
} else {
console.error('无效的响应对象:', res);
this.$message.error('删除失败,请检查后端服务是否正常')
}
})
}).catch(() => {
this.$message.info('已取消删除')
})
},
// 修改batchDeleteTemplate方法,使用selectedTemplates
batchDeleteTemplate() {
if (this.selectedTemplates.length === 0) {
this.$message.warning('请选择要删除的模板')
return
}
this.$confirm(`确定要删除选中的 ${this.selectedTemplates.length} 个模板吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const ids = this.selectedTemplates.map(template => template.id)
agentApi.batchDeleteAgentTemplate(ids, (res) => {
if (res && typeof res === 'object') {
if (res.data && res.data.code === 0) {
this.$message.success('模板批量删除成功')
this.loadTemplateList()
this.selectedTemplates = []
this.isAllSelected = false
} else {
this.$message.error(res?.data?.msg || '模板批量删除失败')
}
} else {
console.error('无效的响应对象:', res)
this.$message.error('删除失败')
}
})
}).catch(() => {
this.$message.info('已取消删除')
})
},
// 分页相关方法
handlePageSizeChange() {
this.currentPage = 1
},
goFirst() {
this.currentPage = 1
},
goPrev() {
this.currentPage--
},
goNext() {
this.currentPage++
},
goToPage(page) {
this.currentPage = page
},
getVisiblePages() {
const pages = []
const totalPages = this.pageCount
const currentPage = this.currentPage
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
pages.push(i)
}
} else {
if (currentPage <= 4) {
for (let i = 1; i <= 5; i++) {
pages.push(i)
}
pages.push('...')
pages.push(totalPages)
} else if (currentPage >= totalPages - 3) {
pages.push(1)
pages.push('...')
for (let i = totalPages - 4; i <= totalPages; i++) {
pages.push(i)
}
} else {
pages.push(1)
pages.push('...')
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
pages.push(i)
}
pages.push('...')
pages.push(totalPages)
}
}
return pages
},
// 修改handleSelectAll方法
handleSelectAll() {
this.isAllSelected = !this.isAllSelected
this.templateList.forEach(row => {
row.selected = this.isAllSelected
})
// 更新选中的模板列表
this.selectedTemplates = this.isAllSelected ? [...this.templateList] : []
},
// 处理行选择变化
handleRowSelectionChange(row) {
// 查找选中的模板
this.selectedTemplates = this.templateList.filter(template => template.selected);
// 更新全选状态
this.isAllSelected = this.templateList.length > 0 && this.selectedTemplates.length === this.templateList.length;
},
// 修改handleRowClick方法,实现点击行选中/取消选中
handleRowClick(row, event, column) {
// 如果点击的是选择框所在的列,则不触发行选择
if (column && column.label === '选择') {
return;
}
row.selected = !row.selected;
this.handleRowSelectionChange(row);
},
}
}
</script>
<style scoped lang="scss">
/* 首先确保html和body元素有正确的背景设置 */
:global(html),
:global(body) {
background: #f5f7fa;
margin: 0;
padding: 0;
height: 100%;
}
.welcome {
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
background-size: cover;
background: linear-gradient(to bottom right, #dce8ff, #e4eeff, #e6cbfd) center;
-webkit-background-size: cover;
-o-background-size: cover;
overflow: hidden;
width: 100%;
}
.operation-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
}
.page-title {
font-size: 24px;
margin: 0;
}
.right-operations {
display: flex;
align-items: center;
gap: 10px;
}
.search-input {
width: 200px;
}
.btn-search {
background: linear-gradient(135deg, #6b8cff, #a966ff);
border: none;
color: white;
}
.main-wrapper {
margin: 5px 22px;
border-radius: 15px;
min-height: calc(100vh - 24vh);
height: auto;
max-height: 80vh;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
position: relative;
background: rgba(237, 242, 255, 0.5);
display: flex;
flex-direction: column;
}
.content-panel {
width: 100%;
flex: 1;
display: flex;
overflow: hidden;
border-radius: 15px;
background: transparent;
border: 1px solid #fff;
}
.content-area {
flex: 1;
min-width: 600px;
overflow-x: auto;
background-color: white;
display: flex;
flex-direction: column;
position: relative;
}
.template-card {
border: none;
box-shadow: none;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
:deep(.el-card__body) {
padding: 15px;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
}
.transparent-table {
width: 100%;
/* 关键:为表格容器设置最大高度,确保有足够空间将按钮推到底部 */
flex: 1;
min-height: 0;
}
/* 添加表格内部容器的样式,确保表格本身有高度限制 */
:deep(.el-table) {
height: 100%;
display: flex;
flex-direction: column;
--table-max-height: calc(100vh - 42vh);
max-height: var(--table-max-height);
.el-table__body-wrapper {
flex: 1;
overflow-y: auto;
overflow-x: auto;
}
}
// 表格底部操作栏
.table_bottom {
display: flex;
justify-content: space-between !important;
align-items: center;
margin-top: auto; /* 关键:使用auto margin将按钮栏推到底部 */
padding: 0 20px 15px !important; /* 增加底部padding,让按钮看起来更靠下 */
width: 100% !important;
box-sizing: border-box !important;
}
.ctrl_btn {
display: flex;
gap: 8px;
padding-left: 0 !important;
margin-left: 0 !important;
.el-button {
min-width: 72px;
height: 32px;
padding: 7px 12px 7px 10px;
font-size: 12px;
border-radius: 4px;
line-height: 1;
font-weight: 500;
border: none;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
}
.el-button--primary {
background: #5f70f3;
color: white;
}
.el-button--success {
background: #5bc98c;
color: white;
}
.el-button--danger {
background: #fd5b63;
color: white;
}
}
// 自定义分页样式
.custom-pagination {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto !important;
padding-right: 0 !important;
margin-right: 0 !important;
}
// 调整分页按钮样式
.pagination-btn:last-child {
margin-right: 0;
}
.el-select {
margin-right: 8px;
}
.pagination-btn:first-child,
.pagination-btn:nth-child(2),
.pagination-btn:nth-child(3),
.pagination-btn:nth-last-child(2) {
min-width: 60px;
height: 32px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #e4e7ed;
background: #dee7ff;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #d7dce6;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.pagination-btn:not(:first-child):not(:nth-child(2)):not(:nth-child(3)):not(:nth-last-child(2)) {
min-width: 28px;
height: 32px;
padding: 0;
border-radius: 4px;
border: 1px solid transparent;
background: transparent;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: rgba(245, 247, 250, 0.3);
}
}
.pagination-btn.active {
background: #5f70f3 !important;
color: #ffffff !important;
border-color: #5f70f3 !important;
&:hover {
background: #6d7cf5 !important;
}
}
.total-text {
color: #909399;
font-size: 14px;
margin-left: 10px;
}
// 表格内部样式调整
:deep(.el-table__header th) {
padding: 8px 0 !important;
height: 40px !important;
}
:deep(.el-table__header th .cell) {
color: #303133 !important;
font-weight: 600;
}
:deep(.el-table__body) {
.el-table__row td {
padding: 12px 0 !important;
border-bottom: 1px solid #ebeef5;
}
.el-table__row:hover {
background-color: #f5f7fa;
}
}
:deep(.el-table .el-button--text) {
color: #7079aa;
}
:deep(.el-table .el-button--text:hover) {
color: #5a64b5;
}
:deep(.el-table .cell) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// 选择框样式
:deep(.el-checkbox__inner) {
background-color: #ffffff !important;
border-color: #dcdfe6 !important;
width: 16px !important;
height: 16px !important;
border-radius: 2px !important;
}
:deep(.el-checkbox__inner:hover) {
border-color: #c0c4cc !important;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #5f70f3 !important;
border-color: #5f70f3 !important;
}
// 调整表格最大高度
:deep(.el-table) {
--table-max-height: calc(100vh - 42vh);
max-height: var(--table-max-height);
.el-table__body-wrapper {
max-height: calc(var(--table-max-height) - 40px);
}
}
</style>
@@ -0,0 +1,738 @@
<template>
<div class="welcome">
<HeaderBar />
<div class="operation-bar">
<h2 class="page-title">模板快速配置</h2>
</div>
<div class="main-wrapper">
<div class="content-panel">
<div class="content-area">
<el-card class="config-card" shadow="never">
<div class="config-header">
<!-- 使用角色配置页面相同的彩色图标效果 -->
<div class="header-icon">
<img loading="lazy" src="@/assets/home/setting-user.png" alt="">
</div>
<span class="header-title">{{ form.agentName }}</span>
<div class="header-actions">
<el-button type="primary" class="save-btn" @click="saveConfig">保存配置</el-button>
<el-button class="reset-btn" @click="resetConfig">重置</el-button>
<button class="custom-close-btn" @click="goToHome">
×
</button>
</div>
</div>
<div class="divider"></div>
<el-form ref="form" :model="form" label-width="72px" class="full-height-form">
<!-- 助手昵称 -->
<el-form-item label="助手昵称" prop="agentName" class="nickname-item">
<el-input
v-model="form.agentName"
placeholder="请输入助手昵称"
:validate-event="false"
class="form-input"
/>
</el-form-item>
<!-- 角色介绍 -->
<el-form-item label="角色介绍" prop="systemPrompt" class="description-item">
<el-input
v-model="form.systemPrompt"
type="textarea"
placeholder="请输入角色介绍"
:validate-event="false"
show-word-limit
maxlength="2000"
/>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</div>
</div>
</template>
<script>
import Api from '@/apis/api';
import FunctionDialog from "@/components/FunctionDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue";
import agentApi from '@/apis/module/agent';
export default {
name: 'TemplateQuickConfig',
components: { HeaderBar, FunctionDialog },
data() {
return {
form: {
agentCode: "小智", // 设置默认值
agentName: "",
ttsVoiceId: "TTS_EdgeTTS0001", // 添加默认值
chatHistoryConf: 0,
systemPrompt: "",
summaryMemory: "",
langCode: "zh", // 设置默认值
language: "中文", // 设置默认值
sort: 0, // 设置默认值
model: {
ttsModelId: "TTS_EdgeTTS", // 添加默认值
vadModelId: "VAD_SileroVAD", // 设置默认值
asrModelId: "ASR_FunASR", // 设置默认值
llmModelId: "LLM_ChatGLMLLM", // 设置默认值
vllmModelId: "VLLM_ChatGLMVLLM", // 设置默认值
memModelId: "Memory_nomem", // 设置默认值
intentModelId: "Intent_function_call", // 设置默认值
}
},
models: [
{ label: '语音活动检测(VAD)', key: 'vadModelId', type: 'VAD' },
{ label: '语音识别(ASR)', key: 'asrModelId', type: 'ASR' },
{ label: '大语言模型(LLM)', key: 'llmModelId', type: 'LLM' },
{ label: '视觉大模型(VLLM)', key: 'vllmModelId', type: 'VLLM' },
{ label: '意图识别(Intent)', key: 'intentModelId', type: 'Intent' },
{ label: '记忆(Memory)', key: 'memModelId', type: 'Memory' },
{ label: '语音合成(TTS)', key: 'ttsModelId', type: 'TTS' }
],
llmModeTypeMap: new Map(),
modelOptions: {},
templateId: '',
voiceOptions: [],
showFunctionDialog: false,
currentFunctions: [],
functionColorMap: [
'#FF6B6B', '#4ECDC4', '#45B7D1',
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
],
allFunctions: [],
originalFunctions: [],
originalForm: null
}
},
methods: {
goToHome() {
this.$router.push('/agent-template-management');
},
// 修改saveConfig方法中的响应检查逻辑
saveConfig() {
const configData = {
agentCode: this.form.agentCode,
agentName: this.form.agentName,
// 不需要单独提交agentDescription,使用systemPrompt字段
asrModelId: this.form.model.asrModelId,
vadModelId: this.form.model.vadModelId,
llmModelId: this.form.model.llmModelId,
vllmModelId: this.form.model.vllmModelId,
ttsModelId: this.form.model.ttsModelId,
ttsVoiceId: this.form.ttsVoiceId,
chatHistoryConf: this.form.chatHistoryConf,
memModelId: this.form.model.memModelId,
intentModelId: this.form.model.intentModelId,
systemPrompt: this.form.systemPrompt, // 这个字段会保存角色介绍
summaryMemory: this.form.summaryMemory,
langCode: this.form.langCode,
language: this.form.language,
sort: this.form.sort,
functions: this.currentFunctions.map(item => {
return ({
pluginId: item.id,
paramInfo: item.params
})
})
};
// 修复saveConfig方法中的回调参数结构
// 如果有templateId,使用更新模板API
if (this.templateId) {
configData.id = this.templateId;
agentApi.updateAgentTemplate(configData, (res) => { // 修改为(res)而不是({ res })
// 添加调试日志以便排查问题
console.log('保存模板响应:', res);
if (res && typeof res === 'object') {
// 检查res.data是否存在且包含code=0
if (res.data && res.data.code === 0) {
this.$message.success({
message: '模板配置保存成功',
showClose: true
});
this.originalForm = JSON.parse(JSON.stringify(this.form));
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
} else {
this.$message.error({
message: res?.data?.msg || '模板配置保存失败',
showClose: true
});
}
} else {
console.error('无效的响应对象:', res);
this.$message.error('保存失败,请检查后端服务是否正常');
}
});
} else {
// 否则使用添加模板API
agentApi.addAgentTemplate(configData, (res) => { // 修改为(res)而不是({ res })
// 添加调试日志以便排查问题
console.log('添加模板响应:', res);
if (res && typeof res === 'object') {
// 检查res.data是否存在且包含code=0
if (res.data && res.data.code === 0) {
this.$message.success({
message: '模板配置保存成功',
showClose: true
});
this.goToHome();
} else {
this.$message.error({
message: res?.data?.msg || '模板配置保存失败',
showClose: true
});
}
} else {
console.error('无效的响应对象:', res);
this.$message.error('保存失败,请检查后端服务是否正常');
}
});
}
},
resetConfig() {
this.$confirm('确定要重置配置吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (this.originalForm) {
this.form = JSON.parse(JSON.stringify(this.originalForm));
this.currentFunctions = JSON.parse(JSON.stringify(this.originalFunctions));
}
this.$message.success({
message: '配置已重置',
showClose: true
})
}).catch(() => {
});
},
// 修改fetchTemplateById方法中的回调参数结构
fetchTemplateById(templateId) {
// 获取所有模板,然后找到指定ID的模板
agentApi.getAgentTemplate((res) => { // 修改为(res)而不是({ data })
// 添加调试日志以便排查问题
console.log('获取模板列表完整响应:', res);
if (res && typeof res === 'object') {
// 检查res.data是否存在且包含code=0
if (res.data && res.data.code === 0) {
// 实际数据在res.data.data中,而不是res.data
const templateList = res.data.data || [];
console.log('实际模板列表数据:', templateList);
const template = templateList.find(t => t.id === templateId);
if (template) {
this.applyTemplateData(template);
this.templateId = templateId;
this.originalForm = JSON.parse(JSON.stringify(this.form));
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
} else {
console.error('未找到指定模板,ID:', templateId);
this.$message.error('未找到指定模板');
}
} else {
console.error('获取模板失败:', res);
this.$message.error(res?.data?.msg || '获取模板失败');
}
} else {
console.error('无效的响应对象:', res);
this.$message.error('获取模板失败,请检查后端服务是否正常');
}
});
},
applyTemplateData(templateData) {
this.form = {
...this.form,
agentName: templateData.agentName || this.form.agentName,
agentCode: templateData.agentCode || this.form.agentCode,
// 删除agentDescription字段的处理
ttsVoiceId: templateData.ttsVoiceId || this.form.ttsVoiceId,
chatHistoryConf: templateData.chatHistoryConf || this.form.chatHistoryConf,
systemPrompt: templateData.systemPrompt || this.form.systemPrompt,
summaryMemory: templateData.summaryMemory || this.form.summaryMemory,
langCode: templateData.langCode || this.form.langCode,
language: templateData.language || this.form.language,
sort: templateData.sort || this.form.sort,
model: {
ttsModelId: templateData.ttsModelId || this.form.model.ttsModelId,
vadModelId: templateData.vadModelId || this.form.model.vadModelId,
asrModelId: templateData.asrModelId || this.form.model.asrModelId,
llmModelId: templateData.llmModelId || this.form.model.llmModelId,
vllmModelId: templateData.vllmModelId || this.form.model.vllmModelId,
memModelId: templateData.memModelId || this.form.model.memModelId,
intentModelId: templateData.intentModelId || this.form.model.intentModelId
}
};
},
fetchModelOptions() {
this.models.forEach(model => {
if (model.type != "LLM") {
Api.model.getModelNames(model.type, '', ({ data }) => {
if (data.code === 0) {
this.$set(this.modelOptions, model.type, data.data.map(item => ({
value: item.id,
label: item.modelName,
isHidden: false
})));
// 如果是意图识别选项,需要根据当前LLM类型更新可见性
if (model.type === 'Intent') {
this.updateIntentOptionsVisibility();
}
} else {
this.$message.error(data.msg || '获取模型列表失败');
}
});
} else {
Api.model.getLlmModelCodeList('', ({ data }) => {
if (data.code === 0) {
let LLMdata = []
data.data.forEach(item => {
LLMdata.push({
value: item.id,
label: item.modelName,
isHidden: false
})
this.llmModeTypeMap.set(item.id, item.type)
})
this.$set(this.modelOptions, model.type, LLMdata);
} else {
this.$message.error(data.msg || '获取LLM模型列表失败');
}
});
}
});
},
fetchVoiceOptions(modelId) {
if (!modelId) {
this.voiceOptions = [];
return;
}
Api.model.getModelVoices(modelId, '', ({ data }) => {
if (data.code === 0 && data.data) {
this.voiceOptions = data.data.map(voice => ({
value: voice.id,
label: voice.name
}));
} else {
this.voiceOptions = [];
}
});
},
getFunctionColor(name) {
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
return this.functionColorMap[hash % this.functionColorMap.length];
},
showFunctionIcons(type) {
return type === 'Intent' &&
this.form.model.intentModelId !== 'Intent_nointent';
},
handleModelChange(type, value) {
if (type === 'Intent' && value !== 'Intent_nointent') {
this.fetchAllFunctions();
}
if (type === 'Memory' && value === 'Memory_nomem') {
this.form.chatHistoryConf = 0;
}
if (type === 'Memory' && value !== 'Memory_nomem' && (this.form.chatHistoryConf === 0 || this.form.chatHistoryConf === null)) {
this.form.chatHistoryConf = 2;
}
if (type === 'LLM') {
// 当LLM类型改变时,更新意图识别选项的可见性
this.updateIntentOptionsVisibility();
}
},
fetchAllFunctions() {
return new Promise((resolve, reject) => {
Api.model.getPluginFunctionList(null, ({ data }) => {
if (data.code === 0) {
this.allFunctions = data.data.map(item => {
const meta = JSON.parse(item.fields || '[]');
const params = meta.reduce((m, f) => {
m[f.key] = f.default;
return m;
}, {});
return { ...item, fieldsMeta: meta, params };
});
resolve();
} else {
this.$message.error(data.msg || '获取插件列表失败');
reject();
}
});
});
},
openFunctionDialog() {
// 显示编辑对话框时,确保 allFunctions 已经加载
if (this.allFunctions.length === 0) {
this.fetchAllFunctions().then(() => this.showFunctionDialog = true);
} else {
this.showFunctionDialog = true;
}
},
handleUpdateFunctions(selected) {
this.currentFunctions = selected;
},
handleDialogClosed(saved) {
if (!saved) {
this.currentFunctions = JSON.parse(JSON.stringify(this.originalFunctions));
} else {
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
}
this.showFunctionDialog = false;
},
updateIntentOptionsVisibility() {
// 根据当前选择的LLM类型更新意图识别选项的可见性
const currentLlmId = this.form.model.llmModelId;
if (!currentLlmId || !this.modelOptions['Intent']) return;
const llmType = this.llmModeTypeMap.get(currentLlmId);
if (!llmType) return;
this.modelOptions['Intent'].forEach(item => {
if (item.value === "Intent_function_call") {
// 如果llmType是openai或ollama,允许选择function_call
// 否则隐藏function_call选项
if (llmType === "openai" || llmType === "ollama") {
item.isHidden = false;
} else {
item.isHidden = true;
}
} else {
// 其他意图识别选项始终可见
item.isHidden = false;
}
});
// 如果当前选择的意图识别是function_call,但LLM类型不支持,则设置为可选的第一项
if (this.form.model.intentModelId === "Intent_function_call" &&
llmType !== "openai" && llmType !== "ollama") {
// 找到第一个可见的选项
const firstVisibleOption = this.modelOptions['Intent'].find(item => !item.isHidden);
if (firstVisibleOption) {
this.form.model.intentModelId = firstVisibleOption.value;
} else {
// 如果没有可见选项,设置为Intent_nointent
this.form.model.intentModelId = 'Intent_nointent';
}
}
},
updateChatHistoryConf() {
if (this.form.model.memModelId === 'Memory_nomem') {
this.form.chatHistoryConf = 0;
}
},
},
watch: {
'form.model.ttsModelId': {
handler(newVal, oldVal) {
if (oldVal && newVal !== oldVal) {
this.form.ttsVoiceId = '';
this.fetchVoiceOptions(newVal);
} else {
this.fetchVoiceOptions(newVal);
}
},
immediate: true
},
voiceOptions: {
handler(newVal) {
if (newVal && newVal.length > 0 && !this.form.ttsVoiceId) {
this.form.ttsVoiceId = newVal[0].value;
}
},
immediate: true
}
},
mounted() {
// 从URL参数获取templateId
const templateId = this.$route.query.templateId;
this.fetchModelOptions();
this.fetchAllFunctions();
if (templateId) {
this.fetchTemplateById(templateId);
} else {
// 如果没有templateId,初始化一个新的模板
this.form.agentName = '新模板';
// 获取所有模板以计算最大的sort值
agentApi.getAgentTemplate((res) => {
if (res && typeof res === 'object' && res.data && res.data.code === 0) {
const templateList = res.data.data || [];
if (templateList && templateList.length > 0) {
// 计算最大的sort值
const maxSort = Math.max(...templateList.map(t => t.sort || 0));
// 设置新模板的sort值为最大值+1
this.form.sort = maxSort + 1;
} else {
// 如果没有模板,设置默认值为1
this.form.sort = 1;
}
} else {
console.error('获取模板列表失败,使用默认sort值');
// 获取失败时使用默认值
this.form.sort = 1;
}
// 保存初始状态
this.originalForm = JSON.parse(JSON.stringify(this.form));
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
});
}
}
}
</script>
<style scoped>
.welcome {
min-width: 900px;
height: 100vh;
display: flex;
position: relative;
flex-direction: column;
background: linear-gradient(to bottom right, #dce8ff, #e4eeff, #e6cbfd);
background-size: cover;
-webkit-background-size: cover;
-o-background-size: cover;
overflow: hidden;
}
.operation-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5vh 24px;
}
.page-title {
font-size: 24px;
margin: 0;
color: #2c3e50;
}
.main-wrapper {
margin: 0 22px 22px 22px; /* 保留左右和底部边距 */
border-radius: 15px;
height: calc(100vh - 14vh); /* 调整高度计算 */
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
position: relative;
background: rgba(237, 242, 255, 0.5);
display: flex;
flex-direction: column;
padding: 0 !important; /* 确保没有内边距 */
}
.content-panel {
flex: 1;
overflow-y: auto;
padding: 0 !important; /* 确保没有内边距 */
margin: 0 !important; /* 确保没有外边距 */
}
.content-area {
width: 100% !important;
max-width: none !important;
margin: 0 !important;
height: 100% !important;
}
/* 调整config-card样式 */
.config-card {
background: white !important;
border-radius: 15px !important;
overflow: hidden !important;
height: 100% !important;
margin: 0 !important;
border: none !important;
box-shadow: none !important;
}
/* 修复表单容器样式 */
.full-height-form {
display: flex;
flex-direction: column;
padding: 20px;
gap: 20px;
height: calc(100% - 120px);
}
/* 助手昵称样式 */
.nickname-item {
margin-bottom: 0 !important;
}
/* 修复角色介绍项目样式 */
.description-item {
margin-bottom: 0 !important;
display: flex;
flex-direction: column;
}
/* 修复Element UI的textarea容器样式 */
::v-deep .description-item .el-textarea {
height: 300px; /* 设置固定高度 */
min-height: 200px; /* 最小高度 */
max-height: 400px; /* 最大高度,防止超出页面 */
display: flex;
flex-direction: column;
}
/* 修复textarea样式 */
::v-deep .description-item .el-textarea__inner {
height: 100% !important;
min-height: 200px !important;
max-height: 400px !important;
resize: vertical !important; /* 允许垂直调整大小 */
line-height: 1.6;
font-size: 14px;
padding: 10px;
border: 1px solid #dcdfe6; /* 添加边框使其更清晰可见 */
border-radius: 4px; /* 添加圆角 */
background-color: #fff; /* 确保背景色为白色 */
color: #303133; /* 确保文字颜色正常 */
}
/* 其他样式保持不变 */
::v-deep .el-form-item__label {
font-size: 12px !important;
color: #3d4566 !important;
font-weight: 400;
line-height: 22px;
padding-bottom: 2px;
}
::v-deep .el-textarea .el-input__count {
color: #909399;
background: rgba(255, 255, 255, 0.8);
position: absolute;
font-size: 12px;
right: 10px;
bottom: 10px;
padding: 2px 5px;
border-radius: 3px;
}
/* 配置头部样式调整 */
.config-header {
padding: 20px;
display: flex;
align-items: center;
gap: 15px;
background: #f8f9ff;
position: relative;
}
/* 使用角色配置页面相同的彩色图标样式 */
.header-icon {
width: 37px;
height: 37px;
background: #5778ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.header-icon img {
width: 19px;
height: 19px;
}
.header-title {
font-size: 20px;
font-weight: 600;
color: #2c3e50;
}
/* 其他按钮和操作区样式保持不变 */
.custom-close-btn {
position: absolute;
top: 25%;
right: 0;
transform: translateY(-50%);
width: 35px;
height: 35px;
border-radius: 50%;
border: 2px solid #cfcfcf;
background: none;
font-size: 30px;
font-weight: lighter;
color: #cfcfcf;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
padding: 0;
outline: none;
}
.custom-close-btn:hover {
color: #409EFF;
border-color: #409EFF;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.header-actions .hint-text {
display: flex;
align-items: center;
gap: 4px;
color: #979db1;
font-size: 12px;
margin-right: 8px;
}
.header-actions .hint-text img {
width: 16px;
height: 16px;
}
.header-actions .save-btn {
background: #5778ff;
color: white;
border: none;
border-radius: 18px;
padding: 8px 16px;
height: 32px;
font-size: 14px;
}
.header-actions .reset-btn {
background: #e6ebff;
color: #5778ff;
border: 1px solid #adbdff;
border-radius: 18px;
padding: 8px 16px;
height: 32px;
}
.header-actions .custom-close-btn {
position: static;
transform: none;
width: 32px;
height: 32px;
margin-left: 8px;
}
/* 隐藏所有不需要的元素 */
.model-select-wrapper, .model-row, .function-icons, .icon-dot, .edit-function-btn, .chat-history-options {
display: none !important;
}
</style>