mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
Merge remote-tracking branch 'upstream/main' into update/enhance-gemini-with-proxy
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
package xiaozhi.common.constant;
|
package xiaozhi.common.constant;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 常量
|
* 常量
|
||||||
* Copyright (c) 人人开源 All rights reserved.
|
* Copyright (c) 人人开源 All rights reserved.
|
||||||
@@ -109,6 +111,11 @@ public interface Constant {
|
|||||||
*/
|
*/
|
||||||
String FILE_EXTENSION_SEG = ".";
|
String FILE_EXTENSION_SEG = ".";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无记忆
|
||||||
|
*/
|
||||||
|
String MEMORY_NO_MEM = "Memory_nomem";
|
||||||
|
|
||||||
enum SysBaseParam {
|
enum SysBaseParam {
|
||||||
/**
|
/**
|
||||||
* 系统全称
|
* 系统全称
|
||||||
@@ -174,8 +181,23 @@ public interface Constant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
enum ChatHistoryConfEnum {
|
||||||
|
IGNORE(0, "不记录"),
|
||||||
|
RECORD_TEXT(1, "记录文本"),
|
||||||
|
RECORD_TEXT_AUDIO(2, "文本音频都记录");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
ChatHistoryConfEnum(int code, String name) {
|
||||||
|
this.code = code;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本号
|
* 版本号
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = "0.4.2";
|
public static final String VERSION = "0.4.3";
|
||||||
}
|
}
|
||||||
+14
-1
@@ -109,6 +109,7 @@ public class AgentController {
|
|||||||
entity.setMemModelId(template.getMemModelId());
|
entity.setMemModelId(template.getMemModelId());
|
||||||
entity.setIntentModelId(template.getIntentModelId());
|
entity.setIntentModelId(template.getIntentModelId());
|
||||||
entity.setSystemPrompt(template.getSystemPrompt());
|
entity.setSystemPrompt(template.getSystemPrompt());
|
||||||
|
entity.setChatHistoryConf(template.getChatHistoryConf());
|
||||||
entity.setLangCode(template.getLangCode());
|
entity.setLangCode(template.getLangCode());
|
||||||
entity.setLanguage(template.getLanguage());
|
entity.setLanguage(template.getLanguage());
|
||||||
}
|
}
|
||||||
@@ -166,6 +167,9 @@ public class AgentController {
|
|||||||
if (dto.getSystemPrompt() != null) {
|
if (dto.getSystemPrompt() != null) {
|
||||||
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
existingEntity.setSystemPrompt(dto.getSystemPrompt());
|
||||||
}
|
}
|
||||||
|
if (dto.getChatHistoryConf() != null) {
|
||||||
|
existingEntity.setChatHistoryConf(dto.getChatHistoryConf());
|
||||||
|
}
|
||||||
if (dto.getLangCode() != null) {
|
if (dto.getLangCode() != null) {
|
||||||
existingEntity.setLangCode(dto.getLangCode());
|
existingEntity.setLangCode(dto.getLangCode());
|
||||||
}
|
}
|
||||||
@@ -183,6 +187,15 @@ public class AgentController {
|
|||||||
|
|
||||||
agentService.updateById(existingEntity);
|
agentService.updateById(existingEntity);
|
||||||
|
|
||||||
|
// 更新记忆策略
|
||||||
|
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
|
// 删除所有记录
|
||||||
|
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
|
||||||
|
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
|
||||||
|
// 删除音频数据
|
||||||
|
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
|
||||||
|
}
|
||||||
|
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +206,7 @@ public class AgentController {
|
|||||||
// 先删除关联的设备
|
// 先删除关联的设备
|
||||||
deviceService.deleteByAgentId(id);
|
deviceService.deleteByAgentId(id);
|
||||||
// 删除关联的聊天记录
|
// 删除关联的聊天记录
|
||||||
agentChatHistoryService.deleteByAgentId(id);
|
agentChatHistoryService.deleteByAgentId(id, true, true);
|
||||||
// 再删除智能体
|
// 再删除智能体
|
||||||
agentService.deleteById(id);
|
agentService.deleteById(id);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
|
|||||||
@@ -28,4 +28,11 @@ public interface AiAgentChatHistoryDao extends BaseMapper<AgentChatHistoryEntity
|
|||||||
* @param agentId 智能体ID
|
* @param agentId 智能体ID
|
||||||
*/
|
*/
|
||||||
void deleteHistoryByAgentId(String agentId);
|
void deleteHistoryByAgentId(String agentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据智能体ID删除音频ID
|
||||||
|
*
|
||||||
|
* @param agentId 智能体ID
|
||||||
|
*/
|
||||||
|
void deleteAudioIdByAgentId(String agentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ public class AgentUpdateDTO implements Serializable {
|
|||||||
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
|
@Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false)
|
||||||
private String systemPrompt;
|
private String systemPrompt;
|
||||||
|
|
||||||
|
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false)
|
||||||
|
private Integer chatHistoryConf;
|
||||||
|
|
||||||
@Schema(description = "语言编码", example = "zh_CN", required = false)
|
@Schema(description = "语言编码", example = "zh_CN", required = false)
|
||||||
private String langCode;
|
private String langCode;
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ public class AgentEntity {
|
|||||||
@Schema(description = "意图模型标识")
|
@Schema(description = "意图模型标识")
|
||||||
private String intentModelId;
|
private String intentModelId;
|
||||||
|
|
||||||
|
@Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)")
|
||||||
|
private Integer chatHistoryConf;
|
||||||
|
|
||||||
@Schema(description = "角色设定参数")
|
@Schema(description = "角色设定参数")
|
||||||
private String systemPrompt;
|
private String systemPrompt;
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,11 @@ public class AgentTemplateEntity implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String intentModelId;
|
private String intentModelId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)
|
||||||
|
*/
|
||||||
|
private Integer chatHistoryConf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色设定参数
|
* 角色设定参数
|
||||||
*/
|
*/
|
||||||
|
|||||||
+4
-2
@@ -39,7 +39,9 @@ public interface AgentChatHistoryService extends IService<AgentChatHistoryEntity
|
|||||||
/**
|
/**
|
||||||
* 根据智能体ID删除聊天记录
|
* 根据智能体ID删除聊天记录
|
||||||
*
|
*
|
||||||
* @param agentId 智能体ID
|
* @param agentId 智能体ID
|
||||||
|
* @param deleteAudio 是否删除音频
|
||||||
|
* @param deleteText 是否删除文本
|
||||||
*/
|
*/
|
||||||
void deleteByAgentId(String agentId);
|
void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText);
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-15
@@ -2,12 +2,14 @@ package xiaozhi.modules.agent.service.biz.impl;
|
|||||||
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
import xiaozhi.common.redis.RedisUtils;
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
|
import xiaozhi.modules.agent.dto.AgentChatHistoryReportDTO;
|
||||||
@@ -47,8 +49,33 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
|
|||||||
Byte chatType = report.getChatType();
|
Byte chatType = report.getChatType();
|
||||||
log.info("小智设备聊天上报请求: macAddress={}, type={}", macAddress, chatType);
|
log.info("小智设备聊天上报请求: macAddress={}, type={}", macAddress, chatType);
|
||||||
|
|
||||||
// 1. base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表
|
// 根据设备MAC地址查询对应的默认智能体,判断是否需要上报
|
||||||
|
AgentEntity agentEntity = agentService.getDefaultAgentByMacAddress(macAddress);
|
||||||
|
if (agentEntity == null) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer chatHistoryConf = agentEntity.getChatHistoryConf();
|
||||||
|
String agentId = agentEntity.getId();
|
||||||
|
|
||||||
|
if (Objects.equals(chatHistoryConf, Constant.ChatHistoryConfEnum.RECORD_TEXT.getCode())) {
|
||||||
|
saveChatText(report, agentId, macAddress, null);
|
||||||
|
} else if (Objects.equals(chatHistoryConf, Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode())) {
|
||||||
|
String audioId = saveChatAudio(report);
|
||||||
|
saveChatText(report, agentId, macAddress, audioId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新设备最后对话时间
|
||||||
|
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date());
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base64解码report.getOpusDataBase64(),存入ai_agent_chat_audio表
|
||||||
|
*/
|
||||||
|
private String saveChatAudio(AgentChatHistoryReportDTO report) {
|
||||||
String audioId = null;
|
String audioId = null;
|
||||||
|
|
||||||
if (report.getAudioBase64() != null && !report.getAudioBase64().isEmpty()) {
|
if (report.getAudioBase64() != null && !report.getAudioBase64().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
byte[] audioData = Base64.getDecoder().decode(report.getAudioBase64());
|
byte[] audioData = Base64.getDecoder().decode(report.getAudioBase64());
|
||||||
@@ -56,20 +83,18 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
|
|||||||
log.info("音频数据保存成功,audioId={}", audioId);
|
log.info("音频数据保存成功,audioId={}", audioId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("音频数据保存失败", e);
|
log.error("音频数据保存失败", e);
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return audioId;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 组装上报数据
|
/**
|
||||||
// 2.1 根据设备MAC地址查询对应的默认智能体,判断是否需要上报
|
* 组装上报数据
|
||||||
AgentEntity agentEntity = agentService.getDefaultAgentByMacAddress(macAddress);
|
*/
|
||||||
if (agentEntity == null) {
|
private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String agentId = agentEntity.getId();
|
|
||||||
log.info("设备 {} 对应智能体 {} 上报", macAddress, agentEntity.getId());
|
|
||||||
|
|
||||||
// 2.2 构建聊天记录实体
|
// 构建聊天记录实体
|
||||||
AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder()
|
AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder()
|
||||||
.macAddress(macAddress)
|
.macAddress(macAddress)
|
||||||
.agentId(agentId)
|
.agentId(agentId)
|
||||||
@@ -79,10 +104,9 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
|
|||||||
.audioId(audioId)
|
.audioId(audioId)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 3. 保存数据
|
// 保存数据
|
||||||
agentChatHistoryService.save(entity);
|
agentChatHistoryService.save(entity);
|
||||||
// 4. 更新设备最后对话时间
|
|
||||||
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date());
|
log.info("设备 {} 对应智能体 {} 上报成功", macAddress, agentId);
|
||||||
return Boolean.TRUE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -78,8 +78,16 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void deleteByAgentId(String agentId) {
|
public void deleteByAgentId(String agentId, Boolean deleteAudio, Boolean deleteText) {
|
||||||
baseMapper.deleteAudioByAgentId(agentId);
|
if (deleteAudio) {
|
||||||
baseMapper.deleteHistoryByAgentId(agentId);
|
baseMapper.deleteAudioByAgentId(agentId);
|
||||||
|
}
|
||||||
|
if (deleteAudio && !deleteText) {
|
||||||
|
baseMapper.deleteAudioIdByAgentId(agentId);
|
||||||
|
}
|
||||||
|
if (deleteText) {
|
||||||
|
baseMapper.deleteHistoryByAgentId(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
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;
|
||||||
@@ -46,7 +47,15 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AgentEntity getAgentById(String id) {
|
public AgentEntity getAgentById(String id) {
|
||||||
return agentDao.selectById(id);
|
AgentEntity agent = agentDao.selectById(id);
|
||||||
|
if (agent != null && agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode());
|
||||||
|
} else if (agent != null && agent.getMemModelId() != null
|
||||||
|
&& !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)
|
||||||
|
&& agent.getChatHistoryConf() == null) {
|
||||||
|
agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode());
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+12
@@ -9,6 +9,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.redis.RedisKeys;
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
@@ -108,6 +109,17 @@ public class ConfigServiceImpl implements ConfigService {
|
|||||||
// 获取单台设备每天最多输出字数
|
// 获取单台设备每天最多输出字数
|
||||||
String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true);
|
String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true);
|
||||||
result.put("device_max_output_size", deviceMaxOutputSize);
|
result.put("device_max_output_size", deviceMaxOutputSize);
|
||||||
|
|
||||||
|
// 获取聊天记录配置
|
||||||
|
Integer chatHistoryConf = agent.getChatHistoryConf();
|
||||||
|
if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||||
|
chatHistoryConf = Constant.ChatHistoryConfEnum.IGNORE.getCode();
|
||||||
|
} else if (agent.getMemModelId() != null
|
||||||
|
&& !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)
|
||||||
|
&& agent.getChatHistoryConf() == null) {
|
||||||
|
chatHistoryConf = Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode();
|
||||||
|
}
|
||||||
|
result.put("chat_history_conf", chatHistoryConf);
|
||||||
// 如果客户端已实例化模型,则不返回
|
// 如果客户端已实例化模型,则不返回
|
||||||
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
|
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
|
||||||
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- 添加聊天记录配置字段
|
||||||
|
ALTER TABLE `ai_agent`
|
||||||
|
ADD COLUMN `chat_history_conf` tinyint NOT NULL DEFAULT 0 COMMENT '聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)' AFTER `system_prompt`;
|
||||||
|
|
||||||
|
ALTER TABLE `ai_agent_template`
|
||||||
|
ADD COLUMN `chat_history_conf` tinyint NOT NULL DEFAULT 0 COMMENT '聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)' AFTER `system_prompt`;
|
||||||
@@ -113,4 +113,11 @@ databaseChangeLog:
|
|||||||
changes:
|
changes:
|
||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202505091409.sql
|
path: classpath:db/changelog/202505091409.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202505111914
|
||||||
|
author: hrz
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202505111914.sql
|
||||||
@@ -28,11 +28,17 @@
|
|||||||
SELECT audio_id
|
SELECT audio_id
|
||||||
FROM ai_agent_chat_history
|
FROM ai_agent_chat_history
|
||||||
WHERE agent_id = #{agentId}
|
WHERE agent_id = #{agentId}
|
||||||
);
|
)
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<update id="deleteAudioIdByAgentId">
|
||||||
|
UPDATE ai_agent_chat_history
|
||||||
|
SET audio_id = NULL
|
||||||
|
WHERE agent_id = #{agentId}
|
||||||
|
</update>
|
||||||
|
|
||||||
<delete id="deleteHistoryByAgentId">
|
<delete id="deleteHistoryByAgentId">
|
||||||
DELETE FROM ai_agent_chat_history
|
DELETE FROM ai_agent_chat_history
|
||||||
WHERE agent_id = #{agentId};
|
WHERE agent_id = #{agentId}
|
||||||
</delete>
|
</delete>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export default {
|
|||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
// 获取智能体配置
|
// 获取智能体配置
|
||||||
getDeviceConfig(deviceId, callback) {
|
getDeviceConfig(agentId, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/agent/${deviceId}`)
|
.url(`${getServiceUrl()}/agent/${agentId}`)
|
||||||
.method('GET')
|
.method('GET')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime();
|
RequestService.clearRequestTime();
|
||||||
@@ -61,7 +61,7 @@ export default {
|
|||||||
.fail((err) => {
|
.fail((err) => {
|
||||||
console.error('获取配置失败:', err);
|
console.error('获取配置失败:', err);
|
||||||
RequestService.reAjaxFun(() => {
|
RequestService.reAjaxFun(() => {
|
||||||
this.getDeviceConfig(deviceId, callback);
|
this.getDeviceConfig(agentId, callback);
|
||||||
});
|
});
|
||||||
}).send();
|
}).send();
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 101 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 88 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :title="title" :visible.sync="dialogVisible" width="30%" @close="handleClose" @open="handleOpen">
|
<el-dialog :title="title" :visible.sync="dialogVisible" @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>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<el-progress v-if="isUploading || uploadStatus === 'success'" :percentage="uploadProgress"
|
<el-progress v-if="isUploading || uploadStatus === 'success'" :percentage="uploadProgress"
|
||||||
:status="uploadStatus"></el-progress>
|
:status="uploadStatus"></el-progress>
|
||||||
<div class="hint-text">
|
<div class="hint-text">
|
||||||
<span>温馨提示:请上传xiaozhi.bin文件,而不是merged-binary.bin文件</span>
|
<span>温馨提示:请上传合并前的xiaozhi.bin文件,而不是合并后的merged-binary.bin文件</span>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
@@ -229,7 +229,6 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: #979db1;
|
font-size: 14px;
|
||||||
font-size: 11px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer :visible.sync="dialogVisible" direction="rtl" size="50%" :wrapperClosable="false" :withHeader="false">
|
||||||
|
<!-- 自定义标题区域 -->
|
||||||
|
<div class="custom-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<h3 class="bold-title">功能管理</h3>
|
||||||
|
</div>
|
||||||
|
<button class="custom-close-btn" @click="closeDialog">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="function-manager">
|
||||||
|
<!-- 左侧:未选功能 -->
|
||||||
|
<div class="function-column">
|
||||||
|
<div class="column-header">
|
||||||
|
<h4 class="column-title">未选功能</h4>
|
||||||
|
<el-button type="text" @click="selectAll" class="select-all-btn">全选</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="function-list">
|
||||||
|
<div v-for="func in unselected" :key="func.name" class="function-item">
|
||||||
|
<el-checkbox :label="func.name" v-model="selectedNames" @change="(val) => handleCheckboxChange(func, val)" @click.native.stop></el-checkbox>
|
||||||
|
<div class="func-tag" @click="handleFunctionClick(func)">
|
||||||
|
<div class="color-dot" :style="{backgroundColor: getFunctionColor(func.name)}"></div>
|
||||||
|
<span>{{ func.name }}</span>
|
||||||
|
</div>
|
||||||
|
<el-tooltip class="item" effect="dark" :content="func.description || '暂无功能描述'" placement="top">
|
||||||
|
<img src="@/assets/home/info.png" alt="" class="info-icon">
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 中间:已选功能 -->
|
||||||
|
<div class="function-column">
|
||||||
|
<div class="column-header">
|
||||||
|
<h4 class="column-title">已选功能</h4>
|
||||||
|
<el-button type="text" @click="deselectAll" class="select-all-btn">全选</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="function-list">
|
||||||
|
<div v-for="func in selectedList" :key="func.name" class="function-item">
|
||||||
|
<el-checkbox :label="func.name" v-model="selectedNames" @change="(val) => handleCheckboxChange(func, val)" @click.native.stop></el-checkbox>
|
||||||
|
<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 class="params-column">
|
||||||
|
<h4 v-if="currentFunction" class="column-title">参数配置 - {{ currentFunction.name }}</h4>
|
||||||
|
<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-item v-for="(value, key) in currentFunction.params" :key="key" :label="key" class="param-item">
|
||||||
|
<el-input v-model="currentFunction.params[key]" size="mini" class="param-input" @change="(val) => handleParamChange(currentFunction, key, val)"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div v-else class="empty-tip">请选择已配置的功能进行参数设置</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="drawer-footer">
|
||||||
|
<el-button @click="closeDialog">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveSelection">保存配置</el-button>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Boolean,
|
||||||
|
functions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialogVisible: this.value,
|
||||||
|
selectedNames: [],
|
||||||
|
currentFunction: null,
|
||||||
|
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: [
|
||||||
|
'#FF6B6B', '#4ECDC4', '#45B7D1',
|
||||||
|
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
|
||||||
|
],
|
||||||
|
tempFunctions: {},
|
||||||
|
// 添加一个标志位来跟踪是否已经保存
|
||||||
|
hasSaved: false,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedList() {
|
||||||
|
return this.allFunctions.filter(f => this.selectedNames.includes(f.name));
|
||||||
|
},
|
||||||
|
unselected() {
|
||||||
|
return this.allFunctions.filter(f => !this.selectedNames.includes(f.name));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newVal) {
|
||||||
|
this.dialogVisible = newVal;
|
||||||
|
if (newVal) {
|
||||||
|
this.selectedNames = this.functions.map(f => f.name);
|
||||||
|
this.currentFunction = this.selectedList[0] || null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dialogVisible(newVal) {
|
||||||
|
this.$emit('input', newVal);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleFunctionClick(func) {
|
||||||
|
if (this.selectedNames.includes(func.name)) {
|
||||||
|
this.loading = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
const tempFunc = this.tempFunctions[func.name];
|
||||||
|
this.currentFunction = tempFunc ? tempFunc : JSON.parse(JSON.stringify(func));
|
||||||
|
this.loading = false;
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleParamChange(func, key, value) {
|
||||||
|
if (!this.tempFunctions[func.name]) {
|
||||||
|
this.tempFunctions[func.name] = JSON.parse(JSON.stringify(func));
|
||||||
|
}
|
||||||
|
this.tempFunctions[func.name].params[key] = value;
|
||||||
|
},
|
||||||
|
handleCheckboxChange(func, checked) {
|
||||||
|
if (checked) {
|
||||||
|
if (!this.selectedNames.includes(func.name)) {
|
||||||
|
this.selectedNames = [...this.selectedNames, func.name];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedNames = this.selectedNames.filter(name => name !== func.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedList.length > 0) {
|
||||||
|
this.currentFunction = this.selectedList[0];
|
||||||
|
} else {
|
||||||
|
this.currentFunction = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectAll() {
|
||||||
|
this.selectedNames = [...this.allFunctions.map(f => f.name)];
|
||||||
|
if (this.selectedList.length > 0) {
|
||||||
|
this.currentFunction = JSON.parse(JSON.stringify(this.selectedList[0]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deselectAll() {
|
||||||
|
this.selectedNames = [];
|
||||||
|
this.currentFunction = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
closeDialog() {
|
||||||
|
this.tempFunctions = {};
|
||||||
|
this.selectedNames = this.functions.map(f => f.name);
|
||||||
|
this.currentFunction = null;
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.$emit('input', false);
|
||||||
|
this.$emit('dialog-closed', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
saveSelection() {
|
||||||
|
Object.keys(this.tempFunctions).forEach(name => {
|
||||||
|
this.modifiedFunctions[name] = JSON.parse(JSON.stringify(this.tempFunctions[name]));
|
||||||
|
});
|
||||||
|
this.tempFunctions = {};
|
||||||
|
this.hasSaved = true;
|
||||||
|
|
||||||
|
const selected = this.selectedList.map(f => {
|
||||||
|
const modified = this.modifiedFunctions[f.name];
|
||||||
|
return modified || f;
|
||||||
|
}).map(f => ({
|
||||||
|
...f,
|
||||||
|
params: JSON.parse(JSON.stringify(f.params))
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.$emit('update-functions', selected);
|
||||||
|
this.dialogVisible = false;
|
||||||
|
this.$message.success('配置保存成功');
|
||||||
|
// 通知父组件对话框已关闭且已保存
|
||||||
|
this.$emit('dialog-closed', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
getFunctionColor(name) {
|
||||||
|
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
|
return this.functionColorMap[hash % 7];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.function-manager {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(120px, 0.5fr) minmax(120px, 0.5fr) minmax(200px, 2fr);
|
||||||
|
gap: 12px;
|
||||||
|
height: calc(70vh - 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-header {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #EBEEF5;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bold-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-all-btn {
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-column {
|
||||||
|
position: relative;
|
||||||
|
width: auto;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-right: 1px solid #EBEEF5;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-column::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 4px 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-column {
|
||||||
|
min-width: 280px;
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-column::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-title {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.func-tag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-dot {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-form {
|
||||||
|
::v-deep .el-form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.el-form-item__label {
|
||||||
|
font-size: 14px !important;
|
||||||
|
color: #606266;
|
||||||
|
text-align: left;
|
||||||
|
padding-right: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item__content {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.params-container {
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
padding: 20px;
|
||||||
|
color: #909399;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
padding: 10px 16px;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 1vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #cfcfcf;
|
||||||
|
background: none;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: lighter;
|
||||||
|
color: #cfcfcf;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-close-btn:hover {
|
||||||
|
color: #409EFF;
|
||||||
|
border-color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-checkbox__label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -60,12 +60,44 @@
|
|||||||
<div class="form-column">
|
<div class="form-column">
|
||||||
<el-form-item v-for="(model, index) in models" :key="`model-${index}`" :label="model.label"
|
<el-form-item v-for="(model, index) in models" :key="`model-${index}`" :label="model.label"
|
||||||
class="model-item">
|
class="model-item">
|
||||||
<el-select v-model="form.model[model.key]" filterable placeholder="请选择" class="form-select">
|
<div class="model-select-wrapper">
|
||||||
<el-option v-for="(item, optionIndex) in modelOptions[model.type]"
|
<el-select v-model="form.model[model.key]" filterable placeholder="请选择" class="form-select"
|
||||||
:key="`option-${index}-${optionIndex}`" :label="item.label" :value="item.value" />
|
@change="handleModelChange(model.type, $event)">
|
||||||
</el-select>
|
<el-option v-for="(item, optionIndex) in modelOptions[model.type]"
|
||||||
|
:key="`option-${index}-${optionIndex}`" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
<div v-if="showFunctionIcons(model.type)" class="function-icons">
|
||||||
|
<el-tooltip v-for="func in currentFunctions" :key="func.name" effect="dark" placement="top"
|
||||||
|
popper-class="custom-tooltip">
|
||||||
|
<div slot="content">
|
||||||
|
<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 class="icon-dot" :style="{ backgroundColor: getFunctionColor(func.name) }">
|
||||||
|
{{ func.name.charAt(0) }}
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button class="edit-function-btn" @click="showFunctionDialog = true"
|
||||||
|
:class="{ 'active-btn': showFunctionDialog }">
|
||||||
|
编辑功能
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="model.type === 'Memory' && form.model.memModelId !== 'Memory_nomem'"
|
||||||
|
class="chat-history-options">
|
||||||
|
<el-radio-group v-model="form.chatHistoryConf" @change="updateChatHistoryConf">
|
||||||
|
<el-radio-button :label="1">上报文字</el-radio-button>
|
||||||
|
<el-radio-button :label="2">上报文字+语音</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="角色音色:">
|
<el-form-item label="角色音色">
|
||||||
<el-select v-model="form.ttsVoiceId" placeholder="请选择" class="form-select">
|
<el-select v-model="form.ttsVoiceId" placeholder="请选择" class="form-select">
|
||||||
<el-option v-for="(item, index) in voiceOptions" :key="`voice-${index}`" :label="item.label"
|
<el-option v-for="(item, index) in voiceOptions" :key="`voice-${index}`" :label="item.label"
|
||||||
:value="item.value" />
|
:value="item.value" />
|
||||||
@@ -75,27 +107,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<function-dialog v-model="showFunctionDialog" :functions="currentFunctions"
|
||||||
|
@update-functions="handleUpdateFunctions" @dialog-closed="handleDialogClosed" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
|
import FunctionDialog from "@/components/FunctionDialog.vue";
|
||||||
import HeaderBar from "@/components/HeaderBar.vue";
|
import HeaderBar from "@/components/HeaderBar.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RoleConfigPage',
|
name: 'RoleConfigPage',
|
||||||
components: { HeaderBar },
|
components: { HeaderBar, FunctionDialog },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
agentCode: "",
|
agentCode: "",
|
||||||
agentName: "",
|
agentName: "",
|
||||||
ttsVoiceId: "",
|
ttsVoiceId: "",
|
||||||
|
chatHistoryConf: 0,
|
||||||
systemPrompt: "",
|
systemPrompt: "",
|
||||||
langCode: "",
|
langCode: "",
|
||||||
language: "",
|
language: "",
|
||||||
@@ -121,6 +157,18 @@ export default {
|
|||||||
templates: [],
|
templates: [],
|
||||||
loadingTemplate: false,
|
loadingTemplate: false,
|
||||||
voiceOptions: [],
|
voiceOptions: [],
|
||||||
|
showFunctionDialog: false,
|
||||||
|
currentFunctions: [],
|
||||||
|
functionColorMap: [
|
||||||
|
'#FF6B6B', '#4ECDC4', '#45B7D1',
|
||||||
|
'#96CEB4', '#FFEEAD', '#D4A5A5', '#A2836E'
|
||||||
|
],
|
||||||
|
allFunctions: [
|
||||||
|
{ name: '天气', params: {} },
|
||||||
|
{ name: '新闻', params: {} },
|
||||||
|
{ name: '工具', params: {} },
|
||||||
|
{ name: '退出', params: {} }
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -136,12 +184,14 @@ export default {
|
|||||||
llmModelId: this.form.model.llmModelId,
|
llmModelId: this.form.model.llmModelId,
|
||||||
ttsModelId: this.form.model.ttsModelId,
|
ttsModelId: this.form.model.ttsModelId,
|
||||||
ttsVoiceId: this.form.ttsVoiceId,
|
ttsVoiceId: this.form.ttsVoiceId,
|
||||||
|
chatHistoryConf: this.form.chatHistoryConf,
|
||||||
memModelId: this.form.model.memModelId,
|
memModelId: this.form.model.memModelId,
|
||||||
intentModelId: this.form.model.intentModelId,
|
intentModelId: this.form.model.intentModelId,
|
||||||
systemPrompt: this.form.systemPrompt,
|
systemPrompt: this.form.systemPrompt,
|
||||||
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
|
||||||
};
|
};
|
||||||
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) {
|
||||||
@@ -167,6 +217,7 @@ export default {
|
|||||||
agentCode: "",
|
agentCode: "",
|
||||||
agentName: "",
|
agentName: "",
|
||||||
ttsVoiceId: "",
|
ttsVoiceId: "",
|
||||||
|
chatHistoryConf: 0,
|
||||||
systemPrompt: "",
|
systemPrompt: "",
|
||||||
langCode: "",
|
langCode: "",
|
||||||
language: "",
|
language: "",
|
||||||
@@ -180,12 +231,12 @@ export default {
|
|||||||
intentModelId: "",
|
intentModelId: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.currentFunctions = [];
|
||||||
this.$message.success({
|
this.$message.success({
|
||||||
message: '配置已重置',
|
message: '配置已重置',
|
||||||
showClose: true
|
showClose: true
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
}).catch(() => { });
|
||||||
});
|
|
||||||
},
|
},
|
||||||
fetchTemplates() {
|
fetchTemplates() {
|
||||||
Api.agent.getAgentTemplate(({ data }) => {
|
Api.agent.getAgentTemplate(({ data }) => {
|
||||||
@@ -220,6 +271,7 @@ export default {
|
|||||||
...this.form,
|
...this.form,
|
||||||
agentName: templateData.agentName || this.form.agentName,
|
agentName: templateData.agentName || this.form.agentName,
|
||||||
ttsVoiceId: templateData.ttsVoiceId || this.form.ttsVoiceId,
|
ttsVoiceId: templateData.ttsVoiceId || this.form.ttsVoiceId,
|
||||||
|
chatHistoryConf: templateData.chatHistoryConf || this.form.chatHistoryConf,
|
||||||
systemPrompt: templateData.systemPrompt || this.form.systemPrompt,
|
systemPrompt: templateData.systemPrompt || this.form.systemPrompt,
|
||||||
langCode: templateData.langCode || this.form.langCode,
|
langCode: templateData.langCode || this.form.langCode,
|
||||||
model: {
|
model: {
|
||||||
@@ -247,6 +299,7 @@ export default {
|
|||||||
intentModelId: data.data.intentModelId
|
intentModelId: data.data.intentModelId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.currentFunctions = data.data.functions || [];
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data.msg || '获取配置失败');
|
this.$message.error(data.msg || '获取配置失败');
|
||||||
}
|
}
|
||||||
@@ -281,7 +334,56 @@ export default {
|
|||||||
this.voiceOptions = [];
|
this.voiceOptions = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
getFunctionColor(name) {
|
||||||
|
const hash = [...name].reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
|
return this.functionColorMap[hash % 7];
|
||||||
|
},
|
||||||
|
showFunctionIcons(type) {
|
||||||
|
// TODO 暂时不放出来
|
||||||
|
return false;
|
||||||
|
// return type === 'Intent' &&
|
||||||
|
// this.form.model.intentModelId !== 'Intent_nointent';
|
||||||
|
},
|
||||||
|
handleModelChange(type, value) {
|
||||||
|
if (type === 'Intent' && value !== 'Intent_nointent') {
|
||||||
|
this.fetchFunctionList();
|
||||||
|
}
|
||||||
|
if (type === 'Memory' && value === 'Memory_nomem') {
|
||||||
|
this.form.chatHistoryConf = 0;
|
||||||
|
}
|
||||||
|
if (type === 'Memory' && value !== 'Memory_nomem' && (this.form.chatHistoryConf === 0 || this.form.chatHistoryConf === null)) {
|
||||||
|
this.form.chatHistoryConf = 2;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchFunctionList() {
|
||||||
|
// 使用假数据代替API调用
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.currentFunctions = [
|
||||||
|
{ name: '天气', params: { city: '北京' } },
|
||||||
|
{ name: '新闻', params: { type: '科技' } }
|
||||||
|
];
|
||||||
|
resolve();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleUpdateFunctions(selected) {
|
||||||
|
this.currentFunctions = selected;
|
||||||
|
console.log('保存的功能列表:', selected);
|
||||||
|
this.$message.success('功能配置已保存');
|
||||||
|
},
|
||||||
|
handleDialogClosed(saved) {
|
||||||
|
if (!saved) {
|
||||||
|
// 如果未保存,恢复原始功能列表
|
||||||
|
this.currentFunctions = JSON.parse(JSON.stringify(this.originalFunctions));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateChatHistoryConf() {
|
||||||
|
if (this.form.model.memModelId === 'Memory_nomem') {
|
||||||
|
this.form.chatHistoryConf = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'form.model.ttsModelId': {
|
'form.model.ttsModelId': {
|
||||||
@@ -308,6 +410,9 @@ 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.originalFunctions = JSON.parse(JSON.stringify(this.currentFunctions));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.fetchModelOptions();
|
this.fetchModelOptions();
|
||||||
this.fetchTemplates();
|
this.fetchTemplates();
|
||||||
@@ -509,8 +614,35 @@ export default {
|
|||||||
height: 19px;
|
height: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.model-select-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.function-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dot {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep .el-form-item__label {
|
::v-deep .el-form-item__label {
|
||||||
font-size: 10px !important;
|
font-size: 12px !important;
|
||||||
color: #3d4566 !important;
|
color: #3d4566 !important;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
@@ -551,4 +683,25 @@ export default {
|
|||||||
color: #409EFF;
|
color: #409EFF;
|
||||||
border-color: #409EFF;
|
border-color: #409EFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-function-btn {
|
||||||
|
background: #e6ebff;
|
||||||
|
color: #5778ff;
|
||||||
|
border: 1px solid #adbdff;
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-function-btn.active-btn {
|
||||||
|
background: #5778ff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-history-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 250px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -4,7 +4,7 @@ from loguru import logger
|
|||||||
from config.config_loader import load_config
|
from config.config_loader import load_config
|
||||||
from config.settings import check_config_file
|
from config.settings import check_config_file
|
||||||
|
|
||||||
SERVER_VERSION = "0.4.2"
|
SERVER_VERSION = "0.4.3"
|
||||||
|
|
||||||
|
|
||||||
def get_module_abbreviation(module_name, module_dict):
|
def get_module_abbreviation(module_name, module_dict):
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class ConnectionHandler:
|
|||||||
self.prompt = None
|
self.prompt = None
|
||||||
self.welcome_msg = None
|
self.welcome_msg = None
|
||||||
self.max_output_size = 0
|
self.max_output_size = 0
|
||||||
|
self.chat_history_conf = 0
|
||||||
|
|
||||||
# 客户端状态相关
|
# 客户端状态相关
|
||||||
self.client_abort = False
|
self.client_abort = False
|
||||||
@@ -250,11 +251,15 @@ class ConnectionHandler:
|
|||||||
self.logger.bind(tag=TAG).info("收到服务器重启指令,准备执行...")
|
self.logger.bind(tag=TAG).info("收到服务器重启指令,准备执行...")
|
||||||
|
|
||||||
# 发送确认响应
|
# 发送确认响应
|
||||||
await self.websocket.send(json.dumps({
|
await self.websocket.send(
|
||||||
"type": "server_response",
|
json.dumps(
|
||||||
"status": "success",
|
{
|
||||||
"message": "服务器重启中..."
|
"type": "server_response",
|
||||||
}))
|
"status": "success",
|
||||||
|
"message": "服务器重启中...",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# 异步执行重启操作
|
# 异步执行重启操作
|
||||||
def restart_server():
|
def restart_server():
|
||||||
@@ -266,7 +271,7 @@ class ConnectionHandler:
|
|||||||
stdin=sys.stdin,
|
stdin=sys.stdin,
|
||||||
stdout=sys.stdout,
|
stdout=sys.stdout,
|
||||||
stderr=sys.stderr,
|
stderr=sys.stderr,
|
||||||
start_new_session=True
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
@@ -275,11 +280,15 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.bind(tag=TAG).error(f"重启失败: {str(e)}")
|
self.logger.bind(tag=TAG).error(f"重启失败: {str(e)}")
|
||||||
await self.websocket.send(json.dumps({
|
await self.websocket.send(
|
||||||
"type": "server_response",
|
json.dumps(
|
||||||
"status": "error",
|
{
|
||||||
"message": f"Restart failed: {str(e)}"
|
"type": "server_response",
|
||||||
}))
|
"status": "error",
|
||||||
|
"message": f"Restart failed: {str(e)}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _initialize_components(self):
|
def _initialize_components(self):
|
||||||
"""初始化组件"""
|
"""初始化组件"""
|
||||||
@@ -306,6 +315,8 @@ class ConnectionHandler:
|
|||||||
"""初始化ASR和TTS上报线程"""
|
"""初始化ASR和TTS上报线程"""
|
||||||
if not self.read_config_from_api or self.need_bind:
|
if not self.read_config_from_api or self.need_bind:
|
||||||
return
|
return
|
||||||
|
if self.chat_history_conf == 0:
|
||||||
|
return
|
||||||
if self.tts_report_thread is None or not self.tts_report_thread.is_alive():
|
if self.tts_report_thread is None or not self.tts_report_thread.is_alive():
|
||||||
self.tts_report_thread = threading.Thread(
|
self.tts_report_thread = threading.Thread(
|
||||||
target=self._tts_report_worker, daemon=True
|
target=self._tts_report_worker, daemon=True
|
||||||
@@ -379,7 +390,8 @@ class ConnectionHandler:
|
|||||||
self.config["prompt"] = private_config["prompt"]
|
self.config["prompt"] = private_config["prompt"]
|
||||||
if private_config.get("device_max_output_size", None) is not None:
|
if private_config.get("device_max_output_size", None) is not None:
|
||||||
self.max_output_size = int(private_config["device_max_output_size"])
|
self.max_output_size = int(private_config["device_max_output_size"])
|
||||||
|
if private_config.get("chat_history_conf", None) is not None:
|
||||||
|
self.chat_history_conf = int(private_config["chat_history_conf"])
|
||||||
try:
|
try:
|
||||||
modules = initialize_modules(
|
modules = initialize_modules(
|
||||||
self.logger,
|
self.logger,
|
||||||
@@ -838,7 +850,7 @@ class ConnectionHandler:
|
|||||||
if future is None:
|
if future is None:
|
||||||
continue
|
continue
|
||||||
text = None
|
text = None
|
||||||
opus_datas, tts_file = [], None
|
audio_datas, tts_file = [], None
|
||||||
try:
|
try:
|
||||||
self.logger.bind(tag=TAG).debug("正在处理TTS任务...")
|
self.logger.bind(tag=TAG).debug("正在处理TTS任务...")
|
||||||
tts_timeout = int(self.config.get("tts_timeout", 10))
|
tts_timeout = int(self.config.get("tts_timeout", 10))
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ def opus_to_wav(conn, opus_data):
|
|||||||
def enqueue_tts_report(conn, type, text, opus_data):
|
def enqueue_tts_report(conn, type, text, opus_data):
|
||||||
if not conn.read_config_from_api or conn.need_bind:
|
if not conn.read_config_from_api or conn.need_bind:
|
||||||
return
|
return
|
||||||
|
if conn.chat_history_conf == 0:
|
||||||
|
return
|
||||||
"""将TTS数据加入上报队列
|
"""将TTS数据加入上报队列
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -101,10 +103,15 @@ def enqueue_tts_report(conn, type, text, opus_data):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 使用连接对象的队列,传入文本和二进制数据而非文件路径
|
# 使用连接对象的队列,传入文本和二进制数据而非文件路径
|
||||||
conn.tts_report_queue.put((type, text, opus_data))
|
if conn.chat_history_conf == 2:
|
||||||
|
conn.tts_report_queue.put((type, text, opus_data))
|
||||||
conn.logger.bind(tag=TAG).debug(
|
conn.logger.bind(tag=TAG).debug(
|
||||||
f"TTS数据已加入上报队列: {conn.device_id}, 音频大小: {len(opus_data)} "
|
f"TTS数据已加入上报队列: {conn.device_id}, 音频大小: {len(opus_data)} "
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
conn.tts_report_queue.put((type, text, None))
|
||||||
|
conn.logger.bind(tag=TAG).debug(
|
||||||
|
f"TTS数据已加入上报队列: {conn.device_id}, 不上报音频"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.logger.bind(tag=TAG).error(f"加入TTS上报队列失败: {text}, {e}")
|
conn.logger.bind(tag=TAG).error(f"加入TTS上报队列失败: {text}, {e}")
|
||||||
|
|||||||
@@ -61,5 +61,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
yield "【LLM服务响应异常】"
|
yield "【LLM服务响应异常】"
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
logger.bind(tag=TAG).info(f"阿里百练暂未实现完整的工具调用(function call)")
|
logger.bind(tag=TAG).error(
|
||||||
return self.response(session_id, dialogue)
|
f"阿里百练暂未实现完整的工具调用(function call),建议使用其他意图识别"
|
||||||
|
)
|
||||||
|
|||||||
@@ -66,5 +66,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
yield "【服务响应异常】"
|
yield "【服务响应异常】"
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
logger.bind(tag=TAG).info(f"fastgpt暂未实现完整的工具调用(function call)")
|
logger.bind(tag=TAG).error(
|
||||||
return self.response(session_id, dialogue)
|
f"fastgpt暂未实现完整的工具调用(function call),建议使用其他意图识别"
|
||||||
|
)
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
logger.bind(tag=TAG).error(f"生成响应时出错: {e}")
|
logger.bind(tag=TAG).error(f"生成响应时出错: {e}")
|
||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
logger.bind(tag=TAG).info(
|
logger.bind(tag=TAG).error(
|
||||||
f"homeassistant不支持(function call),建议使用意图识别使用:nointent"
|
f"homeassistant不支持(function call),建议使用其他意图识别"
|
||||||
)
|
)
|
||||||
return self.response(session_id, dialogue)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user