feature:角色配置能力部分实现

This commit is contained in:
Erlei Chen
2025-03-27 14:06:40 +08:00
parent 4b90e70aad
commit 41db6aafd9
8 changed files with 369 additions and 32 deletions
@@ -95,6 +95,17 @@ public class UserAgentController extends BaseController {
return new Result<Agent>().ok(null);
}
@GetMapping("/{agentId}")
@Operation(summary = "获取智能体信息")
@RequiresPermissions("sys:role:normal")
public Result<Agent> getAgent(@PathVariable String agentId) {
if (StringUtils.isBlank(agentId)) {
log.error("智能体ID不能为空");
}
Agent agent = agentService.getById(agentId);
return new Result<Agent>().ok(agent);
}
@GetMapping
@Operation(summary = "智能体列表")
@RequiresPermissions("sys:role:normal")
@@ -0,0 +1,134 @@
package xiaozhi.modules.agentTemplate.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.controller.BaseController;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.agent.domain.AgentTemplate;
import xiaozhi.modules.agent.service.AgentTemplateService;
import xiaozhi.modules.agent.vo.AgentTemplateVO;
import xiaozhi.modules.device.domain.Device;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.model.domain.ModelConfig;
import xiaozhi.modules.model.domain.TtsVoice;
import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.model.service.TtsVoiceService;
import xiaozhi.modules.security.user.SecurityUser;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Slf4j
@Tag(name = "智能体模板管理")
@AllArgsConstructor
@RestController
@RequestMapping("/user/agent/template")
public class UserAgentTemplateController extends BaseController {
private final AgentTemplateService agentTemplateService;
private final ModelConfigService modelConfigService;
private final TtsVoiceService ttsVoiceService;
private final DeviceService deviceService;
@PostMapping
@Operation(summary = "添加智能体模板")
@RequiresPermissions("sys:role:normal")
public Result<AgentTemplate> addTemplate(@RequestBody AgentTemplate agentTemplate) {
UserDetail user = SecurityUser.getUser();
if (StringUtils.isBlank(agentTemplate.getAgentName())) {
log.error("智能体模板名称不能为空");
}
try {
agentTemplate = new AgentTemplate();
agentTemplate.setId(UUID.randomUUID().toString().replace("-", ""));
agentTemplate.setAgentName(agentTemplate.getAgentName());
agentTemplate.setCreator(user.getId());
agentTemplate.setCreatedAt(new Date());
agentTemplateService.save(agentTemplate);
} catch (Exception e) {
log.error("对象赋值异常", e);
}
return new Result<AgentTemplate>().ok(agentTemplate);
}
@DeleteMapping("/{templateId}")
@Operation(summary = "删除智能体模板")
@RequiresPermissions("sys:role:normal")
public Result<AgentTemplate> delTemplate(@PathVariable String templateId) {
UserDetail user = SecurityUser.getUser();
if (StringUtils.isBlank(templateId)) {
log.error("智能体模板ID不能为空");
}
boolean bool = agentTemplateService.removeById(templateId);
if (!bool) {
return new Result<AgentTemplate>().error("删除失败");
}
return new Result<AgentTemplate>().ok(null);
}
@GetMapping
@Operation(summary = "智能体模板模板列表")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentTemplate>> templateList() {
List<AgentTemplate> list = agentTemplateService.list();
return new Result<List<AgentTemplate>>().ok(list);
}
@GetMapping("/vo")
@Operation(summary = "智能体模板模板列表")
@RequiresPermissions("sys:role:normal")
public Result<List<AgentTemplateVO>> templateVOList() {
UserDetail user = SecurityUser.getUser();
List<AgentTemplate> templates = agentTemplateService.list();
List<AgentTemplateVO> list = new ArrayList<>();
this.convertAgentTemplateVOList(list, templates);
return new Result<List<AgentTemplateVO>>().ok(list);
}
/**
* 将Agent对象列表转换为AgentTemplateVO对象列表
* 此方法遍历Agent对象列表,将每个Agent对象转换为AgentTemplateVO对象,并添加到AgentTemplateVO列表中
*
* @param agentVOList 转换后的AgentTemplateVO对象列表
* @param agentList 原始的Agent对象列表
*/
private void convertAgentTemplateVOList(List<AgentTemplateVO> agentVOList, List<AgentTemplate> agentList) {
// 遍历Agent对象列表
for (AgentTemplate agentTemplate : agentList) {
// 创建一个新的AgentTemplateVO对象
AgentTemplateVO agentTemplateVO = new AgentTemplateVO();
// 将当前Agent对象的属性值转换并设置到AgentTemplateVO对象中
this.convertAgentTemplateVO(agentTemplateVO, agentTemplate);
// 将转换后的AgentTemplateVO对象添加到AgentTemplateVO列表中
agentVOList.add(agentTemplateVO);
}
}
private void convertAgentTemplateVO(AgentTemplateVO agentTemplateVO, AgentTemplate agentTemplate) {
try {
BeanUtils.copyProperties(agentTemplateVO, agentTemplate);
agentTemplateVO.setTtsModelName("未知");
TtsVoice ttsVoice = ttsVoiceService.getOne(new QueryWrapper<TtsVoice>().eq("id", agentTemplate.getTtsVoiceId()));
if (ObjectUtils.isNotNull(ttsVoice)) {
agentTemplateVO.setTtsModelName(ttsVoice.getName());
}
agentTemplateVO.setLlmModelName("未知");
ModelConfig modelConfig = modelConfigService.getOne(new QueryWrapper<ModelConfig>().eq("id", agentTemplate.getLlmModelId()));
if (ObjectUtils.isNotNull(modelConfig)) {
agentTemplateVO.setLlmModelName(modelConfig.getModelName());
}
} catch (Exception e) {
log.error("[convertAgentTemplateVO]对象转换报错", e);
}
}
}
@@ -0,0 +1,14 @@
package xiaozhi.modules.agent.vo;
import lombok.Data;
import xiaozhi.modules.agent.domain.Agent;
import xiaozhi.modules.agent.domain.AgentTemplate;
@Data
public class AgentTemplateVO extends AgentTemplate {
//角色音色
private String ttsModelName;
//角色模型
private String llmModelName;
}
@@ -0,0 +1,146 @@
package xiaozhi.modules.model.controller;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;
import xiaozhi.common.controller.BaseController;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.device.constant.DeviceConstant;
import xiaozhi.modules.device.domain.Device;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.device.utils.CodeGeneratorUtil;
import xiaozhi.modules.device.vo.DeviceOtaVO;
import xiaozhi.modules.ota.domain.Ota;
import xiaozhi.modules.ota.service.OtaService;
import xiaozhi.modules.security.user.SecurityUser;
import java.util.Date;
@Slf4j
@Tag(name = "模型数据管理")
@AllArgsConstructor
@RestController
@RequestMapping("/model/")
public class ModelController extends BaseController {
private final DeviceService deviceService;
private final OtaService otaService;
@Resource
private RedisUtils redisUtils;
@CrossOrigin(originPatterns = "*", methods = {RequestMethod.GET, RequestMethod.POST})
@RequestMapping(path = "/", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json")
@Operation(summary = "设备OTA升级")
public String ota(@RequestHeader(required = false) HttpHeaders headers, @RequestBody(required = false) JSONObject jsonObject) {
log.info("OTA升级请求:header:{}body:{}", headers, jsonObject);
if (ObjectUtils.isNull(headers) || StringUtils.isBlank(headers.getFirst("device-id"))) {
log.error("设备ID不能为空");
return "{\"error\": \"Device ID is required\"}";
}
DeviceOtaVO otaVO = new DeviceOtaVO();
Device device = deviceService.getOne(new UpdateWrapper<Device>().eq("mac_address", headers.getFirst("device-id").toUpperCase()).eq("id", headers.getFirst("client-Id").replace("-", "")));
if (ObjectUtils.isNull(device)) {
// 从 Redis 中获取设备信息
Object redisValue = redisUtils.hGet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, headers.getFirst("device-id").toUpperCase());
if (ObjectUtils.isNull(redisValue)) {
String code = CodeGeneratorUtil.generateCode(6);
log.info("[gen]授权码已广播:{}", code);
otaVO.setActivation(new DeviceOtaVO.Activation(code, "youxlife.com\n" + code));
redisUtils.hSet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, headers.getFirst("device-id").toUpperCase(), code, RedisUtils.HOUR_ONE_EXPIRE);
redisUtils.hSet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, code, jsonObject, RedisUtils.HOUR_ONE_EXPIRE);
} else {
log.warn("[get]授权码已广播:{}", redisValue);
otaVO.setActivation(new DeviceOtaVO.Activation(redisValue.toString(), "youxlife.com\n" + redisValue));
}
}
if (ObjectUtils.isNull(device) || device.getAutoUpdate() == 1) {
//{"version":"1.0.0","url":"http://https://youxlife.oss-cn-zhangjiakou.aliyuncs.com/v1.4.5.bin"}
String board = ObjectUtils.isNull(device)?headers.getFirst("user-agent").split("/")[0]:device.getBoard();
Ota ota = otaService.getOne(new UpdateWrapper<Ota>().eq("board", board).eq("is_enabled", 1));
if (ObjectUtils.isNull(ota)) {
log.warn("OTA升级信息未配置");
} else {
otaVO.setFirmware(new DeviceOtaVO.Firmware(ota.getAppVersion(), ota.getUrl()));
}
}
otaVO.setServer_time(new DeviceOtaVO.ServerTime(new Date().getTime(), 8 * 60));
return JSONUtil.toJsonStr(otaVO);
}
@GetMapping("/sys/getOtalist")
@Operation(summary = "设备OTA列表")
@RequiresPermissions("sys:role:superAdmin")
public Result<Page<Ota>> getOtalist(@RequestParam Integer pageNo, @RequestParam Integer pageSize) {
UserDetail user = SecurityUser.getUser();
Page page = new Page<Ota>(pageNo, pageSize);
page = otaService.page(page);
return new Result<Page<Ota>>().ok(page);
}
@PostMapping("/sys/save")
@Operation(summary = "添加设备OTA")
@RequiresPermissions("sys:role:superAdmin")
public Result<Ota> save(@RequestBody Ota ota) {
UserDetail user = SecurityUser.getUser();
ota.setCreator(user.getId());
ota.setCreateDate(new Date());
boolean bool = otaService.save(ota);
if (!bool) {
return new Result().error("设备OTA添加失败");
}
return new Result<Ota>().ok(null);
}
@PostMapping("/sys/update")
@Operation(summary = "更新设备OTA")
@RequiresPermissions("sys:role:superAdmin")
public Result<Ota> update(@RequestBody Ota ota) {
UserDetail user = SecurityUser.getUser();
boolean bool = otaService.update(
new UpdateWrapper<Ota>().eq("id", ota.getId())
.set("board", ota.getBoard().trim().toLowerCase())
.set("app_version", ota.getAppVersion())
.set("url", ota.getUrl())
.set("is_enabled", ota.getIsEnabled())
.set("updater", user.getId())
.set("update_date", new Date())
);
if (!bool) {
return new Result().error("设备OTA更新失败");
}
return new Result<Ota>().ok(null);
}
@PostMapping("/sys/toggleEnabled")
@Operation(summary = "切换设备OTA可用状态")
@RequiresPermissions("sys:role:superAdmin")
public Result<Ota> toggleEnabled(@RequestBody Ota ota) {
UserDetail user = SecurityUser.getUser();
boolean bool = otaService.update(
new UpdateWrapper<Ota>().eq("id", ota.getId())
.set("is_enabled", ota.getIsEnabled())
.set("updater", user.getId())
.set("update_date", new Date())
);
if (!bool) {
return new Result().error("切换设备OTA可用状态失败");
}
return new Result<Ota>().ok(null);
}
}
+12
View File
@@ -78,4 +78,16 @@ export default {
// });
}).send();
},
//智能体模板列表
getAgentTemplateList(callback) {
RequestService.sendRequest().url(`${getServiceUrl()}/user/agent/template`)
.method('GET')
.success((res) => {
RequestService.clearRequestTime()
callback(res)
})
.fail((err) => {
console.error('获取智能体模板列表失败:', err);
}).send()
},
}
@@ -20,7 +20,7 @@
最近对话{{ agent.lastConnectedAt }}
</div>
<div style="display: flex;justify-content: space-between;align-items: center;gap: 10px;">
<div class="settings-btn" @click="$emit('configure', agent.id, agent.agentName)">
<div class="settings-btn" @click="$emit('configure', agent.id)">
配置角色
</div>
<div class="settings-btn" @click="$emit('asr', agent.id)">
+2 -2
View File
@@ -87,9 +87,9 @@ export default {
showAddDialog() {
this.addAgentDialogVisible = true
},
goToRoleConfig(agentId, agentName) {
goToRoleConfig(agentId) {
// 点击配置角色后跳转到角色配置页
this.$router.push({path:'/role-config', query: {agentId: agentId, agentName: agentName}})
this.$router.push({path:'/role-config', query: {agentId: agentId}})
},
goToDevice(agentId) {
+49 -29
View File
@@ -18,25 +18,25 @@
style="width: 37px;height: 37px;background: #5778ff;border-radius: 50%;display: flex;align-items: center;justify-content: center;">
<img src="@/assets/home/setting-user.png" alt="" style="width: 19px;height: 19px;"/>
</div>
{{ agentName }} ({{ agentId }})
{{ form.agentName }} ({{ agentId }})
</div>
<div style="height: 1px;background: #e8f0ff;"/>
<div style="padding: 16px 24px;max-width: 792px;">
<el-form ref="form" :model="form" label-width="100px">
<el-form-item label="角色模版:">
<div style="display: flex;gap: 8px;flex-wrap: wrap;">
<div v-for="template in templates" :key="template" class="template-item" @click="selectTemplate(template)">
{{ template }}
<div v-for="template in templates" :key="template.id" class="template-item" @click="selectTemplate(template)">
{{ template.agentName }}
</div>
</div>
</el-form-item>
<el-form-item label="助手昵称:">
<el-input v-model="form.name"/>
<el-input v-model="form.agentCode"/>
</el-form-item>
<el-form-item label="角色音色:">
<div style="display: flex;gap: 8px;align-items: center;">
<div style="flex:1;">
<el-select v-model="form.timbre" placeholder="请选择" style="width: 100%;">
<el-select v-model="form.ttsVoiceId" placeholder="请选择" style="width: 100%;">
<el-option v-for="item in options" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
@@ -49,10 +49,10 @@
</div>
</el-form-item>
<el-form-item label="角色介绍:">
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.introduction" maxlength="2000" show-word-limit/>
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.systemPrompt" maxlength="2000" show-word-limit/>
</el-form-item>
<el-form-item label="记忆体:">
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.prompt" maxlength="1000" show-word-limit/>
<el-input type="textarea" rows="5" resize="none" placeholder="请输入内容" v-model="form.systemPrompt" maxlength="1000" show-word-limit/>
<div style="display: flex;gap: 8px;align-items: center;">
<div style="color: #979db1;font-size: 11px;">当前记忆每次对话后重新生成</div>
<div class="clear-btn">
@@ -95,6 +95,7 @@
</template>
<script>
import Api from '@/apis/api';
import HeaderBar from "@/components/HeaderBar.vue";
import Footer from "@/components/Footer.vue";
@@ -106,39 +107,58 @@ export default {
agentId: this.$route.query.agentId,
agentName: this.$route.query.agentName,
form: {
name: "",
timbre: "",
introduction: "",
prompt: "",
model: {
tts: "",
vad: "",
asr: "",
llm: "",
memory:"",
intent:""
}
agentCode:"",
agentName:"",
asrModelId:"",
intentModelId:"",
llmModelId:"",
memModelId:"",
systemPrompt:"",
ttsModelId:"",
ttsVoiceId:"",
vadModelId:"",
model:{}
},
options: [
{ value: '选项1', label: '黄金糕' },
{ value: '选项2', label: '双皮奶' }
],
models: [
{ label: '大语言模型(LLM)', key: 'llm' },
{ label: '语音转文本模型(ASR)', key: 'asr' },
{ label: '语音活动检测模型(VAD)', key: 'vad' },
{ label: '语音生成模型(TTS)', key: 'tts' },
{ label: '意图分类模型(Intent)', key: 'intent' },
{ label: '记忆增强模型(Memory)', key: 'memory' }
],
models: [],
//[
// { label: '大语言模型(LLM)', key: 'llm' },
// { label: '语音转文本模型(ASR)', key: 'asr' },
// { label: '语音活动检测模型(VAD)', key: 'vad' },
// { label: '语音生成模型(TTS)', key: 'tts' },
// { label: '意图分类模型(Intent)', key: 'intent' },
// { label: '记忆增强模型(Memory)', key: 'memory' }
// ],
templates: ['通用男声','通用女声','阳光男生','奶气萌娃']
}
},
mounted() {
// 获取智能体列表
this.fetchAgentTemplateList();
this.handleGetConfig();
},
methods: {
fetchAgentTemplateList() {
// 获取智能体列表
Api.agent.getAgentTemplateList(({data}) => {
if (data.code === 0) {
this.templates = data.data
} else {
showDanger(data.msg)
}
})
},
handleGetConfig(){
api.agent.getAgentConfig(this.agentId, ({data}) => {
this.form = data
Api.agent.getAgentConfig(this.agentId, ({data}) => {
if (data.code === 0) {
this.form = data.data
} else {
showDanger(data.msg)
}
})
},
saveConfig() {