mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 10:03:54 +08:00
feature:
1.OTA基本能力(固定配置),待优化 2.智能体管理与设备管理对接api,实现流程闭环(已测试)
This commit is contained in:
@@ -168,6 +168,12 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- SLF4J + Log4j2 实现(可选) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-slf4j-impl</artifactId>
|
||||||
|
<version>2.20.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<!-- 阿里云maven仓库 -->
|
<!-- 阿里云maven仓库 -->
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package xiaozhi.common.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected HttpServletRequest request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求头
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected HashMap<String, String> getRequestHeaders() {
|
||||||
|
HashMap<String, String> requestHeaders = new HashMap<String, String>();
|
||||||
|
Enumeration<String> headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String headerName = headerNames.nextElement();
|
||||||
|
String headerValue = request.getHeader(headerName);
|
||||||
|
requestHeaders.put(headerName, headerValue);
|
||||||
|
}
|
||||||
|
return requestHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求参数
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected HashMap<String, String> getRequestParams() {
|
||||||
|
HashMap<String, String> requestParams = new HashMap<String, String>();
|
||||||
|
Enumeration<String> paramNames = request.getParameterNames();
|
||||||
|
while (paramNames.hasMoreElements()) {
|
||||||
|
String paramName = paramNames.nextElement();
|
||||||
|
String paramValue = request.getParameter(paramName);
|
||||||
|
requestParams.put(paramName, paramValue);
|
||||||
|
}
|
||||||
|
return requestParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
package xiaozhi.modules.agent.controller;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
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.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.agent.domain.Agent;
|
||||||
|
import xiaozhi.modules.agent.domain.AgentTemplate;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentConfigVO;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentVO;
|
||||||
|
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")
|
||||||
|
public class UserAgentController extends BaseController {
|
||||||
|
private final AgentService agentService;
|
||||||
|
private final AgentTemplateService agentTemplateService;
|
||||||
|
private final ModelConfigService modelConfigService;
|
||||||
|
private final TtsVoiceService ttsVoiceService;
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "添加智能体")
|
||||||
|
public Result<Agent> addAgent(@RequestBody Agent agent) {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
if(StringUtils.isBlank(agent.getAgentName())){
|
||||||
|
log.error("智能体名称不能为空");
|
||||||
|
}
|
||||||
|
Agent oldAgent = agentService.getOne(new QueryWrapper<Agent>().eq("agent_name", agent.getAgentName()));
|
||||||
|
if(ObjectUtils.isNull(oldAgent)){
|
||||||
|
AgentTemplate agentTemplate = agentTemplateService.getOne(new QueryWrapper<AgentTemplate>().eq("is_default", 1));
|
||||||
|
if(ObjectUtils.isNull(agentTemplate)){
|
||||||
|
|
||||||
|
}else{
|
||||||
|
try {
|
||||||
|
oldAgent = new Agent();
|
||||||
|
BeanUtils.copyProperties(oldAgent, agentTemplate);
|
||||||
|
oldAgent.setId(UUID.randomUUID().toString().replace("-",""));
|
||||||
|
oldAgent.setAgentName(agent.getAgentName());
|
||||||
|
oldAgent.setUserId(user.getId());
|
||||||
|
oldAgent.setCreator(user.getId());
|
||||||
|
oldAgent.setCreatedAt(new Date());
|
||||||
|
agentService.save(oldAgent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("对象赋值异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
|
||||||
|
}
|
||||||
|
return new Result<Agent>().ok(agent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "智能体列表")
|
||||||
|
public Result<List<AgentVO>> agentList() {
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
List<Agent> agents = agentService.list(new QueryWrapper<Agent>().eq("user_id",user.getId()));
|
||||||
|
List<AgentVO> list = new ArrayList<>();
|
||||||
|
this.convertAgetVOList(list, agents);
|
||||||
|
return new Result<List<AgentVO>>().ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/loadAgentConfig/{deviceId}")
|
||||||
|
@Operation(summary = "下载智能体配置")
|
||||||
|
// public Result<AgentConfigVO> loadAgentConfig(@PathVariable String deviceId){
|
||||||
|
public Result<JSONObject> loadAgentConfig(@PathVariable String deviceId){
|
||||||
|
Device device = deviceService.getOne(new QueryWrapper<Device>().eq("mac_address", deviceId.toUpperCase()));
|
||||||
|
if(ObjectUtils.isNull(device)){
|
||||||
|
return new Result<JSONObject>().error("设备不存在");
|
||||||
|
}
|
||||||
|
Agent agent = agentService.getOne(new QueryWrapper<Agent>().eq("id", device.getAgentId()));
|
||||||
|
if(ObjectUtils.isNull(agent)){
|
||||||
|
return new Result<JSONObject>().error("智能体不存在");
|
||||||
|
}
|
||||||
|
AgentConfigVO agentConfigVO = new AgentConfigVO();
|
||||||
|
// return new Result<AgentConfigVO>().ok(agentConfigVO);
|
||||||
|
String json = "{\n" +
|
||||||
|
" \"ASR\": {\n" +
|
||||||
|
" \"FunASR\": {\n" +
|
||||||
|
" \"model_dir\": \"models/SenseVoiceSmall\",\n" +
|
||||||
|
" \"output_dir\": \"tmp/\",\n" +
|
||||||
|
" \"type\": \"fun_local\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"LLM\": {\n" +
|
||||||
|
" \"ChatGLMLLM\": {\n" +
|
||||||
|
" \"api_key\": \"0415dad4014847babc3e3f03024c50a3.qH7FgTy5Yawc85fl\",\n" +
|
||||||
|
" \"model_name\": \"glm-4-flash\",\n" +
|
||||||
|
" \"type\": \"openai\",\n" +
|
||||||
|
" \"url\": \"https://open.bigmodel.cn/api/paas/v4/\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"TTS\": {\n" +
|
||||||
|
" \"DoubaoTTS\": {\n" +
|
||||||
|
" \"access_token\": \"hrnx22F9WutWBm7YJzE62r_Z1myUmHEL\",\n" +
|
||||||
|
" \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\",\n" +
|
||||||
|
" \"appid\": \"6295576095\",\n" +
|
||||||
|
" \"authorization\": \"Bearer;\",\n" +
|
||||||
|
" \"cluster\": \"volcano_tts\",\n" +
|
||||||
|
" \"output_dir\": \"tmp/\",\n" +
|
||||||
|
" \"type\": \"doubao\",\n" +
|
||||||
|
" \"voice\": \"BV034_streaming\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"VAD\": {\n" +
|
||||||
|
" \"SileroVAD\": {\n" +
|
||||||
|
" \"min_silence_duration_ms\": 700,\n" +
|
||||||
|
" \"model_dir\": \"models/snakers4_silero-vad\",\n" +
|
||||||
|
" \"threshold\": 0.5\n" +
|
||||||
|
" }\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"auth_code\": \"642365\",\n" +
|
||||||
|
" \"prompt\": \"你是一个叫小优的女孩,来自优享生活公司的AI智能体,声音好听,习惯简短表达,爱用网络梗。\\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\\n现在我正在和你进行语音聊天,我们开始吧。\\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。\\n\",\n" +
|
||||||
|
" \"selected_module\": {\n" +
|
||||||
|
" \"ASR\": \"FunASR\",\n" +
|
||||||
|
" \"Intent\": \"function_call\",\n" +
|
||||||
|
" \"LLM\": \"ChatGLMLLM\",\n" +
|
||||||
|
" \"Memory\": \"mem0ai\",\n" +
|
||||||
|
" \"TTS\": \"DoubaoTTS\",\n" +
|
||||||
|
" \"VAD\": \"SileroVAD\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"owner\":\"18600806164\"\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
return new Result<JSONObject>().ok(new JSONObject(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Agent对象列表转换为AgentVO对象列表
|
||||||
|
* 此方法遍历Agent对象列表,将每个Agent对象转换为AgentVO对象,并添加到AgentVO列表中
|
||||||
|
*
|
||||||
|
* @param agentVOList 转换后的AgentVO对象列表
|
||||||
|
* @param agentList 原始的Agent对象列表
|
||||||
|
*/
|
||||||
|
private void convertAgetVOList(List<AgentVO> agentVOList, List<Agent> agentList){
|
||||||
|
// 遍历Agent对象列表
|
||||||
|
for(Agent agent : agentList) {
|
||||||
|
// 创建一个新的AgentVO对象
|
||||||
|
AgentVO agentVO = new AgentVO();
|
||||||
|
// 将当前Agent对象的属性值转换并设置到AgentVO对象中
|
||||||
|
this.convertAgentVO(agentVO, agent);
|
||||||
|
// 将转换后的AgentVO对象添加到AgentVO列表中
|
||||||
|
agentVOList.add(agentVO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void convertAgentVO(AgentVO agentVO, Agent agent){
|
||||||
|
try {
|
||||||
|
BeanUtils.copyProperties(agentVO, agent);
|
||||||
|
agentVO.setTtsModelName("未知");
|
||||||
|
TtsVoice ttsVoice = ttsVoiceService.getOne(new QueryWrapper<TtsVoice>().eq("id", agent.getTtsVoiceId()));
|
||||||
|
if(ObjectUtils.isNotNull(ttsVoice)){
|
||||||
|
agentVO.setTtsModelName(ttsVoice.getName());
|
||||||
|
}
|
||||||
|
agentVO.setLlmModelName("未知");
|
||||||
|
ModelConfig modelConfig = modelConfigService.getOne(new QueryWrapper<ModelConfig>().eq("id", agent.getLlmModelId()));
|
||||||
|
if(ObjectUtils.isNotNull(modelConfig)){
|
||||||
|
agentVO.setLlmModelName(modelConfig.getModelName());
|
||||||
|
}
|
||||||
|
agentVO.setLastConnectedAt("今天");
|
||||||
|
agentVO.setDeviceCount(0L);
|
||||||
|
long deviceCount = deviceService.count(new QueryWrapper<Device>().eq("agent_id", agent.getId()));
|
||||||
|
agentVO.setDeviceCount(deviceCount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[convertAgentVO]对象转换报错",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package xiaozhi.modules.agent.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体配置表
|
||||||
|
* @TableName ai_agent
|
||||||
|
*/
|
||||||
|
@TableName(value ="ai_agent")
|
||||||
|
@Data
|
||||||
|
public class Agent implements Serializable {
|
||||||
|
/**
|
||||||
|
* 智能体唯一标识
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所属用户 ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体编码
|
||||||
|
*/
|
||||||
|
private String agentCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体名称
|
||||||
|
*/
|
||||||
|
private String agentName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音识别模型标识
|
||||||
|
*/
|
||||||
|
private String asrModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音活动检测标识
|
||||||
|
*/
|
||||||
|
private String vadModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大语言模型标识
|
||||||
|
*/
|
||||||
|
private String llmModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音合成模型标识
|
||||||
|
*/
|
||||||
|
private String ttsModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音色标识
|
||||||
|
*/
|
||||||
|
private String ttsVoiceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记忆模型标识
|
||||||
|
*/
|
||||||
|
private String memModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 意图模型标识
|
||||||
|
*/
|
||||||
|
private String intentModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色设定参数
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言编码
|
||||||
|
*/
|
||||||
|
private String langCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交互语种
|
||||||
|
*/
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序权重
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者 ID
|
||||||
|
*/
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者 ID
|
||||||
|
*/
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updatedAt;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package xiaozhi.modules.agent.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体配置模板表
|
||||||
|
* @TableName ai_agent_template
|
||||||
|
*/
|
||||||
|
@TableName(value ="ai_agent_template")
|
||||||
|
@Data
|
||||||
|
public class AgentTemplate implements Serializable {
|
||||||
|
/**
|
||||||
|
* 智能体唯一标识
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体编码
|
||||||
|
*/
|
||||||
|
private String agentCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体名称
|
||||||
|
*/
|
||||||
|
private String agentName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音识别模型标识
|
||||||
|
*/
|
||||||
|
private String asrModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音活动检测标识
|
||||||
|
*/
|
||||||
|
private String vadModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大语言模型标识
|
||||||
|
*/
|
||||||
|
private String llmModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语音合成模型标识
|
||||||
|
*/
|
||||||
|
private String ttsModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音色标识
|
||||||
|
*/
|
||||||
|
private String ttsVoiceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记忆模型标识
|
||||||
|
*/
|
||||||
|
private String memModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 意图模型标识
|
||||||
|
*/
|
||||||
|
private String intentModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色设定参数
|
||||||
|
*/
|
||||||
|
private String systemPrompt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言编码
|
||||||
|
*/
|
||||||
|
private String langCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 交互语种
|
||||||
|
*/
|
||||||
|
private String language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序权重
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否默认模板:1:是,0:不是
|
||||||
|
*/
|
||||||
|
private Integer isDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者 ID
|
||||||
|
*/
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者 ID
|
||||||
|
*/
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updatedAt;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xiaozhi.modules.agent.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.modules.agent.domain.Agent;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent(智能体配置表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
* @Entity xiaozhi.modules.agent.domain.Agent
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AgentMapper extends BaseMapper<Agent> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xiaozhi.modules.agent.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.modules.agent.domain.AgentTemplate;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
* @Entity xiaozhi.modules.agent.domain.AgentTemplate
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AgentTemplateMapper extends BaseMapper<AgentTemplate> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.domain.Agent;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent(智能体配置表)】的数据库操作Service
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
*/
|
||||||
|
public interface AgentService extends IService<Agent> {
|
||||||
|
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.domain.AgentTemplate;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
*/
|
||||||
|
public interface AgentTemplateService extends IService<AgentTemplate> {
|
||||||
|
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import xiaozhi.modules.agent.domain.Agent;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.mapper.AgentMapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent(智能体配置表)】的数据库操作Service实现
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class AgentServiceImpl extends ServiceImpl<AgentMapper, Agent>
|
||||||
|
implements AgentService{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import xiaozhi.modules.agent.domain.AgentTemplate;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.agent.mapper.AgentTemplateMapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现
|
||||||
|
* @createDate 2025-03-22 11:48:18
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateMapper, AgentTemplate>
|
||||||
|
implements AgentTemplateService{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package xiaozhi.modules.agent.vo;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AgentConfigVO {
|
||||||
|
private JSONObject selected_module;
|
||||||
|
private String prompt;
|
||||||
|
private JSONObject LLM;
|
||||||
|
private JSONObject TTS;
|
||||||
|
private JSONObject ASR;
|
||||||
|
private JSONObject VAD;
|
||||||
|
private JSONObject Memory;
|
||||||
|
private JSONObject Intent;
|
||||||
|
private String owner;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package xiaozhi.modules.agent.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import xiaozhi.modules.agent.domain.Agent;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class AgentVO extends Agent {
|
||||||
|
//角色音色
|
||||||
|
private String ttsModelName;
|
||||||
|
|
||||||
|
//角色明星
|
||||||
|
private String llmModelName;
|
||||||
|
|
||||||
|
//最近对话
|
||||||
|
private String lastConnectedAt;
|
||||||
|
|
||||||
|
//设备数量
|
||||||
|
private Long deviceCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package xiaozhi.modules.device.constant;
|
||||||
|
|
||||||
|
public class DeviceConstant {
|
||||||
|
|
||||||
|
public static final String REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE = "device:activation:code";
|
||||||
|
public static final String REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC = "device:activation:mac";
|
||||||
|
}
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
package xiaozhi.modules.device.controller;
|
|
||||||
|
|
||||||
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 lombok.AllArgsConstructor;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import xiaozhi.common.constant.Constant;
|
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
|
||||||
import xiaozhi.common.page.PageData;
|
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
|
||||||
import xiaozhi.common.user.UserDetail;
|
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
|
||||||
import xiaozhi.common.utils.Result;
|
|
||||||
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
|
||||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Tag(name = "设备管理")
|
|
||||||
@AllArgsConstructor
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/device")
|
|
||||||
public class DeviceController {
|
|
||||||
private final DeviceService deviceService;
|
|
||||||
private final RedisUtils redisUtils;
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/bind/{deviceCode}")
|
|
||||||
@Operation(summary = "绑定设备")
|
|
||||||
@RequiresPermissions("sys:role:normal")
|
|
||||||
public Result<DeviceEntity> bindDevice(@PathVariable String deviceCode) {
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
|
|
||||||
String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
|
|
||||||
if (StringUtils.isBlank(deviceHeaders)) {
|
|
||||||
return new Result<DeviceEntity>().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
|
|
||||||
}
|
|
||||||
DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class);
|
|
||||||
DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader);
|
|
||||||
return new Result<DeviceEntity>().ok(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/bind")
|
|
||||||
@Operation(summary = "获取已绑定设备")
|
|
||||||
@RequiresPermissions("sys:role:normal")
|
|
||||||
public Result<List<DeviceEntity>> getUserDevices() {
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
List<DeviceEntity> devices = deviceService.getUserDevices(user.getId());
|
|
||||||
return new Result<List<DeviceEntity>>().ok(devices);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/unbind")
|
|
||||||
@Operation(summary = "解绑设备")
|
|
||||||
@RequiresPermissions("sys:role:normal")
|
|
||||||
public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
|
|
||||||
return new Result();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/all")
|
|
||||||
@Operation(summary = "设备列表(管理员)")
|
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
|
||||||
@Parameters({
|
|
||||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
|
||||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
|
||||||
})
|
|
||||||
public Result<PageData<DeviceEntity>> adminDeviceList(
|
|
||||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
|
||||||
PageData<DeviceEntity> page = deviceService.adminDeviceList(params);
|
|
||||||
return new Result<PageData<DeviceEntity>>().ok(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package xiaozhi.modules.device.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 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.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import xiaozhi.common.controller.BaseController;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
|
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.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "设备OTA管理")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/ota")
|
||||||
|
public class OtaController extends BaseController {
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
@Resource
|
||||||
|
private RedisUtils redisUtils;
|
||||||
|
private final String OTA_PARAM_CODE = "OTA";
|
||||||
|
|
||||||
|
@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 value = sysParamsService.getValue(OTA_PARAM_CODE);
|
||||||
|
if (StringUtils.isBlank(value)) {
|
||||||
|
log.warn("OTA升级信息未配置");
|
||||||
|
} else {
|
||||||
|
JSONObject object = new JSONObject(value);
|
||||||
|
otaVO.setFirmware(new DeviceOtaVO.Firmware(object.getStr("version"), object.getStr("url")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
otaVO.setServer_time(new DeviceOtaVO.ServerTime(new Date().getTime(), 8 * 60));
|
||||||
|
return JSONUtil.toJsonStr(otaVO);
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package xiaozhi.modules.device.controller;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
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 lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
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.agent.domain.Agent;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.device.constant.DeviceConstant;
|
||||||
|
import xiaozhi.modules.device.domain.Device;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.device.vo.DeviceCodeVO;
|
||||||
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Tag(name = "设备管理")
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/agent/device")
|
||||||
|
public class UserDeviceController extends BaseController {
|
||||||
|
private final AgentService agentService;
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
|
@GetMapping("/bind/{agentId}")
|
||||||
|
@Operation(summary = "已绑定设备列表")
|
||||||
|
public Result<Page<Device>> bindDeviceList(@PathVariable String agentId, @RequestParam Integer pageNo, @RequestParam Integer pageSize) {
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
log.error("智能体ID不能为空");
|
||||||
|
return new Result().error("智能体ID不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
Agent agent = agentService.getById(agentId);
|
||||||
|
if (ObjectUtils.isNull(agent)) {
|
||||||
|
log.error("智能体不存在");
|
||||||
|
return new Result().error("智能体不存在");
|
||||||
|
}
|
||||||
|
Page page = new Page<Device>(pageNo, pageSize);
|
||||||
|
page = deviceService.page(page, new QueryWrapper<Device>().eq("user_id", user.getId()).eq("agent_id", agentId));
|
||||||
|
return new Result<Page<Device>>().ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/bind/{agentId}")
|
||||||
|
@Operation(summary = "绑定设备")
|
||||||
|
public Result<Device> bindDevice(@PathVariable String agentId, @RequestBody DeviceCodeVO deviceCodeVO) {
|
||||||
|
if (ObjectUtils.isNull(deviceCodeVO) || StringUtils.isBlank(deviceCodeVO.getDeviceCode())) {
|
||||||
|
log.error("授权码不能为空");
|
||||||
|
return new Result().error("授权码不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(agentId)) {
|
||||||
|
log.error("智能体ID不能为空");
|
||||||
|
return new Result().error("智能体ID不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
|
||||||
|
// 从 Redis 中获取设备信息
|
||||||
|
Object redisValue = redisUtils.hGet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, deviceCodeVO.getDeviceCode());
|
||||||
|
if (ObjectUtils.isNull(redisValue)) {
|
||||||
|
log.error("授权码不存在:{}", deviceCodeVO.getDeviceCode());
|
||||||
|
return new Result().error("授权码不存在");
|
||||||
|
}
|
||||||
|
JSONObject jsonObject = (JSONObject) redisValue;
|
||||||
|
try {
|
||||||
|
redisUtils.hDel(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, deviceCodeVO.getDeviceCode());
|
||||||
|
redisUtils.hDel(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, jsonObject.getStr("mac_address").toUpperCase());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("授权缓存删除失败", e);
|
||||||
|
}
|
||||||
|
JSONObject board = jsonObject.getJSONObject("board");
|
||||||
|
JSONObject application = jsonObject.getJSONObject("application");
|
||||||
|
if (ObjectUtils.isNull(board) || ObjectUtils.isNull(application)) {
|
||||||
|
log.error("设备码绑定信息无效,{}:{}", jsonObject.getStr("mac_address").toUpperCase(), deviceCodeVO.getDeviceCode());
|
||||||
|
return new Result().error("设备码绑定信息无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
Agent agent = agentService.getById(agentId);
|
||||||
|
if (ObjectUtils.isNull(agent)) {
|
||||||
|
log.error("智能体不存在");
|
||||||
|
return new Result().error("智能体不存在");
|
||||||
|
}
|
||||||
|
Device device = new Device();
|
||||||
|
device.setId(jsonObject.getStr("uuid").replace("-", ""));
|
||||||
|
device.setUserId(user.getId());
|
||||||
|
device.setMacAddress(jsonObject.getStr("mac_address").toUpperCase());
|
||||||
|
device.setBoard(board.getStr("type"));
|
||||||
|
device.setAppVersion(application.getStr("version"));
|
||||||
|
device.setAgentId(agentId);
|
||||||
|
device.setCreator(user.getId());
|
||||||
|
device.setCreateDate(new Date());
|
||||||
|
device.setAutoUpdate(1);
|
||||||
|
boolean bool = deviceService.save(device);
|
||||||
|
if (!bool) {
|
||||||
|
return new Result().error("绑定失败");
|
||||||
|
}
|
||||||
|
return new Result<Device>().ok(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/unbind/{deviceId}")
|
||||||
|
@Operation(summary = "解绑设备")
|
||||||
|
public Result<Device> bindDevice(@PathVariable String deviceId) {
|
||||||
|
if (StringUtils.isBlank(deviceId)) {
|
||||||
|
log.error("设备ID不能为空");
|
||||||
|
return new Result().error("设备ID不能为空");
|
||||||
|
}
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
boolean bool = deviceService.removeById(deviceId);
|
||||||
|
if (!bool) {
|
||||||
|
return new Result().error("解绑失败");
|
||||||
|
}
|
||||||
|
return new Result<Device>().ok(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package xiaozhi.modules.device.dao;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
|
||||||
|
|
||||||
@Mapper
|
|
||||||
public interface DeviceDao extends BaseMapper<DeviceEntity> {
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package xiaozhi.modules.device.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备信息表
|
||||||
|
* @TableName ai_device
|
||||||
|
*/
|
||||||
|
@TableName(value ="ai_device")
|
||||||
|
@Data
|
||||||
|
public class Device implements Serializable {
|
||||||
|
/**
|
||||||
|
* 设备唯一标识
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联用户 ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MAC 地址
|
||||||
|
*/
|
||||||
|
private String macAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后连接时间
|
||||||
|
*/
|
||||||
|
private Date lastConnectedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动更新开关(0 关闭/1 开启)
|
||||||
|
*/
|
||||||
|
private Integer autoUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备硬件型号
|
||||||
|
*/
|
||||||
|
private String board;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备别名
|
||||||
|
*/
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体 ID
|
||||||
|
*/
|
||||||
|
private String agentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 固件版本号
|
||||||
|
*/
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateDate;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package xiaozhi.modules.device.dto;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Schema(description = "设备连接头信息")
|
|
||||||
public class DeviceHeaderDTO {
|
|
||||||
|
|
||||||
@Schema(description = "设备ID")
|
|
||||||
private String deviceId;
|
|
||||||
|
|
||||||
@Schema(description = "协议版本号")
|
|
||||||
private Long protocolVersion;
|
|
||||||
|
|
||||||
@Schema(description = "认证信息")
|
|
||||||
private String authorization;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package xiaozhi.modules.device.dto;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设备解绑表单
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Schema(description = "设备解绑表单")
|
|
||||||
public class DeviceUnBindDTO implements Serializable {
|
|
||||||
|
|
||||||
@Schema(description = "设备ID")
|
|
||||||
@NotBlank(message = "设备ID不能为空")
|
|
||||||
private Long deviceId;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package xiaozhi.modules.device.entity;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@TableName("ai_device")
|
|
||||||
@Schema(description = "设备信息")
|
|
||||||
public class DeviceEntity {
|
|
||||||
@Schema(description = "设备ID")
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Schema(description = "关联用户ID")
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
@Schema(description = "MAC地址")
|
|
||||||
private String macAddress;
|
|
||||||
|
|
||||||
@Schema(description = "最后连接时间")
|
|
||||||
private Date lastConnectedAt;
|
|
||||||
|
|
||||||
@Schema(description = "自动更新开关(0关闭/1开启)")
|
|
||||||
private Integer autoUpdate;
|
|
||||||
|
|
||||||
@Schema(description = "设备硬件型号")
|
|
||||||
private String board;
|
|
||||||
|
|
||||||
@Schema(description = "设备别名")
|
|
||||||
private String alias;
|
|
||||||
|
|
||||||
@Schema(description = "智能体ID")
|
|
||||||
private String agentId;
|
|
||||||
|
|
||||||
@Schema(description = "固件版本号")
|
|
||||||
private String appVersion;
|
|
||||||
|
|
||||||
@Schema(description = "排序")
|
|
||||||
private Integer sort;
|
|
||||||
|
|
||||||
@Schema(description = "创建者")
|
|
||||||
private Long creator;
|
|
||||||
|
|
||||||
@Schema(description = "创建时间")
|
|
||||||
private Date createDate;
|
|
||||||
|
|
||||||
@Schema(description = "更新者")
|
|
||||||
private Long updater;
|
|
||||||
|
|
||||||
@Schema(description = "更新时间")
|
|
||||||
private Date updateDate;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xiaozhi.modules.device.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.modules.device.domain.Device;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_device(设备信息表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-03-22 13:26:35
|
||||||
|
* @Entity xiaozhi.modules.device.domain.Device
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface DeviceMapper extends BaseMapper<Device> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,18 +1,13 @@
|
|||||||
package xiaozhi.modules.device.service;
|
package xiaozhi.modules.device.service;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.modules.device.domain.Device;
|
||||||
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
|
||||||
|
/**
|
||||||
import java.util.List;
|
* @author chenerlei
|
||||||
import java.util.Map;
|
* @description 针对表【ai_device(设备信息表)】的数据库操作Service
|
||||||
|
* @createDate 2025-03-22 13:26:35
|
||||||
public interface DeviceService {
|
*/
|
||||||
DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader);
|
public interface DeviceService extends IService<Device> {
|
||||||
|
|
||||||
List<DeviceEntity> getUserDevices(Long userId);
|
}
|
||||||
|
|
||||||
void unbindDevice(Long userId, Long deviceId);
|
|
||||||
|
|
||||||
PageData<DeviceEntity> adminDeviceList(Map<String, Object> params);
|
|
||||||
}
|
|
||||||
|
|||||||
+22
-57
@@ -1,57 +1,22 @@
|
|||||||
package xiaozhi.modules.device.service.impl;
|
package xiaozhi.modules.device.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import xiaozhi.modules.device.domain.Device;
|
||||||
import org.springframework.stereotype.Service;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.modules.device.mapper.DeviceMapper;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import org.springframework.stereotype.Service;
|
||||||
import xiaozhi.modules.device.dao.DeviceDao;
|
|
||||||
import xiaozhi.modules.device.dto.DeviceHeaderDTO;
|
/**
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
* @author chenerlei
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
* @description 针对表【ai_device(设备信息表)】的数据库操作Service实现
|
||||||
|
* @createDate 2025-03-22 13:26:35
|
||||||
import java.util.Date;
|
*/
|
||||||
import java.util.List;
|
@Service
|
||||||
import java.util.Map;
|
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device>
|
||||||
|
implements DeviceService{
|
||||||
@Service
|
|
||||||
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
}
|
||||||
private final DeviceDao deviceDao;
|
|
||||||
|
|
||||||
// 添加构造函数来初始化 deviceMapper
|
|
||||||
public DeviceServiceImpl(DeviceDao deviceDao) {
|
|
||||||
this.deviceDao = deviceDao;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader) {
|
|
||||||
DeviceEntity device = new DeviceEntity();
|
|
||||||
device.setUserId(userId);
|
|
||||||
device.setMacAddress(deviceHeader.getDeviceId());
|
|
||||||
device.setCreateDate(new Date());
|
|
||||||
deviceDao.insert(device);
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<DeviceEntity> getUserDevices(Long userId) {
|
|
||||||
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
|
||||||
wrapper.eq("user_id", userId);
|
|
||||||
return deviceDao.selectList(wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unbindDevice(Long userId, Long deviceId) {
|
|
||||||
deviceDao.deleteById(deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PageData<DeviceEntity> adminDeviceList(Map<String, Object> params) {
|
|
||||||
IPage<DeviceEntity> page = deviceDao.selectPage(
|
|
||||||
getPage(params, "sort", true),
|
|
||||||
new QueryWrapper<>()
|
|
||||||
);
|
|
||||||
return new PageData<>(page.getRecords(), page.getTotal());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package xiaozhi.modules.device.utils;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class CodeGeneratorUtil {
|
||||||
|
|
||||||
|
private static long lastTime = 0;
|
||||||
|
private static int counter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成6位数字码,首位不为0,短期内不会重复。
|
||||||
|
* @return 6位数字码
|
||||||
|
*/
|
||||||
|
public static synchronized String generateCode() {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 如果当前时间与上次相同,增加计数器以避免重复
|
||||||
|
if (currentTime == lastTime) {
|
||||||
|
counter++;
|
||||||
|
} else {
|
||||||
|
counter = 0; // 时间变化时重置计数器
|
||||||
|
}
|
||||||
|
lastTime = currentTime;
|
||||||
|
|
||||||
|
// 使用当前时间和计数器生成随机种子
|
||||||
|
long seed = currentTime + counter;
|
||||||
|
Random random = new Random(seed);
|
||||||
|
|
||||||
|
// 确保首位不为0
|
||||||
|
int firstDigit = random.nextInt(9) + 1;
|
||||||
|
int remainingDigits = random.nextInt(100000); // 剩余5位
|
||||||
|
|
||||||
|
// 拼接生成的6位数字码
|
||||||
|
return String.format("%d%05d", firstDigit, remainingDigits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定位数的数字码,首位不为0,短期内不会重复。
|
||||||
|
* @param digits 生成的数字码位数
|
||||||
|
* @return 指定位数的数字码
|
||||||
|
*/
|
||||||
|
public static synchronized String generateCode(int digits) {
|
||||||
|
if (digits < 2) {
|
||||||
|
throw new IllegalArgumentException("数字码位数必须大于等于2");
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 如果当前时间与上次相同,增加计数器以避免重复
|
||||||
|
if (currentTime == lastTime) {
|
||||||
|
counter++;
|
||||||
|
} else {
|
||||||
|
counter = 0; // 时间变化时重置计数器
|
||||||
|
}
|
||||||
|
lastTime = currentTime;
|
||||||
|
|
||||||
|
// 使用当前时间和计数器生成随机种子
|
||||||
|
long seed = currentTime + counter;
|
||||||
|
Random random = new Random(seed);
|
||||||
|
|
||||||
|
// 确保首位不为0
|
||||||
|
int firstDigit = random.nextInt(9) + 1;
|
||||||
|
int remainingDigits = random.nextInt((int) Math.pow(10, digits - 1)); // 剩余位数
|
||||||
|
|
||||||
|
// 拼接生成的动态位数数字码
|
||||||
|
return String.format("%d%0" + (digits - 1) + "d", firstDigit, remainingDigits);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package xiaozhi.modules.device.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DeviceCodeVO {
|
||||||
|
private String deviceId;
|
||||||
|
private String deviceCode;
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package xiaozhi.modules.device.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DeviceOtaVO {
|
||||||
|
private Activation activation;
|
||||||
|
private Mqtt mqtt;
|
||||||
|
private ServerTime server_time;
|
||||||
|
private Firmware firmware;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Activation {
|
||||||
|
private String code;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public Activation() {
|
||||||
|
}
|
||||||
|
public Activation(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Mqtt {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ServerTime {
|
||||||
|
private Long timestamp;
|
||||||
|
private Integer timezone_offset;
|
||||||
|
|
||||||
|
public ServerTime() {
|
||||||
|
}
|
||||||
|
public ServerTime(Long timestamp, Integer timezone_offset) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.timezone_offset = timezone_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Firmware {
|
||||||
|
private String version;
|
||||||
|
private String url;
|
||||||
|
public Firmware() {
|
||||||
|
}
|
||||||
|
public Firmware(String version, String url) {
|
||||||
|
this.version = version;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package xiaozhi.modules.model.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型配置表
|
||||||
|
* @TableName ai_model_config
|
||||||
|
*/
|
||||||
|
@TableName(value ="ai_model_config")
|
||||||
|
@Data
|
||||||
|
public class ModelConfig implements Serializable {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型类型(Memory/ASR/VAD/LLM/TTS)
|
||||||
|
*/
|
||||||
|
private String modelType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型编码(如AliLLM、DoubaoTTS)
|
||||||
|
*/
|
||||||
|
private String modelCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
private String modelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否默认配置(0否 1是)
|
||||||
|
*/
|
||||||
|
private Integer isDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Integer isEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型配置(JSON格式)
|
||||||
|
*/
|
||||||
|
private Object configJson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方文档链接
|
||||||
|
*/
|
||||||
|
private String docLink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateDate;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package xiaozhi.modules.model.domain;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS 音色表
|
||||||
|
* @TableName ai_tts_voice
|
||||||
|
*/
|
||||||
|
@TableName(value ="ai_tts_voice")
|
||||||
|
@Data
|
||||||
|
public class TtsVoice implements Serializable {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应 TTS 模型主键
|
||||||
|
*/
|
||||||
|
private String ttsModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音色名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音色编码
|
||||||
|
*/
|
||||||
|
private String ttsVoice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 语言
|
||||||
|
*/
|
||||||
|
private String languages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音色 Demo
|
||||||
|
*/
|
||||||
|
private String voiceDemo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private Long creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private Long updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateDate;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xiaozhi.modules.model.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.modules.model.domain.ModelConfig;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_model_config(模型配置表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-03-22 15:31:57
|
||||||
|
* @Entity xiaozhi.modules.model.domain.ModelConfig
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface ModelConfigMapper extends BaseMapper<ModelConfig> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package xiaozhi.modules.model.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import xiaozhi.modules.model.domain.TtsVoice;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-03-22 15:31:57
|
||||||
|
* @Entity xiaozhi.modules.model.domain.TtsVoice
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface TtsVoiceMapper extends BaseMapper<TtsVoice> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.model.service;
|
||||||
|
|
||||||
|
import xiaozhi.modules.model.domain.ModelConfig;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_model_config(模型配置表)】的数据库操作Service
|
||||||
|
* @createDate 2025-03-22 15:31:57
|
||||||
|
*/
|
||||||
|
public interface ModelConfigService extends IService<ModelConfig> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.model.service;
|
||||||
|
|
||||||
|
import xiaozhi.modules.model.domain.TtsVoice;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Service
|
||||||
|
* @createDate 2025-03-22 15:31:57
|
||||||
|
*/
|
||||||
|
public interface TtsVoiceService extends IService<TtsVoice> {
|
||||||
|
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.model.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import xiaozhi.modules.model.domain.ModelConfig;
|
||||||
|
import xiaozhi.modules.model.service.ModelConfigService;
|
||||||
|
import xiaozhi.modules.model.mapper.ModelConfigMapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_model_config(模型配置表)】的数据库操作Service实现
|
||||||
|
* @createDate 2025-03-22 15:31:57
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ModelConfigServiceImpl extends ServiceImpl<ModelConfigMapper, ModelConfig>
|
||||||
|
implements ModelConfigService{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.model.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import xiaozhi.modules.model.domain.TtsVoice;
|
||||||
|
import xiaozhi.modules.model.service.TtsVoiceService;
|
||||||
|
import xiaozhi.modules.model.mapper.TtsVoiceMapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author chenerlei
|
||||||
|
* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Service实现
|
||||||
|
* @createDate 2025-03-22 15:31:57
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class TtsVoiceServiceImpl extends ServiceImpl<TtsVoiceMapper, TtsVoice>
|
||||||
|
implements TtsVoiceService{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -73,6 +73,8 @@ public class ShiroConfig {
|
|||||||
filterMap.put("/user/captcha", "anon");
|
filterMap.put("/user/captcha", "anon");
|
||||||
filterMap.put("/user/login", "anon");
|
filterMap.put("/user/login", "anon");
|
||||||
filterMap.put("/user/register", "anon");
|
filterMap.put("/user/register", "anon");
|
||||||
|
filterMap.put("/ota/**", "anon");
|
||||||
|
filterMap.put("/user/agent/loadAgentConfig/**", "anon");
|
||||||
filterMap.put("/**", "oauth2");
|
filterMap.put("/**", "oauth2");
|
||||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||||
|
|
||||||
|
|||||||
@@ -41,4 +41,22 @@ spring:
|
|||||||
merge-sql: false
|
merge-sql: false
|
||||||
wall:
|
wall:
|
||||||
config:
|
config:
|
||||||
multi-statement-allow: true
|
multi-statement-allow: true
|
||||||
|
redis:
|
||||||
|
host: localhost # Redis服务器地址
|
||||||
|
port: 6379 # Redis服务器连接端口
|
||||||
|
password: # Redis服务器连接密码(默认为空)
|
||||||
|
database: 0 # Redis数据库索引(默认为0)
|
||||||
|
# jedis:
|
||||||
|
# pool:
|
||||||
|
# max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
|
||||||
|
# max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
# max-idle: 8 # 连接池中的最大空闲连接
|
||||||
|
# min-idle: 0 # 连接池中的最小空闲连接
|
||||||
|
timeout: 10000ms # 连接超时时间(毫秒)
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
|
||||||
|
max-idle: 8 # 连接池中的最大空闲连接
|
||||||
|
min-idle: 0 # 连接池中的最小空闲连接
|
||||||
|
shutdown-timeout: 100ms # 客户端优雅关闭的等待时间
|
||||||
@@ -24,19 +24,6 @@ spring:
|
|||||||
max-file-size: 100MB
|
max-file-size: 100MB
|
||||||
max-request-size: 100MB
|
max-request-size: 100MB
|
||||||
enabled: true
|
enabled: true
|
||||||
data:
|
|
||||||
redis:
|
|
||||||
database: 0
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
password: # 密码(默认为空)
|
|
||||||
timeout: 6000ms # 连接超时时长(毫秒)
|
|
||||||
lettuce:
|
|
||||||
pool:
|
|
||||||
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
|
|
||||||
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
|
||||||
max-idle: 10 # 连接池中的最大空闲连接
|
|
||||||
min-idle: 5 # 连接池中的最小空闲连接
|
|
||||||
main:
|
main:
|
||||||
allow-bean-definition-overriding: true
|
allow-bean-definition-overriding: true
|
||||||
|
|
||||||
@@ -51,7 +38,7 @@ knife4j:
|
|||||||
|
|
||||||
renren:
|
renren:
|
||||||
redis:
|
redis:
|
||||||
open: false
|
open: true
|
||||||
xss:
|
xss:
|
||||||
enabled: true
|
enabled: true
|
||||||
exclude-urls:
|
exclude-urls:
|
||||||
@@ -60,7 +47,7 @@ renren:
|
|||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath*:/mapper/**/*.xml
|
mapper-locations: classpath*:/mapper/**/*.xml
|
||||||
#实体扫描,多个package用逗号或者分号分隔
|
#实体扫描,多个package用逗号或者分号分隔
|
||||||
typeAliasesPackage: xiaozhi.modules.*.entity
|
typeAliasesPackage: xiaozhi.modules.*.entity;xiaozhi.modules.*.domain
|
||||||
global-config:
|
global-config:
|
||||||
#数据库相关配置
|
#数据库相关配置
|
||||||
db-config:
|
db-config:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.agent.mapper.AgentMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="xiaozhi.modules.agent.domain.Agent">
|
||||||
|
<id property="id" column="id" />
|
||||||
|
<result property="userId" column="user_id" />
|
||||||
|
<result property="agentCode" column="agent_code" />
|
||||||
|
<result property="agentName" column="agent_name" />
|
||||||
|
<result property="asrModelId" column="asr_model_id" />
|
||||||
|
<result property="vadModelId" column="vad_model_id" />
|
||||||
|
<result property="llmModelId" column="llm_model_id" />
|
||||||
|
<result property="ttsModelId" column="tts_model_id" />
|
||||||
|
<result property="ttsVoiceId" column="tts_voice_id" />
|
||||||
|
<result property="memModelId" column="mem_model_id" />
|
||||||
|
<result property="intentModelId" column="intent_model_id" />
|
||||||
|
<result property="systemPrompt" column="system_prompt" />
|
||||||
|
<result property="langCode" column="lang_code" />
|
||||||
|
<result property="language" column="language" />
|
||||||
|
<result property="sort" column="sort" />
|
||||||
|
<result property="creator" column="creator" />
|
||||||
|
<result property="createdAt" column="created_at" />
|
||||||
|
<result property="updater" column="updater" />
|
||||||
|
<result property="updatedAt" column="updated_at" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id,user_id,agent_code,agent_name,asr_model_id,vad_model_id,
|
||||||
|
llm_model_id,tts_model_id,tts_voice_id,mem_model_id,intent_model_id,
|
||||||
|
system_prompt,lang_code,language,sort,creator,
|
||||||
|
created_at,updater,updated_at
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.agent.mapper.AgentTemplateMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="xiaozhi.modules.agent.domain.AgentTemplate">
|
||||||
|
<id property="id" column="id" />
|
||||||
|
<result property="agentCode" column="agent_code" />
|
||||||
|
<result property="agentName" column="agent_name" />
|
||||||
|
<result property="asrModelId" column="asr_model_id" />
|
||||||
|
<result property="vadModelId" column="vad_model_id" />
|
||||||
|
<result property="llmModelId" column="llm_model_id" />
|
||||||
|
<result property="ttsModelId" column="tts_model_id" />
|
||||||
|
<result property="ttsVoiceId" column="tts_voice_id" />
|
||||||
|
<result property="memModelId" column="mem_model_id" />
|
||||||
|
<result property="intentModelId" column="intent_model_id" />
|
||||||
|
<result property="systemPrompt" column="system_prompt" />
|
||||||
|
<result property="langCode" column="lang_code" />
|
||||||
|
<result property="language" column="language" />
|
||||||
|
<result property="sort" column="sort" />
|
||||||
|
<result property="isDefault" column="is_default" />
|
||||||
|
<result property="creator" column="creator" />
|
||||||
|
<result property="createdAt" column="created_at" />
|
||||||
|
<result property="updater" column="updater" />
|
||||||
|
<result property="updatedAt" column="updated_at" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id,agent_code,agent_name,asr_model_id,vad_model_id,llm_model_id,
|
||||||
|
tts_model_id,tts_voice_id,mem_model_id,intent_model_id,system_prompt,
|
||||||
|
lang_code,language,sort,is_default,creator,
|
||||||
|
created_at,updater,updated_at
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.device.mapper.DeviceMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="xiaozhi.modules.device.domain.Device">
|
||||||
|
<id property="id" column="id" />
|
||||||
|
<result property="userId" column="user_id" />
|
||||||
|
<result property="macAddress" column="mac_address" />
|
||||||
|
<result property="lastConnectedAt" column="last_connected_at" />
|
||||||
|
<result property="autoUpdate" column="auto_update" />
|
||||||
|
<result property="board" column="board" />
|
||||||
|
<result property="alias" column="alias" />
|
||||||
|
<result property="agentId" column="agent_id" />
|
||||||
|
<result property="appVersion" column="app_version" />
|
||||||
|
<result property="sort" column="sort" />
|
||||||
|
<result property="creator" column="creator" />
|
||||||
|
<result property="createDate" column="create_date" />
|
||||||
|
<result property="updater" column="updater" />
|
||||||
|
<result property="updateDate" column="update_date" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id,user_id,mac_address,last_connected_at,auto_update,board,
|
||||||
|
alias,agent_id,app_version,sort,creator,
|
||||||
|
create_date,updater,update_date
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.model.mapper.ModelConfigMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="xiaozhi.modules.model.domain.ModelConfig">
|
||||||
|
<id property="id" column="id" />
|
||||||
|
<result property="modelType" column="model_type" />
|
||||||
|
<result property="modelCode" column="model_code" />
|
||||||
|
<result property="modelName" column="model_name" />
|
||||||
|
<result property="isDefault" column="is_default" />
|
||||||
|
<result property="isEnabled" column="is_enabled" />
|
||||||
|
<result property="configJson" column="config_json" />
|
||||||
|
<result property="docLink" column="doc_link" />
|
||||||
|
<result property="remark" column="remark" />
|
||||||
|
<result property="sort" column="sort" />
|
||||||
|
<result property="creator" column="creator" />
|
||||||
|
<result property="createDate" column="create_date" />
|
||||||
|
<result property="updater" column="updater" />
|
||||||
|
<result property="updateDate" column="update_date" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id,model_type,model_code,model_name,is_default,is_enabled,
|
||||||
|
config_json,doc_link,remark,sort,creator,
|
||||||
|
create_date,updater,update_date
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="xiaozhi.modules.model.mapper.TtsVoiceMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="xiaozhi.modules.model.domain.TtsVoice">
|
||||||
|
<id property="id" column="id" />
|
||||||
|
<result property="ttsModelId" column="tts_model_id" />
|
||||||
|
<result property="name" column="name" />
|
||||||
|
<result property="ttsVoice" column="tts_voice" />
|
||||||
|
<result property="languages" column="languages" />
|
||||||
|
<result property="voiceDemo" column="voice_demo" />
|
||||||
|
<result property="remark" column="remark" />
|
||||||
|
<result property="sort" column="sort" />
|
||||||
|
<result property="creator" column="creator" />
|
||||||
|
<result property="createDate" column="create_date" />
|
||||||
|
<result property="updater" column="updater" />
|
||||||
|
<result property="updateDate" column="update_date" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id,tts_model_id,name,tts_voice,languages,voice_demo,
|
||||||
|
remark,sort,creator,create_date,updater,
|
||||||
|
update_date
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
@@ -9,9 +9,9 @@ import user from './module/user.js'
|
|||||||
* 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求
|
* 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default'
|
// const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default'
|
||||||
// 8002开发完成完成后使用这个
|
// 8002开发完成完成后使用这个
|
||||||
// const DEV_API_SERVICE = '/xiaozhi-esp32-api'
|
const DEV_API_SERVICE = '/xiaozhi-esp32-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据开发环境返回接口url
|
* 根据开发环境返回接口url
|
||||||
|
|||||||
@@ -16,20 +16,17 @@ export default {
|
|||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
//添加智能体
|
//添加智能体
|
||||||
addAgent(name, callback) {
|
addAgent(agentName, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/agent`)
|
.url(`${getServiceUrl()}/api/v1/user/agent`)
|
||||||
.method('POST')
|
.method('POST')
|
||||||
.data({name})
|
.data({agentName})
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
callback(res);
|
callback(res);
|
||||||
})
|
})
|
||||||
.fail((err) => {
|
.fail((err) => {
|
||||||
console.error('添加智能体失败:', err);
|
console.error('添加智能体失败:', err);
|
||||||
RequestService.reAjaxFun(() => {
|
|
||||||
this.addAgent(name, callback);
|
|
||||||
});
|
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
//获取智能体配置
|
//获取智能体配置
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import {getServiceUrl} from '../api'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
//设备列表
|
//设备列表
|
||||||
getDeviceList(agent_id, callback) {
|
getDeviceList(agent_id, page, callback) {
|
||||||
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`)
|
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
|
.data(page)
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
callback(res)
|
callback(res)
|
||||||
@@ -16,11 +17,11 @@ export default {
|
|||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
//绑定设备
|
//绑定设备
|
||||||
bindDevice(agent_id, code, callback) {
|
bindDevice(agentId, deviceCode, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`)
|
.url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agentId}`)
|
||||||
.method('POST')
|
.method('POST')
|
||||||
.data({code})
|
.data({deviceCode})
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
callback(res);
|
callback(res);
|
||||||
@@ -28,14 +29,14 @@ export default {
|
|||||||
.fail((err) => {
|
.fail((err) => {
|
||||||
console.error('绑定设备失败:', err);
|
console.error('绑定设备失败:', err);
|
||||||
RequestService.reAjaxFun(() => {
|
RequestService.reAjaxFun(() => {
|
||||||
this.addDevice(name, callback);
|
this.addDevice(agentId, deviceCode, callback);
|
||||||
});
|
});
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
// 解绑设备
|
// 解绑设备
|
||||||
unbindDevice(device_id, callback) {
|
unbindDevice(deviceId, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`)
|
.url(`${getServiceUrl()}/api/v1/user/agent/device/unbind/${deviceId}`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
@@ -43,7 +44,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.fail(() => {
|
.fail(() => {
|
||||||
RequestService.reAjaxFun(() => {
|
RequestService.reAjaxFun(() => {
|
||||||
this.unbindDevice(device_id, callback);
|
this.unbindDevice(deviceId, callback);
|
||||||
});
|
});
|
||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,12 +46,13 @@ export default {
|
|||||||
this.$message.error('请输入智能体名称');
|
this.$message.error('请输入智能体名称');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
userApi.addAgent(this.agentName, (res) => {
|
this.$emit('confirm',this.agentName)
|
||||||
this.$message.success('添加成功');
|
// userApi.addAgent(this.agentName, (res) => {
|
||||||
this.$emit('confirm', res);
|
// this.$message.success('添加成功');
|
||||||
|
// this.$emit('confirm', res);
|
||||||
this.$emit('update:visible', false);
|
this.$emit('update:visible', false);
|
||||||
this.agentName = "";
|
this.agentName = "";
|
||||||
});
|
// });
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.$emit('update:visible', false)
|
this.$emit('update:visible', false)
|
||||||
|
|||||||
@@ -40,8 +40,12 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
confirm() {
|
confirm() {
|
||||||
|
if (!this.deviceCode.trim()) {
|
||||||
|
this.$message.error('请输入设备验证码');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('confirm',this.deviceCode)
|
||||||
this.$emit('update:visible', false)
|
this.$emit('update:visible', false)
|
||||||
this.$emit('added', this.deviceCode)
|
|
||||||
this.deviceCode = ""
|
this.deviceCode = ""
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
|
|||||||
@@ -39,10 +39,13 @@ function formatDateTool(date, fmt) {
|
|||||||
const o = {
|
const o = {
|
||||||
'M+': date.getMonth() + 1,
|
'M+': date.getMonth() + 1,
|
||||||
'd+': date.getDate(),
|
'd+': date.getDate(),
|
||||||
'h+': date.getHours(),
|
'H+': date.getHours(),
|
||||||
|
'h+': date.getHours() > 12 ? date.getHours() - 12 : date.getHours(),
|
||||||
'm+': date.getMinutes(),
|
'm+': date.getMinutes(),
|
||||||
's+': date.getSeconds()
|
's+': date.getSeconds()
|
||||||
}
|
}
|
||||||
|
// 12小时制
|
||||||
|
const is_12Hours = fmt.indexOf('hh') > -1;
|
||||||
for (const k in o) {
|
for (const k in o) {
|
||||||
if (new RegExp(`(${k})`).test(fmt)) {
|
if (new RegExp(`(${k})`).test(fmt)) {
|
||||||
const str = o[k] + ''
|
const str = o[k] + ''
|
||||||
@@ -52,6 +55,8 @@ function formatDateTool(date, fmt) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 12小时制
|
||||||
|
fmt = is_12Hours ? date.getHours() > 12 ? fmt + " PM" : fmt + " AM" : fmt
|
||||||
return fmt
|
return fmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,50 +18,53 @@
|
|||||||
+ 添加设备
|
+ 添加设备
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-table :data="devices" style="width: 100%; margin-top: 20px" border>
|
<el-table :data="devices" style="width: 100%; margin-top: 20px" border>
|
||||||
<el-table-column label="设备型号" prop="device_type" flex></el-table-column>
|
<el-table-column label="设备型号" prop="board" flex></el-table-column>
|
||||||
<el-table-column label="固件版本" prop="app_version" width="140"></el-table-column>
|
<el-table-column label="固件版本" prop="appVersion" width="140"></el-table-column>
|
||||||
<el-table-column label="MAC地址" prop="mac_address" width="220"></el-table-column>
|
<el-table-column label="MAC地址" prop="macAddress" width="220"></el-table-column>
|
||||||
<el-table-column label="绑定时间" prop="bind_time" width="260"></el-table-column>
|
<el-table-column label="绑定时间" width="260" prop="createDate" :formatter="formatter">
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="最近对话" prop="recent_chat_time" width="100"></el-table-column>
|
<el-table-column label="最近对话" prop="recent_chat_time" width="100"></el-table-column>
|
||||||
<el-table-column label="备注" width="220">
|
<el-table-column label="备注" width="220">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-input v-if="scope.row.isEdit" v-model="scope.row.remark" size="small" @blur="stopEditRemark(scope.$index)"></el-input>
|
<el-input v-if="scope.row.isEdit" v-model="scope.row.alias" size="small" @blur="stopEditRemark(scope.$index)"></el-input>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ scope.row.remark }}
|
{{ scope.row.alias }}
|
||||||
</span>
|
</span>
|
||||||
<i v-if="!scope.row.isEdit" class="el-icon-edit" @click="startEditRemark(scope.$index, scope.row)"></i>
|
<i v-if="!scope.row.isEdit" class="el-icon-edit" @click="startEditRemark(scope.$index, scope.row)"></i>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="OTA升级" width="100" align="center">
|
<el-table-column label="OTA升级" width="100" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-switch v-model="scope.row.ota_upgrade" size="mini" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
|
<el-switch v-model="scope.row.autoUpdate" size="mini" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80" align="center">
|
<el-table-column label="操作" width="80" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button size="mini" type="text" @click="handleUnbind(scope.row)" style="color: #ff4949">
|
<el-button size="mini" type="text" @click="handleUnbind(scope.row.id)" style="color: #ff4949">
|
||||||
解绑
|
解绑
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<el-pagination background :page-size="10" :pager-count="5" layout="prev, pager, next" :total="150" style="margin-top: 4px;text-align: right;"></el-pagination>
|
<el-pagination background :current-page="page.pageNo" :page-size="page.pageSize" :pager-count="5" layout="prev, pager, next" :total="page.total" style="margin-top: 4px;text-align: right;"></el-pagination>
|
||||||
</div>
|
</div>
|
||||||
<!-- 底部 -->
|
<!-- 底部 -->
|
||||||
<Footer :visible="true" />
|
<Footer :visible="true" />
|
||||||
<!-- 添加设备对话框 -->
|
<!-- 添加设备对话框 -->
|
||||||
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" @added="handleDeviceAdded" />
|
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" @confirm="handleDeviceBind" />
|
||||||
</el-main>
|
</el-main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
|
import {getUUID, goToPage, showDanger, showSuccess} from '@/utils'
|
||||||
|
import { formatDate } from '@/utils/date'
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
import AddDeviceDialog from '@/components/AddDeviceDialog.vue'
|
import AddDeviceDialog from '@/components/AddDeviceDialog.vue'
|
||||||
import HeaderBar from '@/components/HeaderBar.vue'
|
import HeaderBar from '@/components/HeaderBar.vue'
|
||||||
import Footer from '@/components/Footer.vue'
|
import Footer from '@/components/Footer.vue'
|
||||||
|
import api from '@/apis/api';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DevicePage',
|
name: 'DevicePage',
|
||||||
@@ -70,18 +73,24 @@ export default {
|
|||||||
return {
|
return {
|
||||||
addDeviceDialogVisible: false,
|
addDeviceDialogVisible: false,
|
||||||
devices: [],
|
devices: [],
|
||||||
agentId: this.$route.query.agentId
|
agentId: this.$route.query.agentId,
|
||||||
|
page: {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.getDeviceList();
|
this.fetchDeviceList();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 获取设备列表
|
// 获取设备列表
|
||||||
getDeviceList() {
|
fetchDeviceList() {
|
||||||
Api.device.getDeviceList(this.agentId, ({data}) => {
|
Api.device.getDeviceList(this.agentId, this.page, ({data}) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.devices = data.data[0].list.map((item)=>{item.isEdit=false;return item;})
|
this.page = {...this.page,total:parseInt(data.data.total)}
|
||||||
|
this.devices = data.data.records.map((item)=>{item.isEdit=false;return item;})
|
||||||
} else {
|
} else {
|
||||||
showDanger(data.msg)
|
showDanger(data.msg)
|
||||||
}
|
}
|
||||||
@@ -97,14 +106,33 @@ export default {
|
|||||||
stopEditRemark(index) {
|
stopEditRemark(index) {
|
||||||
this.devices[index].isEdit = false;
|
this.devices[index].isEdit = false;
|
||||||
},
|
},
|
||||||
handleUnbind(device) {
|
handleUnbind(deviceId) {
|
||||||
// 解绑逻辑
|
// 解绑逻辑
|
||||||
console.log('解绑设备', device);
|
console.log('解绑设备', deviceId);
|
||||||
|
Api.device.unbindDevice(deviceId, ({data}) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
showSuccess('解绑成功')
|
||||||
|
this.fetchDeviceList()
|
||||||
|
} else {
|
||||||
|
showDanger(data.msg)
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
handleDeviceAdded(deviceCode) {
|
handleDeviceBind(deviceCode) {
|
||||||
// 根据需要处理添加设备后逻辑,比如刷新设备列表等
|
// 根据需要处理添加设备后逻辑,比如刷新设备列表等
|
||||||
console.log('设备验证码:', deviceCode)
|
console.log('设备验证码:', deviceCode)
|
||||||
|
Api.device.bindDevice(this.agentId, deviceCode, ({data}) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.fetchDeviceList();
|
||||||
|
this.showBindDialog = false;
|
||||||
|
} else {
|
||||||
|
showDanger(data.msg);
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
formatter(row, column) {
|
||||||
|
return formatDate(row.createDate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<!-- 公共头部 -->
|
<!-- 公共头部 -->
|
||||||
<HeaderBar :devices="devices" @search-result="handleSearchResult" />
|
<HeaderBar :devices="agents" @search-result="handleSearchResult" />
|
||||||
<el-main style="padding: 20px;display: flex;flex-direction: column;">
|
<el-main style="padding: 20px;display: flex;flex-direction: column;">
|
||||||
<div>
|
<div>
|
||||||
<!-- 首页内容 -->
|
<!-- 首页内容 -->
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<!-- 底部 -->
|
<!-- 底部 -->
|
||||||
<Footer :visible="true" />
|
<Footer :visible="true" />
|
||||||
<!-- 添加设备对话框 -->
|
<!-- 添加设备对话框 -->
|
||||||
<AddAgentDialog :visible.sync="addAgentDialogVisible" @added="handleAgentAdded" />
|
<AddAgentDialog :visible.sync="addAgentDialogVisible" @confirm="handleAgentAdded" />
|
||||||
</el-main>
|
</el-main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// 搜索更新智能体列表
|
// 搜索更新智能体列表
|
||||||
handleSearchResult(filteredList) {
|
handleSearchResult(filteredList) {
|
||||||
this.devices = filteredList; // 更新设备列表
|
this.agents = filteredList; // 更新设备列表
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,19 +119,19 @@ export default {
|
|||||||
intent:""
|
intent:""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{ value: '选项1', label: '黄金糕' },
|
{ value: '选项1', label: '黄金糕' },
|
||||||
{ value: '选项2', label: '双皮奶' }
|
{ value: '选项2', label: '双皮奶' }
|
||||||
],
|
],
|
||||||
models: [
|
models: [
|
||||||
{ label: '大语言模型(LLM)', key: 'llm' },
|
{ label: '大语言模型(LLM)', key: 'llm' },
|
||||||
{ label: '语音转文本模型(ASR)', key: 'asr' },
|
{ label: '语音转文本模型(ASR)', key: 'asr' },
|
||||||
{ label: '语音活动检测模型(VAD)', key: 'vad' },
|
{ label: '语音活动检测模型(VAD)', key: 'vad' },
|
||||||
{ label: '语音生成模型(TTS)', key: 'tts' },
|
{ label: '语音生成模型(TTS)', key: 'tts' },
|
||||||
{ label: '意图分类模型(Intent)', key: 'intent' },
|
{ label: '意图分类模型(Intent)', key: 'intent' },
|
||||||
{ label: '记忆增强模型(Memory)', key: 'memory' }
|
{ label: '记忆增强模型(Memory)', key: 'memory' }
|
||||||
],
|
],
|
||||||
templates: ['台湾女友', '土豆子', '英语老师', '好奇小男孩', '汪汪队队长']
|
templates: ['通用男声','通用女声','阳光男生','奶气萌娃']
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -510,6 +510,11 @@ music:
|
|||||||
- ".p3"
|
- ".p3"
|
||||||
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
|
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
|
||||||
|
|
||||||
|
# 远程配置
|
||||||
|
remote_config:
|
||||||
|
enabled: true
|
||||||
|
url: http://192.168.5.11:8002/user/agent/loadAgentConfig/
|
||||||
|
|
||||||
# 以下配置在小于等于0.0.9版本中的docker容器中可用
|
# 以下配置在小于等于0.0.9版本中的docker容器中可用
|
||||||
# 0.0.9以后的新版本源码部署已经无法奏效
|
# 0.0.9以后的新版本源码部署已经无法奏效
|
||||||
manager:
|
manager:
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@@ -22,6 +24,14 @@ class PrivateConfig:
|
|||||||
|
|
||||||
async def load_or_create(self):
|
async def load_or_create(self):
|
||||||
try:
|
try:
|
||||||
|
# 优先通过远程获取配置
|
||||||
|
fetch_config_ = {}
|
||||||
|
remote_config = self.default_config['remote_config']
|
||||||
|
self.logger.bind(tag=TAG).info(f"remote config: {remote_config}")
|
||||||
|
if remote_config and remote_config['enabled']:
|
||||||
|
fetch_config_ = await self.fetch_config(remote_config['url'])
|
||||||
|
|
||||||
|
self.logger.bind(tag=TAG).info(f"fetch_config: {fetch_config_}")
|
||||||
await self.lock_manager.acquire_lock(self.config_path)
|
await self.lock_manager.acquire_lock(self.config_path)
|
||||||
try:
|
try:
|
||||||
if os.path.exists(self.config_path):
|
if os.path.exists(self.config_path):
|
||||||
@@ -30,6 +40,9 @@ class PrivateConfig:
|
|||||||
else:
|
else:
|
||||||
all_configs = {}
|
all_configs = {}
|
||||||
|
|
||||||
|
if fetch_config_:
|
||||||
|
all_configs[self.device_id] = fetch_config_
|
||||||
|
|
||||||
if self.device_id not in all_configs:
|
if self.device_id not in all_configs:
|
||||||
# Get selected module names
|
# Get selected module names
|
||||||
selected_modules = self.default_config['selected_module']
|
selected_modules = self.default_config['selected_module']
|
||||||
@@ -64,9 +77,9 @@ class PrivateConfig:
|
|||||||
|
|
||||||
all_configs[self.device_id] = device_config
|
all_configs[self.device_id] = device_config
|
||||||
|
|
||||||
# Save updated configs
|
# Save updated configs
|
||||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
with open(self.config_path, 'w', encoding='utf-8') as f:
|
||||||
yaml.dump(all_configs, f, allow_unicode=True)
|
yaml.dump(all_configs, f, allow_unicode=True)
|
||||||
|
|
||||||
self.private_config = all_configs[self.device_id]
|
self.private_config = all_configs[self.device_id]
|
||||||
|
|
||||||
@@ -238,4 +251,60 @@ class PrivateConfig:
|
|||||||
|
|
||||||
def get_owner(self) -> Optional[str]:
|
def get_owner(self) -> Optional[str]:
|
||||||
"""获取设备当前所有者"""
|
"""获取设备当前所有者"""
|
||||||
return self.private_config.get('owner')
|
return self.private_config.get('owner')
|
||||||
|
|
||||||
|
async def fetch_config(self, fetch_url: str) -> Dict[str, Any]:
|
||||||
|
"""通过HTTP请求远程获取配置"""
|
||||||
|
url = f"{fetch_url}{self.device_id}"
|
||||||
|
headers = {
|
||||||
|
'device_id': self.device_id
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
response.raise_for_status() # 如果响应状态码不是200,会抛出异常
|
||||||
|
# 检查响应内容类型是否为JSON
|
||||||
|
if response.headers.get('Content-Type') != 'application/json':
|
||||||
|
self.logger.bind(tag=TAG).error("Invalid content type: expected application/json")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 解析JSON数据
|
||||||
|
config_data = response.json()
|
||||||
|
|
||||||
|
# 验证返回的数据结构
|
||||||
|
if not isinstance(config_data, dict):
|
||||||
|
self.logger.bind(tag=TAG).error("Invalid data format: expected a dictionary")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if config_data['code'] != 0:
|
||||||
|
self.logger.bind(tag=TAG).error(f"Fetch config error: {config_data['msg']}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
config_data_ = config_data['data']
|
||||||
|
if not isinstance(config_data_, dict):
|
||||||
|
self.logger.bind(tag=TAG).error("Invalid config data format: expected a dictionary")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
# 检查必要的字段是否存在
|
||||||
|
required_fields = ['selected_module', 'prompt', 'LLM', 'TTS', 'ASR', 'VAD']
|
||||||
|
for field in required_fields:
|
||||||
|
if field not in config_data_:
|
||||||
|
self.logger.bind(tag=TAG).error(f"Missing required field: {field}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 检查每个模块的配置是否正确
|
||||||
|
for module in ['LLM', 'TTS', 'ASR', 'VAD']:
|
||||||
|
if not isinstance(config_data_[module], dict):
|
||||||
|
self.logger.bind(tag=TAG).error(f"Invalid data format for {module}: expected a dictionary")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
selected_module = config_data_['selected_module'].get(module)
|
||||||
|
if selected_module not in config_data_[module]:
|
||||||
|
self.logger.bind(tag=TAG).error(f"Selected {module} not found in config: {selected_module}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return config_data_
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"Error fetching config: {e}")
|
||||||
|
return {}
|
||||||
Reference in New Issue
Block a user