mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 17:43:55 +08:00
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回响应体工具类
|
||||||
|
*/
|
||||||
|
public class ResultUtils
|
||||||
|
{
|
||||||
|
public static <T> Result<T> success(T data) {
|
||||||
|
return new Result<T>().ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error() {
|
||||||
|
return new Result<T>().error();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(String msg) {
|
||||||
|
return new Result<T>().error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(int errorCode, String msg) {
|
||||||
|
return new Result<T>().error(errorCode, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> error(int errorCode) {
|
||||||
|
return new Result<T>().error(errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> empty() {
|
||||||
|
return new Result<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-112
@@ -1,6 +1,5 @@
|
|||||||
package xiaozhi.modules.agent.controller;
|
package xiaozhi.modules.agent.controller;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -33,8 +32,8 @@ import xiaozhi.common.page.PageData;
|
|||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.common.utils.ResultUtils;
|
||||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
|
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
@@ -45,8 +44,10 @@ import xiaozhi.modules.agent.entity.AgentEntity;
|
|||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
@@ -61,6 +62,7 @@ public class AgentController {
|
|||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
private final AgentChatHistoryService agentChatHistoryService;
|
private final AgentChatHistoryService agentChatHistoryService;
|
||||||
private final AgentChatAudioService agentChatAudioService;
|
private final AgentChatAudioService agentChatAudioService;
|
||||||
|
private final AgentPluginMappingService agentPluginMappingService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
|
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@@ -88,46 +90,17 @@ public class AgentController {
|
|||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@Operation(summary = "获取智能体详情")
|
@Operation(summary = "获取智能体详情")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<AgentEntity> getAgentById(@PathVariable("id") String id) {
|
public Result<AgentInfoVO> getAgentById(@PathVariable("id") String id) {
|
||||||
AgentEntity agent = agentService.getAgentById(id);
|
AgentInfoVO agent = agentService.getAgentById(id);
|
||||||
return new Result<AgentEntity>().ok(agent);
|
return ResultUtils.success(agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@Operation(summary = "创建智能体")
|
@Operation(summary = "创建智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<String> save(@RequestBody @Valid AgentCreateDTO dto) {
|
public Result<String> save(@RequestBody @Valid AgentCreateDTO dto) {
|
||||||
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
String agentId = agentService.createAgent(dto);
|
||||||
|
return new Result<String>().ok(agentId);
|
||||||
// 获取默认模板
|
|
||||||
AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
|
|
||||||
if (template != null) {
|
|
||||||
// 设置模板中的默认值
|
|
||||||
entity.setAsrModelId(template.getAsrModelId());
|
|
||||||
entity.setVadModelId(template.getVadModelId());
|
|
||||||
entity.setLlmModelId(template.getLlmModelId());
|
|
||||||
entity.setVllmModelId(template.getVllmModelId());
|
|
||||||
entity.setTtsModelId(template.getTtsModelId());
|
|
||||||
entity.setTtsVoiceId(template.getTtsVoiceId());
|
|
||||||
entity.setMemModelId(template.getMemModelId());
|
|
||||||
entity.setIntentModelId(template.getIntentModelId());
|
|
||||||
entity.setSystemPrompt(template.getSystemPrompt());
|
|
||||||
entity.setSummaryMemory(template.getSummaryMemory());
|
|
||||||
entity.setChatHistoryConf(template.getChatHistoryConf());
|
|
||||||
entity.setLangCode(template.getLangCode());
|
|
||||||
entity.setLanguage(template.getLanguage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置用户ID和创建者信息
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
entity.setUserId(user.getId());
|
|
||||||
entity.setCreator(user.getId());
|
|
||||||
entity.setCreatedAt(new Date());
|
|
||||||
|
|
||||||
// ID、智能体编码和排序会在Service层自动生成
|
|
||||||
agentService.insert(entity);
|
|
||||||
|
|
||||||
return new Result<String>().ok(entity.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/saveMemory/{macAddress}")
|
@PutMapping("/saveMemory/{macAddress}")
|
||||||
@@ -139,88 +112,15 @@ public class AgentController {
|
|||||||
}
|
}
|
||||||
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO();
|
||||||
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory());
|
||||||
return updateAgentById(device.getAgentId(), agentUpdateDTO);
|
agentService.updateAgentById(device.getAgentId(), agentUpdateDTO);
|
||||||
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{id}")
|
@PutMapping("/{id}")
|
||||||
@Operation(summary = "更新智能体")
|
@Operation(summary = "更新智能体")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
public Result<Void> update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) {
|
||||||
return updateAgentById(id, dto);
|
agentService.updateAgentById(id, dto);
|
||||||
}
|
|
||||||
|
|
||||||
private Result<Void> updateAgentById(String id, AgentUpdateDTO dto) {
|
|
||||||
// 先查询现有实体
|
|
||||||
AgentEntity existingEntity = agentService.getAgentById(id);
|
|
||||||
if (existingEntity == null) {
|
|
||||||
return new Result<Void>().error("智能体不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只更新提供的非空字段
|
|
||||||
if (dto.getAgentName() != null) {
|
|
||||||
existingEntity.setAgentName(dto.getAgentName());
|
|
||||||
}
|
|
||||||
if (dto.getAgentCode() != null) {
|
|
||||||
existingEntity.setAgentCode(dto.getAgentCode());
|
|
||||||
}
|
|
||||||
if (dto.getAsrModelId() != null) {
|
|
||||||
existingEntity.setAsrModelId(dto.getAsrModelId());
|
|
||||||
}
|
|
||||||
if (dto.getVadModelId() != null) {
|
|
||||||
existingEntity.setVadModelId(dto.getVadModelId());
|
|
||||||
}
|
|
||||||
if (dto.getLlmModelId() != null) {
|
|
||||||
existingEntity.setLlmModelId(dto.getLlmModelId());
|
|
||||||
}
|
|
||||||
if (dto.getVllmModelId() != null) {
|
|
||||||
existingEntity.setVllmModelId(dto.getVllmModelId());
|
|
||||||
}
|
|
||||||
if (dto.getTtsModelId() != null) {
|
|
||||||
existingEntity.setTtsModelId(dto.getTtsModelId());
|
|
||||||
}
|
|
||||||
if (dto.getTtsVoiceId() != null) {
|
|
||||||
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
|
|
||||||
}
|
|
||||||
if (dto.getMemModelId() != null) {
|
|
||||||
existingEntity.setMemModelId(dto.getMemModelId());
|
|
||||||
}
|
|
||||||
if (dto.getIntentModelId() != null) {
|
|
||||||
existingEntity.setIntentModelId(dto.getIntentModelId());
|
|
||||||
}
|
|
||||||
if (dto.getSystemPrompt() != null) {
|
|
||||||
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
|
||||||
}
|
|
||||||
if (dto.getSummaryMemory() != null) {
|
|
||||||
existingEntity.setSummaryMemory(dto.getSummaryMemory());
|
|
||||||
}
|
|
||||||
if (dto.getChatHistoryConf() != null) {
|
|
||||||
existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
|
|
||||||
}
|
|
||||||
if (dto.getLangCode() != null) {
|
|
||||||
existingEntity.setLangCode(dto.getLangCode());
|
|
||||||
}
|
|
||||||
if (dto.getLanguage() != null) {
|
|
||||||
existingEntity.setLanguage(dto.getLanguage());
|
|
||||||
}
|
|
||||||
if (dto.getSort() != null) {
|
|
||||||
existingEntity.setSort(dto.getSort());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置更新者信息
|
|
||||||
UserDetail user = SecurityUser.getUser();
|
|
||||||
existingEntity.setUpdater(user.getId());
|
|
||||||
existingEntity.setUpdatedAt(new Date());
|
|
||||||
|
|
||||||
// 更新记忆策略
|
|
||||||
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
|
||||||
// 删除所有记录
|
|
||||||
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
|
|
||||||
existingEntity.setSummaryMemory("");
|
|
||||||
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
|
|
||||||
// 删除音频数据
|
|
||||||
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
|
|
||||||
}
|
|
||||||
agentService.updateById(existingEntity);
|
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +132,8 @@ public class AgentController {
|
|||||||
deviceService.deleteByAgentId(id);
|
deviceService.deleteByAgentId(id);
|
||||||
// 删除关联的聊天记录
|
// 删除关联的聊天记录
|
||||||
agentChatHistoryService.deleteByAgentId(id, true, true);
|
agentChatHistoryService.deleteByAgentId(id, true, true);
|
||||||
|
// 删除关联的插件
|
||||||
|
agentPluginMappingService.deleteByAgentId(id);
|
||||||
// 再删除智能体
|
// 再删除智能体
|
||||||
agentService.deleteById(id);
|
agentService.deleteById(id);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param;
|
|||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
import xiaozhi.common.dao.BaseDao;
|
import xiaozhi.common.dao.BaseDao;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface AgentDao extends BaseDao<AgentEntity> {
|
public interface AgentDao extends BaseDao<AgentEntity> {
|
||||||
@@ -28,4 +29,11 @@ public interface AgentDao extends BaseDao<AgentEntity> {
|
|||||||
" WHERE d.mac_address = #{macAddress} " +
|
" WHERE d.mac_address = #{macAddress} " +
|
||||||
" ORDER BY d.id DESC LIMIT 1")
|
" ORDER BY d.id DESC LIMIT 1")
|
||||||
AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress);
|
AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询agent信息,包括插件信息
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.agent.dao;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Mapper
|
||||||
|
* @createDate 2025-05-25 22:33:17
|
||||||
|
* @Entity xiaozhi.modules.agent.entity.AgentPluginMapping
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface AgentPluginMappingMapper extends BaseMapper<AgentPluginMapping> {
|
||||||
|
List<AgentPluginMapping> selectPluginsByAgentId(@Param("agentId") String agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package xiaozhi.modules.agent.dto;
|
package xiaozhi.modules.agent.dto;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -15,19 +17,19 @@ import lombok.Data;
|
|||||||
public class AgentUpdateDTO implements Serializable {
|
public class AgentUpdateDTO implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@Schema(description = "智能体编码", example = "AGT_1234567890", required = false)
|
@Schema(description = "智能体编码", example = "AGT_1234567890", nullable = true)
|
||||||
private String agentCode;
|
private String agentCode;
|
||||||
|
|
||||||
@Schema(description = "智能体名称", example = "客服助手", required = false)
|
@Schema(description = "智能体名称", example = "客服助手", nullable = true)
|
||||||
private String agentName;
|
private String agentName;
|
||||||
|
|
||||||
@Schema(description = "语音识别模型标识", example = "asr_model_02", required = false)
|
@Schema(description = "语音识别模型标识", example = "asr_model_02", nullable = true)
|
||||||
private String asrModelId;
|
private String asrModelId;
|
||||||
|
|
||||||
@Schema(description = "语音活动检测标识", example = "vad_model_02", required = false)
|
@Schema(description = "语音活动检测标识", example = "vad_model_02", nullable = true)
|
||||||
private String vadModelId;
|
private String vadModelId;
|
||||||
|
|
||||||
@Schema(description = "大语言模型标识", example = "llm_model_02", required = false)
|
@Schema(description = "大语言模型标识", example = "llm_model_02", nullable = true)
|
||||||
private String llmModelId;
|
private String llmModelId;
|
||||||
|
|
||||||
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
|
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
|
||||||
@@ -36,31 +38,46 @@ public class AgentUpdateDTO implements Serializable {
|
|||||||
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
|
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
|
||||||
private String ttsModelId;
|
private String ttsModelId;
|
||||||
|
|
||||||
@Schema(description = "音色标识", example = "voice_02", required = false)
|
@Schema(description = "音色标识", example = "voice_02", nullable = true)
|
||||||
private String ttsVoiceId;
|
private String ttsVoiceId;
|
||||||
|
|
||||||
@Schema(description = "记忆模型标识", example = "mem_model_02", required = false)
|
@Schema(description = "记忆模型标识", example = "mem_model_02", nullable = true)
|
||||||
private String memModelId;
|
private String memModelId;
|
||||||
|
|
||||||
@Schema(description = "意图模型标识", example = "intent_model_02", required = false)
|
@Schema(description = "意图模型标识", example = "intent_model_02", nullable = true)
|
||||||
private String intentModelId;
|
private String intentModelId;
|
||||||
|
|
||||||
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
|
@Schema(description = "插件函数信息", nullable = true)
|
||||||
|
private List<FunctionInfo> functions;
|
||||||
|
|
||||||
|
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", nullable = true)
|
||||||
private String systemPrompt;
|
private String systemPrompt;
|
||||||
|
|
||||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n"
|
||||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
+ "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", nullable = true)
|
||||||
private String summaryMemory;
|
private String summaryMemory;
|
||||||
|
|
||||||
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false)
|
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", nullable = true)
|
||||||
private Integer chatHistoryConf;
|
private Integer chatHistoryConf;
|
||||||
|
|
||||||
@Schema(description = "语言编码", example = "zh_CN", required = false)
|
@Schema(description = "语言编码", example = "zh_CN", nullable = true)
|
||||||
private String langCode;
|
private String langCode;
|
||||||
|
|
||||||
@Schema(description = "交互语种", example = "中文", required = false)
|
@Schema(description = "交互语种", example = "中文", nullable = true)
|
||||||
private String language;
|
private String language;
|
||||||
|
|
||||||
@Schema(description = "排序", example = "1", required = false)
|
@Schema(description = "排序", example = "1", nullable = true)
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "插件函数信息")
|
||||||
|
public static class FunctionInfo implements Serializable {
|
||||||
|
@Schema(description = "插件ID", example = "plugin_01")
|
||||||
|
private String pluginId;
|
||||||
|
|
||||||
|
@Schema(description = "函数参数信息", nullable = true)
|
||||||
|
private HashMap<String, Object> paramInfo;
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package xiaozhi.modules.agent.entity;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent与插件的唯一映射表
|
||||||
|
*
|
||||||
|
* @TableName ai_agent_plugin_mapping
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "ai_agent_plugin_mapping")
|
||||||
|
@Schema(description = "Agent与插件的唯一映射表")
|
||||||
|
public class AgentPluginMapping implements Serializable {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
|
@Schema(description = "映射信息主键ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体ID
|
||||||
|
*/
|
||||||
|
@Schema(description = "智能体ID")
|
||||||
|
private String agentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件ID
|
||||||
|
*/
|
||||||
|
@Schema(description = "插件ID")
|
||||||
|
private String pluginId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件参数(Json)格式
|
||||||
|
*/
|
||||||
|
@Schema(description = "插件参数(Json)格式")
|
||||||
|
private String paramInfo;
|
||||||
|
|
||||||
|
// 冗余字段,用于方便在根据id查询插件时,对照查出插件的Provider_code,详见dao层xml文件
|
||||||
|
@TableField(exist = false)
|
||||||
|
@Schema(description = "插件provider_code, 对应表ai_model_provider")
|
||||||
|
private String providerCode;
|
||||||
|
|
||||||
|
@TableField(exist = false)
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package xiaozhi.modules.agent.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service
|
||||||
|
* @createDate 2025-05-25 22:33:17
|
||||||
|
*/
|
||||||
|
public interface AgentPluginMappingService extends IService<AgentPluginMapping> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体id获取插件参数
|
||||||
|
*
|
||||||
|
* @param agentId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<AgentPluginMapping> agentPluginParamsByAgentId(String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体id删除插件参数
|
||||||
|
*
|
||||||
|
* @param agentId
|
||||||
|
*/
|
||||||
|
void deleteByAgentId(String agentId);
|
||||||
|
}
|
||||||
@@ -5,8 +5,11 @@ import java.util.Map;
|
|||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.BaseService;
|
import xiaozhi.common.service.BaseService;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 智能体表处理service
|
* 智能体表处理service
|
||||||
@@ -30,7 +33,7 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
* @param id 智能体ID
|
* @param id 智能体ID
|
||||||
* @return 智能体实体
|
* @return 智能体实体
|
||||||
*/
|
*/
|
||||||
AgentEntity getAgentById(String id);
|
AgentInfoVO getAgentById(String id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入智能体
|
* 插入智能体
|
||||||
@@ -79,4 +82,20 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
* @return 是否有权限
|
* @return 是否有权限
|
||||||
*/
|
*/
|
||||||
boolean checkAgentPermission(String agentId, Long userId);
|
boolean checkAgentPermission(String agentId, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新智能体
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
* @param dto 更新智能体所需的信息
|
||||||
|
*/
|
||||||
|
void updateAgentById(String agentId, AgentUpdateDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建智能体
|
||||||
|
*
|
||||||
|
* @param dto 创建智能体所需的信息
|
||||||
|
* @return 创建的智能体ID
|
||||||
|
*/
|
||||||
|
String createAgent(AgentCreateDTO dto);
|
||||||
}
|
}
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import xiaozhi.modules.agent.dao.AgentPluginMappingMapper;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service实现
|
||||||
|
* @createDate 2025-05-25 22:33:17
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AgentPluginMappingServiceImpl extends ServiceImpl<AgentPluginMappingMapper, AgentPluginMapping>
|
||||||
|
implements AgentPluginMappingService {
|
||||||
|
private final AgentPluginMappingMapper agentPluginMappingMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AgentPluginMapping> agentPluginParamsByAgentId(String agentId) {
|
||||||
|
return agentPluginMappingMapper.selectPluginsByAgentId(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteByAgentId(String agentId) {
|
||||||
|
UpdateWrapper<AgentPluginMapping> updateWrapper = new UpdateWrapper<>();
|
||||||
|
updateWrapper.eq("agent_id", agentId);
|
||||||
|
agentPluginMappingMapper.delete(updateWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+230
-8
@@ -1,12 +1,17 @@
|
|||||||
package xiaozhi.modules.agent.service.impl;
|
package xiaozhi.modules.agent.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
@@ -14,16 +19,30 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.constant.Constant;
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
|
import xiaozhi.common.user.UserDetail;
|
||||||
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.modules.agent.dao.AgentDao;
|
import xiaozhi.modules.agent.dao.AgentDao;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||||
import xiaozhi.modules.agent.dto.AgentDTO;
|
import xiaozhi.modules.agent.dto.AgentDTO;
|
||||||
|
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||||
import xiaozhi.modules.model.service.ModelConfigService;
|
import xiaozhi.modules.model.service.ModelConfigService;
|
||||||
|
import xiaozhi.modules.model.service.ModelProviderService;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
import xiaozhi.modules.sys.enums.SuperAdminEnum;
|
||||||
import xiaozhi.modules.timbre.service.TimbreService;
|
import xiaozhi.modules.timbre.service.TimbreService;
|
||||||
@@ -36,6 +55,10 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final DeviceService deviceService;
|
private final DeviceService deviceService;
|
||||||
|
private final AgentPluginMappingService agentPluginMappingService;
|
||||||
|
private final AgentChatHistoryService agentChatHistoryService;
|
||||||
|
private final AgentTemplateService agentTemplateService;
|
||||||
|
private final ModelProviderService modelProviderService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
public PageData<AgentEntity> adminAgentList(Map<String, Object> params) {
|
||||||
@@ -46,15 +69,20 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AgentEntity getAgentById(String id) {
|
public AgentInfoVO getAgentById(String id) {
|
||||||
AgentEntity agent = agentDao.selectById(id);
|
AgentInfoVO agent = agentDao.selectAgentInfoById(id);
|
||||||
if (agent != null && agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
|
||||||
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
if (agent == null) {
|
||||||
} else if (agent != null && agent.getMemModelId() != null
|
throw new RenException("智能体不存在");
|
||||||
&& !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)
|
|
||||||
&& agent.getChatHistoryConf() == null) {
|
|
||||||
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
||||||
|
if (agent.getChatHistoryConf() == null) {
|
||||||
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 无需额外查询插件列表,已通过SQL查询出来
|
||||||
return agent;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,4 +195,198 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
// 检查是否是智能体的所有者
|
// 检查是否是智能体的所有者
|
||||||
return userId.equals(agent.getUserId());
|
return userId.equals(agent.getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据id更新智能体信息
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateAgentById(String agentId, AgentUpdateDTO dto) {
|
||||||
|
// 先查询现有实体
|
||||||
|
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||||
|
if (existingEntity == null) {
|
||||||
|
throw new RuntimeException("智能体不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只更新提供的非空字段
|
||||||
|
if (dto.getAgentName() != null) {
|
||||||
|
existingEntity.setAgentName(dto.getAgentName());
|
||||||
|
}
|
||||||
|
if (dto.getAgentCode() != null) {
|
||||||
|
existingEntity.setAgentCode(dto.getAgentCode());
|
||||||
|
}
|
||||||
|
if (dto.getAsrModelId() != null) {
|
||||||
|
existingEntity.setAsrModelId(dto.getAsrModelId());
|
||||||
|
}
|
||||||
|
if (dto.getVadModelId() != null) {
|
||||||
|
existingEntity.setVadModelId(dto.getVadModelId());
|
||||||
|
}
|
||||||
|
if (dto.getLlmModelId() != null) {
|
||||||
|
existingEntity.setLlmModelId(dto.getLlmModelId());
|
||||||
|
}
|
||||||
|
if (dto.getVllmModelId() != null) {
|
||||||
|
existingEntity.setVllmModelId(dto.getVllmModelId());
|
||||||
|
}
|
||||||
|
if (dto.getTtsModelId() != null) {
|
||||||
|
existingEntity.setTtsModelId(dto.getTtsModelId());
|
||||||
|
}
|
||||||
|
if (dto.getTtsVoiceId() != null) {
|
||||||
|
existingEntity.setTtsVoiceId(dto.getTtsVoiceId());
|
||||||
|
}
|
||||||
|
if (dto.getMemModelId() != null) {
|
||||||
|
existingEntity.setMemModelId(dto.getMemModelId());
|
||||||
|
}
|
||||||
|
if (dto.getIntentModelId() != null) {
|
||||||
|
existingEntity.setIntentModelId(dto.getIntentModelId());
|
||||||
|
}
|
||||||
|
if (dto.getSystemPrompt() != null) {
|
||||||
|
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
||||||
|
}
|
||||||
|
if (dto.getSummaryMemory() != null) {
|
||||||
|
existingEntity.setSummaryMemory(dto.getSummaryMemory());
|
||||||
|
}
|
||||||
|
if (dto.getChatHistoryConf() != null) {
|
||||||
|
existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
|
||||||
|
}
|
||||||
|
if (dto.getLangCode() != null) {
|
||||||
|
existingEntity.setLangCode(dto.getLangCode());
|
||||||
|
}
|
||||||
|
if (dto.getLanguage() != null) {
|
||||||
|
existingEntity.setLanguage(dto.getLanguage());
|
||||||
|
}
|
||||||
|
if (dto.getSort() != null) {
|
||||||
|
existingEntity.setSort(dto.getSort());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新函数插件信息
|
||||||
|
List<AgentUpdateDTO.FunctionInfo> functions = dto.getFunctions();
|
||||||
|
if (functions != null) {
|
||||||
|
// 1. 收集本次提交的 pluginId
|
||||||
|
List<String> newPluginIds = functions.stream()
|
||||||
|
.map(AgentUpdateDTO.FunctionInfo::getPluginId)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// 2. 查询当前agent现有的所有映射
|
||||||
|
List<AgentPluginMapping> existing = agentPluginMappingService.list(
|
||||||
|
new QueryWrapper<AgentPluginMapping>()
|
||||||
|
.eq("agent_id", agentId));
|
||||||
|
Map<String, AgentPluginMapping> existMap = existing.stream()
|
||||||
|
.collect(Collectors.toMap(AgentPluginMapping::getPluginId, Function.identity()));
|
||||||
|
|
||||||
|
// 3. 构造所有要 保存或更新 的实体
|
||||||
|
List<AgentPluginMapping> allToPersist = functions.stream().map(info -> {
|
||||||
|
AgentPluginMapping m = new AgentPluginMapping();
|
||||||
|
m.setAgentId(agentId);
|
||||||
|
m.setPluginId(info.getPluginId());
|
||||||
|
m.setParamInfo(JsonUtils.toJsonString(info.getParamInfo()));
|
||||||
|
AgentPluginMapping old = existMap.get(info.getPluginId());
|
||||||
|
if (old != null) {
|
||||||
|
// 已存在,设置id表示更新
|
||||||
|
m.setId(old.getId());
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
// 4. 拆分:已有ID的走更新,无ID的走插入
|
||||||
|
List<AgentPluginMapping> toUpdate = allToPersist.stream()
|
||||||
|
.filter(m -> m.getId() != null)
|
||||||
|
.toList();
|
||||||
|
List<AgentPluginMapping> toInsert = allToPersist.stream()
|
||||||
|
.filter(m -> m.getId() == null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (!toUpdate.isEmpty()) {
|
||||||
|
agentPluginMappingService.updateBatchById(toUpdate);
|
||||||
|
}
|
||||||
|
if (!toInsert.isEmpty()) {
|
||||||
|
agentPluginMappingService.saveBatch(toInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 删除本次不在提交列表里的插件映射
|
||||||
|
List<Long> toDelete = existing.stream()
|
||||||
|
.filter(old -> !newPluginIds.contains(old.getPluginId()))
|
||||||
|
.map(AgentPluginMapping::getId)
|
||||||
|
.toList();
|
||||||
|
if (!toDelete.isEmpty()) {
|
||||||
|
agentPluginMappingService.removeBatchByIds(toDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置更新者信息
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
existingEntity.setUpdater(user.getId());
|
||||||
|
existingEntity.setUpdatedAt(new Date());
|
||||||
|
|
||||||
|
// 更新记忆策略
|
||||||
|
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
|
// 删除所有记录
|
||||||
|
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
|
||||||
|
existingEntity.setSummaryMemory("");
|
||||||
|
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
|
||||||
|
// 删除音频数据
|
||||||
|
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
|
||||||
|
}
|
||||||
|
this.updateById(existingEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public String createAgent(AgentCreateDTO dto) {
|
||||||
|
// 转换为实体
|
||||||
|
AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class);
|
||||||
|
|
||||||
|
// 获取默认模板
|
||||||
|
AgentTemplateEntity template = agentTemplateService.getDefaultTemplate();
|
||||||
|
if (template != null) {
|
||||||
|
// 设置模板中的默认值
|
||||||
|
entity.setAsrModelId(template.getAsrModelId());
|
||||||
|
entity.setVadModelId(template.getVadModelId());
|
||||||
|
entity.setLlmModelId(template.getLlmModelId());
|
||||||
|
entity.setVllmModelId(template.getVllmModelId());
|
||||||
|
entity.setTtsModelId(template.getTtsModelId());
|
||||||
|
entity.setTtsVoiceId(template.getTtsVoiceId());
|
||||||
|
entity.setMemModelId(template.getMemModelId());
|
||||||
|
entity.setIntentModelId(template.getIntentModelId());
|
||||||
|
entity.setSystemPrompt(template.getSystemPrompt());
|
||||||
|
entity.setSummaryMemory(template.getSummaryMemory());
|
||||||
|
entity.setChatHistoryConf(template.getChatHistoryConf());
|
||||||
|
entity.setLangCode(template.getLangCode());
|
||||||
|
entity.setLanguage(template.getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置用户ID和创建者信息
|
||||||
|
UserDetail user = SecurityUser.getUser();
|
||||||
|
entity.setUserId(user.getId());
|
||||||
|
entity.setCreator(user.getId());
|
||||||
|
entity.setCreatedAt(new Date());
|
||||||
|
|
||||||
|
// 保存智能体
|
||||||
|
insert(entity);
|
||||||
|
|
||||||
|
// 设置默认插件
|
||||||
|
List<AgentPluginMapping> toInsert = new ArrayList<>();
|
||||||
|
// 播放音乐、查天气、查新闻
|
||||||
|
String[] pluginIds = new String[] { "SYSTEM_PLUGIN_MUSIC", "SYSTEM_PLUGIN_WEATHER",
|
||||||
|
"SYSTEM_PLUGIN_NEWS_NEWSNOW" };
|
||||||
|
for (String pluginId : pluginIds) {
|
||||||
|
ModelProviderDTO provider = modelProviderService.getById(pluginId);
|
||||||
|
if (provider == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AgentPluginMapping mapping = new AgentPluginMapping();
|
||||||
|
mapping.setPluginId(pluginId);
|
||||||
|
|
||||||
|
Map<String, Object> paramInfo = new HashMap<>();
|
||||||
|
List<Map<String, Object>> fields = JsonUtils.parseObject(provider.getFields(), List.class);
|
||||||
|
if (fields != null) {
|
||||||
|
for (Map<String, Object> field : fields) {
|
||||||
|
paramInfo.put((String) field.get("key"), field.get("default"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.setParamInfo(JsonUtils.toJsonString(paramInfo));
|
||||||
|
mapping.setAgentId(entity.getId());
|
||||||
|
toInsert.add(mapping);
|
||||||
|
}
|
||||||
|
// 保存默认插件
|
||||||
|
agentPluginMappingService.saveBatch(toInsert);
|
||||||
|
return entity.getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package xiaozhi.modules.agent.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent信息返回体VO
|
||||||
|
* 这里直接extend了Agent实体类AgentEntity,后续需要规范返回字段可以copy字段出来
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class AgentInfoVO extends AgentEntity
|
||||||
|
{
|
||||||
|
@Schema(description = "插件列表Id")
|
||||||
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
|
private List<AgentPluginMapping> functions;
|
||||||
|
}
|
||||||
+1
-1
@@ -27,7 +27,7 @@ public class ConfigController {
|
|||||||
private final ConfigService configService;
|
private final ConfigService configService;
|
||||||
|
|
||||||
@PostMapping("server-base")
|
@PostMapping("server-base")
|
||||||
@Operation(summary = "获取配置")
|
@Operation(summary = "服务端获取配置接口")
|
||||||
public Result<Object> getConfig() {
|
public Result<Object> getConfig() {
|
||||||
Object config = configService.getConfig(true);
|
Object config = configService.getConfig(true);
|
||||||
return new Result<Object>().ok(config);
|
return new Result<Object>().ok(config);
|
||||||
|
|||||||
+18
-4
@@ -1,9 +1,6 @@
|
|||||||
package xiaozhi.modules.config.service.impl;
|
package xiaozhi.modules.config.service.impl;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -16,7 +13,9 @@ import xiaozhi.common.redis.RedisKeys;
|
|||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.common.utils.JsonUtils;
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
import xiaozhi.modules.agent.entity.AgentEntity;
|
import xiaozhi.modules.agent.entity.AgentEntity;
|
||||||
|
import xiaozhi.modules.agent.entity.AgentPluginMapping;
|
||||||
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
import xiaozhi.modules.agent.entity.AgentTemplateEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||||
import xiaozhi.modules.agent.service.AgentService;
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
import xiaozhi.modules.config.service.ConfigService;
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
@@ -39,6 +38,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
private final AgentTemplateService agentTemplateService;
|
private final AgentTemplateService agentTemplateService;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final TimbreService timbreService;
|
private final TimbreService timbreService;
|
||||||
|
private final AgentPluginMappingService agentPluginMappingService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getConfig(Boolean isCache) {
|
public Object getConfig(Boolean isCache) {
|
||||||
@@ -132,6 +132,19 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
agent.setAsrModelId(null);
|
agent.setAsrModelId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加函数调用参数信息
|
||||||
|
if (!Objects.equals(agent.getIntentModelId(), "Intent_nointent")) {
|
||||||
|
String agentId = agent.getId();
|
||||||
|
List<AgentPluginMapping> pluginMappings = agentPluginMappingService.agentPluginParamsByAgentId(agentId);
|
||||||
|
if (pluginMappings != null && !pluginMappings.isEmpty()) {
|
||||||
|
Map<String, Object> pluginParams = new HashMap<>();
|
||||||
|
for (AgentPluginMapping pluginMapping : pluginMappings) {
|
||||||
|
pluginParams.put(pluginMapping.getProviderCode(), pluginMapping.getParamInfo());
|
||||||
|
}
|
||||||
|
result.put("plugins", pluginParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 构建模块配置
|
// 构建模块配置
|
||||||
buildModuleConfig(
|
buildModuleConfig(
|
||||||
agent.getAgentName(),
|
agent.getAgentName(),
|
||||||
@@ -284,6 +297,7 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
map.put("functions", functions);
|
map.put("functions", functions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
System.out.println("map: " + map);
|
||||||
}
|
}
|
||||||
if ("Memory".equals(modelTypes[i])) {
|
if ("Memory".equals(modelTypes[i])) {
|
||||||
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
|
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
|
||||||
|
|||||||
+7
@@ -18,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.common.utils.ResultUtils;
|
||||||
import xiaozhi.common.validator.group.UpdateGroup;
|
import xiaozhi.common.validator.group.UpdateGroup;
|
||||||
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
import xiaozhi.modules.model.dto.ModelProviderDTO;
|
||||||
import xiaozhi.modules.model.service.ModelProviderService;
|
import xiaozhi.modules.model.service.ModelProviderService;
|
||||||
@@ -65,4 +66,10 @@ public class ModelProviderController {
|
|||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/plugin/names")
|
||||||
|
@Tag(name = "获取插件名称列表")
|
||||||
|
public Result<List<ModelProviderDTO>> getPluginNameList() {
|
||||||
|
return ResultUtils.success(modelProviderService.getPluginList());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,5 +1,6 @@
|
|||||||
package xiaozhi.modules.model.service;
|
package xiaozhi.modules.model.service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
@@ -7,7 +8,11 @@ import xiaozhi.modules.model.dto.ModelProviderDTO;
|
|||||||
|
|
||||||
public interface ModelProviderService {
|
public interface ModelProviderService {
|
||||||
|
|
||||||
// List<String> getModelNames(String modelType, String modelName);
|
List<ModelProviderDTO> getPluginList();
|
||||||
|
|
||||||
|
ModelProviderDTO getById(String id);
|
||||||
|
|
||||||
|
List<ModelProviderDTO> getPluginListByIds(Collection<String> ids);
|
||||||
|
|
||||||
List<ModelProviderDTO> getListByModelType(String modelType);
|
List<ModelProviderDTO> getListByModelType(String modelType);
|
||||||
|
|
||||||
|
|||||||
+25
@@ -1,5 +1,6 @@
|
|||||||
package xiaozhi.modules.model.service.impl;
|
package xiaozhi.modules.model.service.impl;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -8,6 +9,7 @@ import java.util.Map;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
@@ -32,6 +34,29 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
|||||||
|
|
||||||
private final ModelProviderDao modelProviderDao;
|
private final ModelProviderDao modelProviderDao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ModelProviderDTO> getPluginList() {
|
||||||
|
LambdaQueryWrapper<ModelProviderEntity> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(ModelProviderEntity::getModelType, "Plugin");
|
||||||
|
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
|
||||||
|
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModelProviderDTO getById(String id) {
|
||||||
|
ModelProviderEntity entity = modelProviderDao.selectById(id);
|
||||||
|
return ConvertUtils.sourceToTarget(entity, ModelProviderDTO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ModelProviderDTO> getPluginListByIds(Collection<String> ids) {
|
||||||
|
LambdaQueryWrapper<ModelProviderEntity> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.in(ModelProviderEntity::getId, ids);
|
||||||
|
queryWrapper.eq(ModelProviderEntity::getModelType, "Plugin");
|
||||||
|
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
|
||||||
|
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ModelProviderDTO> getListByModelType(String modelType) {
|
public List<ModelProviderDTO> getListByModelType(String modelType) {
|
||||||
|
|
||||||
|
|||||||
@@ -2,36 +2,29 @@ package xiaozhi.modules.sys.enums;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
import xiaozhi.common.exception.RenException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务端动作枚举
|
* 服务端动作枚举
|
||||||
*/
|
*/
|
||||||
public enum ServerActionEnum
|
public enum ServerActionEnum {
|
||||||
{
|
|
||||||
RESTART("restart"),
|
RESTART("restart"),
|
||||||
UPDATE_CONFIG("update_config");
|
UPDATE_CONFIG("update_config");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
ServerActionEnum(String value)
|
ServerActionEnum(String value) {
|
||||||
{
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonValue
|
@JsonValue
|
||||||
public String getValue()
|
public String getValue() {
|
||||||
{
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public static ServerActionEnum fromValue(String value)
|
public static ServerActionEnum fromValue(String value) {
|
||||||
{
|
for (ServerActionEnum action : ServerActionEnum.values()) {
|
||||||
for (ServerActionEnum action : ServerActionEnum.values())
|
if (action.value.equalsIgnoreCase(value)) {
|
||||||
{
|
|
||||||
if (action.value.equalsIgnoreCase(value))
|
|
||||||
{
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -1,16 +1,16 @@
|
|||||||
package xiaozhi.modules.sys.enums;
|
package xiaozhi.modules.sys.enums;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务端调用响应枚举
|
* 服务端调用响应枚举
|
||||||
*/
|
*/
|
||||||
public enum ServerActionResponseEnum
|
public enum ServerActionResponseEnum {
|
||||||
{
|
|
||||||
SUCCESS("success"), FAIL("fail");
|
SUCCESS("success"), FAIL("fail");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
ServerActionResponseEnum(String value) {
|
ServerActionResponseEnum(String value) {
|
||||||
@@ -18,8 +18,7 @@ public enum ServerActionResponseEnum
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonValue
|
@JsonValue
|
||||||
public String getValue()
|
public String getValue() {
|
||||||
{
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+55
-50
@@ -1,13 +1,5 @@
|
|||||||
package xiaozhi.modules.sys.utils;
|
package xiaozhi.modules.sys.utils;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.util.StopWatch;
|
|
||||||
import org.springframework.web.socket.*;
|
|
||||||
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
|
||||||
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
|
||||||
import xiaozhi.common.utils.DateUtils;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -15,30 +7,51 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
|
import org.springframework.web.socket.BinaryMessage;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||||
|
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.utils.DateUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebSocketClientResource:支持 try-with-resources 模式
|
* WebSocketClientResource:支持 try-with-resources 模式
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WebSocketClientManager implements Closeable
|
public class WebSocketClientManager implements Closeable {
|
||||||
{
|
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
// 全局回调线程池
|
// 全局回调线程池
|
||||||
private static final ExecutorService CALLBACK_EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
|
private static final ExecutorService CALLBACK_EXECUTOR = Executors
|
||||||
private final AtomicInteger cnt = new AtomicInteger();
|
.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
|
||||||
|
private final AtomicInteger cnt = new AtomicInteger();
|
||||||
|
|
||||||
public Thread newThread(Runnable r)
|
public Thread newThread(Runnable r) {
|
||||||
{
|
Thread t = new Thread(r, "ws-callback-" + cnt.getAndIncrement());
|
||||||
Thread t = new Thread(r, "ws-callback-" + cnt.getAndIncrement());
|
t.setDaemon(true);
|
||||||
t.setDaemon(true);
|
return t;
|
||||||
return t;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
private volatile WebSocketSession session;
|
private volatile WebSocketSession session;
|
||||||
private final BlockingQueue<String> textMessageQueue;
|
private final BlockingQueue<String> textMessageQueue;
|
||||||
@@ -51,18 +64,10 @@ public class WebSocketClientManager implements Closeable
|
|||||||
private volatile Consumer<byte[]> onBinary;
|
private volatile Consumer<byte[]> onBinary;
|
||||||
private volatile Consumer<Throwable> onError;
|
private volatile Consumer<Throwable> onError;
|
||||||
|
|
||||||
private final String uri;
|
|
||||||
private final WebSocketHttpHeaders headers;
|
|
||||||
private final long connectTimeout;
|
|
||||||
private final TimeUnit connectUnit;
|
|
||||||
private final int queueCapacity;
|
private final int queueCapacity;
|
||||||
|
|
||||||
// 私有构造,仅由 Builder 调用
|
// 私有构造,仅由 Builder 调用
|
||||||
private WebSocketClientManager(Builder b) {
|
private WebSocketClientManager(Builder b) {
|
||||||
this.uri = b.uri;
|
|
||||||
this.headers = b.headers != null ? b.headers : new WebSocketHttpHeaders();
|
|
||||||
this.connectTimeout = b.connectTimeout;
|
|
||||||
this.connectUnit = b.connectUnit;
|
|
||||||
this.maxSessionDuration = b.maxSessionDuration;
|
this.maxSessionDuration = b.maxSessionDuration;
|
||||||
this.maxSessionDurationUnit = b.maxSessionDurationUnit;
|
this.maxSessionDurationUnit = b.maxSessionDurationUnit;
|
||||||
this.queueCapacity = b.queueCapacity;
|
this.queueCapacity = b.queueCapacity;
|
||||||
@@ -71,13 +76,14 @@ public class WebSocketClientManager implements Closeable
|
|||||||
this.errorFuture = new CompletableFuture<>();
|
this.errorFuture = new CompletableFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebSocketClientManager build(Builder b) throws InterruptedException, ExecutionException, TimeoutException, IOException {
|
public static WebSocketClientManager build(Builder b)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException, IOException {
|
||||||
WebSocketClientManager ws = new WebSocketClientManager(b);
|
WebSocketClientManager ws = new WebSocketClientManager(b);
|
||||||
StandardWebSocketClient client = new StandardWebSocketClient();
|
StandardWebSocketClient client = new StandardWebSocketClient();
|
||||||
CompletableFuture<WebSocketSession> future = client.execute(ws.new InternalHandler(b.uri), b.headers, URI.create(b.uri));
|
CompletableFuture<WebSocketSession> future = client.execute(ws.new InternalHandler(b.uri), b.headers,
|
||||||
|
URI.create(b.uri));
|
||||||
WebSocketSession sess = future.get(b.connectTimeout, b.connectUnit);
|
WebSocketSession sess = future.get(b.connectTimeout, b.connectUnit);
|
||||||
if (sess == null || !sess.isOpen())
|
if (sess == null || !sess.isOpen()) {
|
||||||
{
|
|
||||||
throw new IOException("握手失败或会话未打开");
|
throw new IOException("握手失败或会话未打开");
|
||||||
}
|
}
|
||||||
ws.session = sess;
|
ws.session = sess;
|
||||||
@@ -100,13 +106,10 @@ public class WebSocketClientManager implements Closeable
|
|||||||
session.sendMessage(new TextMessage(json));
|
session.sendMessage(new TextMessage(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private <T> List<T> listenerCustom(
|
private <T> List<T> listenerCustom(
|
||||||
BlockingQueue<T> queue,
|
BlockingQueue<T> queue,
|
||||||
Predicate<T> predicate)
|
Predicate<T> predicate)
|
||||||
throws InterruptedException, TimeoutException, ExecutionException
|
throws InterruptedException, TimeoutException, ExecutionException {
|
||||||
{
|
|
||||||
List<T> collected = new ArrayList<>();
|
List<T> collected = new ArrayList<>();
|
||||||
long deadline = System.currentTimeMillis() + maxSessionDurationUnit.toMillis(maxSessionDuration);
|
long deadline = System.currentTimeMillis() + maxSessionDurationUnit.toMillis(maxSessionDuration);
|
||||||
|
|
||||||
@@ -136,17 +139,16 @@ public class WebSocketClientManager implements Closeable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步接收多条消息,直到 predicate 为 true 或超时抛异常;
|
* 同步接收多条消息,直到 predicate 为 true 或超时抛异常;
|
||||||
|
*
|
||||||
* @return 返回监听期间的所有消息列表
|
* @return 返回监听期间的所有消息列表
|
||||||
*/
|
*/
|
||||||
public List<String> listener(Predicate<String> predicate)
|
public List<String> listener(Predicate<String> predicate)
|
||||||
throws InterruptedException, TimeoutException, ExecutionException
|
throws InterruptedException, TimeoutException, ExecutionException {
|
||||||
{
|
|
||||||
return listenerCustom(textMessageQueue, predicate);
|
return listenerCustom(textMessageQueue, predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<byte[]> listenerBinary(Predicate<byte[]> predicate)
|
public List<byte[]> listenerBinary(Predicate<byte[]> predicate)
|
||||||
throws InterruptedException, TimeoutException, ExecutionException
|
throws InterruptedException, TimeoutException, ExecutionException {
|
||||||
{
|
|
||||||
return listenerCustom(binaryMessageQueue, predicate);
|
return listenerCustom(binaryMessageQueue, predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,8 +185,8 @@ public class WebSocketClientManager implements Closeable
|
|||||||
if (session != null && session.isOpen()) {
|
if (session != null && session.isOpen()) {
|
||||||
session.close(CloseStatus.NORMAL);
|
session.close(CloseStatus.NORMAL);
|
||||||
}
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
catch (IOException ignored) {}
|
|
||||||
textMessageQueue.clear();
|
textMessageQueue.clear();
|
||||||
binaryMessageQueue.clear();
|
binaryMessageQueue.clear();
|
||||||
errorFuture.completeExceptionally(new IOException("WebSocket 已关闭"));
|
errorFuture.completeExceptionally(new IOException("WebSocket 已关闭"));
|
||||||
@@ -207,7 +209,8 @@ public class WebSocketClientManager implements Closeable
|
|||||||
// 保存会话
|
// 保存会话
|
||||||
WebSocketClientManager.this.session = session;
|
WebSocketClientManager.this.session = session;
|
||||||
this.stopWatch.start();
|
this.stopWatch.start();
|
||||||
log.info("ws连接成功, 目标URI: {}, 连接时间: {}", targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN));
|
log.info("ws连接成功, 目标URI: {}, 连接时间: {}", targetUri,
|
||||||
|
DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,18 +267,19 @@ public class WebSocketClientManager implements Closeable
|
|||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
}
|
}
|
||||||
log.info("ws连接关闭, 目标URI: {}, 关闭时间: {}, 连接总时长: {}s",
|
log.info("ws连接关闭, 目标URI: {}, 关闭时间: {}, 连接总时长: {}s",
|
||||||
targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN), DateUtils.millsToSecond(stopWatch.getTotalTimeMillis()));
|
targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN),
|
||||||
|
DateUtils.millsToSecond(stopWatch.getTotalTimeMillis()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private String uri; // 目标 WS URI
|
private String uri; // 目标 WS URI
|
||||||
private long connectTimeout = 3; // 请求连接等待时间
|
private long connectTimeout = 3; // 请求连接等待时间
|
||||||
private TimeUnit connectUnit = TimeUnit.SECONDS; // 请求连接等待时间单位
|
private TimeUnit connectUnit = TimeUnit.SECONDS; // 请求连接等待时间单位
|
||||||
private long maxSessionDuration = 5; // 最大连线时间,默认5秒
|
private long maxSessionDuration = 5; // 最大连线时间,默认5秒
|
||||||
private TimeUnit maxSessionDurationUnit = TimeUnit.SECONDS; // 最大连线时间单位
|
private TimeUnit maxSessionDurationUnit = TimeUnit.SECONDS; // 最大连线时间单位
|
||||||
private int queueCapacity = 100; // 消息队列容量
|
private int queueCapacity = 100; // 消息队列容量
|
||||||
private WebSocketHttpHeaders headers; // 请求头
|
private WebSocketHttpHeaders headers; // 请求头
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 目标 WS URI
|
* 目标 WS URI
|
||||||
@@ -307,7 +311,8 @@ public class WebSocketClientManager implements Closeable
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocketClientManager build() throws InterruptedException, ExecutionException, TimeoutException, IOException {
|
public WebSocketClientManager build()
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException, IOException {
|
||||||
return WebSocketClientManager.build(this);
|
return WebSocketClientManager.build(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
-- 更新intent_llmM供应器
|
|
||||||
update `ai_model_provider` set fields = '[{"key":"llm","label":"LLM模型","type":"string"},{"key":"functions","label":"函数列表","type":"dict","dict_name":"functions"}]' where id = 'SYSTEM_Intent_intent_llm';
|
|
||||||
-- 更新ChatGLMLLM的意图识别配置
|
|
||||||
update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\", \"functions\": \"get_weather;get_news_from_newsnow;play_music\"}' where id = 'Intent_intent_llm';
|
|
||||||
-- 更新函数调用意图识别配置
|
|
||||||
UPDATE `ai_model_config` SET config_json = REPLACE(config_json, ';get_news;', ';get_news_from_newsnow;') WHERE id = 'Intent_function_call';
|
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
-- ===============================
|
||||||
|
-- 一、在ai_model_provider中插入plugin 记录
|
||||||
|
-- ===============================
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
|
||||||
|
-- intent_llm和function_call不设置函数列表
|
||||||
|
update `ai_model_provider` set fields = '[{"key":"llm","label":"LLM模型","type":"string"}]' where id = 'SYSTEM_Intent_intent_llm';
|
||||||
|
update `ai_model_provider` set fields = '[]' where id = 'SYSTEM_Intent_function_call';
|
||||||
|
update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\"}' where id = 'Intent_intent_llm';
|
||||||
|
UPDATE `ai_model_config` SET config_json = '{\"type\": \"function_call\"}' WHERE id = 'Intent_function_call';
|
||||||
|
|
||||||
|
|
||||||
|
delete from ai_model_provider where model_type = 'Plugin';
|
||||||
|
-- 1. 天气查询
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_WEATHER',
|
||||||
|
'Plugin',
|
||||||
|
'get_weather',
|
||||||
|
'天气查询',
|
||||||
|
JSON_ARRAY(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'api_key',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '天气插件 API 密钥',
|
||||||
|
'default', (SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_weather.api_key')
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'default_location',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '默认查询城市',
|
||||||
|
'default',
|
||||||
|
(SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_weather.default_location')
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'api_host',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '开发者 API Host',
|
||||||
|
'default',
|
||||||
|
(SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_weather.api_host')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
10, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
-- 6. 本地播放音乐
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_MUSIC',
|
||||||
|
'Plugin',
|
||||||
|
'play_music',
|
||||||
|
'服务器音乐播放',
|
||||||
|
JSON_ARRAY(),
|
||||||
|
20, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
-- 2. 新闻订阅
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_NEWS_CHINANEWS',
|
||||||
|
'Plugin',
|
||||||
|
'get_news_from_chinanews',
|
||||||
|
'中新网新闻',
|
||||||
|
JSON_ARRAY(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'default_rss_url',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '默认 RSS 源',
|
||||||
|
'default',
|
||||||
|
(SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_news.default_rss_url')
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'society_rss_url',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '社会新闻 RSS 地址',
|
||||||
|
'default',
|
||||||
|
'https://www.chinanews.com.cn/rss/society.xml'
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'world_rss_url',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '国际新闻 RSS 地址',
|
||||||
|
'default',
|
||||||
|
'https://www.chinanews.com.cn/rss/world.xml'
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'finance_rss_url',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '财经新闻 RSS 地址',
|
||||||
|
'default',
|
||||||
|
'https://www.chinanews.com.cn/rss/finance.xml'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
30, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
-- 3. 新闻订阅
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_NEWS_NEWSNOW',
|
||||||
|
'Plugin',
|
||||||
|
'get_news_from_newsnow',
|
||||||
|
'newsnow新闻聚合',
|
||||||
|
JSON_ARRAY(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'url',
|
||||||
|
'type', 'string',
|
||||||
|
'label', '接口地址',
|
||||||
|
'default',
|
||||||
|
'https://newsnow.busiyi.world/api/s?id='
|
||||||
|
)
|
||||||
|
),
|
||||||
|
40, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
|
||||||
|
-- 4. HomeAssistant 状态查询
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_HA_GET_STATE',
|
||||||
|
'Plugin',
|
||||||
|
'hass_get_state',
|
||||||
|
'HomeAssistant设备状态查询',
|
||||||
|
JSON_ARRAY(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'base_url',
|
||||||
|
'type', 'string',
|
||||||
|
'label', 'HA 服务器地址',
|
||||||
|
'default',
|
||||||
|
(SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.base_url')
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'api_key',
|
||||||
|
'type', 'string',
|
||||||
|
'label', 'HA API 访问令牌',
|
||||||
|
'default',
|
||||||
|
(SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.api_key')
|
||||||
|
),
|
||||||
|
JSON_OBJECT(
|
||||||
|
'key', 'devices',
|
||||||
|
'type', 'array',
|
||||||
|
'label', '设备列表(名称,实体ID;…)',
|
||||||
|
'default',
|
||||||
|
(SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.devices')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
50, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
-- 5. HomeAssistant 状态写入
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_HA_SET_STATE',
|
||||||
|
'Plugin',
|
||||||
|
'hass_set_state',
|
||||||
|
'HomeAssistant设备状态修改',
|
||||||
|
JSON_ARRAY(),
|
||||||
|
60, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
-- 5. HomeAssistant 音乐播放
|
||||||
|
INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields,
|
||||||
|
sort, creator, create_date, updater, update_date)
|
||||||
|
VALUES ('SYSTEM_PLUGIN_HA_PLAY_MUSIC',
|
||||||
|
'Plugin',
|
||||||
|
'hass_play_music',
|
||||||
|
'HomeAssistant音乐播放',
|
||||||
|
JSON_ARRAY(),
|
||||||
|
70, 0, NOW(), 0, NOW());
|
||||||
|
|
||||||
|
|
||||||
|
-- ===============================
|
||||||
|
-- 二、删除sys_params中旧的plugins.*参数
|
||||||
|
-- ===============================
|
||||||
|
DELETE
|
||||||
|
FROM sys_params
|
||||||
|
WHERE param_code LIKE 'plugins.%';
|
||||||
|
|
||||||
|
|
||||||
|
-- ===============================
|
||||||
|
-- 三、添加智能体插件id字段
|
||||||
|
-- ===============================
|
||||||
|
CREATE TABLE IF NOT EXISTS ai_agent_plugin_mapping
|
||||||
|
(
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
||||||
|
agent_id VARCHAR(32) NOT NULL COMMENT '智能体ID',
|
||||||
|
plugin_id VARCHAR(32) NOT NULL COMMENT '插件ID',
|
||||||
|
param_info JSON NOT NULL COMMENT '参数信息',
|
||||||
|
UNIQUE KEY uk_agent_provider (agent_id, plugin_id)
|
||||||
|
) COMMENT 'Agent与插件的唯一映射表';
|
||||||
|
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
@@ -107,13 +107,6 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202505081146.sql
|
path: classpath:db/changelog/202505081146.sql
|
||||||
- changeSet:
|
|
||||||
id: 202505091409
|
|
||||||
author: hrz
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202505091409.sql
|
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202505091555
|
id: 202505091555
|
||||||
author: whosmyqueen
|
author: whosmyqueen
|
||||||
@@ -170,6 +163,13 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202505271414.sql
|
path: classpath:db/changelog/202505271414.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202505292203
|
||||||
|
author: CAIXYPROMISE
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202505292203.sql
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202506010920
|
id: 202506010920
|
||||||
author: hrz
|
author: hrz
|
||||||
@@ -204,4 +204,4 @@ databaseChangeLog:
|
|||||||
changes:
|
changes:
|
||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202506080955.sql
|
path: classpath:db/changelog/202506080955.sql
|
||||||
|
|||||||
@@ -5,4 +5,72 @@
|
|||||||
<select id="getDeviceCountByAgentId" resultType="java.lang.Integer">
|
<select id="getDeviceCountByAgentId" resultType="java.lang.Integer">
|
||||||
SELECT COUNT(*) FROM ai_device WHERE agent_id = #{agentId}
|
SELECT COUNT(*) FROM ai_device WHERE agent_id = #{agentId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<resultMap id="AgentInfoMap" type="xiaozhi.modules.agent.vo.AgentInfoVO">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="userId" property="userId"/>
|
||||||
|
<result column="agentCode" property="agentCode"/>
|
||||||
|
<result column="agentName" property="agentName"/>
|
||||||
|
<result column="asrModelId" property="asrModelId"/>
|
||||||
|
<result column="vadModelId" property="vadModelId"/>
|
||||||
|
<result column="llmModelId" property="llmModelId"/>
|
||||||
|
<result column="ttsModelId" property="ttsModelId"/>
|
||||||
|
<result column="ttsVoiceId" property="ttsVoiceId"/>
|
||||||
|
<result column="memModelId" property="memModelId"/>
|
||||||
|
<result column="intentModelId" property="intentModelId"/>
|
||||||
|
|
||||||
|
<result column="functions" property="functions"
|
||||||
|
typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
|
||||||
|
|
||||||
|
<result column="chatHistoryConf" property="chatHistoryConf"/>
|
||||||
|
<result column="systemPrompt" property="systemPrompt"/>
|
||||||
|
<result column="summaryMemory" property="summaryMemory"/>
|
||||||
|
<result column="langCode" property="langCode"/>
|
||||||
|
<result column="language" property="language"/>
|
||||||
|
<result column="sort" property="sort"/>
|
||||||
|
<result column="creator" property="creator"/>
|
||||||
|
<result column="createdAt" property="createdAt"/>
|
||||||
|
<result column="updater" property="updater"/>
|
||||||
|
<result column="updatedAt" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectAgentInfoById" resultMap="AgentInfoMap">
|
||||||
|
SELECT a.id,
|
||||||
|
a.user_id AS userId,
|
||||||
|
a.agent_code AS agentCode,
|
||||||
|
a.agent_name AS agentName,
|
||||||
|
a.asr_model_id AS asrModelId,
|
||||||
|
a.vad_model_id AS vadModelId,
|
||||||
|
a.llm_model_id AS llmModelId,
|
||||||
|
a.vllm_model_id AS vllmModelId,
|
||||||
|
a.tts_model_id AS ttsModelId,
|
||||||
|
a.tts_voice_id AS ttsVoiceId,
|
||||||
|
a.mem_model_id AS memModelId,
|
||||||
|
a.intent_model_id AS intentModelId,
|
||||||
|
COALESCE(
|
||||||
|
(SELECT JSON_ARRAYAGG(
|
||||||
|
JSON_OBJECT(
|
||||||
|
'id', m.id,
|
||||||
|
'agentId', m.agent_id,
|
||||||
|
'pluginId', m.plugin_id,
|
||||||
|
'paramInfo', m.param_info
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM ai_agent_plugin_mapping m
|
||||||
|
WHERE m.agent_id = a.id),
|
||||||
|
JSON_ARRAY()
|
||||||
|
) AS functions,
|
||||||
|
a.chat_history_conf AS chatHistoryConf,
|
||||||
|
a.system_prompt AS systemPrompt,
|
||||||
|
a.summary_memory AS summaryMemory,
|
||||||
|
a.lang_code AS langCode,
|
||||||
|
a.language AS language,
|
||||||
|
a.sort,
|
||||||
|
a.creator,
|
||||||
|
a.created_at AS createdAt,
|
||||||
|
a.updater,
|
||||||
|
a.updated_at AS updatedAt
|
||||||
|
FROM ai_agent a
|
||||||
|
WHERE a.id = #{agentId}
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?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.dao.AgentPluginMappingMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="xiaozhi.modules.agent.entity.AgentPluginMapping">
|
||||||
|
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||||
|
<result property="agentId" column="agent_id" jdbcType="VARCHAR"/>
|
||||||
|
<result property="pluginId" column="plugin_id" jdbcType="VARCHAR"/>
|
||||||
|
<result property="paramInfo" column="param_info" jdbcType="VARCHAR"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 用于映射根据agentId查询完整插件信息 -->
|
||||||
|
<resultMap id="AgentPluginWithCodeMap" type="xiaozhi.modules.agent.entity.AgentPluginMapping">
|
||||||
|
<id column="id" property="id" jdbcType="BIGINT"/>
|
||||||
|
<result column="agentId" property="agentId" jdbcType="VARCHAR"/>
|
||||||
|
<result column="pluginId" property="pluginId" jdbcType="VARCHAR"/>
|
||||||
|
<result column="paramInfo" property="paramInfo" jdbcType="VARCHAR"/>
|
||||||
|
<result column="providerCode" property="providerCode" jdbcType="VARCHAR"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
id,agent_id,plugin_id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
|
||||||
|
<select id="selectPluginsByAgentId" resultMap="AgentPluginWithCodeMap">
|
||||||
|
SELECT m.id AS id,
|
||||||
|
m.agent_id AS agentId,
|
||||||
|
m.plugin_id AS pluginId,
|
||||||
|
m.param_info AS paramInfo,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
p.provider_code
|
||||||
|
FROM
|
||||||
|
ai_model_provider p
|
||||||
|
WHERE
|
||||||
|
p.id = m.plugin_id
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
) AS providerCode
|
||||||
|
FROM ai_agent_plugin_mapping m
|
||||||
|
WHERE m.agent_id = #{agentId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -305,4 +305,20 @@ export default {
|
|||||||
})
|
})
|
||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
|
// 获取插件列表
|
||||||
|
getPluginFunctionList(params, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/models/provider/plugin/names`)
|
||||||
|
.method('GET')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.networkFail((err) => {
|
||||||
|
this.$message.error(err.msg || '获取插件列表失败')
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.getPluginFunctionList(params, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :title="title" :visible.sync="dialogVisible" @close="handleClose" @open="handleOpen">
|
<el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" @close="handleClose" @open="handleOpen">
|
||||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||||
<el-form-item label="固件名称" prop="firmwareName">
|
<el-form-item label="固件名称" prop="firmwareName">
|
||||||
<el-input v-model="form.firmwareName" placeholder="请输入固件名称(板子+版本号)"></el-input>
|
<el-input v-model="form.firmwareName" placeholder="请输入固件名称(板子+版本号)"></el-input>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer :visible.sync="dialogVisible" direction="rtl" size="50%" :wrapperClosable="false" :withHeader="false">
|
<el-drawer :visible.sync="dialogVisible" direction="rtl" size="80%" :wrapperClosable="false" :withHeader="false">
|
||||||
<!-- 自定义标题区域 -->
|
<!-- 自定义标题区域 -->
|
||||||
<div class="custom-header">
|
<div class="custom-header">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
@@ -16,15 +16,18 @@
|
|||||||
<el-button type="text" @click="selectAll" class="select-all-btn">全选</el-button>
|
<el-button type="text" @click="selectAll" class="select-all-btn">全选</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="function-list">
|
<div class="function-list">
|
||||||
<div v-for="func in unselected" :key="func.name" class="function-item">
|
<div v-if="unselected.length">
|
||||||
<el-checkbox :label="func.name" v-model="selectedNames" @change="(val) => handleCheckboxChange(func, val)" @click.native.stop></el-checkbox>
|
<div v-for="func in unselected" :key="func.name" class="function-item">
|
||||||
<div class="func-tag" @click="handleFunctionClick(func)">
|
<el-checkbox :label="func.name" v-model="selectedNames" @change="(val) => handleCheckboxChange(func, val)"
|
||||||
<div class="color-dot" :style="{backgroundColor: getFunctionColor(func.name)}"></div>
|
@click.native.stop></el-checkbox>
|
||||||
<span>{{ func.name }}</span>
|
<div class="func-tag" @click="handleFunctionClick(func)">
|
||||||
|
<div class="color-dot" :style="{ backgroundColor: getFunctionColor(func.name) }"></div>
|
||||||
|
<span>{{ func.name }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-tooltip class="item" effect="dark" :content="func.description || '暂无功能描述'" placement="top">
|
</div>
|
||||||
<img src="@/assets/home/info.png" alt="" class="info-icon">
|
<div v-else style="display: flex; justify-content: center; align-items: center;">
|
||||||
</el-tooltip>
|
<el-empty description="没有更多的插件了" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,26 +39,62 @@
|
|||||||
<el-button type="text" @click="deselectAll" class="select-all-btn">全选</el-button>
|
<el-button type="text" @click="deselectAll" class="select-all-btn">全选</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="function-list">
|
<div class="function-list">
|
||||||
<div v-for="func in selectedList" :key="func.name" class="function-item">
|
<div v-if="selectedList.length > 0">
|
||||||
<el-checkbox :label="func.name" v-model="selectedNames" @change="(val) => handleCheckboxChange(func, val)" @click.native.stop></el-checkbox>
|
<div v-for="func in selectedList" :key="func.name" class="function-item">
|
||||||
<div class="func-tag" @click="handleFunctionClick(func)">
|
<el-checkbox :label="func.name" v-model="selectedNames" @change="(val) => handleCheckboxChange(func, val)"
|
||||||
<div class="color-dot" :style="{backgroundColor: getFunctionColor(func.name)}"></div>
|
@click.native.stop></el-checkbox>
|
||||||
<span>{{ func.name }}</span>
|
<div class="func-tag" @click="handleFunctionClick(func)">
|
||||||
|
<div class="color-dot" :style="{ backgroundColor: getFunctionColor(func.name) }"></div>
|
||||||
|
<span>{{ func.name }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else style="display: flex; justify-content: center; align-items: center;">
|
||||||
|
<el-empty description="请选择插件功能" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧:参数配置 -->
|
<!-- 右侧:参数配置 -->
|
||||||
<div class="params-column">
|
<div class="params-column">
|
||||||
<h4 v-if="currentFunction" class="column-title">参数配置 - {{ currentFunction.name }}</h4>
|
<h4 v-if="currentFunction" class="column-title">参数配置 - {{ currentFunction.name }}</h4>
|
||||||
<div v-if="currentFunction" class="params-container">
|
<div v-if="currentFunction" class="params-container">
|
||||||
<el-form :model="currentFunction" size="mini" class="param-form" v-loading="loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading" element-loading-background="rgba(255, 255, 255, 0.7)">
|
<el-form :model="currentFunction" class="param-form">
|
||||||
<el-form-item v-for="(value, key) in currentFunction.params" :key="key" :label="key" class="param-item">
|
<!-- 遍历 fieldsMeta,而不是 params 的 keys -->
|
||||||
<el-input v-model="currentFunction.params[key]" size="mini" class="param-input" @change="(val) => handleParamChange(currentFunction, key, val)"/>
|
<div v-if="currentFunction.fieldsMeta.length == 0">
|
||||||
</el-form-item>
|
<el-empty :description="currentFunction.name + ' 无需配置参数'" />
|
||||||
</el-form>
|
</div>
|
||||||
</div>
|
<el-form-item v-for="field in currentFunction.fieldsMeta" :key="field.key" :label="field.label"
|
||||||
|
class="param-item" :class="{ 'textarea-field': field.type === 'array' || field.type === 'json' }">
|
||||||
|
<template #label>
|
||||||
|
<span style="font-size: 16px; margin-right: 6px;">{{ field.label }}</span>
|
||||||
|
<el-tooltip effect="dark" :content="fieldRemark(field)" placement="top">
|
||||||
|
<img src="@/assets/home/info.png" alt="" class="info-icon">
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<!-- ARRAY -->
|
||||||
|
<el-input v-if="field.type === 'array'" type="textarea" v-model="currentFunction.params[field.key]"
|
||||||
|
@change="val => handleParamChange(currentFunction, field.key, val)" />
|
||||||
|
|
||||||
|
<!-- JSON -->
|
||||||
|
<el-input v-else-if="field.type === 'json'" type="textarea" :rows="6" placeholder="请输入合法的 JSON"
|
||||||
|
v-model="textCache[field.key]" @blur="flushJson(field)" />
|
||||||
|
|
||||||
|
<!-- number -->
|
||||||
|
<el-input-number v-else-if="field.type === 'number'" :value="currentFunction.params[field.key]"
|
||||||
|
@change="val => handleParamChange(currentFunction, field.key, val)" />
|
||||||
|
|
||||||
|
<!-- boolean -->
|
||||||
|
<el-switch v-else-if="field.type === 'boolean' || field.type === 'bool'"
|
||||||
|
:value="currentFunction.params[field.key]"
|
||||||
|
@change="val => handleParamChange(currentFunction, field.key, val)" />
|
||||||
|
|
||||||
|
<!-- string or fallback -->
|
||||||
|
<el-input v-else v-model="currentFunction.params[field.key]"
|
||||||
|
@change="val => handleParamChange(currentFunction, field.key, val)" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
<div v-else class="empty-tip">请选择已配置的功能进行参数设置</div>
|
<div v-else class="empty-tip">请选择已配置的功能进行参数设置</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,24 +113,19 @@ export default {
|
|||||||
functions: {
|
functions: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
|
},
|
||||||
|
allFunctions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
textCache: {},
|
||||||
dialogVisible: this.value,
|
dialogVisible: this.value,
|
||||||
selectedNames: [],
|
selectedNames: [],
|
||||||
currentFunction: null,
|
currentFunction: null,
|
||||||
modifiedFunctions: {},
|
modifiedFunctions: {},
|
||||||
allFunctions: [
|
|
||||||
{name: '天气', params: {city: '北京'}, description: '查看指定城市的天气情况'},
|
|
||||||
{name: '新闻', params: {type: '科技'}, description: '获取最新科技类新闻资讯'},
|
|
||||||
{name: '工具', params: {category: '常用'}, description: '提供常用工具集合'},
|
|
||||||
{name: '退出', params: {}, description: '退出当前系统'},
|
|
||||||
{name: '音乐', params: {genre: '流行'}, description: '播放流行音乐'},
|
|
||||||
{name: '翻译', params: {from: '中文', to: '英文'}, description: '提供中英文互译功能'},
|
|
||||||
{name: '计算', params: {precision: '2'}, description: '提供精确计算功能'},
|
|
||||||
{name: '日历', params: {view: '月'}, description: '查看月历视图'}
|
|
||||||
],
|
|
||||||
functionColorMap: [
|
functionColorMap: [
|
||||||
'#FF6B6B', '#4ECDC4', '#45B7D1',
|
'#FF6B6B', '#4ECDC4', '#45B7D1',
|
||||||
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
|
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
|
||||||
@@ -111,10 +145,37 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
value(newVal) {
|
currentFunction(newFn) {
|
||||||
this.dialogVisible = newVal;
|
if (!newFn) return;
|
||||||
if (newVal) {
|
// 对每个字段,如果是 array 或 json,就在 textCache 里生成初始字符串
|
||||||
|
newFn.fieldsMeta.forEach(f => {
|
||||||
|
const v = newFn.params[f.key];
|
||||||
|
if (f.type === 'array') {
|
||||||
|
this.$set(this.textCache, f.key, Array.isArray(v) ? v.join('\n') : '');
|
||||||
|
}
|
||||||
|
else if (f.type === 'json') {
|
||||||
|
try {
|
||||||
|
this.$set(this.textCache, f.key, JSON.stringify(v ?? {}, null, 2));
|
||||||
|
} catch {
|
||||||
|
this.$set(this.textCache, f.key, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
value(v) {
|
||||||
|
this.dialogVisible = v;
|
||||||
|
if (v) {
|
||||||
|
// 对话框打开时,初始化选中态
|
||||||
this.selectedNames = this.functions.map(f => f.name);
|
this.selectedNames = this.functions.map(f => f.name);
|
||||||
|
// 把后端传来的 this.functions(带 params)merge 到 allFunctions 上
|
||||||
|
this.functions.forEach(saved => {
|
||||||
|
const idx = this.allFunctions.findIndex(f => f.name === saved.name);
|
||||||
|
if (idx >= 0) {
|
||||||
|
// 保留用户之前在 saved.params 上的改动
|
||||||
|
this.allFunctions[idx].params = { ...saved.params };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 右侧默认指向第一个
|
||||||
this.currentFunction = this.selectedList[0] || null;
|
this.currentFunction = this.selectedList[0] || null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,14 +184,32 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
flushArray(key) {
|
||||||
|
const text = this.textCache[key] || '';
|
||||||
|
const arr = text
|
||||||
|
.split('\n')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
this.handleParamChange(this.currentFunction, key, arr);
|
||||||
|
},
|
||||||
|
|
||||||
|
flushJson(field) {
|
||||||
|
const key = field.key;
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = this.textCache[key] || '';
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(text);
|
||||||
|
this.handleParamChange(this.currentFunction, key, obj);
|
||||||
|
} catch {
|
||||||
|
this.$message.error(`${this.currentFunction.name}的${key}字段格式错误:JSON格式有误`);
|
||||||
|
}
|
||||||
|
},
|
||||||
handleFunctionClick(func) {
|
handleFunctionClick(func) {
|
||||||
if (this.selectedNames.includes(func.name)) {
|
if (this.selectedNames.includes(func.name)) {
|
||||||
this.loading = true;
|
const tempFunc = this.tempFunctions[func.name];
|
||||||
setTimeout(() => {
|
this.currentFunction = tempFunc ? tempFunc : func;
|
||||||
const tempFunc = this.tempFunctions[func.name];
|
|
||||||
this.currentFunction = tempFunc ? tempFunc : JSON.parse(JSON.stringify(func));
|
|
||||||
this.loading = false;
|
|
||||||
}, 300);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleParamChange(func, key, value) {
|
handleParamChange(func, key, value) {
|
||||||
@@ -185,23 +264,31 @@ export default {
|
|||||||
|
|
||||||
const selected = this.selectedList.map(f => {
|
const selected = this.selectedList.map(f => {
|
||||||
const modified = this.modifiedFunctions[f.name];
|
const modified = this.modifiedFunctions[f.name];
|
||||||
return modified || f;
|
return {
|
||||||
}).map(f => ({
|
id: f.id,
|
||||||
...f,
|
name: f.name,
|
||||||
params: JSON.parse(JSON.stringify(f.params))
|
params: modified
|
||||||
}));
|
? { ...modified.params }
|
||||||
|
: { ...f.params }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.$emit('update-functions', selected);
|
this.$emit('update-functions', selected);
|
||||||
this.dialogVisible = false;
|
this.dialogVisible = false;
|
||||||
this.$message.success('配置保存成功');
|
|
||||||
// 通知父组件对话框已关闭且已保存
|
// 通知父组件对话框已关闭且已保存
|
||||||
this.$emit('dialog-closed', true);
|
this.$emit('dialog-closed', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
getFunctionColor(name) {
|
getFunctionColor(name) {
|
||||||
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
return this.functionColorMap[hash % 7];
|
return this.functionColorMap[hash % this.functionColorMap.length];
|
||||||
}
|
},
|
||||||
|
fieldRemark(field) {
|
||||||
|
let description = (field && field.label) ? field.label : '';
|
||||||
|
if (field.default) {
|
||||||
|
description += `(默认值:${field.default})`;
|
||||||
|
}
|
||||||
|
return description;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -209,7 +296,7 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.function-manager {
|
.function-manager {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(120px, 0.5fr) minmax(120px, 0.5fr) minmax(200px, 2fr);
|
grid-template-columns: max-content max-content 1fr;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
height: calc(70vh - 60px);
|
height: calc(70vh - 60px);
|
||||||
}
|
}
|
||||||
@@ -248,6 +335,7 @@ export default {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-right: 1px solid #EBEEF5;
|
border-right: 1px solid #EBEEF5;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.function-column::-webkit-scrollbar {
|
.function-column::-webkit-scrollbar {
|
||||||
@@ -257,7 +345,7 @@ export default {
|
|||||||
.function-list {
|
.function-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.function-item {
|
.function-item {
|
||||||
@@ -317,9 +405,31 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.param-form {
|
.param-form {
|
||||||
|
.param-item {
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&.textarea-field {
|
||||||
|
::v-deep .el-form-item__content {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-form-item__label {
|
||||||
|
display: block;
|
||||||
|
width: 100% !important;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep .el-form-item {
|
::v-deep .el-form-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
@@ -356,9 +466,6 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.param-input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-footer {
|
.drawer-footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :visible.sync="dialogVisible" width="57%" center custom-class="custom-dialog" :show-close="false"
|
<el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false" width="57%" center custom-class="custom-dialog" :show-close="false"
|
||||||
class="center-dialog" >
|
class="center-dialog" >
|
||||||
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
|
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
|
||||||
<div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;">
|
<div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :visible="visible" @update:visible="handleVisibleChange" width="57%" center custom-class="custom-dialog"
|
<el-dialog :visible="visible" :close-on-click-modal="false" @update:visible="handleVisibleChange" width="57%" center custom-class="custom-dialog"
|
||||||
:show-close="false" class="center-dialog">
|
:show-close="false" class="center-dialog">
|
||||||
|
|
||||||
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
|
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="供应器编码" prop="providerCode" style="flex: 1;">
|
<el-form-item label="编码" prop="providerCode" style="flex: 1;">
|
||||||
<el-input v-model="form.providerCode" placeholder="请输入供应器编码" class="custom-input-bg"></el-input>
|
<el-input v-model="form.providerCode" placeholder="请输入供应器编码" class="custom-input-bg"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
<el-option label="数字" value="number"></el-option>
|
<el-option label="数字" value="number"></el-option>
|
||||||
<el-option label="布尔值" value="boolean"></el-option>
|
<el-option label="布尔值" value="boolean"></el-option>
|
||||||
<el-option label="字典" value="dict"></el-option>
|
<el-option label="字典" value="dict"></el-option>
|
||||||
|
<el-option label="分号分割的列表" value="array"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -97,10 +98,10 @@
|
|||||||
<el-table-column label="默认值">
|
<el-table-column label="默认值">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<template v-if="scope.row.editing">
|
<template v-if="scope.row.editing">
|
||||||
<el-input v-model="scope.row.default_value" placeholder="请输入默认值"></el-input>
|
<el-input v-model="scope.row.default" placeholder="请输入默认值"></el-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ scope.row.default_value }}
|
{{ scope.row.default }}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -161,7 +162,8 @@ export default {
|
|||||||
'string': '字符串',
|
'string': '字符串',
|
||||||
'number': '数字',
|
'number': '数字',
|
||||||
'boolean': '布尔值',
|
'boolean': '布尔值',
|
||||||
'dict': '字典'
|
'dict': '字典',
|
||||||
|
'array': '分号分割的列表'
|
||||||
};
|
};
|
||||||
return typeMap[type];
|
return typeMap[type];
|
||||||
},
|
},
|
||||||
@@ -220,7 +222,7 @@ export default {
|
|||||||
key: '',
|
key: '',
|
||||||
label: '',
|
label: '',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default_value: '',
|
default: '',
|
||||||
selected: false,
|
selected: false,
|
||||||
editing: true
|
editing: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -143,9 +143,11 @@ export default {
|
|||||||
{ value: "ASR", label: "语音识别" },
|
{ value: "ASR", label: "语音识别" },
|
||||||
{ value: "TTS", label: "语音合成" },
|
{ value: "TTS", label: "语音合成" },
|
||||||
{ value: "LLM", label: "大语言模型" },
|
{ value: "LLM", label: "大语言模型" },
|
||||||
|
{ value: "VLLM", label: "视觉大语言模型" },
|
||||||
{ value: "Intent", label: "意图识别" },
|
{ value: "Intent", label: "意图识别" },
|
||||||
{ value: "Memory", label: "记忆模块" },
|
{ value: "Memory", label: "记忆模块" },
|
||||||
{ value: "VAD", label: "语音活动检测" }
|
{ value: "VAD", label: "语音活动检测" },
|
||||||
|
{ value: "Plugin", label: "插件工具" }
|
||||||
],
|
],
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -97,19 +97,12 @@
|
|||||||
popper-class="custom-tooltip">
|
popper-class="custom-tooltip">
|
||||||
<div slot="content">
|
<div slot="content">
|
||||||
<div><strong>功能名称:</strong> {{ func.name }}</div>
|
<div><strong>功能名称:</strong> {{ func.name }}</div>
|
||||||
<div v-if="Object.keys(func.params).length > 0">
|
|
||||||
<strong>参数配置:</strong>
|
|
||||||
<div v-for="(value, key) in func.params" :key="key">
|
|
||||||
{{ key }}: {{ value }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>无参数配置</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-dot" :style="{ backgroundColor: getFunctionColor(func.name) }">
|
<div class="icon-dot" :style="{ backgroundColor: getFunctionColor(func.name) }">
|
||||||
{{ func.name.charAt(0) }}
|
{{ func.name.charAt(0) }}
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-button class="edit-function-btn" @click="showFunctionDialog = true"
|
<el-button class="edit-function-btn" @click="openFunctionDialog"
|
||||||
:class="{ 'active-btn': showFunctionDialog }">
|
:class="{ 'active-btn': showFunctionDialog }">
|
||||||
编辑功能
|
编辑功能
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -138,7 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<function-dialog v-model="showFunctionDialog" :functions="currentFunctions"
|
<function-dialog v-model="showFunctionDialog" :functions="currentFunctions" :all-functions="allFunctions"
|
||||||
@update-functions="handleUpdateFunctions" @dialog-closed="handleDialogClosed" />
|
@update-functions="handleUpdateFunctions" @dialog-closed="handleDialogClosed" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -192,12 +185,8 @@ export default {
|
|||||||
'#FF6B6B', '#4ECDC4', '#45B7D1',
|
'#FF6B6B', '#4ECDC4', '#45B7D1',
|
||||||
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
|
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
|
||||||
],
|
],
|
||||||
allFunctions: [
|
allFunctions: [],
|
||||||
{ name: '天气', params: {} },
|
originalFunctions: [],
|
||||||
{ name: '新闻', params: {} },
|
|
||||||
{ name: '工具', params: {} },
|
|
||||||
{ name: '退出', params: {} }
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -222,7 +211,12 @@ export default {
|
|||||||
langCode: this.form.langCode,
|
langCode: this.form.langCode,
|
||||||
language: this.form.language,
|
language: this.form.language,
|
||||||
sort: this.form.sort,
|
sort: this.form.sort,
|
||||||
functions: this.currentFunctions
|
functions: this.currentFunctions.map(item => {
|
||||||
|
return ({
|
||||||
|
pluginId: item.id,
|
||||||
|
paramInfo: item.params
|
||||||
|
})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
Api.agent.updateAgentConfig(this.$route.query.agentId, configData, ({ data }) => {
|
Api.agent.updateAgentConfig(this.$route.query.agentId, configData, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
@@ -269,7 +263,8 @@ export default {
|
|||||||
message: '配置已重置',
|
message: '配置已重置',
|
||||||
showClose: true
|
showClose: true
|
||||||
})
|
})
|
||||||
}).catch(() => { });
|
}).catch(() => {
|
||||||
|
});
|
||||||
},
|
},
|
||||||
fetchTemplates() {
|
fetchTemplates() {
|
||||||
Api.agent.getAgentTemplate(({ data }) => {
|
Api.agent.getAgentTemplate(({ data }) => {
|
||||||
@@ -335,7 +330,33 @@ export default {
|
|||||||
intentModelId: data.data.intentModelId
|
intentModelId: data.data.intentModelId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.currentFunctions = data.data.functions || [];
|
// 后端只给了最小映射:[{ id, agentId, pluginId }, ...]
|
||||||
|
const savedMappings = data.data.functions || [];
|
||||||
|
|
||||||
|
// 先保证 allFunctions 已经加载(如果没有,则先 fetchAllFunctions)
|
||||||
|
const ensureFuncs = this.allFunctions.length
|
||||||
|
? Promise.resolve()
|
||||||
|
: this.fetchAllFunctions();
|
||||||
|
|
||||||
|
ensureFuncs.then(() => {
|
||||||
|
// 合并:按照 pluginId(id 字段)把全量元数据信息补齐
|
||||||
|
this.currentFunctions = savedMappings.map(mapping => {
|
||||||
|
const meta = this.allFunctions.find(f => f.id === mapping.pluginId);
|
||||||
|
if (!meta) {
|
||||||
|
// 插件定义没找到,退化处理
|
||||||
|
return { id: mapping.pluginId, name: mapping.pluginId, params: {} };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: mapping.pluginId,
|
||||||
|
name: meta.name,
|
||||||
|
// 后端如果还有 paramInfo 字段就用 mapping.paramInfo,否则用 meta.params 默认值
|
||||||
|
params: mapping.paramInfo || { ...meta.params },
|
||||||
|
fieldsMeta: meta.fieldsMeta // 保留以便对话框渲染 tooltip
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// 备份原始,以备取消时恢复
|
||||||
|
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || '获取配置失败');
|
this.$message.error(data.msg || '获取配置失败');
|
||||||
}
|
}
|
||||||
@@ -373,17 +394,15 @@ export default {
|
|||||||
},
|
},
|
||||||
getFunctionColor(name) {
|
getFunctionColor(name) {
|
||||||
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
return this.functionColorMap[hash % 7];
|
return this.functionColorMap[hash % this.functionColorMap.length];
|
||||||
},
|
},
|
||||||
showFunctionIcons(type) {
|
showFunctionIcons(type) {
|
||||||
// TODO 暂时不放出来
|
return type === 'Intent' &&
|
||||||
return false;
|
this.form.model.intentModelId !== 'Intent_nointent';
|
||||||
// return type === 'Intent' &&
|
|
||||||
// this.form.model.intentModelId !== 'Intent_nointent';
|
|
||||||
},
|
},
|
||||||
handleModelChange(type, value) {
|
handleModelChange(type, value) {
|
||||||
if (type === 'Intent' && value !== 'Intent_nointent') {
|
if (type === 'Intent' && value !== 'Intent_nointent') {
|
||||||
this.fetchFunctionList();
|
this.fetchAllFunctions();
|
||||||
}
|
}
|
||||||
if (type === 'Memory' && value === 'Memory_nomem') {
|
if (type === 'Memory' && value === 'Memory_nomem') {
|
||||||
this.form.chatHistoryConf = 0;
|
this.form.chatHistoryConf = 0;
|
||||||
@@ -392,28 +411,44 @@ export default {
|
|||||||
this.form.chatHistoryConf = 2;
|
this.form.chatHistoryConf = 2;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchFunctionList() {
|
fetchAllFunctions() {
|
||||||
// 使用假数据代替API调用
|
return new Promise((resolve, reject) => {
|
||||||
return new Promise(resolve => {
|
Api.model.getPluginFunctionList(null, ({ data }) => {
|
||||||
setTimeout(() => {
|
if (data.code === 0) {
|
||||||
this.currentFunctions = [
|
this.allFunctions = data.data.map(item => {
|
||||||
{ name: '天气', params: { city: '北京' } },
|
const meta = JSON.parse(item.fields || '[]');
|
||||||
{ name: '新闻', params: { type: '科技' } }
|
const params = meta.reduce((m, f) => {
|
||||||
];
|
m[f.key] = f.default;
|
||||||
resolve();
|
return m;
|
||||||
}, 500);
|
}, {});
|
||||||
|
return { ...item, fieldsMeta: meta, params };
|
||||||
|
});
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
this.$message.error(data.msg || '获取插件列表失败');
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
openFunctionDialog() {
|
||||||
|
// 显示编辑对话框时,确保 allFunctions 已经加载
|
||||||
|
if (this.allFunctions.length === 0) {
|
||||||
|
this.fetchAllFunctions().then(() => this.showFunctionDialog = true);
|
||||||
|
} else {
|
||||||
|
this.showFunctionDialog = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
handleUpdateFunctions(selected) {
|
handleUpdateFunctions(selected) {
|
||||||
this.currentFunctions = selected;
|
this.currentFunctions = selected;
|
||||||
console.log('保存的功能列表:', selected);
|
|
||||||
this.$message.success('功能配置已保存');
|
|
||||||
},
|
},
|
||||||
handleDialogClosed(saved) {
|
handleDialogClosed(saved) {
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
// 如果未保存,恢复原始功能列表
|
|
||||||
this.currentFunctions = JSON.parse(JSON.stringify(this.originalFunctions));
|
this.currentFunctions = JSON.parse(JSON.stringify(this.originalFunctions));
|
||||||
|
} else {
|
||||||
|
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
|
||||||
}
|
}
|
||||||
|
this.showFunctionDialog = false;
|
||||||
},
|
},
|
||||||
updateChatHistoryConf() {
|
updateChatHistoryConf() {
|
||||||
if (this.form.model.memModelId === 'Memory_nomem') {
|
if (this.form.model.memModelId === 'Memory_nomem') {
|
||||||
@@ -446,9 +481,7 @@ export default {
|
|||||||
const agentId = this.$route.query.agentId;
|
const agentId = this.$route.query.agentId;
|
||||||
if (agentId) {
|
if (agentId) {
|
||||||
this.fetchAgentConfig(agentId);
|
this.fetchAgentConfig(agentId);
|
||||||
this.fetchFunctionList().then(() => {
|
this.fetchAllFunctions();
|
||||||
this.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.fetchModelOptions();
|
this.fetchModelOptions();
|
||||||
this.fetchTemplates();
|
this.fetchTemplates();
|
||||||
|
|||||||
@@ -117,10 +117,9 @@ plugins:
|
|||||||
# 更多类型的新闻列表查看 https://www.chinanews.com.cn/rss/
|
# 更多类型的新闻列表查看 https://www.chinanews.com.cn/rss/
|
||||||
get_news_from_chinanews:
|
get_news_from_chinanews:
|
||||||
default_rss_url: "https://www.chinanews.com.cn/rss/society.xml"
|
default_rss_url: "https://www.chinanews.com.cn/rss/society.xml"
|
||||||
category_urls:
|
society_rss_url: "https://www.chinanews.com.cn/rss/society.xml"
|
||||||
society: "https://www.chinanews.com.cn/rss/society.xml"
|
world_rss_url: "https://www.chinanews.com.cn/rss/world.xml"
|
||||||
world: "https://www.chinanews.com.cn/rss/world.xml"
|
finance_rss_url: "https://www.chinanews.com.cn/rss/finance.xml"
|
||||||
finance: "https://www.chinanews.com.cn/rss/finance.xml"
|
|
||||||
get_news_from_newsnow: {"url": "https://newsnow.busiyi.world/api/s?id="}
|
get_news_from_newsnow: {"url": "https://newsnow.busiyi.world/api/s?id="}
|
||||||
home_assistant:
|
home_assistant:
|
||||||
devices:
|
devices:
|
||||||
|
|||||||
@@ -453,9 +453,17 @@ class ConnectionHandler:
|
|||||||
if private_config.get("Intent", None) is not None:
|
if private_config.get("Intent", None) is not None:
|
||||||
init_intent = True
|
init_intent = True
|
||||||
self.config["Intent"] = private_config["Intent"]
|
self.config["Intent"] = private_config["Intent"]
|
||||||
self.config["selected_module"]["Intent"] = private_config[
|
model_intent = private_config.get("selected_module", {}).get("Intent", {})
|
||||||
"selected_module"
|
self.config["selected_module"]["Intent"] = model_intent
|
||||||
]["Intent"]
|
# 加载插件配置
|
||||||
|
if model_intent != "Intent_nointent":
|
||||||
|
plugin_from_server = private_config.get("plugins", {})
|
||||||
|
for plugin, config_str in plugin_from_server.items():
|
||||||
|
plugin_from_server[plugin] = json.loads(config_str)
|
||||||
|
self.config["plugins"] = plugin_from_server
|
||||||
|
self.config["Intent"][self.config["selected_module"]["Intent"]][
|
||||||
|
"functions"
|
||||||
|
] = plugin_from_server.keys()
|
||||||
if private_config.get("prompt", None) is not None:
|
if private_config.get("prompt", None) is not None:
|
||||||
self.config["prompt"] = private_config["prompt"]
|
self.config["prompt"] = private_config["prompt"]
|
||||||
if private_config.get("summaryMemory", None) is not None:
|
if private_config.get("summaryMemory", None) is not None:
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ class FunctionHandler:
|
|||||||
self.function_registry.register_function("plugin_loader")
|
self.function_registry.register_function("plugin_loader")
|
||||||
self.function_registry.register_function("get_time")
|
self.function_registry.register_function("get_time")
|
||||||
self.function_registry.register_function("get_lunar")
|
self.function_registry.register_function("get_lunar")
|
||||||
# self.function_registry.register_function("handle_speaker_volume_or_screen_brightness")
|
|
||||||
|
|
||||||
def register_config_functions(self):
|
def register_config_functions(self):
|
||||||
"""注册配置中的函数,可以不同客户端使用不同的配置"""
|
"""注册配置中的函数,可以不同客户端使用不同的配置"""
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.bot_id = str(config.get("bot_id"))
|
self.bot_id = str(config.get("bot_id"))
|
||||||
self.user_id = str(config.get("user_id"))
|
self.user_id = str(config.get("user_id"))
|
||||||
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
||||||
check_model_key("CozeLLM", self.personal_access_token)
|
model_key_msg = check_model_key("CozeLLM", self.personal_access_token)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
coze_api_token = self.personal_access_token
|
coze_api_token = self.personal_access_token
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.mode = config.get("mode", "chat-messages")
|
self.mode = config.get("mode", "chat-messages")
|
||||||
self.base_url = config.get("base_url", "https://api.dify.ai/v1").rstrip("/")
|
self.base_url = config.get("base_url", "https://api.dify.ai/v1").rstrip("/")
|
||||||
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
self.session_conversation_map = {} # 存储session_id和conversation_id的映射
|
||||||
check_model_key("DifyLLM", self.api_key)
|
model_key_msg = check_model_key("DifyLLM", self.api_key)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.base_url = config.get("base_url")
|
self.base_url = config.get("base_url")
|
||||||
self.detail = config.get("detail", False)
|
self.detail = config.get("detail", False)
|
||||||
self.variables = config.get("variables", {})
|
self.variables = config.get("variables", {})
|
||||||
check_model_key("FastGPTLLM", self.api_key)
|
model_key_msg = check_model_key("FastGPTLLM", self.api_key)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ class LLMProvider(LLMProviderBase):
|
|||||||
http_proxy = cfg.get("http_proxy")
|
http_proxy = cfg.get("http_proxy")
|
||||||
https_proxy = cfg.get("https_proxy")
|
https_proxy = cfg.get("https_proxy")
|
||||||
|
|
||||||
if not check_model_key("LLM", self.api_key):
|
model_key_msg = check_model_key("LLM", self.api_key)
|
||||||
raise ValueError("无效的Gemini API Key,请检查是否配置正确")
|
if model_key_msg:
|
||||||
|
log.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
if http_proxy or https_proxy:
|
if http_proxy or https_proxy:
|
||||||
log.bind(tag=TAG).info(
|
log.bind(tag=TAG).info(
|
||||||
|
|||||||
@@ -21,20 +21,27 @@ class LLMProvider(LLMProviderBase):
|
|||||||
"max_tokens": (500, int),
|
"max_tokens": (500, int),
|
||||||
"temperature": (0.7, lambda x: round(float(x), 1)),
|
"temperature": (0.7, lambda x: round(float(x), 1)),
|
||||||
"top_p": (1.0, lambda x: round(float(x), 1)),
|
"top_p": (1.0, lambda x: round(float(x), 1)),
|
||||||
"frequency_penalty": (0, lambda x: round(float(x), 1))
|
"frequency_penalty": (0, lambda x: round(float(x), 1)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for param, (default, converter) in param_defaults.items():
|
for param, (default, converter) in param_defaults.items():
|
||||||
value = config.get(param)
|
value = config.get(param)
|
||||||
try:
|
try:
|
||||||
setattr(self, param, converter(value) if value not in (None, "") else default)
|
setattr(
|
||||||
|
self,
|
||||||
|
param,
|
||||||
|
converter(value) if value not in (None, "") else default,
|
||||||
|
)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
setattr(self, param, default)
|
setattr(self, param, default)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"意图识别参数初始化: {self.temperature}, {self.max_tokens}, {self.top_p}, {self.frequency_penalty}")
|
f"意图识别参数初始化: {self.temperature}, {self.max_tokens}, {self.top_p}, {self.frequency_penalty}"
|
||||||
|
)
|
||||||
|
|
||||||
check_model_key("LLM", self.api_key)
|
model_key_msg = check_model_key("LLM", self.api_key)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
@@ -46,7 +53,9 @@ class LLMProvider(LLMProviderBase):
|
|||||||
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
max_tokens=kwargs.get("max_tokens", self.max_tokens),
|
||||||
temperature=kwargs.get("temperature", self.temperature),
|
temperature=kwargs.get("temperature", self.temperature),
|
||||||
top_p=kwargs.get("top_p", self.top_p),
|
top_p=kwargs.get("top_p", self.top_p),
|
||||||
frequency_penalty=kwargs.get("frequency_penalty", self.frequency_penalty),
|
frequency_penalty=kwargs.get(
|
||||||
|
"frequency_penalty", self.frequency_penalty
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
is_active = True
|
is_active = True
|
||||||
@@ -84,12 +93,14 @@ class LLMProvider(LLMProviderBase):
|
|||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
# 检查是否存在有效的choice且content不为空
|
# 检查是否存在有效的choice且content不为空
|
||||||
if getattr(chunk, "choices", None):
|
if getattr(chunk, "choices", None):
|
||||||
yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls
|
yield chunk.choices[0].delta.content, chunk.choices[
|
||||||
|
0
|
||||||
|
].delta.tool_calls
|
||||||
# 存在 CompletionUsage 消息时,生成 Token 消耗 log
|
# 存在 CompletionUsage 消息时,生成 Token 消耗 log
|
||||||
elif isinstance(getattr(chunk, 'usage', None), CompletionUsage):
|
elif isinstance(getattr(chunk, "usage", None), CompletionUsage):
|
||||||
usage_info = getattr(chunk, 'usage', None)
|
usage_info = getattr(chunk, "usage", None)
|
||||||
logger.bind(tag=TAG).info(
|
logger.bind(tag=TAG).info(
|
||||||
f"Token 消耗:输入 {getattr(usage_info, 'prompt_tokens', '未知')},"
|
f"Token 消耗:输入 {getattr(usage_info, 'prompt_tokens', '未知')},"
|
||||||
f"输出 {getattr(usage_info, 'completion_tokens', '未知')},"
|
f"输出 {getattr(usage_info, 'completion_tokens', '未知')},"
|
||||||
f"共计 {getattr(usage_info, 'total_tokens', '未知')}"
|
f"共计 {getattr(usage_info, 'total_tokens', '未知')}"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ class MemoryProviderBase(ABC):
|
|||||||
|
|
||||||
def set_llm(self, llm):
|
def set_llm(self, llm):
|
||||||
self.llm = llm
|
self.llm = llm
|
||||||
# 获取模型名称和类型信息
|
|
||||||
model_name = getattr(llm, "model_name", str(llm.__class__.__name__))
|
|
||||||
# 记录更详细的日志
|
|
||||||
logger.bind(tag=TAG).info(f"记忆总结设置LLM: {model_name}")
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def save_memory(self, msgs):
|
async def save_memory(self, msgs):
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.api_key = config.get("api_key", "")
|
self.api_key = config.get("api_key", "")
|
||||||
self.api_version = config.get("api_version", "v1.1")
|
self.api_version = config.get("api_version", "v1.1")
|
||||||
have_key = check_model_key("Mem0ai", self.api_key)
|
model_key_msg = check_model_key("Mem0ai", self.api_key)
|
||||||
if not have_key:
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
self.use_mem0 = False
|
self.use_mem0 = False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.use_mem0 = True
|
self.use_mem0 = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.client = MemoryClient(api_key=self.api_key)
|
self.client = MemoryClient(api_key=self.api_key)
|
||||||
logger.bind(tag=TAG).info("成功连接到 Mem0ai 服务")
|
logger.bind(tag=TAG).info("成功连接到 Mem0ai 服务")
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.api_url = config.get("api_url")
|
self.api_url = config.get("api_url")
|
||||||
self.authorization = config.get("authorization")
|
self.authorization = config.get("authorization")
|
||||||
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
||||||
check_model_key("TTS", self.access_token)
|
model_key_msg = check_model_key("TTS", self.access_token)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_json = {
|
request_json = {
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.format = config.get("response_format", "wav")
|
self.format = config.get("response_format", "wav")
|
||||||
self.audio_file_type = config.get("response_format", "wav")
|
self.audio_file_type = config.get("response_format", "wav")
|
||||||
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
||||||
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
model_key_msg = check_model_key("FishSpeech TTS", self.api_key)
|
||||||
if not have_key:
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
return
|
return
|
||||||
self.normalize = str(config.get("normalize", True)).lower() in (
|
self.normalize = str(config.get("normalize", True)).lower() in (
|
||||||
"true",
|
"true",
|
||||||
|
|||||||
@@ -157,7 +157,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.opus_encoder = opus_encoder_utils.OpusEncoderUtils(
|
self.opus_encoder = opus_encoder_utils.OpusEncoderUtils(
|
||||||
sample_rate=16000, channels=1, frame_size_ms=60
|
sample_rate=16000, channels=1, frame_size_ms=60
|
||||||
)
|
)
|
||||||
check_model_key("TTS", self.access_token)
|
model_key_msg = check_model_key("TTS", self.access_token)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
async def open_audio_channels(self, conn):
|
async def open_audio_channels(self, conn):
|
||||||
try:
|
try:
|
||||||
@@ -267,7 +269,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
await handleAbortMessage(self.conn)
|
await handleAbortMessage(self.conn)
|
||||||
logger.bind(tag=TAG).error(f"WebSocket连接不存在,终止发送文本")
|
logger.bind(tag=TAG).error(f"WebSocket连接不存在,终止发送文本")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 过滤Markdown
|
# 过滤Markdown
|
||||||
filtered_text = MarkdownCleaner.clean_markdown(text)
|
filtered_text = MarkdownCleaner.clean_markdown(text)
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
) as resp:
|
) as resp:
|
||||||
|
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
logger.error(f"TTS请求失败: {resp.status}, {await resp.text()}")
|
logger.bind(tag=TAG).error(
|
||||||
|
f"TTS请求失败: {resp.status}, {await resp.text()}"
|
||||||
|
)
|
||||||
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self._process_before_stop_play_files()
|
self._process_before_stop_play_files()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"TTS请求异常: {e}")
|
logger.bind(tag=TAG).error(f"TTS请求异常: {e}")
|
||||||
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
self.tts_audio_queue.put((SentenceType.LAST, [], None))
|
||||||
|
|
||||||
def to_tts(self, text: str) -> list:
|
def to_tts(self, text: str) -> list:
|
||||||
@@ -263,7 +265,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.api_url, params=params, headers=headers, timeout=5
|
self.api_url, params=params, headers=headers, timeout=5
|
||||||
) as response:
|
) as response:
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error(
|
logger.bind(tag=TAG).error(
|
||||||
f"TTS请求失败: {response.status_code}, {response.text}"
|
f"TTS请求失败: {response.status_code}, {response.text}"
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
@@ -299,5 +301,5 @@ class TTSProvider(TTSProviderBase):
|
|||||||
return opus_datas
|
return opus_datas
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"TTS请求异常: {e}")
|
logger.bind(tag=TAG).error(f"TTS请求异常: {e}")
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.speed = float(speed) if speed else 1.0
|
self.speed = float(speed) if speed else 1.0
|
||||||
|
|
||||||
self.output_file = config.get("output_dir", "tmp/")
|
self.output_file = config.get("output_dir", "tmp/")
|
||||||
check_model_key("TTS", self.api_key)
|
model_key_msg = check_model_key("TTS", self.api_key)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
headers = {
|
headers = {
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ class VLLMProvider(VLLMProviderBase):
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
setattr(self, param, default)
|
setattr(self, param, default)
|
||||||
|
|
||||||
check_model_key("VLLM", self.api_key)
|
model_key_msg = check_model_key("VLLM", self.api_key)
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||||
|
|
||||||
def response(self, question, base64_image):
|
def response(self, question, base64_image):
|
||||||
|
|||||||
@@ -186,10 +186,8 @@ def remove_punctuation_and_length(text):
|
|||||||
|
|
||||||
def check_model_key(modelType, modelKey):
|
def check_model_key(modelType, modelKey):
|
||||||
if "你" in modelKey:
|
if "你" in modelKey:
|
||||||
raise ValueError(
|
return f"配置错误: {modelType} 的 API key 未设置,当前值为: {modelKey}"
|
||||||
"你还没配置" + modelType + "的密钥,请检查一下所使用的LLM是否配置了密钥"
|
return None
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def parse_string_to_list(value, separator=";"):
|
def parse_string_to_list(value, separator=";"):
|
||||||
@@ -785,7 +783,9 @@ def audio_bytes_to_data(audio_bytes, file_type, is_opus=True):
|
|||||||
return p3.decode_opus_from_bytes(audio_bytes)
|
return p3.decode_opus_from_bytes(audio_bytes)
|
||||||
else:
|
else:
|
||||||
# 其他格式用pydub
|
# 其他格式用pydub
|
||||||
audio = AudioSegment.from_file(BytesIO(audio_bytes), format=file_type, parameters=["-nostdin"])
|
audio = AudioSegment.from_file(
|
||||||
|
BytesIO(audio_bytes), format=file_type, parameters=["-nostdin"]
|
||||||
|
)
|
||||||
audio = audio.set_channels(1).set_frame_rate(16000).set_sample_width(2)
|
audio = audio.set_channels(1).set_frame_rate(16000).set_sample_width(2)
|
||||||
duration = len(audio) / 1000.0
|
duration = len(audio) / 1000.0
|
||||||
raw_data = audio.raw_data
|
raw_data = audio.raw_data
|
||||||
@@ -838,11 +838,11 @@ def opus_datas_to_wav_bytes(opus_datas, sample_rate=16000, channels=1):
|
|||||||
pcm = decoder.decode(opus_frame, frame_size)
|
pcm = decoder.decode(opus_frame, frame_size)
|
||||||
pcm_datas.append(pcm)
|
pcm_datas.append(pcm)
|
||||||
|
|
||||||
pcm_bytes = b''.join(pcm_datas)
|
pcm_bytes = b"".join(pcm_datas)
|
||||||
|
|
||||||
# 写入wav字节流
|
# 写入wav字节流
|
||||||
wav_buffer = BytesIO()
|
wav_buffer = BytesIO()
|
||||||
with wave.open(wav_buffer, 'wb') as wf:
|
with wave.open(wav_buffer, "wb") as wf:
|
||||||
wf.setnchannels(channels)
|
wf.setnchannels(channels)
|
||||||
wf.setsampwidth(2) # 16bit
|
wf.setsampwidth(2) # 16bit
|
||||||
wf.setframerate(sample_rate)
|
wf.setframerate(sample_rate)
|
||||||
|
|||||||
@@ -120,16 +120,16 @@ def map_category(category_text):
|
|||||||
# 类别映射字典,目前支持社会、国际、财经新闻,如需更多类型,参见配置文件
|
# 类别映射字典,目前支持社会、国际、财经新闻,如需更多类型,参见配置文件
|
||||||
category_map = {
|
category_map = {
|
||||||
# 社会新闻
|
# 社会新闻
|
||||||
"社会": "society",
|
"社会": "society_rss_url",
|
||||||
"社会新闻": "society",
|
"社会新闻": "society_rss_url",
|
||||||
# 国际新闻
|
# 国际新闻
|
||||||
"国际": "world",
|
"国际": "world_rss_url",
|
||||||
"国际新闻": "world",
|
"国际新闻": "world_rss_url",
|
||||||
# 财经新闻
|
# 财经新闻
|
||||||
"财经": "finance",
|
"财经": "finance_rss_url",
|
||||||
"财经新闻": "finance",
|
"财经新闻": "finance_rss_url",
|
||||||
"金融": "finance",
|
"金融": "finance_rss_url",
|
||||||
"经济": "finance",
|
"经济": "finance_rss_url",
|
||||||
}
|
}
|
||||||
|
|
||||||
# 转换为小写并去除空格
|
# 转换为小写并去除空格
|
||||||
@@ -205,8 +205,8 @@ def get_news_from_chinanews(
|
|||||||
|
|
||||||
# 如果提供了类别,尝试从配置中获取对应的URL
|
# 如果提供了类别,尝试从配置中获取对应的URL
|
||||||
rss_url = default_rss_url
|
rss_url = default_rss_url
|
||||||
if mapped_category and mapped_category in rss_config.get("category_urls", {}):
|
if mapped_category and mapped_category in rss_config:
|
||||||
rss_url = rss_config["category_urls"][mapped_category]
|
rss_url = rss_config[mapped_category]
|
||||||
|
|
||||||
logger.bind(tag=TAG).info(
|
logger.bind(tag=TAG).info(
|
||||||
f"获取新闻: 原始类别={category}, 映射类别={mapped_category}, URL={rss_url}"
|
f"获取新闻: 原始类别={category}, 映射类别={mapped_category}, URL={rss_url}"
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
from config.logger import setup_logging
|
|
||||||
from plugins_func.register import register_function, ToolType, ActionResponse, Action
|
|
||||||
from core.handle.iotHandle import get_iot_status, send_iot_conn
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
TAG = __name__
|
|
||||||
logger = setup_logging()
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_device_status(conn, device_name, device_type, property_name):
|
|
||||||
"""获取设备状态"""
|
|
||||||
status = await get_iot_status(conn, device_type, property_name)
|
|
||||||
if status is None:
|
|
||||||
raise Exception(f"你的设备不支持{device_name}控制")
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
async def _set_device_property(
|
|
||||||
conn,
|
|
||||||
device_name,
|
|
||||||
device_type,
|
|
||||||
method_name,
|
|
||||||
property_name,
|
|
||||||
new_value=None,
|
|
||||||
action=None,
|
|
||||||
step=10,
|
|
||||||
):
|
|
||||||
"""设置设备属性"""
|
|
||||||
current_value = await _get_device_status(
|
|
||||||
conn, device_name, device_type, property_name
|
|
||||||
)
|
|
||||||
|
|
||||||
if action == "raise":
|
|
||||||
current_value += step
|
|
||||||
elif action == "lower":
|
|
||||||
current_value -= step
|
|
||||||
elif action == "set":
|
|
||||||
if new_value is None:
|
|
||||||
raise Exception(f"缺少{property_name}参数")
|
|
||||||
current_value = new_value
|
|
||||||
|
|
||||||
# 限制属性范围在0到100之间
|
|
||||||
current_value = max(0, min(100, current_value))
|
|
||||||
|
|
||||||
await send_iot_conn(conn, device_type, method_name, {property_name: current_value})
|
|
||||||
return current_value
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_device_action(conn, func, success_message, error_message, *args, **kwargs):
|
|
||||||
"""处理设备操作的通用函数"""
|
|
||||||
future = asyncio.run_coroutine_threadsafe(func(conn, *args, **kwargs), conn.loop)
|
|
||||||
try:
|
|
||||||
result = future.result()
|
|
||||||
logger.bind(tag=TAG).info(f"{success_message}: {result}")
|
|
||||||
response = f"{success_message}{result}"
|
|
||||||
return ActionResponse(action=Action.RESPONSE, result=result, response=response)
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"{error_message}: {e}")
|
|
||||||
response = f"{error_message}: {e}"
|
|
||||||
return ActionResponse(action=Action.RESPONSE, result=None, response=response)
|
|
||||||
|
|
||||||
|
|
||||||
# 设备控制
|
|
||||||
handle_device_function_desc = {
|
|
||||||
"type": "function",
|
|
||||||
"function": {
|
|
||||||
"name": "handle_speaker_volume_or_screen_brightness",
|
|
||||||
"description": (
|
|
||||||
"用户想要获取或者设置设备的音量/亮度大小,或者用户觉得声音/亮度过高或过低,或者用户想提高或降低音量/亮度。\n"
|
|
||||||
"**严格限制**:仅当用户明确操作 **Speaker(音量)或Screen(亮度)** 时才能调用此函数!\n"
|
|
||||||
"对于其他设备(如AC、Battery、Switch等),请不要调用此函数,而是继续正常的对话。\n\n"
|
|
||||||
"示例:\n"
|
|
||||||
"- 用户说『现在亮度多少』 → 调用函数:device_type: Screen, action: get\n"
|
|
||||||
"- 用户说『设置音量为50』 → 调用函数:device_type: Speaker, action: set, value: 50\n"
|
|
||||||
"- 用户说『亮度太高了』 → 调用函数:device_type: Screen, action: lower\n"
|
|
||||||
"- 用户说『调大音量』 → 调用函数:device_type: Speaker, action: raise\n\n"
|
|
||||||
"**拒绝调用示例**(应继续对话而非调用本函数):\n"
|
|
||||||
"- 用户说『空调调低一度』 → 不调用(设备类型为AC)\n"
|
|
||||||
"- 用户说『开关灯』 → 不调用(设备类型为Switch)\n"
|
|
||||||
"- 用户说『电量多少』 → 不调用(设备类型为Battery)\n"
|
|
||||||
),
|
|
||||||
"parameters": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"device_type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "设备类型,**严格限定为Speaker(音量)或Screen(亮度)**,其他设备类型禁止调用此函数",
|
|
||||||
"enum": ["Speaker", "Screen"],
|
|
||||||
},
|
|
||||||
"action": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "动作名称,可选值:get(获取),set(设置),raise(提高),lower(降低)",
|
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "值大小,可选值:0-100之间的整数",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["device_type", "action"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register_function(
|
|
||||||
"handle_speaker_volume_or_screen_brightness",
|
|
||||||
handle_device_function_desc,
|
|
||||||
ToolType.IOT_CTL,
|
|
||||||
)
|
|
||||||
def handle_speaker_volume_or_screen_brightness(
|
|
||||||
conn, device_type: str, action: str, value: int = None
|
|
||||||
):
|
|
||||||
# 检查value是否为中文值
|
|
||||||
if (
|
|
||||||
value is not None
|
|
||||||
and isinstance(value, str)
|
|
||||||
and any("\u4e00" <= char <= "\u9fff" for char in str(value))
|
|
||||||
):
|
|
||||||
raise Exception(
|
|
||||||
f"请直接告诉我要将{'音量' if device_type=='Speaker' else '亮度'}调整成多少"
|
|
||||||
)
|
|
||||||
|
|
||||||
if device_type == "Speaker":
|
|
||||||
method_name, property_name, device_name = "SetVolume", "volume", "音量"
|
|
||||||
elif device_type == "Screen":
|
|
||||||
method_name, property_name, device_name = "SetBrightness", "brightness", "亮度"
|
|
||||||
else:
|
|
||||||
raise Exception(f"未识别的设备类型: {device_type}")
|
|
||||||
|
|
||||||
if action not in ["get", "set", "raise", "lower"]:
|
|
||||||
raise Exception(f"未识别的动作名称: {action}")
|
|
||||||
|
|
||||||
if action == "get":
|
|
||||||
# get
|
|
||||||
return _handle_device_action(
|
|
||||||
conn,
|
|
||||||
_get_device_status,
|
|
||||||
f"当前{device_name}",
|
|
||||||
f"获取{device_name}失败",
|
|
||||||
device_name=device_name,
|
|
||||||
device_type=device_type,
|
|
||||||
property_name=property_name,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# set, raise, lower
|
|
||||||
return _handle_device_action(
|
|
||||||
conn,
|
|
||||||
_set_device_property,
|
|
||||||
f"{device_name}已调整到",
|
|
||||||
f"{device_name}调整失败",
|
|
||||||
device_name=device_name,
|
|
||||||
device_type=device_type,
|
|
||||||
method_name=method_name,
|
|
||||||
property_name=property_name,
|
|
||||||
new_value=value,
|
|
||||||
action=action,
|
|
||||||
)
|
|
||||||
@@ -17,57 +17,78 @@ hass_get_state_function_desc = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "需要操作的设备id,homeassistant里的entity_id"
|
"description": "需要操作的设备id,homeassistant里的entity_id",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["entity_id"]
|
"required": ["entity_id"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_function("hass_get_state", hass_get_state_function_desc, ToolType.SYSTEM_CTL)
|
@register_function("hass_get_state", hass_get_state_function_desc, ToolType.SYSTEM_CTL)
|
||||||
def hass_get_state(conn, entity_id=''):
|
def hass_get_state(conn, entity_id=""):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
handle_hass_get_state(conn, entity_id),
|
handle_hass_get_state(conn, entity_id), conn.loop
|
||||||
conn.loop
|
|
||||||
)
|
)
|
||||||
ha_response = future.result()
|
ha_response = future.result()
|
||||||
return ActionResponse( Action.REQLLM, ha_response , None )
|
return ActionResponse(Action.REQLLM, ha_response, None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"处理设置属性意图错误: {e}")
|
logger.bind(tag=TAG).error(f"处理设置属性意图错误: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def handle_hass_get_state(conn, entity_id):
|
async def handle_hass_get_state(conn, entity_id):
|
||||||
HASS_CACHE = initialize_hass_handler(conn)
|
ha_config = initialize_hass_handler(conn)
|
||||||
api_key = HASS_CACHE['api_key']
|
api_key = ha_config.get("api_key")
|
||||||
base_url = HASS_CACHE['base_url']
|
base_url = ha_config.get("base_url")
|
||||||
url = f"{base_url}/api/states/{entity_id}"
|
url = f"{base_url}/api/states/{entity_id}"
|
||||||
headers = {
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||||
"Authorization": f"Bearer {api_key}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
response = requests.get(url, headers=headers)
|
response = requests.get(url, headers=headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
responsetext = '设备状态:' + response.json()['state'] + ' '
|
responsetext = "设备状态:" + response.json()["state"] + " "
|
||||||
logger.bind(tag=TAG).info(f"api返回内容: {response.json()}")
|
logger.bind(tag=TAG).info(f"api返回内容: {response.json()}")
|
||||||
|
|
||||||
if 'media_title' in response.json()['attributes']:
|
if "media_title" in response.json()["attributes"]:
|
||||||
responsetext = responsetext+ '正在播放的是:'+str(response.json()['attributes']['media_title'])+' '
|
responsetext = (
|
||||||
if 'volume_level' in response.json()['attributes']:
|
responsetext
|
||||||
responsetext = responsetext+ '音量是:'+str(response.json()['attributes']['volume_level'])+' '
|
+ "正在播放的是:"
|
||||||
if 'color_temp_kelvin' in response.json()['attributes']:
|
+ str(response.json()["attributes"]["media_title"])
|
||||||
responsetext = responsetext+ '色温是:'+str(response.json()['attributes']['color_temp_kelvin'])+' '
|
+ " "
|
||||||
if 'rgb_color' in response.json()['attributes']:
|
)
|
||||||
responsetext = responsetext+ 'rgb颜色是:'+str(response.json()['attributes']['rgb_color'])+' '
|
if "volume_level" in response.json()["attributes"]:
|
||||||
if 'brightness' in response.json()['attributes']:
|
responsetext = (
|
||||||
responsetext = responsetext+ '亮度是:'+str(response.json()['attributes']['brightness'])+' '
|
responsetext
|
||||||
|
+ "音量是:"
|
||||||
|
+ str(response.json()["attributes"]["volume_level"])
|
||||||
|
+ " "
|
||||||
|
)
|
||||||
|
if "color_temp_kelvin" in response.json()["attributes"]:
|
||||||
|
responsetext = (
|
||||||
|
responsetext
|
||||||
|
+ "色温是:"
|
||||||
|
+ str(response.json()["attributes"]["color_temp_kelvin"])
|
||||||
|
+ " "
|
||||||
|
)
|
||||||
|
if "rgb_color" in response.json()["attributes"]:
|
||||||
|
responsetext = (
|
||||||
|
responsetext
|
||||||
|
+ "rgb颜色是:"
|
||||||
|
+ str(response.json()["attributes"]["rgb_color"])
|
||||||
|
+ " "
|
||||||
|
)
|
||||||
|
if "brightness" in response.json()["attributes"]:
|
||||||
|
responsetext = (
|
||||||
|
responsetext
|
||||||
|
+ "亮度是:"
|
||||||
|
+ str(response.json()["attributes"]["brightness"])
|
||||||
|
+ " "
|
||||||
|
)
|
||||||
logger.bind(tag=TAG).info(f"查询返回内容: {responsetext}")
|
logger.bind(tag=TAG).info(f"查询返回内容: {responsetext}")
|
||||||
return responsetext
|
return responsetext
|
||||||
#return response.json()['attributes']
|
# return response.json()['attributes']
|
||||||
#response.attributes
|
# response.attributes
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return f"切换失败,错误码: {response.status_code}"
|
return f"切换失败,错误码: {response.status_code}"
|
||||||
|
|||||||
@@ -4,40 +4,49 @@ from core.utils.util import check_model_key
|
|||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
HASS_CACHE = {}
|
|
||||||
|
|
||||||
|
|
||||||
def append_devices_to_prompt(conn):
|
def append_devices_to_prompt(conn):
|
||||||
if conn.intent_type == "function_call":
|
if conn.intent_type == "function_call":
|
||||||
funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
|
funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
|
||||||
"functions", []
|
"functions", []
|
||||||
)
|
)
|
||||||
|
|
||||||
|
config_source = (
|
||||||
|
"home_assistant"
|
||||||
|
if conn.config["plugins"].get("home_assistant")
|
||||||
|
else "hass_get_state"
|
||||||
|
)
|
||||||
|
|
||||||
if "hass_get_state" in funcs or "hass_set_state" in funcs:
|
if "hass_get_state" in funcs or "hass_set_state" in funcs:
|
||||||
prompt = "\n下面是我家智能设备列表(位置,设备名,entity_id),可以通过homeassistant控制\n"
|
prompt = "\n下面是我家智能设备列表(位置,设备名,entity_id),可以通过homeassistant控制\n"
|
||||||
devices = conn.config["plugins"]["home_assistant"].get("devices", [])
|
deviceStr = conn.config["plugins"].get(config_source, {}).get("devices", "")
|
||||||
if len(devices) == 0:
|
conn.prompt += prompt + deviceStr + "\n"
|
||||||
return
|
|
||||||
for device in devices:
|
|
||||||
prompt += device + "\n"
|
|
||||||
conn.prompt += prompt
|
|
||||||
# 更新提示词
|
# 更新提示词
|
||||||
conn.dialogue.update_system_message(conn.prompt)
|
conn.dialogue.update_system_message(conn.prompt)
|
||||||
|
|
||||||
|
|
||||||
def initialize_hass_handler(conn):
|
def initialize_hass_handler(conn):
|
||||||
global HASS_CACHE
|
ha_config = {}
|
||||||
if HASS_CACHE == {}:
|
if not conn.load_function_plugin:
|
||||||
if conn.load_function_plugin:
|
return ha_config
|
||||||
funcs = conn.config["Intent"][conn.config["selected_module"]["Intent"]].get(
|
|
||||||
"functions", []
|
|
||||||
)
|
|
||||||
if "hass_get_state" in funcs or "hass_set_state" in funcs:
|
|
||||||
HASS_CACHE["base_url"] = conn.config["plugins"]["home_assistant"].get(
|
|
||||||
"base_url"
|
|
||||||
)
|
|
||||||
HASS_CACHE["api_key"] = conn.config["plugins"]["home_assistant"].get(
|
|
||||||
"api_key"
|
|
||||||
)
|
|
||||||
|
|
||||||
check_model_key("home_assistant", HASS_CACHE["api_key"])
|
# 确定配置来源
|
||||||
return HASS_CACHE
|
config_source = (
|
||||||
|
"home_assistant"
|
||||||
|
if conn.config["plugins"].get("home_assistant")
|
||||||
|
else "hass_get_state"
|
||||||
|
)
|
||||||
|
if not conn.config["plugins"].get(config_source):
|
||||||
|
return ha_config
|
||||||
|
|
||||||
|
# 统一获取配置
|
||||||
|
plugin_config = conn.config["plugins"][config_source]
|
||||||
|
ha_config["base_url"] = plugin_config.get("base_url")
|
||||||
|
ha_config["api_key"] = plugin_config.get("api_key")
|
||||||
|
|
||||||
|
# 统一检查API密钥
|
||||||
|
model_key_msg = check_model_key("home_assistant", ha_config.get("api_key"))
|
||||||
|
if model_key_msg:
|
||||||
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
|
|
||||||
|
return ha_config
|
||||||
|
|||||||
@@ -17,46 +17,43 @@ hass_play_music_function_desc = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"media_content_id": {
|
"media_content_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "可以是音乐或有声书的专辑名称、歌曲名、演唱者,如果未指定就填random"
|
"description": "可以是音乐或有声书的专辑名称、歌曲名、演唱者,如果未指定就填random",
|
||||||
},
|
},
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "需要操作的音箱的设备id,homeassistant里的entity_id,media_player开头"
|
"description": "需要操作的音箱的设备id,homeassistant里的entity_id,media_player开头",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"required": ["media_content_id", "entity_id"]
|
"required": ["media_content_id", "entity_id"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_function('hass_play_music', hass_play_music_function_desc, ToolType.SYSTEM_CTL)
|
@register_function(
|
||||||
def hass_play_music(conn, entity_id='', media_content_id='random'):
|
"hass_play_music", hass_play_music_function_desc, ToolType.SYSTEM_CTL
|
||||||
|
)
|
||||||
|
def hass_play_music(conn, entity_id="", media_content_id="random"):
|
||||||
try:
|
try:
|
||||||
# 执行音乐播放命令
|
# 执行音乐播放命令
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
handle_hass_play_music(conn, entity_id, media_content_id),
|
handle_hass_play_music(conn, entity_id, media_content_id), conn.loop
|
||||||
conn.loop
|
|
||||||
)
|
)
|
||||||
ha_response = future.result()
|
ha_response = future.result()
|
||||||
return ActionResponse(action=Action.RESPONSE, result="退出意图已处理", response=ha_response)
|
return ActionResponse(
|
||||||
|
action=Action.RESPONSE, result="退出意图已处理", response=ha_response
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
|
logger.bind(tag=TAG).error(f"处理音乐意图错误: {e}")
|
||||||
|
|
||||||
|
|
||||||
async def handle_hass_play_music(conn, entity_id, media_content_id):
|
async def handle_hass_play_music(conn, entity_id, media_content_id):
|
||||||
HASS_CACHE = initialize_hass_handler(conn)
|
ha_config = initialize_hass_handler(conn)
|
||||||
api_key = HASS_CACHE['api_key']
|
api_key = ha_config.get("api_key")
|
||||||
base_url = HASS_CACHE['base_url']
|
base_url = ha_config.get("base_url")
|
||||||
url = f"{base_url}/api/services/music_assistant/play_media"
|
url = f"{base_url}/api/services/music_assistant/play_media"
|
||||||
headers = {
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||||
"Authorization": f"Bearer {api_key}",
|
data = {"entity_id": entity_id, "media_id": media_content_id}
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
"entity_id": entity_id,
|
|
||||||
"media_id": media_content_id
|
|
||||||
}
|
|
||||||
response = requests.post(url, headers=headers, json=data)
|
response = requests.post(url, headers=headers, json=data)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return f"正在播放{media_content_id}的音乐"
|
return f"正在播放{media_content_id}的音乐"
|
||||||
|
|||||||
@@ -20,40 +20,39 @@ hass_set_state_function_desc = {
|
|||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加音量:volume_up,降低音量:volume_down,设置音量:volume_set,设置色温:set_kelvin,设置颜色:set_color,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute"
|
"description": "需要操作的动作,打开设备:turn_on,关闭设备:turn_off,增加亮度:brightness_up,降低亮度:brightness_down,设置亮度:brightness_value,增加音量:volume_up,降低音量:volume_down,设置音量:volume_set,设置色温:set_kelvin,设置颜色:set_color,设备暂停:pause,设备继续:continue,静音/取消静音:volume_mute",
|
||||||
},
|
},
|
||||||
"input": {
|
"input": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%"
|
"description": "只有在设置音量,设置亮度时候才需要,有效值为1-100,对应音量和亮度的1%-100%",
|
||||||
},
|
},
|
||||||
"is_muted": {
|
"is_muted": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "只有在设置静音操作时才需要,设置静音的时候该值为true,取消静音时该值为false"
|
"description": "只有在设置静音操作时才需要,设置静音的时候该值为true,取消静音时该值为false",
|
||||||
|
},
|
||||||
|
"rgb_color": {
|
||||||
|
"type": "list",
|
||||||
|
"description": "只有在设置颜色时需要,这里填目标颜色的rgb值",
|
||||||
},
|
},
|
||||||
"rgb_color":{
|
|
||||||
"type":"list",
|
|
||||||
"description": "只有在设置颜色时需要,这里填目标颜色的rgb值"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"required": ["type"]
|
"required": ["type"],
|
||||||
},
|
},
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "需要操作的设备id,homeassistant里的entity_id"
|
"description": "需要操作的设备id,homeassistant里的entity_id",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"required": ["state", "entity_id"]
|
"required": ["state", "entity_id"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@register_function('hass_set_state', hass_set_state_function_desc, ToolType.SYSTEM_CTL)
|
@register_function("hass_set_state", hass_set_state_function_desc, ToolType.SYSTEM_CTL)
|
||||||
def hass_set_state(conn, entity_id='', state={}):
|
def hass_set_state(conn, entity_id="", state={}):
|
||||||
try:
|
try:
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
handle_hass_set_state(conn, entity_id, state),
|
handle_hass_set_state(conn, entity_id, state), conn.loop
|
||||||
conn.loop
|
|
||||||
)
|
)
|
||||||
ha_response = future.result()
|
ha_response = future.result()
|
||||||
return ActionResponse(Action.REQLLM, ha_response, None)
|
return ActionResponse(Action.REQLLM, ha_response, None)
|
||||||
@@ -62,21 +61,21 @@ def hass_set_state(conn, entity_id='', state={}):
|
|||||||
|
|
||||||
|
|
||||||
async def handle_hass_set_state(conn, entity_id, state):
|
async def handle_hass_set_state(conn, entity_id, state):
|
||||||
HASS_CACHE = initialize_hass_handler(conn)
|
ha_config = initialize_hass_handler(conn)
|
||||||
api_key = HASS_CACHE['api_key']
|
api_key = ha_config.get("api_key")
|
||||||
base_url = HASS_CACHE['base_url']
|
base_url = ha_config.get("base_url")
|
||||||
'''
|
"""
|
||||||
state = { "type":"brightness_up","input":"80","is_muted":"true"}
|
state = { "type":"brightness_up","input":"80","is_muted":"true"}
|
||||||
'''
|
"""
|
||||||
domains = entity_id.split(".")
|
domains = entity_id.split(".")
|
||||||
if len(domains) > 1:
|
if len(domains) > 1:
|
||||||
domain = domains[0]
|
domain = domains[0]
|
||||||
else:
|
else:
|
||||||
return "执行失败,错误的设备id"
|
return "执行失败,错误的设备id"
|
||||||
action = ''
|
action = ""
|
||||||
arg = ''
|
arg = ""
|
||||||
value = ''
|
value = ""
|
||||||
if state['type'] == 'turn_on':
|
if state["type"] == "turn_on":
|
||||||
description = "设备已打开"
|
description = "设备已打开"
|
||||||
if domain == "cover":
|
if domain == "cover":
|
||||||
action = "open_cover"
|
action = "open_cover"
|
||||||
@@ -84,91 +83,87 @@ async def handle_hass_set_state(conn, entity_id, state):
|
|||||||
action = "start"
|
action = "start"
|
||||||
else:
|
else:
|
||||||
action = "turn_on"
|
action = "turn_on"
|
||||||
elif state['type'] == 'turn_off':
|
elif state["type"] == "turn_off":
|
||||||
description = "设备已关闭"
|
description = "设备已关闭"
|
||||||
if domain == 'cover':
|
if domain == "cover":
|
||||||
action = "close_cover"
|
action = "close_cover"
|
||||||
elif domain == 'vacuum':
|
elif domain == "vacuum":
|
||||||
action = "stop"
|
action = "stop"
|
||||||
else:
|
else:
|
||||||
action = "turn_off"
|
action = "turn_off"
|
||||||
elif state['type'] == 'brightness_up':
|
elif state["type"] == "brightness_up":
|
||||||
description = "灯光已调亮"
|
description = "灯光已调亮"
|
||||||
action = 'turn_on'
|
action = "turn_on"
|
||||||
arg = 'brightness_step_pct'
|
arg = "brightness_step_pct"
|
||||||
value = 10
|
value = 10
|
||||||
elif state['type'] == 'brightness_down':
|
elif state["type"] == "brightness_down":
|
||||||
description = "灯光已调暗"
|
description = "灯光已调暗"
|
||||||
action = 'turn_on'
|
action = "turn_on"
|
||||||
arg = 'brightness_step_pct'
|
arg = "brightness_step_pct"
|
||||||
value = -10
|
value = -10
|
||||||
elif state['type'] == 'brightness_value':
|
elif state["type"] == "brightness_value":
|
||||||
description = f"亮度已调整到{state['input']}"
|
description = f"亮度已调整到{state['input']}"
|
||||||
action = 'turn_on'
|
action = "turn_on"
|
||||||
arg = 'brightness_pct'
|
arg = "brightness_pct"
|
||||||
value = state['input']
|
value = state["input"]
|
||||||
elif state['type'] == 'set_color':
|
elif state["type"] == "set_color":
|
||||||
description = f"颜色已调整到{state['rgb_color']}"
|
description = f"颜色已调整到{state['rgb_color']}"
|
||||||
action = 'turn_on'
|
action = "turn_on"
|
||||||
arg = 'rgb_color'
|
arg = "rgb_color"
|
||||||
value = state['rgb_color']
|
value = state["rgb_color"]
|
||||||
elif state['type'] == 'set_kelvin':
|
elif state["type"] == "set_kelvin":
|
||||||
description = f"色温已调整到{state['input']}K"
|
description = f"色温已调整到{state['input']}K"
|
||||||
action = 'turn_on'
|
action = "turn_on"
|
||||||
arg = 'kelvin'
|
arg = "kelvin"
|
||||||
value = state['input']
|
value = state["input"]
|
||||||
elif state['type'] == 'volume_up':
|
elif state["type"] == "volume_up":
|
||||||
description = "音量已调大"
|
description = "音量已调大"
|
||||||
action = state['type']
|
action = state["type"]
|
||||||
elif state['type'] == 'volume_down':
|
elif state["type"] == "volume_down":
|
||||||
description = "音量已调小"
|
description = "音量已调小"
|
||||||
action = state['type']
|
action = state["type"]
|
||||||
elif state['type'] == 'volume_set':
|
elif state["type"] == "volume_set":
|
||||||
description = f"音量已调整到{state['input']}"
|
description = f"音量已调整到{state['input']}"
|
||||||
action = state['type']
|
action = state["type"]
|
||||||
arg = 'volume_level'
|
arg = "volume_level"
|
||||||
value = state['input']
|
value = state["input"]
|
||||||
if state['input'] >= 1:
|
if state["input"] >= 1:
|
||||||
value = state['input']/100
|
value = state["input"] / 100
|
||||||
elif state['type'] == 'volume_mute':
|
elif state["type"] == "volume_mute":
|
||||||
description = f"设备已静音"
|
description = f"设备已静音"
|
||||||
action = state['type']
|
action = state["type"]
|
||||||
arg = 'is_volume_muted'
|
arg = "is_volume_muted"
|
||||||
value = state['is_muted']
|
value = state["is_muted"]
|
||||||
elif state['type'] == 'pause':
|
elif state["type"] == "pause":
|
||||||
description = f"设备已暂停"
|
description = f"设备已暂停"
|
||||||
action = state['type']
|
action = state["type"]
|
||||||
if domain == 'media_player':
|
if domain == "media_player":
|
||||||
action = 'media_pause'
|
action = "media_pause"
|
||||||
if domain == 'cover':
|
if domain == "cover":
|
||||||
action = 'stop_cover'
|
action = "stop_cover"
|
||||||
if domain == 'vacuum':
|
if domain == "vacuum":
|
||||||
action = 'pause'
|
action = "pause"
|
||||||
elif state['type'] == 'continue':
|
elif state["type"] == "continue":
|
||||||
description = f"设备已继续"
|
description = f"设备已继续"
|
||||||
if domain == 'media_player':
|
if domain == "media_player":
|
||||||
action = 'media_play'
|
action = "media_play"
|
||||||
if domain == 'vacuum':
|
if domain == "vacuum":
|
||||||
action = 'start'
|
action = "start"
|
||||||
else:
|
else:
|
||||||
return f"{domain} {state.type}功能尚未支持"
|
return f"{domain} {state.type}功能尚未支持"
|
||||||
|
|
||||||
if arg == '':
|
if arg == "":
|
||||||
data = {
|
data = {
|
||||||
"entity_id": entity_id,
|
"entity_id": entity_id,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
data = {
|
data = {"entity_id": entity_id, arg: value}
|
||||||
"entity_id": entity_id,
|
|
||||||
arg: value
|
|
||||||
}
|
|
||||||
url = f"{base_url}/api/services/{domain}/{action}"
|
url = f"{base_url}/api/services/{domain}/{action}"
|
||||||
headers = {
|
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
||||||
"Authorization": f"Bearer {api_key}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
response = requests.post(url, headers=headers, json=data)
|
response = requests.post(url, headers=headers, json=data)
|
||||||
logger.bind(tag=TAG).info(f"设置状态:{description},url:{url},return_code:{response.status_code}")
|
logger.bind(tag=TAG).info(
|
||||||
|
f"设置状态:{description},url:{url},return_code:{response.status_code}"
|
||||||
|
)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return description
|
return description
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user