From d6697948c2f1c0b0a6a86be3428003bae6a6c624 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Fri, 12 Dec 2025 12:53:59 +0800 Subject: [PATCH 1/8] =?UTF-8?q?fix=EF=BC=9A=E4=BD=BF=E7=94=A8java=E7=AB=AF?= =?UTF-8?q?=E5=81=9A=E8=81=8A=E5=A4=A9=E8=AE=B0=E5=BD=95=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controller/AgentController.java | 33 ++ .../agent/dto/AgentChatSummaryDTO.java | 48 ++ .../service/AgentChatSummaryService.java | 25 ++ .../impl/AgentChatHistoryBizServiceImpl.java | 34 +- .../impl/AgentChatSummaryServiceImpl.java | 424 ++++++++++++++++++ .../modules/llm/service/LLMService.java | 70 +++ .../impl/OpenAIStyleLLMServiceImpl.java | 306 +++++++++++++ .../model/service/ModelConfigService.java | 8 + .../service/impl/ModelConfigServiceImpl.java | 18 + .../config/manage_api_client.py | 31 +- main/xiaozhi-server/core/connection.py | 4 +- .../core/providers/memory/base.py | 2 +- .../core/providers/memory/mem0ai/mem0ai.py | 6 +- .../memory/mem_local_short/mem_local_short.py | 23 +- .../core/providers/memory/nomem/nomem.py | 2 +- 15 files changed, 1013 insertions(+), 21 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index c09aa141..ac1852a5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -36,6 +36,7 @@ import xiaozhi.common.utils.Result; import xiaozhi.common.utils.ResultUtils; import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; +import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentMemoryDTO; @@ -44,6 +45,7 @@ import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatSummaryService; import xiaozhi.modules.agent.service.AgentContextProviderService; import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentService; @@ -66,6 +68,7 @@ public class AgentController { private final AgentChatAudioService agentChatAudioService; private final AgentPluginMappingService agentPluginMappingService; private final AgentContextProviderService agentContextProviderService; + private final AgentChatSummaryService agentChatSummaryService; private final RedisUtils redisUtils; @GetMapping("/list") @@ -119,6 +122,36 @@ public class AgentController { return new Result<>(); } + @PostMapping("/chat-summary/{sessionId}") + @Operation(summary = "根据会话ID生成聊天记录总结") + public Result generateChatSummary(@PathVariable String sessionId) { + try { + AgentChatSummaryDTO summary = agentChatSummaryService.generateChatSummary(sessionId); + if (summary.isSuccess()) { + return new Result().ok(summary); + } else { + return new Result().error(summary.getErrorMessage()); + } + } catch (Exception e) { + return new Result().error("生成聊天记录总结失败: " + e.getMessage()); + } + } + + @PostMapping("/chat-summary/{sessionId}/save") + @Operation(summary = "根据会话ID生成聊天记录总结并保存") + public Result generateAndSaveChatSummary(@PathVariable String sessionId) { + try { + boolean success = agentChatSummaryService.generateAndSaveChatSummary(sessionId); + if (success) { + return new Result().ok(null); + } else { + return new Result().error("保存聊天记录总结失败"); + } + } catch (Exception e) { + return new Result().error("生成并保存聊天记录总结失败: " + e.getMessage()); + } + } + @PutMapping("/{id}") @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java new file mode 100644 index 00000000..e0bf12a7 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java @@ -0,0 +1,48 @@ +package xiaozhi.modules.agent.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 智能体聊天记录总结DTO + */ +@Data +@Schema(description = "智能体聊天记录总结对象") +public class AgentChatSummaryDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "会话ID") + private String sessionId; + + @Schema(description = "智能体ID") + private String agentId; + + @Schema(description = "总结内容") + private String summary; + + @Schema(description = "总结状态") + private boolean success; + + @Schema(description = "错误信息") + private String errorMessage; + + public AgentChatSummaryDTO() { + this.success = true; + } + + public AgentChatSummaryDTO(String sessionId, String agentId, String summary) { + this.sessionId = sessionId; + this.agentId = agentId; + this.summary = summary; + this.success = true; + } + + public AgentChatSummaryDTO(String sessionId, String errorMessage) { + this.sessionId = sessionId; + this.errorMessage = errorMessage; + this.success = false; + } + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java new file mode 100644 index 00000000..c442deb8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java @@ -0,0 +1,25 @@ +package xiaozhi.modules.agent.service; + +import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; + +/** + * 智能体聊天记录总结服务接口 + */ +public interface AgentChatSummaryService { + + /** + * 根据会话ID生成聊天记录总结 + * + * @param sessionId 会话ID + * @return 总结结果 + */ + AgentChatSummaryDTO generateChatSummary(String sessionId); + + /** + * 根据会话ID生成聊天记录总结并保存到智能体记忆 + * + * @param sessionId 会话ID + * @return 保存结果 + */ + boolean generateAndSaveChatSummary(String sessionId); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java index 36b59993..16bcce50 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/biz/impl/AgentChatHistoryBizServiceImpl.java @@ -17,6 +17,7 @@ import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatSummaryService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService; import xiaozhi.modules.device.entity.DeviceEntity; @@ -36,6 +37,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic private final AgentService agentService; private final AgentChatHistoryService agentChatHistoryService; private final AgentChatAudioService agentChatAudioService; + private final AgentChatSummaryService agentChatSummaryService; private final RedisUtils redisUtils; private final DeviceService deviceService; @@ -50,7 +52,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic public Boolean report(AgentChatHistoryReportDTO report) { String macAddress = report.getMacAddress(); Byte chatType = report.getChatType(); - Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000 : System.currentTimeMillis(); + Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000 + : System.currentTimeMillis(); log.info("小智设备聊天上报请求: macAddress={}, type={} reportTime={}", macAddress, chatType, reportTimeMillis); // 根据设备MAC地址查询对应的默认智能体,判断是否需要上报 @@ -80,6 +83,9 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic log.warn("聊天记录上报时,未找到mac地址为 {} 的设备", macAddress); } + // 异步触发聊天记录总结(仅在对话结束时触发) + triggerChatSummaryAsync(report.getSessionId(), chatType); + return Boolean.TRUE; } @@ -105,7 +111,8 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic /** * 组装上报数据 */ - private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId, Long reportTime) { + private void saveChatText(AgentChatHistoryReportDTO report, String agentId, String macAddress, String audioId, + Long reportTime) { // 构建聊天记录实体 AgentChatHistoryEntity entity = AgentChatHistoryEntity.builder() .macAddress(macAddress) @@ -123,4 +130,27 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic log.info("设备 {} 对应智能体 {} 上报成功", macAddress, agentId); } + + /** + * 异步触发聊天记录总结 + * 仅在对话结束时(chatType=2)触发总结,避免频繁总结 + */ + private void triggerChatSummaryAsync(String sessionId, Byte chatType) { + // 仅在对话结束时触发总结(chatType=2表示对话结束) + if (chatType != null && chatType == 2) { + new Thread(() -> { + try { + log.info("开始为会话 {} 生成聊天记录总结", sessionId); + boolean success = agentChatSummaryService.generateAndSaveChatSummary(sessionId); + if (success) { + log.info("会话 {} 的聊天记录总结生成并保存成功", sessionId); + } else { + log.warn("会话 {} 的聊天记录总结生成失败", sessionId); + } + } catch (Exception e) { + log.error("触发会话 {} 的聊天记录总结时发生异常", sessionId, e); + } + }).start(); + } + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java new file mode 100644 index 00000000..80ad03d6 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java @@ -0,0 +1,424 @@ +package xiaozhi.modules.agent.service.impl; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; +import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; +import xiaozhi.modules.agent.dto.AgentMemoryDTO; +import xiaozhi.modules.agent.dto.AgentUpdateDTO; +import xiaozhi.modules.agent.entity.AgentChatHistoryEntity; +import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentChatSummaryService; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.config.service.ConfigService; +import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.llm.service.LLMService; +import xiaozhi.modules.agent.vo.AgentInfoVO; +import xiaozhi.modules.model.entity.ModelConfigEntity; +import xiaozhi.modules.model.service.ModelConfigService; +import java.util.Map; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 智能体聊天记录总结服务实现类 + * 实现Python端mem_local_short.py中的总结逻辑 + */ +@Service +@RequiredArgsConstructor +public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { + + private static final Logger log = LoggerFactory.getLogger(AgentChatSummaryServiceImpl.class); + + private final AgentChatHistoryService agentChatHistoryService; + private final AgentService agentService; + private final DeviceService deviceService; + private final LLMService llmService; + private final ConfigService configService; + private final ModelConfigService modelConfigService; + + // 总结规则常量 + private static final int MAX_SUMMARY_LENGTH = 1800; // 最大总结长度 + private static final Pattern JSON_PATTERN = Pattern.compile("\\{.*?\\}", Pattern.DOTALL); + private static final Pattern DEVICE_CONTROL_PATTERN = Pattern.compile("设备控制|设备操作|控制设备|设备状态", + Pattern.CASE_INSENSITIVE); + private static final Pattern WEATHER_PATTERN = Pattern.compile("天气|温度|湿度|降雨|气象", Pattern.CASE_INSENSITIVE); + private static final Pattern DATE_PATTERN = Pattern.compile("日期|时间|星期|月份|年份", Pattern.CASE_INSENSITIVE); + + @Override + public AgentChatSummaryDTO generateChatSummary(String sessionId) { + try { + System.out.println("开始生成会话 " + sessionId + " 的聊天记录总结"); + + // 1. 根据sessionId获取聊天记录 + List chatHistory = getChatHistoryBySessionId(sessionId); + if (chatHistory == null || chatHistory.isEmpty()) { + return new AgentChatSummaryDTO(sessionId, "未找到该会话的聊天记录"); + } + + // 2. 获取智能体信息 + String agentId = getAgentIdFromSession(sessionId, chatHistory); + if (StringUtils.isBlank(agentId)) { + return new AgentChatSummaryDTO(sessionId, "无法获取智能体信息"); + } + + // 3. 提取关键对话内容 + List meaningfulMessages = extractMeaningfulMessages(chatHistory); + if (meaningfulMessages.isEmpty()) { + return new AgentChatSummaryDTO(sessionId, "没有有效的对话内容可总结"); + } + + // 4. 生成总结(generateSummaryFromMessages方法已包含长度限制逻辑) + String summary = generateSummaryFromMessages(meaningfulMessages, agentId); + + System.out.println("成功生成会话 " + sessionId + " 的聊天记录总结,长度: " + summary.length() + " 字符"); + return new AgentChatSummaryDTO(sessionId, agentId, summary); + + } catch (Exception e) { + System.err.println("生成会话 " + sessionId + " 的聊天记录总结时发生错误: " + e.getMessage()); + return new AgentChatSummaryDTO(sessionId, "生成总结时发生错误: " + e.getMessage()); + } + } + + @Override + public boolean generateAndSaveChatSummary(String sessionId) { + try { + // 1. 生成总结 + AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId); + if (!summaryDTO.isSuccess()) { + System.err.println("生成总结失败: " + summaryDTO.getErrorMessage()); + return false; + } + + // 2. 获取设备信息(通过会话关联的设备) + DeviceEntity device = getDeviceBySessionId(sessionId); + if (device == null) { + System.err.println("未找到与会话 " + sessionId + " 关联的设备"); + return false; + } + + // 3. 更新智能体记忆 + AgentMemoryDTO memoryDTO = new AgentMemoryDTO(); + memoryDTO.setSummaryMemory(summaryDTO.getSummary()); + + // 调用现有接口更新记忆 + agentService.updateAgentById(device.getAgentId(), + new AgentUpdateDTO() { + { + setSummaryMemory(summaryDTO.getSummary()); + } + }); + + System.out.println("成功保存会话 " + sessionId + " 的聊天记录总结到智能体 " + device.getAgentId()); + return true; + + } catch (Exception e) { + System.err.println("保存会话 " + sessionId + " 的聊天记录总结时发生错误: " + e.getMessage()); + return false; + } + } + + /** + * 根据会话ID获取聊天记录 + */ + private List getChatHistoryBySessionId(String sessionId) { + try { + // 这里需要根据sessionId获取聊天记录 + // 由于现有接口需要agentId,我们需要先找到关联的agentId + String agentId = findAgentIdBySessionId(sessionId); + if (StringUtils.isBlank(agentId)) { + return null; + } + return agentChatHistoryService.getChatHistoryBySessionId(agentId, sessionId); + } catch (Exception e) { + System.err.println("获取会话 " + sessionId + " 的聊天记录失败: " + e.getMessage()); + return null; + } + } + + /** + * 根据会话ID查找关联的智能体ID + */ + private String findAgentIdBySessionId(String sessionId) { + try { + // 查询该会话的第一条记录获取agentId + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("agent_id") + .eq("session_id", sessionId) + .last("LIMIT 1"); + + AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper); + return entity != null ? entity.getAgentId() : null; + } catch (Exception e) { + System.err.println("根据会话ID " + sessionId + " 查找智能体ID失败: " + e.getMessage()); + return null; + } + } + + /** + * 从会话中获取智能体ID + */ + private String getAgentIdFromSession(String sessionId, List chatHistory) { + // 直接从数据库查询智能体ID + return findAgentIdBySessionId(sessionId); + } + + /** + * 提取有意义的对话内容(只提取用户消息,排除AI回复) + */ + private List extractMeaningfulMessages(List chatHistory) { + List meaningfulMessages = new ArrayList<>(); + + for (AgentChatHistoryDTO message : chatHistory) { + // 只处理用户消息(chatType = 1) + if (message.getChatType() != null && message.getChatType() == 1) { + String content = extractContentFromMessage(message); + if (isMeaningfulMessage(content)) { + meaningfulMessages.add(content); + } + } + } + + return meaningfulMessages; + } + + /** + * 从消息中提取内容(处理JSON格式) + */ + private String extractContentFromMessage(AgentChatHistoryDTO message) { + String content = message.getContent(); + if (StringUtils.isBlank(content)) { + return ""; + } + + // 处理JSON格式内容(与前端ChatHistoryDialog.vue逻辑一致) + Matcher matcher = JSON_PATTERN.matcher(content); + if (matcher.find()) { + String jsonContent = matcher.group(); + // 简化处理:提取JSON中的文本内容 + return extractTextFromJson(jsonContent); + } + + return content; + } + + /** + * 从JSON中提取文本内容 + */ + private String extractTextFromJson(String jsonContent) { + // 简化处理:提取"content"字段的值 + Pattern contentPattern = Pattern.compile("\"content\"\s*:\s*\"([^\"]*)\""); + Matcher matcher = contentPattern.matcher(jsonContent); + if (matcher.find()) { + return matcher.group(1); + } + return jsonContent; + } + + /** + * 判断是否为有意义的消息 + */ + private boolean isMeaningfulMessage(String content) { + if (StringUtils.isBlank(content)) { + return false; + } + + // 排除设备控制信息 + if (DEVICE_CONTROL_PATTERN.matcher(content).find()) { + return false; + } + + // 排除日期天气等无关内容 + if (WEATHER_PATTERN.matcher(content).find() || DATE_PATTERN.matcher(content).find()) { + return false; + } + + // 排除过短的消息 + return content.length() >= 5; + } + + /** + * 从消息生成总结 + */ + private String generateSummaryFromMessages(List messages, String agentId) { + if (messages.isEmpty()) { + return "本次对话内容较少,没有需要总结的重要信息。"; + } + + // 构建完整的对话内容 + StringBuilder conversation = new StringBuilder(); + for (int i = 0; i < messages.size(); i++) { + conversation.append("消息").append(i + 1).append(": ").append(messages.get(i)).append("\n"); + } + + try { + // 获取当前智能体的历史记忆 + String historyMemory = getCurrentAgentMemory(agentId); + + // 调用LLM服务进行智能总结,传递agentId以获取正确的模型配置 + String summary = callJavaLLMForSummaryWithHistory(conversation.toString(), historyMemory, agentId); + + // 应用总结规则:限制最大长度 + if (summary.length() > MAX_SUMMARY_LENGTH) { + summary = summary.substring(0, MAX_SUMMARY_LENGTH) + "..."; + } + + return summary; + } catch (Exception e) { + System.err.println("调用Java端LLM服务失败: " + e.getMessage()); + throw new RuntimeException("LLM服务不可用,无法生成聊天总结"); + } + } + + /** + * 获取当前智能体的历史记忆 + */ + private String getCurrentAgentMemory(String agentId) { + try { + if (StringUtils.isBlank(agentId)) { + return null; + } + + // 获取智能体信息 + AgentInfoVO agentInfo = agentService.getAgentById(agentId); + if (agentInfo == null) { + return null; + } + + // 返回智能体的当前总结记忆 + return agentInfo.getSummaryMemory(); + } catch (Exception e) { + System.err.println("获取智能体历史记忆失败,agentId: " + agentId + ", 错误: " + e.getMessage()); + return null; + } + } + + /** + * 调用Java端LLM服务进行智能总结(支持历史记忆合并) + */ + private String callJavaLLMForSummaryWithHistory(String conversation, String historyMemory, String agentId) { + try { + // 获取智能体配置,从中提取记忆总结的模型ID + String modelId = getMemorySummaryModelId(agentId); + + if (StringUtils.isBlank(modelId)) { + System.out.println("未找到记忆总结的LLM模型配置,使用默认LLM服务"); + return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null); + } + + // 使用指定的模型ID调用LLM服务(支持历史记忆合并) + String summary = llmService.generateSummaryWithHistory(conversation, historyMemory, null, modelId); + + if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) { + return summary; + } + + throw new RuntimeException("Java端LLM服务返回异常: " + summary); + + } catch (Exception e) { + System.err.println("调用Java端LLM服务异常,agentId: " + agentId + ", 错误: " + e.getMessage()); + throw e; + } + } + + /** + * 调用Java端LLM服务进行智能总结 + */ + private String callJavaLLMForSummary(String conversation, String agentId) { + try { + // 获取智能体配置,从中提取记忆总结的模型ID + String modelId = getMemorySummaryModelId(agentId); + + if (StringUtils.isBlank(modelId)) { + System.out.println("未找到记忆总结的LLM模型配置,使用默认LLM服务"); + return llmService.generateSummary(conversation); + } + + // 使用指定的模型ID调用LLM服务 + String summary = llmService.generateSummaryWithModel(conversation, modelId); + + if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) { + return summary; + } + + throw new RuntimeException("Java端LLM服务返回异常: " + summary); + + } catch (Exception e) { + System.err.println("调用Java端LLM服务异常,agentId: " + agentId + ", 错误: " + e.getMessage()); + throw e; + } + } + + /** + * 获取记忆总结的LLM模型ID + */ + private String getMemorySummaryModelId(String agentId) { + try { + if (StringUtils.isBlank(agentId)) { + return null; + } + + // 获取智能体信息 + AgentInfoVO agentInfo = agentService.getAgentById(agentId); + if (agentInfo == null) { + return null; + } + + // 获取智能体的记忆模型ID + String memModelId = agentInfo.getMemModelId(); + if (StringUtils.isBlank(memModelId)) { + return null; + } + + // 获取记忆模型配置 + ModelConfigEntity memModelConfig = modelConfigService.getModelByIdFromCache(memModelId); + if (memModelConfig == null || memModelConfig.getConfigJson() == null) { + return null; + } + + // 从记忆模型配置中提取对应的LLM模型ID + Map configMap = memModelConfig.getConfigJson(); + String llmModelId = (String) configMap.get("llm"); + + if (StringUtils.isBlank(llmModelId)) { + // 如果记忆模型没有配置独立的LLM,则使用智能体的默认LLM模型 + return agentInfo.getLlmModelId(); + } + + return llmModelId; + } catch (Exception e) { + System.err.println("获取记忆总结LLM模型ID失败,agentId: " + agentId + ", 错误: " + e.getMessage()); + return null; + } + } + + /** + * 根据会话ID获取设备信息 + */ + private DeviceEntity getDeviceBySessionId(String sessionId) { + try { + // 查询该会话的第一条记录获取macAddress + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("mac_address") + .eq("session_id", sessionId) + .last("LIMIT 1"); + + AgentChatHistoryEntity entity = agentChatHistoryService.getOne(wrapper); + if (entity != null && StringUtils.isNotBlank(entity.getMacAddress())) { + return deviceService.getDeviceByMacAddress(entity.getMacAddress()); + } + return null; + } catch (Exception e) { + System.err.println("根据会话ID " + sessionId + " 查找设备信息失败: " + e.getMessage()); + return null; + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java new file mode 100644 index 00000000..a3f10e9f --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/LLMService.java @@ -0,0 +1,70 @@ +package xiaozhi.modules.llm.service; + +/** + * LLM服务接口 + * 支持多种大模型调用 + */ +public interface LLMService { + + /** + * 生成聊天记录总结 + * + * @param conversation 对话内容 + * @param promptTemplate 提示词模板 + * @return 总结结果 + */ + String generateSummary(String conversation, String promptTemplate); + + /** + * 生成聊天记录总结(使用默认提示词) + * + * @param conversation 对话内容 + * @return 总结结果 + */ + String generateSummary(String conversation); + + /** + * 生成聊天记录总结(指定模型ID) + * + * @param conversation 对话内容 + * @param modelId 模型ID + * @return 总结结果 + */ + String generateSummaryWithModel(String conversation, String modelId); + + /** + * 生成聊天记录总结(指定模型ID和提示词模板) + * + * @param conversation 对话内容 + * @param promptTemplate 提示词模板 + * @param modelId 模型ID + * @return 总结结果 + */ + String generateSummary(String conversation, String promptTemplate, String modelId); + + /** + * 生成聊天记录总结(包含历史记忆合并) + * + * @param conversation 对话内容 + * @param historyMemory 历史记忆 + * @param promptTemplate 提示词模板 + * @param modelId 模型ID + * @return 总结结果 + */ + String generateSummaryWithHistory(String conversation, String historyMemory, String promptTemplate, String modelId); + + /** + * 检查服务是否可用 + * + * @return 是否可用 + */ + boolean isAvailable(); + + /** + * 检查指定模型的服务是否可用 + * + * @param modelId 模型ID + * @return 是否可用 + */ + boolean isAvailable(String modelId); +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java new file mode 100644 index 00000000..e0b8a3b2 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/llm/service/impl/OpenAIStyleLLMServiceImpl.java @@ -0,0 +1,306 @@ +package xiaozhi.modules.llm.service.impl; + +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import xiaozhi.modules.llm.service.LLMService; +import xiaozhi.modules.model.entity.ModelConfigEntity; +import xiaozhi.modules.model.service.ModelConfigService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * OpenAI风格API的LLM服务实现 + * 支持阿里云、DeepSeek、ChatGLM等兼容OpenAI API的模型 + */ +@Slf4j +@Service +public class OpenAIStyleLLMServiceImpl implements LLMService { + + @Autowired + private ModelConfigService modelConfigService; + + private final RestTemplate restTemplate = new RestTemplate(); + + private static final String DEFAULT_SUMMARY_PROMPT = "你是一个经验丰富的记忆总结者,擅长将对话内容进行总结摘要,遵循以下规则:\n1、总结用户的重要信息,以便在未来的对话中提供更个性化的服务\n2、不要重复总结,不要遗忘之前记忆,除非原来的记忆超过了1800字,否则不要遗忘、不要压缩用户的历史记忆\n3、用户操控的设备音量、播放音乐、天气、退出、不想对话等和用户本身无关的内容,这些信息不需要加入到总结中\n4、聊天内容中的今天的日期时间、今天的天气情况与用户事件无关的数据,这些信息如果当成记忆存储会影响后续对话,这些信息不需要加入到总结中\n5、不要把设备操控的成果结果和失败结果加入到总结中,也不要把用户的一些废话加入到总结中\n6、不要为了总结而总结,如果用户的聊天没有意义,请返回原来的历史记录也是可以的\n7、只需要返回总结摘要,严格控制在1800字内\n8、不要包含代码、xml,不需要解释、注释和说明,保存记忆时仅从对话提取信息,不要混入示例内容\n9、如果提供了历史记忆,请将新对话内容与历史记忆进行智能合并,保留有价值的历史信息,同时添加新的重要信息\n\n历史记忆:\n{history_memory}\n\n新对话内容:\n{conversation}"; + + @Override + public String generateSummary(String conversation) { + return generateSummary(conversation, null, null); + } + + @Override + public String generateSummaryWithModel(String conversation, String modelId) { + return generateSummary(conversation, null, modelId); + } + + @Override + public String generateSummary(String conversation, String promptTemplate, String modelId) { + if (!isAvailable()) { + log.warn("LLM服务不可用,无法生成总结"); + return "LLM服务不可用,无法生成总结"; + } + + try { + // 从智控台获取LLM模型配置 + ModelConfigEntity llmConfig; + if (modelId != null && !modelId.trim().isEmpty()) { + // 通过具体模型ID获取配置 + llmConfig = modelConfigService.getModelByIdFromCache(modelId); + } else { + // 保持向后兼容,使用默认配置 + llmConfig = getDefaultLLMConfig(); + } + + if (llmConfig == null || llmConfig.getConfigJson() == null) { + log.error("未找到可用的LLM模型配置,modelId: {}", modelId); + return "未找到可用的LLM模型配置"; + } + + JSONObject configJson = llmConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String model = configJson.getStr("model_name"); + String apiKey = configJson.getStr("api_key"); + Double temperature = configJson.getDouble("temperature"); + Integer maxTokens = configJson.getInt("max_tokens"); + + if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) { + log.error("LLM配置不完整,baseUrl或apiKey为空"); + return "LLM配置不完整,无法生成总结"; + } + + // 构建提示词 + String prompt = (promptTemplate != null ? promptTemplate : DEFAULT_SUMMARY_PROMPT).replace("{conversation}", + conversation); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("model", model != null ? model : "gpt-3.5-turbo"); + + Map[] messages = new Map[1]; + Map message = new HashMap<>(); + message.put("role", "user"); + message.put("content", prompt); + messages[0] = message; + + requestBody.put("messages", messages); + requestBody.put("temperature", temperature != null ? temperature : 0.7); + requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000); + + // 发送HTTP请求 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + // 构建完整的API URL + String apiUrl = baseUrl; + if (!apiUrl.endsWith("/chat/completions")) { + if (!apiUrl.endsWith("/")) { + apiUrl += "/"; + } + apiUrl += "chat/completions"; + } + + ResponseEntity response = restTemplate.exchange( + apiUrl, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + JSONObject responseJson = JSONUtil.parseObj(response.getBody()); + JSONArray choices = responseJson.getJSONArray("choices"); + if (choices != null && choices.size() > 0) { + JSONObject choice = choices.getJSONObject(0); + JSONObject messageObj = choice.getJSONObject("message"); + return messageObj.getStr("content"); + } + } else { + log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody()); + } + } catch (Exception e) { + log.error("调用LLM服务生成总结时发生异常,modelId: {}", modelId, e); + } + + return "生成总结失败,请稍后重试"; + } + + @Override + public String generateSummary(String conversation, String promptTemplate) { + return generateSummary(conversation, promptTemplate, null); + } + + @Override + public String generateSummaryWithHistory(String conversation, String historyMemory, String promptTemplate, + String modelId) { + if (!isAvailable()) { + log.warn("LLM服务不可用,无法生成总结"); + return "LLM服务不可用,无法生成总结"; + } + + try { + // 从智控台获取LLM模型配置 + ModelConfigEntity llmConfig; + if (modelId != null && !modelId.trim().isEmpty()) { + // 通过具体模型ID获取配置 + llmConfig = modelConfigService.getModelByIdFromCache(modelId); + } else { + // 保持向后兼容,使用默认配置 + llmConfig = getDefaultLLMConfig(); + } + + if (llmConfig == null || llmConfig.getConfigJson() == null) { + log.error("未找到可用的LLM模型配置,modelId: {}", modelId); + return "未找到可用的LLM模型配置"; + } + + JSONObject configJson = llmConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String model = configJson.getStr("model_name"); + String apiKey = configJson.getStr("api_key"); + Double temperature = configJson.getDouble("temperature"); + Integer maxTokens = configJson.getInt("max_tokens"); + + if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) { + log.error("LLM配置不完整,baseUrl或apiKey为空"); + return "LLM配置不完整,无法生成总结"; + } + + // 构建提示词,包含历史记忆 + String prompt = (promptTemplate != null ? promptTemplate : DEFAULT_SUMMARY_PROMPT) + .replace("{history_memory}", historyMemory != null ? historyMemory : "无历史记忆") + .replace("{conversation}", conversation); + + // 构建请求体 + Map requestBody = new HashMap<>(); + requestBody.put("model", model != null ? model : "gpt-3.5-turbo"); + + Map[] messages = new Map[1]; + Map message = new HashMap<>(); + message.put("role", "user"); + message.put("content", prompt); + messages[0] = message; + + requestBody.put("messages", messages); + requestBody.put("temperature", temperature != null ? temperature : 0.7); + requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000); + + // 发送HTTP请求 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + apiKey); + + HttpEntity> entity = new HttpEntity<>(requestBody, headers); + + // 构建完整的API URL + String apiUrl = baseUrl; + if (!apiUrl.endsWith("/chat/completions")) { + if (!apiUrl.endsWith("/")) { + apiUrl += "/"; + } + apiUrl += "chat/completions"; + } + + ResponseEntity response = restTemplate.exchange( + apiUrl, HttpMethod.POST, entity, String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + JSONObject responseJson = JSONUtil.parseObj(response.getBody()); + JSONArray choices = responseJson.getJSONArray("choices"); + if (choices != null && choices.size() > 0) { + JSONObject choice = choices.getJSONObject(0); + JSONObject messageObj = choice.getJSONObject("message"); + return messageObj.getStr("content"); + } + } else { + log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody()); + } + } catch (Exception e) { + log.error("调用LLM服务生成总结时发生异常,modelId: {}", modelId, e); + } + + return "生成总结失败,请稍后重试"; + } + + @Override + public boolean isAvailable() { + try { + ModelConfigEntity defaultLLMConfig = getDefaultLLMConfig(); + if (defaultLLMConfig == null || defaultLLMConfig.getConfigJson() == null) { + return false; + } + + JSONObject configJson = defaultLLMConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String apiKey = configJson.getStr("api_key"); + + return baseUrl != null && !baseUrl.trim().isEmpty() && + apiKey != null && !apiKey.trim().isEmpty(); + } catch (Exception e) { + log.error("检查LLM服务可用性时发生异常:", e); + return false; + } + } + + @Override + public boolean isAvailable(String modelId) { + try { + if (modelId == null || modelId.trim().isEmpty()) { + return isAvailable(); + } + + // 通过具体模型ID获取配置 + ModelConfigEntity modelConfig = modelConfigService.getModelByIdFromCache(modelId); + if (modelConfig == null || modelConfig.getConfigJson() == null) { + log.warn("未找到指定的LLM模型配置,modelId: {}", modelId); + return false; + } + + JSONObject configJson = modelConfig.getConfigJson(); + String baseUrl = configJson.getStr("base_url"); + String apiKey = configJson.getStr("api_key"); + + return baseUrl != null && !baseUrl.trim().isEmpty() && + apiKey != null && !apiKey.trim().isEmpty(); + } catch (Exception e) { + log.error("检查LLM服务可用性时发生异常,modelId: {}", modelId, e); + return false; + } + } + + /** + * 从智控台获取默认的LLM模型配置 + */ + private ModelConfigEntity getDefaultLLMConfig() { + try { + // 获取所有启用的LLM模型配置 + List llmConfigs = modelConfigService.getEnabledModelsByType("LLM"); + if (llmConfigs == null || llmConfigs.isEmpty()) { + return null; + } + + // 优先返回默认配置,如果没有默认配置则返回第一个启用的配置 + for (ModelConfigEntity config : llmConfigs) { + if (config.getIsDefault() != null && config.getIsDefault() == 1) { + return config; + } + } + + return llmConfigs.get(0); + } catch (Exception e) { + log.error("获取LLM模型配置时发生异常:", e); + return null; + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java index 95b64b20..9ae10761 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java @@ -55,4 +55,12 @@ public interface ModelConfigService extends BaseService { * @return TTS平台列表(id和modelName) */ List> getTtsPlatformList(); + + /** + * 根据模型类型获取所有启用的模型配置 + * + * @param modelType 模型类型(如:LLM, TTS, ASR等) + * @return 启用的模型配置列表 + */ + List getEnabledModelsByType(String modelType); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java index fbfc7723..2a291cf9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -502,4 +502,22 @@ public class ModelConfigServiceImpl extends BaseServiceImpl> getTtsPlatformList() { return modelConfigDao.getTtsPlatformList(); } + + /** + * 根据模型类型获取所有启用的模型配置 + */ + @Override + public List getEnabledModelsByType(String modelType) { + if (StringUtils.isBlank(modelType)) { + return null; + } + + List entities = modelConfigDao.selectList( + new QueryWrapper() + .eq("model_type", modelType) + .eq("is_enabled", 1) + .orderByAsc("sort")); + + return entities; + } } diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py index 88995359..ae703815 100644 --- a/main/xiaozhi-server/config/manage_api_client.py +++ b/main/xiaozhi-server/config/manage_api_client.py @@ -53,6 +53,7 @@ class ManageApiClient: async def _ensure_async_client(cls): """确保异步客户端已创建(为每个事件循环创建独立的客户端)""" import asyncio + try: loop = asyncio.get_running_loop() loop_id = id(loop) @@ -115,6 +116,7 @@ class ManageApiClient: async def _execute_async_request(cls, method: str, endpoint: str, **kwargs) -> Dict: """带重试机制的异步请求执行器""" import asyncio + retry_count = 0 while retry_count <= cls.max_retries: @@ -138,6 +140,7 @@ class ManageApiClient: def safe_close(cls): """安全关闭所有异步连接池""" import asyncio + for client in list(cls._async_clients.values()): try: asyncio.run(client.aclose()) @@ -149,7 +152,9 @@ class ManageApiClient: async def get_server_config() -> Optional[Dict]: """获取服务器基础配置""" - return await ManageApiClient._instance._execute_async_request("POST", "/config/server-base") + return await ManageApiClient._instance._execute_async_request( + "POST", "/config/server-base" + ) async def get_agent_models( @@ -181,6 +186,30 @@ async def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[ return None +async def generate_chat_summary(session_id: str) -> Optional[Dict]: + """生成聊天记录总结""" + try: + return await ManageApiClient._instance._execute_async_request( + "POST", + f"/agent/chat-summary/{session_id}", + ) + except Exception as e: + print(f"生成聊天记录总结失败: {e}") + return None + + +async def generate_and_save_chat_summary(session_id: str) -> Optional[Dict]: + """生成并保存聊天记录总结""" + try: + return await ManageApiClient._instance._execute_async_request( + "POST", + f"/agent/chat-summary/{session_id}/save", + ) + except Exception as e: + print(f"生成并保存聊天记录总结失败: {e}") + return None + + async def report( mac_address: str, session_id: str, chat_type: int, content: str, audio, report_time ) -> Optional[Dict]: diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 6b34e32f..43c77f93 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -243,7 +243,9 @@ class ConnectionHandler: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete( - self.memory.save_memory(self.dialogue.dialogue) + self.memory.save_memory( + self.dialogue.dialogue, self.session_id + ) ) except Exception as e: self.logger.bind(tag=TAG).error(f"保存记忆失败: {e}") diff --git a/main/xiaozhi-server/core/providers/memory/base.py b/main/xiaozhi-server/core/providers/memory/base.py index 2ced898d..70c4b409 100644 --- a/main/xiaozhi-server/core/providers/memory/base.py +++ b/main/xiaozhi-server/core/providers/memory/base.py @@ -14,7 +14,7 @@ class MemoryProviderBase(ABC): self.llm = llm @abstractmethod - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): """Save a new memory for specific role and return memory ID""" print("this is base func", msgs) diff --git a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py index 530aa317..7156ab72 100644 --- a/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py +++ b/main/xiaozhi-server/core/providers/memory/mem0ai/mem0ai.py @@ -28,7 +28,7 @@ class MemoryProvider(MemoryProviderBase): logger.bind(tag=TAG).error(f"详细错误: {traceback.format_exc()}") self.use_mem0 = False - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): if not self.use_mem0: return None if len(msgs) < 2: @@ -41,9 +41,7 @@ class MemoryProvider(MemoryProviderBase): for message in msgs if message.role != "system" ] - result = self.client.add( - messages, user_id=self.role_id - ) + result = self.client.add(messages, user_id=self.role_id) logger.bind(tag=TAG).debug(f"Save memory result: {result}") except Exception as e: logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}") diff --git a/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py b/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py index a6b7dd36..f8b81aa8 100644 --- a/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py +++ b/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py @@ -4,7 +4,10 @@ import json import os import yaml from config.config_loader import get_project_dir -from config.manage_api_client import save_mem_local_short +from config.manage_api_client import ( + save_mem_local_short, + generate_and_save_chat_summary, +) import asyncio from core.utils.util import check_model_key @@ -144,7 +147,7 @@ class MemoryProvider(MemoryProviderBase): with open(self.memory_path, "w", encoding="utf-8") as f: yaml.dump(all_memory, f, allow_unicode=True) - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): # 打印使用的模型信息 model_info = getattr(self.llm, "model_name", str(self.llm.__class__.__name__)) logger.bind(tag=TAG).debug(f"使用记忆保存模型: {model_info}") @@ -188,20 +191,18 @@ class MemoryProvider(MemoryProviderBase): except Exception as e: print("Error:", e) else: - result = self.llm.response_no_stream( - short_term_memory_prompt_only_content, - msgStr, - max_tokens=2000, - temperature=0.2, - ) + # 当save_to_file为False时,调用Java端的聊天记录总结接口 + summary_id = session_id if session_id else self.role_id # 使用异步版本,需要在事件循环中运行 try: loop = asyncio.get_running_loop() - loop.create_task(save_mem_local_short(self.role_id, result)) + loop.create_task(generate_and_save_chat_summary(summary_id)) except RuntimeError: # 如果没有运行中的事件循环,创建一个新的 - asyncio.run(save_mem_local_short(self.role_id, result)) - logger.bind(tag=TAG).info(f"Save memory successful - Role: {self.role_id}") + asyncio.run(generate_and_save_chat_summary(summary_id)) + logger.bind(tag=TAG).info( + f"Save memory successful - Role: {self.role_id}, Session: {session_id}" + ) return self.short_memory diff --git a/main/xiaozhi-server/core/providers/memory/nomem/nomem.py b/main/xiaozhi-server/core/providers/memory/nomem/nomem.py index 51523be4..6fe0a2be 100644 --- a/main/xiaozhi-server/core/providers/memory/nomem/nomem.py +++ b/main/xiaozhi-server/core/providers/memory/nomem/nomem.py @@ -11,7 +11,7 @@ class MemoryProvider(MemoryProviderBase): def __init__(self, config, summary_memory=None): super().__init__(config) - async def save_memory(self, msgs): + async def save_memory(self, msgs, session_id=None): logger.bind(tag=TAG).debug("nomem mode: No memory saving is performed.") return None From 6ac67a7e4187a3d04e62842b8335e7ae4a8262a8 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Fri, 19 Dec 2025 09:55:18 +0800 Subject: [PATCH 2/8] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controller/AgentController.java | 39 +++++++------------ .../agent/dto/AgentChatSummaryDTO.java | 5 +-- .../service/AgentChatSummaryService.java | 10 ----- .../impl/AgentChatSummaryServiceImpl.java | 3 +- .../config/manage_api_client.py | 12 ------ 5 files changed, 17 insertions(+), 52 deletions(-) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index ac1852a5..0052c7cc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -36,7 +36,6 @@ import xiaozhi.common.utils.Result; import xiaozhi.common.utils.ResultUtils; import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; -import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentMemoryDTO; @@ -122,33 +121,24 @@ public class AgentController { return new Result<>(); } - @PostMapping("/chat-summary/{sessionId}") - @Operation(summary = "根据会话ID生成聊天记录总结") - public Result generateChatSummary(@PathVariable String sessionId) { - try { - AgentChatSummaryDTO summary = agentChatSummaryService.generateChatSummary(sessionId); - if (summary.isSuccess()) { - return new Result().ok(summary); - } else { - return new Result().error(summary.getErrorMessage()); - } - } catch (Exception e) { - return new Result().error("生成聊天记录总结失败: " + e.getMessage()); - } - } - @PostMapping("/chat-summary/{sessionId}/save") - @Operation(summary = "根据会话ID生成聊天记录总结并保存") + @Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)") public Result generateAndSaveChatSummary(@PathVariable String sessionId) { try { - boolean success = agentChatSummaryService.generateAndSaveChatSummary(sessionId); - if (success) { - return new Result().ok(null); - } else { - return new Result().error("保存聊天记录总结失败"); - } + // 异步执行总结生成任务,立即返回成功响应 + new Thread(() -> { + try { + agentChatSummaryService.generateAndSaveChatSummary(sessionId); + System.out.println("异步执行会话 " + sessionId + " 的聊天记录总结完成"); + } catch (Exception e) { + System.err.println("异步执行会话 " + sessionId + " 的聊天记录总结失败: " + e.getMessage()); + } + }).start(); + + // 立即返回成功响应,不等待总结生成完成 + return new Result().ok(null); } catch (Exception e) { - return new Result().error("生成并保存聊天记录总结失败: " + e.getMessage()); + return new Result().error("启动异步总结生成任务失败: " + e.getMessage()); } } @@ -219,6 +209,7 @@ public class AgentController { List result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId); return new Result>().ok(result); } + @GetMapping("/{id}/chat-history/user") @Operation(summary = "获取智能体聊天记录(用户)") @RequiresPermissions("sys:role:normal") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java index e0bf12a7..f6fbe660 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentChatSummaryDTO.java @@ -3,15 +3,12 @@ package xiaozhi.modules.agent.dto; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import java.io.Serializable; - /** * 智能体聊天记录总结DTO */ @Data @Schema(description = "智能体聊天记录总结对象") -public class AgentChatSummaryDTO implements Serializable { - private static final long serialVersionUID = 1L; +public class AgentChatSummaryDTO { @Schema(description = "会话ID") private String sessionId; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java index c442deb8..418ee9a0 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentChatSummaryService.java @@ -1,20 +1,10 @@ package xiaozhi.modules.agent.service; -import xiaozhi.modules.agent.dto.AgentChatSummaryDTO; - /** * 智能体聊天记录总结服务接口 */ public interface AgentChatSummaryService { - /** - * 根据会话ID生成聊天记录总结 - * - * @param sessionId 会话ID - * @return 总结结果 - */ - AgentChatSummaryDTO generateChatSummary(String sessionId); - /** * 根据会话ID生成聊天记录总结并保存到智能体记忆 * diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java index 80ad03d6..5e855be6 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentChatSummaryServiceImpl.java @@ -53,8 +53,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService { private static final Pattern WEATHER_PATTERN = Pattern.compile("天气|温度|湿度|降雨|气象", Pattern.CASE_INSENSITIVE); private static final Pattern DATE_PATTERN = Pattern.compile("日期|时间|星期|月份|年份", Pattern.CASE_INSENSITIVE); - @Override - public AgentChatSummaryDTO generateChatSummary(String sessionId) { + private AgentChatSummaryDTO generateChatSummary(String sessionId) { try { System.out.println("开始生成会话 " + sessionId + " 的聊天记录总结"); diff --git a/main/xiaozhi-server/config/manage_api_client.py b/main/xiaozhi-server/config/manage_api_client.py index ae703815..70d9dbf0 100644 --- a/main/xiaozhi-server/config/manage_api_client.py +++ b/main/xiaozhi-server/config/manage_api_client.py @@ -186,18 +186,6 @@ async def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[ return None -async def generate_chat_summary(session_id: str) -> Optional[Dict]: - """生成聊天记录总结""" - try: - return await ManageApiClient._instance._execute_async_request( - "POST", - f"/agent/chat-summary/{session_id}", - ) - except Exception as e: - print(f"生成聊天记录总结失败: {e}") - return None - - async def generate_and_save_chat_summary(session_id: str) -> Optional[Dict]: """生成并保存聊天记录总结""" try: From 96991ae5ef3153f6bb10d59fc3c3a58cd46671c8 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Fri, 19 Dec 2025 13:29:06 +0800 Subject: [PATCH 3/8] =?UTF-8?q?add:=E5=A4=9A=E8=AF=AD=E8=A8=80logo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/assets/xiaozhi-ai.png | Bin 1946 -> 1941 bytes main/manager-web/src/assets/xiaozhi-ai_de.png | Bin 0 -> 6957 bytes main/manager-web/src/assets/xiaozhi-ai_en.png | Bin 0 -> 7354 bytes main/manager-web/src/assets/xiaozhi-ai_vi.png | Bin 0 -> 7171 bytes .../src/assets/xiaozhi-ai_zh_CN.png | Bin 0 -> 1946 bytes .../src/assets/xiaozhi-ai_zh_TW.png | Bin 0 -> 1828 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 main/manager-web/src/assets/xiaozhi-ai_de.png create mode 100644 main/manager-web/src/assets/xiaozhi-ai_en.png create mode 100644 main/manager-web/src/assets/xiaozhi-ai_vi.png create mode 100644 main/manager-web/src/assets/xiaozhi-ai_zh_CN.png create mode 100644 main/manager-web/src/assets/xiaozhi-ai_zh_TW.png diff --git a/main/manager-web/src/assets/xiaozhi-ai.png b/main/manager-web/src/assets/xiaozhi-ai.png index ef3834afc08b6e3c1ffa652275e4a2d3d455facc..8a59549341b7c1ed522b30c6611849681efb6ab9 100644 GIT binary patch delta 1905 zcmV-%2afof50wv)Ie$k>dEz`2g-^ccYalH!B`{m>Pzgy1rUX&~lz>Y} zN-#TbLkZ&&h7x8Lx}G>-z_PAnW1V-G0LFlHbansq^ykkX5JJ-Bzp-zC5HcdxdV{*=HvIS(U{}>3*DX5doJ$v&~FC zbda}MS7lLTCVyW6C>5^fL{%2uX3+%5Ox{=h-n9gvZd<%( z7TyMS^50_tD@w0Q_vB135ooCynGQKTOnMJ|tQY_|!des25mVepz|PspHMjUO@rUq| znS3+WHmk2=Q1go;WGuGOTg;ACSrlT`Xk2qEzMAOV6@LM-0>)Tz2vI{OnC2vF`@L@Z z1svFm(=sZ3wga>RJn4uA_^ceO%;dh;NDBG>HGEVJhqgz*DvLTZd4b{1)`n*Q@0rO> zOuxxZ5ug>IhUOr==4B?=*+6$3ckSKZU@Z+_ab5r@V@3?FZU;m?X$6pnM$Bu?aa?r8 z^<~0pIe+#VA&6q_-SP=&t0y62uvfm`)rl_N{iD+RED=cvI=2JjL~&Km#!>Qu{&CnN zfR8(m6EX@Cc+AKd$86h zL#7C%#U8g;?;*}K*Z8=`1ZLFeE0^HS*SS7t5mz!;Gx-=N*rdKCc+=|RIoJ{qGgE7&X}r@-mR~Vu&wsV4 zBa>b`C;rh$d3+R_nYPX?$1{^}GL!#C7*n&^$N$bu{z=DPH?jg~HL{3})(W1gC2*|D zLiM-r+hgzrejRFuu!K!3-hv^a3#0+7c78lyzw2ge5dy6hJVg;WAwWaYYgL2`*h#P5 zm~jZr79n->x1rt1xrVg}w7#1fc7KqC?Cd-@OW?dv?dtQ+^ZrG|Kha-s*CDD2uUP^o zSP{^QD>t58*DzJ1d{fR|yNP(}wMt7b5Q_lt7lI>fDrzm6gP~9T_~>2jiYwaP@AXo}bboNp7$k(u3yMG- zQqFTH6Ne%YhpA+@5pn@VfRMzA=@|SGWq)p-+I2#Gwd8CQu^~LI%U_Kw!l5zf~qW zm_E&5uiVoL!yAXprhmHgjJ+JmmK+p;z(~Eg>nYDSQ7E1DaVV}l9lUHJJ24m{jDw~K zoG@=_Q+3J`T%^8|cWqA2O2o+-7V6MFi7{`QG8A2Bj5NxG%P7l)4!LehfU0YVaoB0xyu rPy`4`9Et!Ti9-<}BylJLge1;iVFt3b!d7pB00000NkvXXu0mjfX9#%K delta 1910 zcmV-+2Z{KV51J2Y} zN-#TbLkZ&&#wE-wBo7|OfF4(}LFd0q0Go$&bah{P`St4;2qF3M-_&n_5Hc+k0YWl| zB0xyyPy`6cod0c}^z!mD`u{?eO8_g&dv#4+RhG}qf z`lhb-A@@&><9NM`%jYjtxd5;>f9?RN(BBULBpmxzB5;E>fUV_o-vjUUjJ!e*2p`@7 zgbtz={6(k*@K7QE-~eDLbgj*;2Mbk}(7*2rvD72z>LCd6yty+50}Y6D*Hgn6%00?-bv+6#+r z3qASov49q(H=+0BjkgF4l#WbGi2##6fDkJN08Y@>M0AWP9wJzm?9DZIIGgxBtP53c zr`}eDI0hxZR6=61gWQ63YU-*oR*b|ocg8m}8D1h_tbc$hRv$u~FcVC2k_~-ZH~j(* z9E{U)5`4DFJ^D>uwS_7x_&Zm7UIBa*s#Gcc zBt6a|Kr0{!$w7I|>rBpUAv;#b_U`Y{mPV);F96h2L=2p^14dbC1(4Ze%xlT9T8+i^ z&4iV5?0@w_kkqwAd9%#pq;;urKHPS>XT(*_Z#7CjZeN$Ia>Tg182grkByyyM$wl;#W#~>E& zP=6bc%2|9wo+1DMTl1T}NrwY!~xh+)r6LHMWejop{P~{gLduPmwAl29+HNGo)u9m>5sjDcz zbx4oFHNv{p`mlsfE8cOM^peMa_(?%cq zEkbUl@Q`8m<`&u_korDa=s_2=n}748TLPEGXjh+infGr*`wR90yNOv%=wb|oe zWA44Yt|6;l73Y-6b{FHRmnt>=2V)Tc;X-hNPEBo1=3p$m3St7ngto~{(GSTnpFu&3 z?7h#ik6AnfIEa98%Do9W|5^YFCi@9)6Iz+F%}0w4#9YBzKxAw13BBrcgMYtc!J`)xEph#~s&HOBMvtAF>=-h$5`Tv_ zJzWM{KLGx$iso82rcspeNPl8lq#3ju*wxjy(B|f-`D-kKZ-Uu|H*-HcSYjTDP_Y;~ z-6f8l8^y~K7*eS62cA2FOU(N4SfOqCCvEsn4#8g~!;gw^Udr4V9fxx`kd(L)%P(;4 zT)+;0uii(TSCi#L2VMl9rc7j^2&5&Jx82qmIES+eqM5bC50Q*PLVw8PLJ>$y&Ux=- zrceaZGM8*OLjFJzAS839b_{+?al*=sLP?U_f!@=9g0?Vxo0E{tnOX#|p-+I2%%KQOM5IO_ggC?PKq8Fkf2(ZN8=D5O zSKiYK{*6N(x}tNy!G9dVmMs*4M40+<*OQ%ZB2Y>j;!s=#I(6ANb|y?M0=b0S5L5lD>jh)YahiU1LTA}|f16ZpjN8j*q`Fb$z15cMz$l5ZCjfoTX40o&uR2(f}9 z@K_w7&u!pVfQTWHClInKB}p9(Os|cR+++KhhbPc{rpT_$knBr|tqva{ggh_Y4iJ(# w6ahjqhax~o=1>F($sCFRA(=xFAS82s1LfGVwb@@<$p8QV07*qoM6N<$g8H(4pa1{> diff --git a/main/manager-web/src/assets/xiaozhi-ai_de.png b/main/manager-web/src/assets/xiaozhi-ai_de.png new file mode 100644 index 0000000000000000000000000000000000000000..f2c54d80c00fe5bf9e577af81f358fb56b1d32ac GIT binary patch literal 6957 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 Sc^5-Q0@Rgtlu!z=(EkV1BkfrL literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/xiaozhi-ai_vi.png b/main/manager-web/src/assets/xiaozhi-ai_vi.png new file mode 100644 index 0000000000000000000000000000000000000000..a54913ad3edf5c7f3794e062a25f1950601df5cf GIT binary patch literal 7171 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 FV1jL625A5Q literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/xiaozhi-ai_zh_CN.png b/main/manager-web/src/assets/xiaozhi-ai_zh_CN.png new file mode 100644 index 0000000000000000000000000000000000000000..ef3834afc08b6e3c1ffa652275e4a2d3d455facc GIT binary patch literal 1946 zcmV;L2W9w)P)#zkSF%Cpze@m{hjesxUwZlV>lX+i`SRb?Z-5XoEffJlGKV5S zNaj!k2+5rPZJzY<@-q7WLX}GZE6aOzOvD72z>LCd6yty+50}Y6D*Hgn6%00?-bv+6#+r3qASo zv49q(H=+0BjkgF4l#WbGi2##6fDkJN08Y@>M0AWP9wJzm?9DZIIGgxBtP53cr`}eD zI0hxZR6=61gWQ63YU-*oR*b|ocg8m}8D1h_tbi$2A3~fk6HIcF4SicT{Q?dgjMH)w ze76I%0z4Rr28666t3s8b*LZ5o_buUv>NyTQ`b}N6g(@rfJ6C&N0elpyR4M%=J$9OxeJ)ga zfF5Yakm9aFl{L~tDqOae&%{Te%6(H;QR;6(YX``KV!Y@5^0qdDu*V=4?ob<$%2|9w zo+1DMTl1T}Nrw8(gf+nQ)sV=E%1s0&#Uy_GX-Z=9 zFKnYNm)tc62ko@xAbgimAK%VICr|`7lCAojMBs7|k1=pMg@%mw(A{T7%}U1vq%0UT zdRZX6vdxu6p~@5|+2p<@c$e!z?ANnmqZl>^%VfW@1 z+9HtpK3eEO7qgr5qgw)(#b{TbcbWHZMEeW&0=tP>P3U3?bkHIo6=UwbysjauUKQt* z$#xgxsh27>{Rd+a0O3M#f=*3sP3B-Myb59h!i2WTOwkX?F`q#}i|oD6v5#3i1UQI* zamu|3IsaM!3MTsrZWCIWvdu?}4#Zr+T0mrL@Cm)@bc4TR!J`)xEph#~s&HOBMvtAF>=-h$5`Tv_JzWM{KLGx$iso82 zrcspeNMc%~8MGVN)z!Dq=H{sRYb=6qg4u>Qb3Z&-VjhW5u^2ktC61jN#mf>HQmFC= zo;!m}%=++Hp>6pmZTL1ky5>Y&SywKoKA$ zbEb9-eoJw}%8WuulG}ma(|>}tFnpVnkj$A{1g1LFVJbvLU~t0IGv{fL$V@E)Ss~nB zjL0C`s%wn7H-Di|fRN0g2uwtzMj(VZ!|gyKjOl-?Y}6Z@2C!G&(+d8LLms-KbHKqI z!Imu)fkc@4ao3ZbZz51i8{$x01v+)vICdsXEdsfP2qi_J!?dNep>+tKU%4lgy6A>r^>5=k*uo#JmKoLlc@rX-IV2S_{ zfg&&sp%eJT@EVbVA}|f1A`ta33X&HTfoTX40o&uR2(f}9@K_w7&u!pVfQTWHClInK zB}p9(Os|cR+++KhhbPc{rpT_$knBr|tqva{ggh_Y4iJ(#6ahjqhax~o=1>F($sCFR gA(=xFAS82s1LfGVwb@@<$p8QV07*qoM6N<$g5r986951J literal 0 HcmV?d00001 diff --git a/main/manager-web/src/assets/xiaozhi-ai_zh_TW.png b/main/manager-web/src/assets/xiaozhi-ai_zh_TW.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5f59aa4905100f8da843a3cc178fa25956f55d GIT binary patch literal 1828 zcmV+<2iy3GP)^&2XIQ-V@LC;>`9C6E%7 zH@}h+a0$mHa33UtNt{Hsej>~3o5{>MkT@o>m$vrr-PhL_2qDYmpIe^*A>_7D1PEC; z6ahjO4n=^Fh4b(65AN>n2A^07xd!m)c-*$uN_m`D(Nsd-1CSG6eyFwDd%ph@fYsF3 z*IK>$ygvy?2*>`32wdO^z}E4(&lApNN!$Z?nfTgg0A3;hBj!^@r~y#C2vkBo0??j^ z5x#<*f-xd6=AwD2gq(Vj_u151sX$-IXlkv7&7usbgme|;A=VzRz4v_YU9DB)@p=G2 zCFCbOteHgUN=(&SCmH|`z6WsnzGKSid6H*jImcS#- z%`?Ul=x33Q*+Q%xvd>yf+gCMxfeDnsNJw#LT0oZEL&SCXr1>qG&5cc*tu2$g9 z9mlm+mwZaZ0gAwauwmc#fnRL_Y%3vK7(v)#X30pmDl$ts)Q_Mf=%pBKv^=`5(unpop-v+ z@>8`|ZJ=WphrQ|jlg0_bgqGu#klRYgzldYnbo=_&un6%;-u#o5k+kiD>pMs79ezq1dpoNzi=c))-FgN`Qx9=&?Z4qex8v31_ z8yJfq)MwKG_=C7fH^D4{fms-(x4K{3zZo!*p&*OnIZL30`EWzUl@Bn2GB!-r!d#TI z%3kYZzqF57$F~*%;4cIR0Do$&8k;#7i;!u)Yh>ziPrwF&EaX9-K&@3|^Z3B??GBiMa=EfNvjx&v&(h`K+7X|6>qr^&;W8=o7G+V0)M_;FLXL3v+R`01lOq zV#KkYy9>s~ZnGT(a*0~2)~>8ktr`1WtufAeYhXU-{8oZ(mtFB1yjt3f^ z8%PSwX7rN}SF`b^Pzm{tIC=XgX~S1?2!53e&z*(mOKfVbRO*4GWIC}dfpc>KkGCIt z{0uSedze$=Jxw_v*$fKJp#+*DkcL>^cGo zQnqE9_hjBBLlH;=v*bv&Pz2Jze0h>B6ahjOPD;n%LqBIA7MV702)P~TJpBh43u9_? zl45Y|ebH5@P6^d2nDFt{@6o=`JKU#m^$G6ub0MV&e9qV->q}4(=uLYnIcLL}1i~)? z$OPB#o2}U0q*yRJxE)A9tyNQNb=N&WZ+1|uD3+38`UtaNCad112#_MEwR%Qb4%nAU z$WLn-M1>-dm`II4h#~oYIx;H&@0E}`W}i)F3`WN8+dmMYiqOR&cIKRXQdOhWSA-zs zwjfYCuc)=!PiV;dir;>u#-7tUMiCYZh<+2RfanF`Iy61QLOp_vIMI zoY$Di-U Date: Fri, 19 Dec 2025 14:33:35 +0800 Subject: [PATCH 4/8] =?UTF-8?q?add:=E7=99=BB=E5=BD=95=E3=80=81=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E3=80=81=E9=A6=96=E9=A1=B5=E3=80=81=E5=BF=98=E8=AE=B0?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E9=A1=B5=E7=9A=84=E5=A4=9A=E8=AF=AD=E8=A8=80?= =?UTF-8?q?logo=E6=98=BE=E7=A4=BA=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/components/HeaderBar.vue | 20 +++++++++++++- main/manager-web/src/views/login.vue | 20 +++++++++++++- main/manager-web/src/views/register.vue | 27 +++++++++++++++++-- .../src/views/retrievePassword.vue | 27 +++++++++++++++++-- 4 files changed, 88 insertions(+), 6 deletions(-) diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index b274d649..baaa9fa6 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -4,7 +4,7 @@

@@ -257,6 +257,24 @@ export default { return this.$t("language.zhCN"); } }, + // 根据当前语言获取对应的xiaozhi-ai图标 + xiaozhiAiIcon() { + const currentLang = this.currentLanguage; + switch (currentLang) { + case "zh_CN": + return require("@/assets/xiaozhi-ai_zh_CN.png"); + case "zh_TW": + return require("@/assets/xiaozhi-ai_zh_TW.png"); + case "en": + return require("@/assets/xiaozhi-ai_en.png"); + case "de": + return require("@/assets/xiaozhi-ai_de.png"); + case "vi": + return require("@/assets/xiaozhi-ai_vi.png"); + default: + return require("@/assets/xiaozhi-ai_zh_CN.png"); + } + }, // 用户菜单选项 userMenuOptions() { return [ diff --git a/main/manager-web/src/views/login.vue b/main/manager-web/src/views/login.vue index cdcf3754..0f27e9c4 100644 --- a/main/manager-web/src/views/login.vue +++ b/main/manager-web/src/views/login.vue @@ -10,7 +10,7 @@ gap: 10px; "> - +