package xiaozhi.modules.agent.controller; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.JsonUtils; import xiaozhi.common.utils.Result; import xiaozhi.common.utils.ResultUtils; import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentMemoryDTO; import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.dto.AgentTagDTO; import xiaozhi.modules.agent.entity.AgentTagEntity; import xiaozhi.modules.agent.service.AgentTagService; 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.AgentTemplateService; import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO; import xiaozhi.modules.agent.vo.AgentInfoVO; import xiaozhi.modules.security.user.SecurityUser; @Tag(name = "智能体管理") @AllArgsConstructor @RestController @RequestMapping("/agent") public class AgentController { private static final long AUDIO_PLAY_TOKEN_EXPIRE_SECONDS = 300L; private final AgentService agentService; private final AgentTemplateService agentTemplateService; private final AgentChatHistoryService agentChatHistoryService; private final AgentChatAudioService agentChatAudioService; private final AgentChatSummaryService agentChatSummaryService; private final RedisUtils redisUtils; private final AgentTagService agentTagService; private void requireAgentPermission(String agentId) { if (!agentService.checkAgentPermission(agentId, SecurityUser.getUserId())) { throw new RenException(ErrorCode.NO_PERMISSION); } } private String requireSessionAgent(String sessionId) { String agentId = agentChatHistoryService.getAgentIdBySessionId(sessionId); if (StringUtils.isBlank(agentId)) { throw new RenException(ErrorCode.AGENT_NOT_FOUND); } agentService.getAgentById(agentId); return agentId; } private String requireAudioPermission(String audioId) { String agentId = agentChatHistoryService.getAgentIdByAudioId(audioId); if (StringUtils.isBlank(agentId)) { throw new RenException(ErrorCode.NO_PERMISSION); } requireAgentPermission(agentId); return agentId; } @GetMapping("/list") @Operation(summary = "获取用户智能体列表") @RequiresPermissions("sys:role:normal") public Result> getUserAgents( @RequestParam(value = "keyword", required = false) String keyword, @RequestParam(value = "searchType", defaultValue = "name") String searchType) { UserDetail user = SecurityUser.getUser(); // 直接调用整合后的getUserAgents方法,无需再区分搜索和普通查询 List agents = agentService.getUserAgents(user.getId(), keyword, searchType); return new Result>().ok(agents); } @GetMapping("/all") @Operation(summary = "智能体列表(管理员)") @RequiresPermissions("sys:role:superAdmin") @Parameters({ @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), }) public Result> adminAgentList( @Parameter(hidden = true) @RequestParam Map params) { PageData page = agentService.adminAgentList(params); return new Result>().ok(page); } @GetMapping("/{id}") @Operation(summary = "获取智能体详情") @RequiresPermissions("sys:role:normal") public Result getAgentById(@PathVariable("id") String id) { AgentInfoVO agent = agentService.getAgentById(id, SecurityUser.getUserId()); return ResultUtils.success(agent); } @PostMapping @Operation(summary = "创建智能体") @RequiresPermissions("sys:role:normal") public Result save(@RequestBody @Valid AgentCreateDTO dto) { String agentId = agentService.createAgent(dto); return new Result().ok(agentId); } @PutMapping("/saveMemory/{macAddress}") @Operation(summary = "根据设备id更新智能体") @RequiresPermissions("sys:role:normal") public Result updateByDeviceId(@PathVariable String macAddress, @RequestBody @Valid AgentMemoryDTO dto) { agentService.updateAgentMemoryByDeviceMacAddress(macAddress, dto, SecurityUser.getUserId()); return new Result().ok(null); } @PostMapping("/chat-summary/{sessionId}/save") @Operation(summary = "根据会话ID生成聊天记录总结并保存(异步执行)") public Result generateAndSaveChatSummary(@PathVariable String sessionId) { requireSessionAgent(sessionId); try { // 异步执行总结生成任务,立即返回成功响应 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()); } } @PostMapping("/chat-title/{sessionId}/generate") @Operation(summary = "根据会话ID生成聊天标题") public Result generateAndSaveChatTitle(@PathVariable String sessionId) { requireSessionAgent(sessionId); agentChatSummaryService.generateAndSaveChatTitle(sessionId); return new Result().ok(null); } @PutMapping("/{id}") @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") public Result update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) { agentService.updateAgentById(id, dto, SecurityUser.getUserId()); return new Result<>(); } @DeleteMapping("/{id}") @Operation(summary = "删除智能体") @RequiresPermissions("sys:role:normal") public Result delete(@PathVariable String id) { agentService.deleteAgentById(id, SecurityUser.getUserId()); return new Result<>(); } @GetMapping("/template") @Operation(summary = "智能体模板模板列表") @RequiresPermissions("sys:role:normal") public Result> templateList() { List list = agentTemplateService .list(new QueryWrapper().orderByAsc("sort")); return new Result>().ok(list); } @GetMapping("/{id}/sessions") @Operation(summary = "获取智能体会话列表") @RequiresPermissions("sys:role:normal") @Parameters({ @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), }) public Result> getAgentSessions( @PathVariable("id") String id, @Parameter(hidden = true) @RequestParam Map params) { requireAgentPermission(id); params.put("agentId", id); PageData page = agentChatHistoryService.getSessionListByAgentId(params); return new Result>().ok(page); } @GetMapping("/{id}/chat-history/{sessionId}") @Operation(summary = "获取智能体聊天记录") @RequiresPermissions("sys:role:normal") public Result> getAgentChatHistory( @PathVariable("id") String id, @PathVariable("sessionId") String sessionId) { // 获取当前用户 UserDetail user = SecurityUser.getUser(); // 检查权限 if (!agentService.checkAgentPermission(id, user.getId())) { return new Result>().error("没有权限查看该智能体的聊天记录"); } // 查询聊天记录 List result = agentChatHistoryService.getChatHistoryBySessionId(id, sessionId); return new Result>().ok(result); } @GetMapping("/{id}/chat-history/user") @Operation(summary = "获取智能体聊天记录(用户)") @RequiresPermissions("sys:role:normal") public Result> getRecentlyFiftyByAgentId( @PathVariable("id") String id) { // 获取当前用户 UserDetail user = SecurityUser.getUser(); // 检查权限 if (!agentService.checkAgentPermission(id, user.getId())) { return new Result>().error("没有权限查看该智能体的聊天记录"); } // 查询聊天记录 List data = agentChatHistoryService.getRecentlyFiftyByAgentId(id); return new Result>().ok(data); } @GetMapping("/{id}/chat-history/audio") @Operation(summary = "获取音频内容") @RequiresPermissions("sys:role:normal") public Result getContentByAudioId( @PathVariable("id") String id) { requireAudioPermission(id); // 查询聊天记录 String data = agentChatHistoryService.getContentByAudioId(id); return new Result().ok(data); } @PostMapping("/audio/{audioId}") @Operation(summary = "获取音频下载ID") @RequiresPermissions("sys:role:normal") public Result getAudioId(@PathVariable("audioId") String audioId) { requireAudioPermission(audioId); byte[] audioData = agentChatAudioService.getAudio(audioId); if (audioData == null) { return new Result().error("音频不存在"); } String uuid = UUID.randomUUID().toString(); redisUtils.set(RedisKeys.getAgentAudioIdKey(uuid), audioId, AUDIO_PLAY_TOKEN_EXPIRE_SECONDS); return new Result().ok(uuid); } @GetMapping("/play/{uuid}") @Operation(summary = "播放音频") public ResponseEntity playAudio(@PathVariable("uuid") String uuid) { String audioId = (String) redisUtils.get(RedisKeys.getAgentAudioIdKey(uuid)); if (StringUtils.isBlank(audioId)) { return ResponseEntity.notFound().build(); } byte[] audioData = agentChatAudioService.getAudio(audioId); if (audioData == null) { return ResponseEntity.notFound().build(); } redisUtils.delete(RedisKeys.getAgentAudioIdKey(uuid)); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"play.wav\"") .body(audioData); } @PostMapping("/tag") @Operation(summary = "创建标签") @RequiresPermissions("sys:role:normal") public Result createTag(@RequestBody Map params) { String tagName = params.get("tagName"); if (StringUtils.isBlank(tagName)) { return new Result().error("标签名称不能为空"); } AgentTagEntity tag = agentTagService.saveTag(tagName); return new Result().ok(tag); } @GetMapping("/tag/list") @Operation(summary = "获取所有标签列表") @RequiresPermissions("sys:role:normal") public Result> getAllTags() { List tags = agentTagService.getAllTags(); return new Result>().ok(tags); } @DeleteMapping("/tag/{id}") @Operation(summary = "删除标签") @RequiresPermissions("sys:role:normal") public Result deleteTag(@PathVariable String id) { agentTagService.deleteTag(id); return new Result().ok(null); } @GetMapping("/{id}/tags") @Operation(summary = "获取智能体的标签") @RequiresPermissions("sys:role:normal") public Result> getAgentTags(@PathVariable String id) { requireAgentPermission(id); List tags = agentTagService.getTagsByAgentId(id); return new Result>().ok(tags); } @PutMapping("/{id}/tags") @Operation(summary = "保存智能体的标签") @RequiresPermissions("sys:role:normal") public Result saveAgentTags(@PathVariable String id, @RequestBody Map params) { requireAgentPermission(id); List tagIds = JsonUtils.toList(params.get("tagIds"), String.class); List tagNames = JsonUtils.toList(params.get("tagNames"), String.class); AgentUpdateDTO dto = new AgentUpdateDTO(); dto.setTagIds(tagIds); dto.setTagNames(tagNames); agentService.updateAgentById(id, dto); return new Result().ok(null); } }