From a48287611358f7b885ca0e7b99e14ede39af6347 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 22 May 2024 13:12:54 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91AI=EF=BC=9A?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=B6=88=E6=81=AF=E7=9A=84=20Service=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/ai/ErrorCodeConstants.java | 4 +- .../admin/chat/AiChatMessageController.java | 64 +++++++++--- .../vo/message/AiChatMessageSendRespVO.java | 7 +- .../ai/convert/AiChatMessageConvert.java | 30 ------ .../ai/dal/mysql/AiChatMessageMapper.java | 43 -------- .../ai/dal/mysql/AiChatModelMapper.java | 6 -- .../module/ai/dal/mysql/AiImageMapper.java | 1 - .../dal/mysql/chat/AiChatMessageMapper.java | 24 +++++ .../module/ai/service/AiChatService.java | 57 ----------- .../ai/service/chat/AiChatMessageService.java | 57 +++++++++++ .../AiChatMessageServiceImpl.java} | 99 ++++++++----------- .../ai/service/model/AiChatRoleService.java | 18 ++-- .../service/model/AiChatRoleServiceImpl.java | 15 +-- 13 files changed, 198 insertions(+), 227 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 rename yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/{impl/AiChatServiceImpl.java => chat/AiChatMessageServiceImpl.java} (77%) 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 4542c0787..cd1985f81 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 ad9375f39..2522d0dc0 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 3314b1d32..fbc31eea5 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 eda556358..000000000 --- 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 6c9c22ad0..000000000 --- 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 3c959e961..25687d0e0 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 1ee5b436d..d353fea5a 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 000000000..7b3e9c493 --- /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 f8924cb63..000000000 --- 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 000000000..e9dc43ec9 --- /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/impl/AiChatServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java similarity index 77% rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java rename to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index a67517fed..3e4f60258 100644 --- 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/chat/AiChatMessageServiceImpl.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.ai.service.impl; +package cn.iocoder.yudao.module.ai.service.chat; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; @@ -20,13 +20,10 @@ 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.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; @@ -37,21 +34,20 @@ 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.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; /** - * 聊天 service + * AI 聊天消息 Service 实现类 * * @author fansili - * @time 2024/4/14 15:55 - * @since 1.0 */ -@Slf4j @Service -public class AiChatServiceImpl implements AiChatService { +@Slf4j +public class AiChatMessageServiceImpl implements AiChatMessageService { @Resource private AiChatMessageMapper chatMessageMapper; @@ -72,7 +68,7 @@ public class AiChatServiceImpl implements AiChatService { private AdminUserApi adminUserApi; @Transactional(rollbackFor = Exception.class) - public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) { + public AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO req) { return null; // TODO 芋艿:一起改 // Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); // // 查询对话 @@ -117,10 +113,13 @@ public class AiChatServiceImpl implements AiChatService { if (ObjUtil.notEqual(conversation.getUserId(), userId)) { throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; } - List historyMessages = chatMessageMapper.selectByConversationId(conversation.getId()); + 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, @@ -136,19 +135,17 @@ public class AiChatServiceImpl implements AiChatService { // 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())); + 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 -> { @@ -157,11 +154,6 @@ public class AiChatServiceImpl implements AiChatService { }); } - @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 列表 @@ -174,12 +166,11 @@ public class AiChatServiceImpl implements AiChatService { // 1.3 user message 新发送消息 chatMessages.add(new UserMessage(sendReqVO.getContent())); - // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了; + // 2. 构建 ChatOptions 对象 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); } /** @@ -231,42 +222,30 @@ public class AiChatServiceImpl implements AiChatService { } @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()); + public List getChatMessageListByConversationId(Long conversationId) { + return chatMessageMapper.selectListByConversationId(conversationId); } @Override - public Boolean deleteMessage(Long id) { - return chatMessageMapper.deleteById(id) > 0; + 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/model/AiChatRoleService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleService.java index 228b1ee6e..a7bc2501a 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 1413dd8ce..d3083ec9f 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