From a48287611358f7b885ca0e7b99e14ede39af6347 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 22 May 2024 13:12:54 +0800 Subject: [PATCH] 【优化】AI:聊天消息的 Service 实现 --- yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java | 4 ++-- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++------------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java | 7 ++++--- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java | 30 ------------------------------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java | 43 ------------------------------------------- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java | 6 ------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiImageMapper.java | 1 - yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java | 24 ++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java | 57 --------------------------------------------------------- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java | 272 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java | 18 ++++++++++++------ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java | 15 ++++++++------- 14 files changed, 410 insertions(+), 439 deletions(-) delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java index 4542c07..cd1985f 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java @@ -30,8 +30,8 @@ public interface ErrorCodeConstants { ErrorCode CHAT_CONVERSATION_UPDATE_MAX_TOKENS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Token 超过上限"); ErrorCode CHAT_CONVERSATION_UPDATE_MAX_CONTEXTS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Context 超过上限"); - // chat - ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_022_000_100, "提问的 MessageId 不存在!"); + // ========== API 聊天消息 1-040-004-000 ========== + ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); // midjourney diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java index ad9375f..2522d0d 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java @@ -1,8 +1,19 @@ package cn.iocoder.yudao.module.ai.controller.admin.chat; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*; -import cn.iocoder.yudao.module.ai.service.AiChatService; +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; +import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; +import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService; +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -14,9 +25,12 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; +import java.util.Collections; import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 聊天消息") @@ -26,39 +40,65 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti public class AiChatMessageController { @Resource - private AiChatService chatService; + private AiChatMessageService chatMessageService; + @Resource + private AiChatConversationService chatConversationService; + @Resource + private AiChatRoleService chatRoleService; + + @Resource + private AdminUserApi adminUserApi; @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢") @PostMapping("/send") public CommonResult sendMessage(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) { - return success(chatService.chat(sendReqVO)); + return success(chatMessageService.sendMessage(sendReqVO)); } @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题 public Flux sendChatMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) { - return chatService.sendChatMessageStream(sendReqVO, getLoginUserId()); + return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId()); } @Operation(summary = "获得指定会话的消息列表") @GetMapping("/list-by-conversation-id") @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024") - public CommonResult> getMessageListByConversationId(@RequestParam("conversationId") Long conversationId) { - return success(chatService.getMessageListByConversationId(conversationId)); + public CommonResult> getChatMessageListByConversationId( + @RequestParam("conversationId") Long conversationId) { + AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId); + if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) { + return success(Collections.emptyList()); + } + List messageList = chatMessageService.getChatMessageListByConversationId(conversationId); + if (CollUtil.isEmpty(messageList)) { + return success(Collections.emptyList()); + } + + // 拼接数据 + Map roleMap = chatRoleService.getChatRoleMap(convertSet(messageList, AiChatMessageDO::getRoleId)); + AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId()); + return success(BeanUtils.toBean(messageList, AiChatMessageRespVO.class, respVO -> { + MapUtils.findAndThen(roleMap, respVO.getRoleId(), role -> respVO.setRoleAvatar(role.getAvatar())); + respVO.setUserAvatar(user.getAvatar()); + })); } @Operation(summary = "删除消息") @DeleteMapping("/delete") @Parameter(name = "id", required = true, description = "消息编号", example = "1024") - public CommonResult deleteMessage(@RequestParam("id") Long id) { - return success(chatService.deleteMessage(id)); + public CommonResult deleteChatMessage(@RequestParam("id") Long id) { + chatMessageService.deleteMessage(id, getLoginUserId()); + return success(true); } - @Operation(summary = "删除消息-对于对话全部消息") + @Operation(summary = "删除指定会话的消息") @DeleteMapping("/delete-by-conversation-id") - @Parameter(name = "id", required = true, description = "消息编号", example = "1024") - public CommonResult deleteByConversationId(@RequestParam("conversationId") Long conversationId) { - return success(chatService.deleteByConversationId(conversationId)); + @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024") + public CommonResult deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) { + chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId()); + return success(true); } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java index 3314b1d..fbc31ee 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java @@ -31,13 +31,14 @@ public class AiChatMessageSendRespVO { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; - // ========= 扩展字段 + // ========== 扩展字段 ========== - @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx") + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/1.png") private String userAvatar; - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx") + @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/2.png") private String roleAvatar; + } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java deleted file mode 100644 index eda5563..0000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.ai.convert; - -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -/** - * 聊天 对话 convert - * - * @author fansili - * @time 2024/4/18 16:39 - * @since 1.0 - */ -@Mapper -public interface AiChatMessageConvert { - - AiChatMessageConvert INSTANCE = Mappers.getMapper(AiChatMessageConvert.class); - - /** - * 转换 AiChatMessageRespVO - * - * @param aiChatMessageDOList - * @return - */ - List convertAiChatMessageRespVOList(List aiChatMessageDOList); - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java deleted file mode 100644 index 6c9c22a..0000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.iocoder.yudao.module.ai.dal.mysql; - -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import org.apache.ibatis.annotations.Mapper; -import org.springframework.stereotype.Repository; - -import java.util.List; - -/** - * message mapper - * - * @fansili - * @since v1.0 - */ -@Repository -@Mapper -public interface AiChatMessageMapper extends BaseMapperX { - - /** - * 查询 - 根据 对话id查询 - * - * @param conversationId - */ - default List selectByConversationId(Long conversationId) { - return this.selectList( - new LambdaQueryWrapperX() - .eq(AiChatMessageDO::getConversationId, conversationId) - .orderByAsc(AiChatMessageDO::getId) - ); - } - - /** - * 删除 - 根据 conversationId - * - * @param conversationId - */ - default int deleteByConversationId(Long conversationId) { - return this.delete(new LambdaQueryWrapperX().eq(AiChatMessageDO::getConversationId, conversationId)); - } -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java index 3c959e9..25687d0 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java @@ -1,18 +1,13 @@ package cn.iocoder.yudao.module.ai.dal.mysql; -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.ibatis.annotations.Mapper; -import java.util.List; - import java.util.Collection; import java.util.List; @@ -42,7 +37,6 @@ public interface AiChatModelMapper extends BaseMapperX { return this.selectList(new LambdaQueryWrapperX().eq(AiChatModelDO::getId, modalIds)); } - default PageResult selectPage(AiChatModelPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(AiChatModelDO::getName, reqVO.getName()) diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiImageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiImageMapper.java index 1ee5b43..d353fea 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiImageMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiImageMapper.java @@ -13,7 +13,6 @@ import org.springframework.stereotype.Repository; * @time 2024/4/28 14:01 * @since 1.0 */ -@Repository @Mapper public interface AiImageMapper extends BaseMapperX { diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java new file mode 100644 index 0000000..7b3e9c4 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.ai.dal.mysql.chat; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * AI 聊天对话 Mapper + * + * @author fansili + */ +@Mapper +public interface AiChatMessageMapper extends BaseMapperX { + + default List selectListByConversationId(Long conversationId) { + return selectList(new LambdaQueryWrapperX() + .eq(AiChatMessageDO::getConversationId, conversationId) + .orderByAsc(AiChatMessageDO::getId)); + } + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java deleted file mode 100644 index f8924cb..0000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.ai.service; - -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*; -import reactor.core.publisher.Flux; - -import java.util.List; - -/** - * 聊天 chat - * - * @author fansili - * @time 2024/4/14 15:55 - * @since 1.0 - */ -public interface AiChatService { - - /** - * chat - * - * @param sendReqVO - * @return - */ - AiChatMessageRespVO chat(AiChatMessageSendReqVO sendReqVO); - - /** - * 获取 - 获取对话 message list - * - * @param conversationId - * @return - */ - List getMessageListByConversationId(Long conversationId); - - /** - * 删除 - 删除message - * - * @param id - * @return - */ - Boolean deleteMessage(Long id); - - /** - * 发送消息 - * - * @param sendReqVO - * @param userId - * @return - */ - Flux sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId); - - /** - * 删除消息-对于对话全部消息 - * - * @param conversationId - * @return - */ - Boolean deleteByConversationId(Long conversationId); -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java new file mode 100644 index 0000000..e9dc43e --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.ai.service.chat; + +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*; +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; +import reactor.core.publisher.Flux; + +import java.util.List; + +/** + * AI 聊天消息 Service 接口 + * + * @author fansili + */ +public interface AiChatMessageService { + + /** + * 发送消息 + * + * @param sendReqVO 发送信息 + * @return 发送结果 + */ + AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO sendReqVO); + + /** + * 发送消息 + * + * @param sendReqVO 发送信息 + * @param userId 用户编号 + * @return 发送结果 + */ + Flux sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId); + + /** + * 获得指定会话的消息列表 + * + * @param conversationId 会话编号 + * @return 消息列表 + */ + List getChatMessageListByConversationId(Long conversationId); + + /** + * 删除消息 + * + * @param id 消息编号 + * @param userId 用户编号 + */ + void deleteMessage(Long id, Long userId); + + /** + * 删除指定会话的消息 + * + * @param conversationId 会话编号 + * @param userId 用户编号 + */ + void deleteChatMessageByConversationId(Long conversationId, Long userId); + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java new file mode 100644 index 0000000..3e4f602 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -0,0 +1,251 @@ +package cn.iocoder.yudao.module.ai.service.chat; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; +import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; +import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.ChatResponse; +import org.springframework.ai.chat.StreamingChatClient; +import org.springframework.ai.chat.messages.*; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; +import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; +import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST; +import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; + +/** + * AI 聊天消息 Service 实现类 + * + * @author fansili + */ +@Service +@Slf4j +public class AiChatMessageServiceImpl implements AiChatMessageService { + + @Resource + private AiChatMessageMapper chatMessageMapper; + + @Resource + private AiClientFactory clientFactory; + + @Resource + private AiChatConversationService chatConversationService; + @Resource + private AiChatModelService chatModalService; + @Resource + private AiChatRoleService chatRoleService; + @Resource + private AiApiKeyService apiKeyService; + + @Resource + private AdminUserApi adminUserApi; + + @Transactional(rollbackFor = Exception.class) + public AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO req) { + return null; // TODO 芋艿:一起改 +// Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); +// // 查询对话 +// AiChatConversationDO conversation = chatConversationService.validateExists(req.getConversationId()); +// // 获取对话模型 +// AiChatModelDO chatModel = chatModalService.validateChatModel(conversation.getModelId()); +// // 获取角色信息 +// AiChatRoleDO chatRoleDO = conversation.getRoleId() != null ? chatRoleService.validateChatRole(conversation.getRoleId()) : null; +// // 获取 client 类型 +// AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform()); +// // 保存 chat message +// createChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(), +// chatModel.getModel(), chatModel.getId(), req.getContent()); +// String content = null; +// int tokens = 0; +// try { +// // 创建 chat 需要的 Prompt +// Prompt prompt = new Prompt(req.getContent()); +// // TODO @芋艿 @范 看要不要支持这些 +//// req.setTopK(req.getTopK()); +//// req.setTopP(req.getTopP()); +//// req.setTemperature(req.getTemperature()); +// // 发送 call 调用 +// ChatClient chatClient = chatClientFactory.getChatClient(platformEnum); +// ChatResponse call = chatClient.call(prompt); +// content = call.getResult().getOutput().getContent(); +// // 更新 conversation +// } catch (Exception e) { +// content = ExceptionUtil.getMessage(e); +// } finally { +// // 保存 chat message +// createChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), +// chatModel.getModel(), chatModel.getId(), content); +// } +// return new AiChatMessageRespVO().setContent(content); + } + + @Override + public Flux sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { + // 1.1 校验对话存在 + AiChatConversationDO conversation = chatConversationService.validateExists(sendReqVO.getConversationId()); + if (ObjUtil.notEqual(conversation.getUserId(), userId)) { + throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; + } + List historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId()); + // 1.2 校验模型 + AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); + StreamingChatClient chatClient = apiKeyService.getStreamingChatClient(model.getKeyId()); + // 1.3 获取用户头像、角色头像 + AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId()); + AiChatRoleDO role = conversation.getRoleId() != null ? chatRoleService.getChatRole(conversation.getRoleId()) : null; + + // 2. 插入 user 发送消息 + AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, + userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); + + // 3.1 插入 assistant 接收消息 + AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, + userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); + + // 3.2 创建 chat 需要的 Prompt + Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); + Flux streamResponse = chatClient.stream(prompt); + + // 3.3 流式返回 + // 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 + StringBuffer contentBuffer = new StringBuffer(); + return streamResponse.publishOn(Schedulers.single()).map(chunk -> { + String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; + newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 + contentBuffer.append(newContent); + // 响应结果 + AiChatMessageSendRespVO.Message send = BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class, + o -> o.setUserAvatar(user.getAvatar())); + AiChatMessageSendRespVO.Message receive = BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class, + o -> o.setRoleAvatar(role != null ? role.getAvatar() : null)).setContent(newContent); + return new AiChatMessageSendRespVO().setSend(send).setReceive(receive); + }).doOnComplete(() -> { + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())); + }).doOnError(throwable -> { + log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); + chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); + }); + } + + private Prompt buildPrompt(AiChatConversationDO conversation, List messages, + AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { + // 1. 构建 Prompt Message 列表 + List chatMessages = new ArrayList<>(); + // 1.1 system context 角色设定 + chatMessages.add(new SystemMessage(conversation.getSystemMessage())); + // 1.2 history message 历史消息 + List contextMessages = filterContextMessages(messages, conversation, sendReqVO); + contextMessages.forEach(message -> chatMessages.add(new ChatMessage(message.getType().toUpperCase(), message.getContent()))); + // 1.3 user message 新发送消息 + chatMessages.add(new UserMessage(sendReqVO.getContent())); + + // 2. 构建 ChatOptions 对象 + AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); + ChatOptions chatOptions = clientFactory.buildChatOptions(platform, model.getModel(), + conversation.getTemperature(), conversation.getMaxTokens()); + return new Prompt(chatMessages, chatOptions); + } + + /** + * 从历史消息中,获得倒序的 n 组消息作为消息上下文 + * + * n 组:指的是 user + assistant 形成一组 + * + * @param messages 消息列表 + * @param conversation 会话 + * @param sendReqVO 发送请求 + * @return 消息上下文 + */ + private List filterContextMessages(List messages, AiChatConversationDO conversation, AiChatMessageSendReqVO sendReqVO) { + if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { + return Collections.emptyList(); + } + List contextMessages = new ArrayList<>(conversation.getMaxContexts() * 2); + for (int i = messages.size() - 1; i >= 0; i--) { + AiChatMessageDO assistantMessage = CollUtil.get(messages, i); + if (assistantMessage == null || assistantMessage.getReplyId() == null) { + continue; + } + AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); + if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) + || StrUtil.isEmpty(assistantMessage.getContent())) { + continue; + } + // 由于后续要 reverse 反转,所以先添加 assistantMessage + contextMessages.add(assistantMessage); + contextMessages.add(userMessage); + // 超过最大上下文,结束 + if (contextMessages.size() >= conversation.getMaxContexts() * 2) { + break; + } + } + Collections.reverse(contextMessages); + return contextMessages; + } + + private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, + AiChatModelDO model, Long userId, Long roleId, + MessageType messageType, String content, Boolean useContext) { + AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) + .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) + .setType(messageType.getValue()).setContent(content).setUseContext(useContext); + message.setCreateTime(LocalDateTime.now()); + chatMessageMapper.insert(message); + return message; + } + + @Override + public List getChatMessageListByConversationId(Long conversationId) { + return chatMessageMapper.selectListByConversationId(conversationId); + } + + @Override + public void deleteMessage(Long id, Long userId) { + // 1. 校验消息存在 + AiChatMessageDO message = chatMessageMapper.selectById(id); + if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) { + throw exception(AI_CHAT_MESSAGE_NOT_EXIST); + } + // 2. 执行删除 + chatMessageMapper.deleteById(id); + } + + @Override + public void deleteChatMessageByConversationId(Long conversationId, Long userId) { + // 1. 校验消息存在 + List messages = chatMessageMapper.selectListByConversationId(conversationId); + if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) { + throw exception(AI_CHAT_MESSAGE_NOT_EXIST); + } + // 2. 执行删除 + chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); + } + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java deleted file mode 100644 index a67517f..0000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java +++ /dev/null @@ -1,272 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.impl; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ObjUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; -import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import jakarta.annotation.Resource; -import org.springframework.ai.chat.ChatResponse; -import org.springframework.ai.chat.StreamingChatClient; -import org.springframework.ai.chat.messages.*; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; -import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert; -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; -import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper; -import cn.iocoder.yudao.module.ai.service.AiChatService; -import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; -import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; -import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; - -/** - * 聊天 service - * - * @author fansili - * @time 2024/4/14 15:55 - * @since 1.0 - */ -@Slf4j -@Service -public class AiChatServiceImpl implements AiChatService { - - @Resource - private AiChatMessageMapper chatMessageMapper; - - @Resource - private AiClientFactory clientFactory; - - @Resource - private AiChatConversationService chatConversationService; - @Resource - private AiChatModelService chatModalService; - @Resource - private AiChatRoleService chatRoleService; - @Resource - private AiApiKeyService apiKeyService; - - @Resource - private AdminUserApi adminUserApi; - - @Transactional(rollbackFor = Exception.class) - public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) { - return null; // TODO 芋艿:一起改 -// Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); -// // 查询对话 -// AiChatConversationDO conversation = chatConversationService.validateExists(req.getConversationId()); -// // 获取对话模型 -// AiChatModelDO chatModel = chatModalService.validateChatModel(conversation.getModelId()); -// // 获取角色信息 -// AiChatRoleDO chatRoleDO = conversation.getRoleId() != null ? chatRoleService.validateChatRole(conversation.getRoleId()) : null; -// // 获取 client 类型 -// AiPlatformEnum platformEnum = AiPlatformEnum.validatePlatform(chatModel.getPlatform()); -// // 保存 chat message -// createChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(), -// chatModel.getModel(), chatModel.getId(), req.getContent()); -// String content = null; -// int tokens = 0; -// try { -// // 创建 chat 需要的 Prompt -// Prompt prompt = new Prompt(req.getContent()); -// // TODO @芋艿 @范 看要不要支持这些 -//// req.setTopK(req.getTopK()); -//// req.setTopP(req.getTopP()); -//// req.setTemperature(req.getTemperature()); -// // 发送 call 调用 -// ChatClient chatClient = chatClientFactory.getChatClient(platformEnum); -// ChatResponse call = chatClient.call(prompt); -// content = call.getResult().getOutput().getContent(); -// // 更新 conversation -// } catch (Exception e) { -// content = ExceptionUtil.getMessage(e); -// } finally { -// // 保存 chat message -// createChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), -// chatModel.getModel(), chatModel.getId(), content); -// } -// return new AiChatMessageRespVO().setContent(content); - } - - @Override - public Flux sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { - // 1.1 校验对话存在 - AiChatConversationDO conversation = chatConversationService.validateExists(sendReqVO.getConversationId()); - if (ObjUtil.notEqual(conversation.getUserId(), userId)) { - throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; - } - List historyMessages = chatMessageMapper.selectByConversationId(conversation.getId()); - // 1.2 校验模型 - AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId()); - StreamingChatClient chatClient = apiKeyService.getStreamingChatClient(model.getKeyId()); - - // 2. 插入 user 发送消息 - AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, - userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext()); - - // 3.1 插入 assistant 接收消息 - AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, - userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext()); - - // 3.2 创建 chat 需要的 Prompt - Prompt prompt = buildPrompt(conversation, historyMessages, model, sendReqVO); - Flux streamResponse = chatClient.stream(prompt); - - // 3.3 流式返回 - // 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题 - - // 3.4 获取用户头像、角色头像 - AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId()); - AiChatRoleDO chatRole = chatRoleService.getChatRole(assistantMessage.getRoleId()); - - StringBuffer contentBuffer = new StringBuffer(); - return streamResponse.publishOn(Schedulers.single()).map(chunk -> { - String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null; - newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况 - contentBuffer.append(newContent); - // 响应结果 - return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class).setUserAvatar(user.getAvatar())) - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent).setRoleAvatar(chatRole == null ? null : chatRole.getAvatar())); - }).doOnComplete(() -> { - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString())); - }).doOnError(throwable -> { - log.error("[sendChatMessageStream][userId({}) sendReqVO({}) 发生异常]", userId, sendReqVO, throwable); - chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(throwable.getMessage())); - }); - } - - @Override - public Boolean deleteByConversationId(Long conversationId) { - return chatMessageMapper.deleteByConversationId(conversationId) > 0; - } - - private Prompt buildPrompt(AiChatConversationDO conversation, List messages, - AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) { - // 1. 构建 Prompt Message 列表 - List chatMessages = new ArrayList<>(); - // 1.1 system context 角色设定 - chatMessages.add(new SystemMessage(conversation.getSystemMessage())); - // 1.2 history message 历史消息 - List contextMessages = filterContextMessages(messages, conversation, sendReqVO); - contextMessages.forEach(message -> chatMessages.add(new ChatMessage(message.getType().toUpperCase(), message.getContent()))); - // 1.3 user message 新发送消息 - chatMessages.add(new UserMessage(sendReqVO.getContent())); - - // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了; - AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); - ChatOptions chatOptions = clientFactory.buildChatOptions(platform, model.getModel(), - conversation.getTemperature(), conversation.getMaxTokens()); - return new Prompt(chatMessages, chatOptions); -// return new Prompt(chatMessages); - } - - /** - * 从历史消息中,获得倒序的 n 组消息作为消息上下文 - * - * n 组:指的是 user + assistant 形成一组 - * - * @param messages 消息列表 - * @param conversation 会话 - * @param sendReqVO 发送请求 - * @return 消息上下文 - */ - private List filterContextMessages(List messages, AiChatConversationDO conversation, AiChatMessageSendReqVO sendReqVO) { - if (conversation.getMaxContexts() == null || ObjUtil.notEqual(sendReqVO.getUseContext(), Boolean.TRUE)) { - return Collections.emptyList(); - } - List contextMessages = new ArrayList<>(conversation.getMaxContexts() * 2); - for (int i = messages.size() - 1; i >= 0; i--) { - AiChatMessageDO assistantMessage = CollUtil.get(messages, i); - if (assistantMessage == null || assistantMessage.getReplyId() == null) { - continue; - } - AiChatMessageDO userMessage = CollUtil.get(messages, i - 1); - if (userMessage == null || ObjUtil.notEqual(assistantMessage.getReplyId(), userMessage.getId()) - || StrUtil.isEmpty(assistantMessage.getContent())) { - continue; - } - // 由于后续要 reverse 反转,所以先添加 assistantMessage - contextMessages.add(assistantMessage); - contextMessages.add(userMessage); - // 超过最大上下文,结束 - if (contextMessages.size() >= conversation.getMaxContexts() * 2) { - break; - } - } - Collections.reverse(contextMessages); - return contextMessages; - } - - private AiChatMessageDO createChatMessage(Long conversationId, Long replyId, - AiChatModelDO model, Long userId, Long roleId, - MessageType messageType, String content, Boolean useContext) { - AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) - .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) - .setType(messageType.getValue()).setContent(content).setUseContext(useContext); - message.setCreateTime(LocalDateTime.now()); - chatMessageMapper.insert(message); - return message; - } - - @Override - public List getMessageListByConversationId(Long conversationId) { - // 校验对话是否存在 - chatConversationService.validateExists(conversationId); - // 获取对话所有 message - List aiChatMessageDOList = chatMessageMapper.selectByConversationId(conversationId); - // 获取模型信息 - Set roleIds = aiChatMessageDOList.stream().map(AiChatMessageDO::getRoleId).collect(Collectors.toSet()); - List roleList; - if (!CollUtil.isEmpty(roleIds)) { - roleList = chatRoleService.getChatRoles(roleIds); - } else { - roleList = Collections.emptyList(); - } - Map roleMap = roleList.stream().collect(Collectors.toMap(AiChatRoleDO::getId, o -> o)); - // 转换 AiChatMessageRespVO - List aiChatMessageRespList = AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVOList(aiChatMessageDOList); - // 获取用户信息 - AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId()); - // 设置用户头像 和 模型头像 - return aiChatMessageRespList.stream().map(item -> { - // 设置 role 头像 - if (roleMap.containsKey(item.getRoleId())) { - AiChatRoleDO role = roleMap.get(item.getRoleId()); - item.setRoleAvatar(role.getAvatar()); - } - // 设置 user 头像 - if (user != null) { - item.setUserAvatar(user.getAvatar()); - } - return item; - }).collect(Collectors.toList()); - } - - @Override - public Boolean deleteMessage(Long id) { - return chatMessageMapper.deleteById(id) > 0; - } - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java index 228b1ee..a7bc250 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java @@ -4,12 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO; import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; import jakarta.validation.Valid; +import java.util.Collection; import java.util.List; -import java.util.Set; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * AI 聊天角色 Service 接口 @@ -74,12 +76,16 @@ public interface AiChatRoleService { AiChatRoleDO getChatRole(Long id); /** - * 获得聊天角色 - 根据 ids + * 获得聊天角色列表 * - * @param roleIds - * @return + * @param ids 编号数组 + * @return 聊天角色列表 */ - List getChatRoles(Set roleIds); + List getChatRoleList(Collection ids); + + default Map getChatRoleMap(Collection ids) { + return convertMap(getChatRoleList(ids), AiChatRoleDO::getId); + } /** * 校验聊天角色是否合法 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java index 1413dd8..d3083ec 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.ai.service.model; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -14,12 +15,9 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -107,8 +105,11 @@ public class AiChatRoleServiceImpl implements AiChatRoleService { } @Override - public List getChatRoles(Set roleIds) { - return chatRoleMapper.selectBatchIds(roleIds); + public List getChatRoleList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return chatRoleMapper.selectBatchIds(ids); } @Override -- libgit2 0.21.4