Commit a48287611358f7b885ca0e7b99e14ede39af6347

Authored by YunaiV
1 parent 3b02bcf4

【优化】AI:聊天消息的 Service 实现

Showing 12 changed files with 166 additions and 195 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java
... ... @@ -30,8 +30,8 @@ public interface ErrorCodeConstants {
30 30 ErrorCode CHAT_CONVERSATION_UPDATE_MAX_TOKENS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Token 超过上限");
31 31 ErrorCode CHAT_CONVERSATION_UPDATE_MAX_CONTEXTS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Context 超过上限");
32 32  
33   - // chat
34   - ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_022_000_100, "提问的 MessageId 不存在!");
  33 + // ========== API 聊天消息 1-040-004-000 ==========
  34 + ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
35 35  
36 36 // midjourney
37 37  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.chat;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 8 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
5   -import cn.iocoder.yudao.module.ai.service.AiChatService;
  9 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
  10 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  11 +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
  12 +import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
  13 +import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
  14 +import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  15 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  16 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
6 17 import io.swagger.v3.oas.annotations.Operation;
7 18 import io.swagger.v3.oas.annotations.Parameter;
8 19 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -14,9 +25,12 @@ import org.springframework.validation.annotation.Validated;
14 25 import org.springframework.web.bind.annotation.*;
15 26 import reactor.core.publisher.Flux;
16 27  
  28 +import java.util.Collections;
17 29 import java.util.List;
  30 +import java.util.Map;
18 31  
19 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
20 34 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
21 35  
22 36 @Tag(name = "管理后台 - 聊天消息")
... ... @@ -26,39 +40,65 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
26 40 public class AiChatMessageController {
27 41  
28 42 @Resource
29   - private AiChatService chatService;
  43 + private AiChatMessageService chatMessageService;
  44 + @Resource
  45 + private AiChatConversationService chatConversationService;
  46 + @Resource
  47 + private AiChatRoleService chatRoleService;
  48 +
  49 + @Resource
  50 + private AdminUserApi adminUserApi;
30 51  
31 52 @Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
32 53 @PostMapping("/send")
33 54 public CommonResult<AiChatMessageRespVO> sendMessage(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) {
34   - return success(chatService.chat(sendReqVO));
  55 + return success(chatMessageService.sendMessage(sendReqVO));
35 56 }
36 57  
37 58 @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
38 59 @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
39 60 @PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
40 61 public Flux<AiChatMessageSendRespVO> sendChatMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) {
41   - return chatService.sendChatMessageStream(sendReqVO, getLoginUserId());
  62 + return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
42 63 }
43 64  
44 65 @Operation(summary = "获得指定会话的消息列表")
45 66 @GetMapping("/list-by-conversation-id")
46 67 @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
47   - public CommonResult<List<AiChatMessageRespVO>> getMessageListByConversationId(@RequestParam("conversationId") Long conversationId) {
48   - return success(chatService.getMessageListByConversationId(conversationId));
  68 + public CommonResult<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
  69 + @RequestParam("conversationId") Long conversationId) {
  70 + AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
  71 + if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
  72 + return success(Collections.emptyList());
  73 + }
  74 + List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
  75 + if (CollUtil.isEmpty(messageList)) {
  76 + return success(Collections.emptyList());
  77 + }
  78 +
  79 + // 拼接数据
  80 + Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(convertSet(messageList, AiChatMessageDO::getRoleId));
  81 + AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
  82 + return success(BeanUtils.toBean(messageList, AiChatMessageRespVO.class, respVO -> {
  83 + MapUtils.findAndThen(roleMap, respVO.getRoleId(), role -> respVO.setRoleAvatar(role.getAvatar()));
  84 + respVO.setUserAvatar(user.getAvatar());
  85 + }));
49 86 }
50 87  
51 88 @Operation(summary = "删除消息")
52 89 @DeleteMapping("/delete")
53 90 @Parameter(name = "id", required = true, description = "消息编号", example = "1024")
54   - public CommonResult<Boolean> deleteMessage(@RequestParam("id") Long id) {
55   - return success(chatService.deleteMessage(id));
  91 + public CommonResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
  92 + chatMessageService.deleteMessage(id, getLoginUserId());
  93 + return success(true);
56 94 }
57 95  
58   - @Operation(summary = "删除消息-对于对话全部消息")
  96 + @Operation(summary = "删除指定会话的消息")
59 97 @DeleteMapping("/delete-by-conversation-id")
60   - @Parameter(name = "id", required = true, description = "消息编号", example = "1024")
61   - public CommonResult<Boolean> deleteByConversationId(@RequestParam("conversationId") Long conversationId) {
62   - return success(chatService.deleteByConversationId(conversationId));
  98 + @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
  99 + public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
  100 + chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
  101 + return success(true);
63 102 }
  103 +
64 104 }
... ...
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 {
31 31 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
32 32 private LocalDateTime createTime;
33 33  
34   - // ========= 扩展字段
  34 + // ========== 扩展字段 ==========
35 35  
36   - @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx")
  36 + @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/1.png")
37 37 private String userAvatar;
38 38  
39   - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx")
  39 + @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/2.png")
40 40 private String roleAvatar;
  41 +
41 42 }
42 43  
43 44 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/convert/AiChatMessageConvert.java deleted
1   -package cn.iocoder.yudao.module.ai.convert;
2   -
3   -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
4   -import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
5   -import org.mapstruct.Mapper;
6   -import org.mapstruct.factory.Mappers;
7   -
8   -import java.util.List;
9   -
10   -/**
11   - * 聊天 对话 convert
12   - *
13   - * @author fansili
14   - * @time 2024/4/18 16:39
15   - * @since 1.0
16   - */
17   -@Mapper
18   -public interface AiChatMessageConvert {
19   -
20   - AiChatMessageConvert INSTANCE = Mappers.getMapper(AiChatMessageConvert.class);
21   -
22   - /**
23   - * 转换 AiChatMessageRespVO
24   - *
25   - * @param aiChatMessageDOList
26   - * @return
27   - */
28   - List<AiChatMessageRespVO> convertAiChatMessageRespVOList(List<AiChatMessageDO> aiChatMessageDOList);
29   -
30   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java deleted
1   -package cn.iocoder.yudao.module.ai.dal.mysql;
2   -
3   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5   -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
6   -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
7   -import org.apache.ibatis.annotations.Mapper;
8   -import org.springframework.stereotype.Repository;
9   -
10   -import java.util.List;
11   -
12   -/**
13   - * message mapper
14   - *
15   - * @fansili
16   - * @since v1.0
17   - */
18   -@Repository
19   -@Mapper
20   -public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
21   -
22   - /**
23   - * 查询 - 根据 对话id查询
24   - *
25   - * @param conversationId
26   - */
27   - default List<AiChatMessageDO> selectByConversationId(Long conversationId) {
28   - return this.selectList(
29   - new LambdaQueryWrapperX<AiChatMessageDO>()
30   - .eq(AiChatMessageDO::getConversationId, conversationId)
31   - .orderByAsc(AiChatMessageDO::getId)
32   - );
33   - }
34   -
35   - /**
36   - * 删除 - 根据 conversationId
37   - *
38   - * @param conversationId
39   - */
40   - default int deleteByConversationId(Long conversationId) {
41   - return this.delete(new LambdaQueryWrapperX<AiChatMessageDO>().eq(AiChatMessageDO::getConversationId, conversationId));
42   - }
43   -}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatModelMapper.java
1 1 package cn.iocoder.yudao.module.ai.dal.mysql;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 6 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
9 7 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;
10 8 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
11   -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
12 9 import org.apache.ibatis.annotations.Mapper;
13 10  
14   -import java.util.List;
15   -
16 11 import java.util.Collection;
17 12 import java.util.List;
18 13  
... ... @@ -42,7 +37,6 @@ public interface AiChatModelMapper extends BaseMapperX&lt;AiChatModelDO&gt; {
42 37 return this.selectList(new LambdaQueryWrapperX<AiChatModelDO>().eq(AiChatModelDO::getId, modalIds));
43 38 }
44 39  
45   -
46 40 default PageResult<AiChatModelDO> selectPage(AiChatModelPageReqVO reqVO) {
47 41 return selectPage(reqVO, new LambdaQueryWrapperX<AiChatModelDO>()
48 42 .likeIfPresent(AiChatModelDO::getName, reqVO.getName())
... ...
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;
13 13 * @time 2024/4/28 14:01
14 14 * @since 1.0
15 15 */
16   -@Repository
17 16 @Mapper
18 17 public interface AiImageMapper extends BaseMapperX<AiImageDO> {
19 18  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.dal.mysql.chat;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * AI 聊天对话 Mapper
  12 + *
  13 + * @author fansili
  14 + */
  15 +@Mapper
  16 +public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
  17 +
  18 + default List<AiChatMessageDO> selectListByConversationId(Long conversationId) {
  19 + return selectList(new LambdaQueryWrapperX<AiChatMessageDO>()
  20 + .eq(AiChatMessageDO::getConversationId, conversationId)
  21 + .orderByAsc(AiChatMessageDO::getId));
  22 + }
  23 +
  24 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java
1   -package cn.iocoder.yudao.module.ai.service;
  1 +package cn.iocoder.yudao.module.ai.service.chat;
2 2  
3 3 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
  4 +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
4 5 import reactor.core.publisher.Flux;
5 6  
6 7 import java.util.List;
7 8  
8 9 /**
9   - * 聊天 chat
  10 + * AI 聊天消息 Service 接口
10 11 *
11 12 * @author fansili
12   - * @time 2024/4/14 15:55
13   - * @since 1.0
14 13 */
15   -public interface AiChatService {
  14 +public interface AiChatMessageService {
16 15  
17 16 /**
18   - * chat
  17 + * 发送消息
19 18 *
20   - * @param sendReqVO
21   - * @return
  19 + * @param sendReqVO 发送信息
  20 + * @return 发送结果
22 21 */
23   - AiChatMessageRespVO chat(AiChatMessageSendReqVO sendReqVO);
  22 + AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO sendReqVO);
24 23  
25 24 /**
26   - * 获取 - 获取对话 message list
  25 + * 发送消息
27 26 *
28   - * @param conversationId
29   - * @return
  27 + * @param sendReqVO 发送信息
  28 + * @param userId 用户编号
  29 + * @return 发送结果
30 30 */
31   - List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId);
  31 + Flux<AiChatMessageSendRespVO> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId);
32 32  
33 33 /**
34   - * 删除 - 删除message
  34 + * 获得指定会话的消息列表
35 35 *
36   - * @param id
37   - * @return
  36 + * @param conversationId 会话编号
  37 + * @return 消息列表
38 38 */
39   - Boolean deleteMessage(Long id);
  39 + List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId);
40 40  
41 41 /**
42   - * 发送消息
  42 + * 删除消息
43 43 *
44   - * @param sendReqVO
45   - * @param userId
46   - * @return
  44 + * @param id 消息编号
  45 + * @param userId 用户编号
47 46 */
48   - Flux<AiChatMessageSendRespVO> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId);
  47 + void deleteMessage(Long id, Long userId);
49 48  
50 49 /**
51   - * 删除消息-对于对话全部消息
  50 + * 删除指定会话的消息
52 51 *
53   - * @param conversationId
54   - * @return
  52 + * @param conversationId 会话编号
  53 + * @param userId 用户编号
55 54 */
56   - Boolean deleteByConversationId(Long conversationId);
  55 + void deleteChatMessageByConversationId(Long conversationId, Long userId);
  56 +
57 57 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
1   -package cn.iocoder.yudao.module.ai.service.impl;
  1 +package cn.iocoder.yudao.module.ai.service.chat;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjUtil;
... ... @@ -20,13 +20,10 @@ import org.springframework.ai.chat.prompt.Prompt;
20 20 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
21 21 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
22 22 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
23   -import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert;
24 23 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
25 24 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
26 25 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
27   -import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper;
28   -import cn.iocoder.yudao.module.ai.service.AiChatService;
29   -import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
  26 +import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
30 27 import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
31 28 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
32 29 import lombok.extern.slf4j.Slf4j;
... ... @@ -37,21 +34,20 @@ import reactor.core.scheduler.Schedulers;
37 34  
38 35 import java.time.LocalDateTime;
39 36 import java.util.*;
40   -import java.util.stream.Collectors;
41 37  
42 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  40 +import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST;
43 41 import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
44 42  
45 43 /**
46   - * 聊天 service
  44 + * AI 聊天消息 Service 实现类
47 45 *
48 46 * @author fansili
49   - * @time 2024/4/14 15:55
50   - * @since 1.0
51 47 */
52   -@Slf4j
53 48 @Service
54   -public class AiChatServiceImpl implements AiChatService {
  49 +@Slf4j
  50 +public class AiChatMessageServiceImpl implements AiChatMessageService {
55 51  
56 52 @Resource
57 53 private AiChatMessageMapper chatMessageMapper;
... ... @@ -72,7 +68,7 @@ public class AiChatServiceImpl implements AiChatService {
72 68 private AdminUserApi adminUserApi;
73 69  
74 70 @Transactional(rollbackFor = Exception.class)
75   - public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) {
  71 + public AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO req) {
76 72 return null; // TODO 芋艿:一起改
77 73 // Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
78 74 // // 查询对话
... ... @@ -117,10 +113,13 @@ public class AiChatServiceImpl implements AiChatService {
117 113 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
118 114 throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接;
119 115 }
120   - List<AiChatMessageDO> historyMessages = chatMessageMapper.selectByConversationId(conversation.getId());
  116 + List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
121 117 // 1.2 校验模型
122 118 AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
123 119 StreamingChatClient chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());
  120 + // 1.3 获取用户头像、角色头像
  121 + AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId());
  122 + AiChatRoleDO role = conversation.getRoleId() != null ? chatRoleService.getChatRole(conversation.getRoleId()) : null;
124 123  
125 124 // 2. 插入 user 发送消息
126 125 AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
... ... @@ -136,19 +135,17 @@ public class AiChatServiceImpl implements AiChatService {
136 135  
137 136 // 3.3 流式返回
138 137 // 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题
139   -
140   - // 3.4 获取用户头像、角色头像
141   - AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId());
142   - AiChatRoleDO chatRole = chatRoleService.getChatRole(assistantMessage.getRoleId());
143   -
144 138 StringBuffer contentBuffer = new StringBuffer();
145 139 return streamResponse.publishOn(Schedulers.single()).map(chunk -> {
146 140 String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
147 141 newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
148 142 contentBuffer.append(newContent);
149 143 // 响应结果
150   - return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class).setUserAvatar(user.getAvatar()))
151   - .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent).setRoleAvatar(chatRole == null ? null : chatRole.getAvatar()));
  144 + AiChatMessageSendRespVO.Message send = BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class,
  145 + o -> o.setUserAvatar(user.getAvatar()));
  146 + AiChatMessageSendRespVO.Message receive = BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class,
  147 + o -> o.setRoleAvatar(role != null ? role.getAvatar() : null)).setContent(newContent);
  148 + return new AiChatMessageSendRespVO().setSend(send).setReceive(receive);
152 149 }).doOnComplete(() -> {
153 150 chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()));
154 151 }).doOnError(throwable -> {
... ... @@ -157,11 +154,6 @@ public class AiChatServiceImpl implements AiChatService {
157 154 });
158 155 }
159 156  
160   - @Override
161   - public Boolean deleteByConversationId(Long conversationId) {
162   - return chatMessageMapper.deleteByConversationId(conversationId) > 0;
163   - }
164   -
165 157 private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
166 158 AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {
167 159 // 1. 构建 Prompt Message 列表
... ... @@ -174,12 +166,11 @@ public class AiChatServiceImpl implements AiChatService {
174 166 // 1.3 user message 新发送消息
175 167 chatMessages.add(new UserMessage(sendReqVO.getContent()));
176 168  
177   - // 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了;
  169 + // 2. 构建 ChatOptions 对象
178 170 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
179 171 ChatOptions chatOptions = clientFactory.buildChatOptions(platform, model.getModel(),
180 172 conversation.getTemperature(), conversation.getMaxTokens());
181 173 return new Prompt(chatMessages, chatOptions);
182   -// return new Prompt(chatMessages);
183 174 }
184 175  
185 176 /**
... ... @@ -231,42 +222,30 @@ public class AiChatServiceImpl implements AiChatService {
231 222 }
232 223  
233 224 @Override
234   - public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) {
235   - // 校验对话是否存在
236   - chatConversationService.validateExists(conversationId);
237   - // 获取对话所有 message
238   - List<AiChatMessageDO> aiChatMessageDOList = chatMessageMapper.selectByConversationId(conversationId);
239   - // 获取模型信息
240   - Set<Long> roleIds = aiChatMessageDOList.stream().map(AiChatMessageDO::getRoleId).collect(Collectors.toSet());
241   - List<AiChatRoleDO> roleList;
242   - if (!CollUtil.isEmpty(roleIds)) {
243   - roleList = chatRoleService.getChatRoles(roleIds);
244   - } else {
245   - roleList = Collections.emptyList();
  225 + public List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId) {
  226 + return chatMessageMapper.selectListByConversationId(conversationId);
  227 + }
  228 +
  229 + @Override
  230 + public void deleteMessage(Long id, Long userId) {
  231 + // 1. 校验消息存在
  232 + AiChatMessageDO message = chatMessageMapper.selectById(id);
  233 + if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) {
  234 + throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
246 235 }
247   - Map<Long, AiChatRoleDO> roleMap = roleList.stream().collect(Collectors.toMap(AiChatRoleDO::getId, o -> o));
248   - // 转换 AiChatMessageRespVO
249   - List<AiChatMessageRespVO> aiChatMessageRespList = AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVOList(aiChatMessageDOList);
250   - // 获取用户信息
251   - AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId());
252   - // 设置用户头像 和 模型头像
253   - return aiChatMessageRespList.stream().map(item -> {
254   - // 设置 role 头像
255   - if (roleMap.containsKey(item.getRoleId())) {
256   - AiChatRoleDO role = roleMap.get(item.getRoleId());
257   - item.setRoleAvatar(role.getAvatar());
258   - }
259   - // 设置 user 头像
260   - if (user != null) {
261   - item.setUserAvatar(user.getAvatar());
262   - }
263   - return item;
264   - }).collect(Collectors.toList());
  236 + // 2. 执行删除
  237 + chatMessageMapper.deleteById(id);
265 238 }
266 239  
267 240 @Override
268   - public Boolean deleteMessage(Long id) {
269   - return chatMessageMapper.deleteById(id) > 0;
  241 + public void deleteChatMessageByConversationId(Long conversationId, Long userId) {
  242 + // 1. 校验消息存在
  243 + List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId);
  244 + if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) {
  245 + throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
  246 + }
  247 + // 2. 执行删除
  248 + chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
270 249 }
271 250  
272 251 }
... ...
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;
4 4 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO;
5 5 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO;
6 6 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
7   -import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
8 7 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
9 8 import jakarta.validation.Valid;
10 9  
  10 +import java.util.Collection;
11 11 import java.util.List;
12   -import java.util.Set;
  12 +import java.util.Map;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
13 15  
14 16 /**
15 17 * AI 聊天角色 Service 接口
... ... @@ -74,12 +76,16 @@ public interface AiChatRoleService {
74 76 AiChatRoleDO getChatRole(Long id);
75 77  
76 78 /**
77   - * 获得聊天角色 - 根据 ids
  79 + * 获得聊天角色列表
78 80 *
79   - * @param roleIds
80   - * @return
  81 + * @param ids 编号数组
  82 + * @return 聊天角色列表
81 83 */
82   - List<AiChatRoleDO> getChatRoles(Set<Long> roleIds);
  84 + List<AiChatRoleDO> getChatRoleList(Collection<Long> ids);
  85 +
  86 + default Map<Long, AiChatRoleDO> getChatRoleMap(Collection<Long> ids) {
  87 + return convertMap(getChatRoleList(ids), AiChatRoleDO::getId);
  88 + }
83 89  
84 90 /**
85 91 * 校验聊天角色是否合法
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.model;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.util.ObjectUtil;
4 5 import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
... ... @@ -14,12 +15,9 @@ import jakarta.annotation.Resource;
14 15 import lombok.extern.slf4j.Slf4j;
15 16 import org.springframework.stereotype.Service;
16 17  
  18 +import java.util.Collection;
  19 +import java.util.Collections;
17 20 import java.util.List;
18   -import java.util.Objects;
19   -import java.util.Set;
20   -import java.util.function.Function;
21   -import java.util.function.Predicate;
22   -import java.util.stream.Collectors;
23 21  
24 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
25 23 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
... ... @@ -107,8 +105,11 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
107 105 }
108 106  
109 107 @Override
110   - public List<AiChatRoleDO> getChatRoles(Set<Long> roleIds) {
111   - return chatRoleMapper.selectBatchIds(roleIds);
  108 + public List<AiChatRoleDO> getChatRoleList(Collection<Long> ids) {
  109 + if (CollUtil.isEmpty(ids)) {
  110 + return Collections.emptyList();
  111 + }
  112 + return chatRoleMapper.selectBatchIds(ids);
112 113 }
113 114  
114 115 @Override
... ...