Commit 2d11f085c8a1d65d3e7af6e1f196acc3936d1ac8

Authored by YunaiV
1 parent 4fddec5f

【新增】AI:对话管理 50%

Showing 16 changed files with 196 additions and 68 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java
1 1 package cn.iocoder.yudao.module.ai.controller.admin.chat;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.util.ObjUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 8 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
7 10 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO;
8 11 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
9 12 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
10 13 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
  14 +import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
11 15 import io.swagger.v3.oas.annotations.Operation;
12 16 import io.swagger.v3.oas.annotations.Parameter;
13 17 import io.swagger.v3.oas.annotations.tags.Tag;
14 18 import jakarta.annotation.Resource;
15 19 import jakarta.validation.Valid;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
16 21 import org.springframework.validation.annotation.Validated;
17 22 import org.springframework.web.bind.annotation.*;
18 23  
19 24 import java.util.List;
  25 +import java.util.Map;
  26 +import java.util.function.Consumer;
20 27  
21 28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
22 30 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
23 31  
24   -@Tag(name = "管理后台 - AI 聊天会话")
  32 +@Tag(name = "管理后台 - AI 聊天对话")
25 33 @RestController
26 34 @RequestMapping("/ai/chat/conversation")
27 35 @Validated
... ... @@ -29,30 +37,32 @@ public class AiChatConversationController {
29 37  
30 38 @Resource
31 39 private AiChatConversationService chatConversationService;
  40 + @Resource
  41 + private AiChatMessageService chatMessageService;
32 42  
33 43 @PostMapping("/create-my")
34   - @Operation(summary = "创建【我的】聊天会话")
  44 + @Operation(summary = "创建【我的】聊天对话")
35 45 public CommonResult<Long> createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) {
36 46 return success(chatConversationService.createChatConversationMy(createReqVO, getLoginUserId()));
37 47 }
38 48  
39 49 @PutMapping("/update-my")
40   - @Operation(summary = "更新【我的】聊天会话")
  50 + @Operation(summary = "更新【我的】聊天对话")
41 51 public CommonResult<Boolean> updateChatConversationMy(@RequestBody @Valid AiChatConversationUpdateMyReqVO updateReqVO) {
42 52 chatConversationService.updateChatConversationMy(updateReqVO, getLoginUserId());
43 53 return success(true);
44 54 }
45 55  
46 56 @GetMapping("/my-list")
47   - @Operation(summary = "获得【我的】聊天会话列表")
  57 + @Operation(summary = "获得【我的】聊天对话列表")
48 58 public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() {
49 59 List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
50 60 return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
51 61 }
52 62  
53 63 @GetMapping("/get-my")
54   - @Operation(summary = "获得【我的】聊天会话")
55   - @Parameter(name = "id", required = true, description = "会话编号", example = "1024")
  64 + @Operation(summary = "获得【我的】聊天对话")
  65 + @Parameter(name = "id", required = true, description = "对话编号", example = "1024")
56 66 public CommonResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
57 67 AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
58 68 if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
... ... @@ -62,20 +72,36 @@ public class AiChatConversationController {
62 72 }
63 73  
64 74 @DeleteMapping("/delete-my")
65   - @Operation(summary = "删除聊天会话")
66   - @Parameter(name = "id", required = true, description = "会话编号", example = "1024")
  75 + @Operation(summary = "删除聊天对话")
  76 + @Parameter(name = "id", required = true, description = "对话编号", example = "1024")
67 77 public CommonResult<Boolean> deleteChatConversationMy(@RequestParam("id") Long id) {
68 78 chatConversationService.deleteChatConversationMy(id, getLoginUserId());
69 79 return success(true);
70 80 }
71 81  
  82 + // TODO 芋艿:这个 url 可以改下
72 83 @DeleteMapping("/delete-my-all-except-pinned")
73 84 @Operation(summary = "删除所有对话(置顶除外)")
74   - @Parameter(name = "id", required = true, description = "会话编号", example = "1024")
75   - public CommonResult<Boolean> deleteMyAllExceptPinned() {
76   - chatConversationService.deleteMyAllExceptPinned(getLoginUserId());
  85 + public CommonResult<Boolean> deleteChatConversationMyByUnpinned() {
  86 + chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId());
77 87 return success(true);
78 88 }
79   - // ========== 会话管理 ==========
  89 +
  90 + // ========== 对话管理 ==========
  91 +
  92 + @GetMapping("/page")
  93 + @Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
  94 + @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
  95 + public CommonResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
  96 + PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
  97 + if (CollUtil.isEmpty(pageResult.getList())) {
  98 + return success(PageResult.empty());
  99 + }
  100 + // 拼接关联数据
  101 + Map<Long, Integer> messageCountMap = chatMessageService.getChatMessageCountMap(
  102 + convertList(pageResult.getList(), AiChatConversationDO::getId));
  103 + return success(BeanUtils.toBean(pageResult, AiChatConversationRespVO.class,
  104 + conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0))));
  105 + }
80 106  
81 107 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
... ... @@ -62,9 +62,9 @@ public class AiChatMessageController {
62 62 return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
63 63 }
64 64  
65   - @Operation(summary = "获得指定会话的消息列表")
  65 + @Operation(summary = "获得指定对话的消息列表")
66 66 @GetMapping("/list-by-conversation-id")
67   - @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
  67 + @Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
68 68 public CommonResult<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
69 69 @RequestParam("conversationId") Long conversationId) {
70 70 AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
... ... @@ -93,12 +93,16 @@ public class AiChatMessageController {
93 93 return success(true);
94 94 }
95 95  
96   - @Operation(summary = "删除指定会话的消息")
  96 + @Operation(summary = "删除指定对话的消息")
97 97 @DeleteMapping("/delete-by-conversation-id")
98   - @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
  98 + @Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024")
99 99 public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
100 100 chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
101 101 return success(true);
102 102 }
103 103  
  104 + // ========== 对话管理 ==========
  105 +
  106 +
  107 +
104 108 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java
... ... @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -@Schema(description = "管理后台 - AI 聊天会话创建【我的】 Request VO")
  6 +@Schema(description = "管理后台 - AI 聊天对话创建【我的】 Request VO")
7 7 @Data
8 8 public class AiChatConversationCreateMyReqVO {
9 9  
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@Schema(description = "管理后台 - AI 聊天对话的分页 Request VO")
  13 +@Data
  14 +public class AiChatConversationPageReqVO extends PageParam {
  15 +
  16 + @Schema(description = "用户编号", example = "1024")
  17 + private Long userId;
  18 +
  19 + @Schema(description = "对话标题", example = "你好")
  20 + private String title;
  21 +
  22 + @Schema(description = "创建时间")
  23 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  24 + private LocalDateTime[] createTime;
  25 +
  26 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java
... ... @@ -10,24 +10,24 @@ import lombok.Data;
10 10  
11 11 import java.time.LocalDateTime;
12 12  
13   -@Schema(description = "管理后台 - AI 聊天会话 Response VO")
  13 +@Schema(description = "管理后台 - AI 聊天对话 Response VO")
14 14 @Data
15 15 public class AiChatConversationRespVO implements VO {
16 16  
17   - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
18 18 private Long id;
19 19  
20 20 @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
21 21 private Long userId;
22 22  
23   - @Schema(description = "会话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
  23 + @Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题")
24 24 private String title;
25 25  
26 26 @Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
27 27 private Boolean pinned;
28 28  
29   - @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
30   - @Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = "avatar", ref = "roleAvatar")
  29 + @Schema(description = "角色编号", example = "1")
  30 + @Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"})
31 31 private Long roleId;
32 32  
33 33 @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
... ... @@ -52,12 +52,20 @@ public class AiChatConversationRespVO implements VO {
52 52 @Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
53 53 private Integer maxContexts;
54 54  
55   - @Schema(description = "最后更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
56   - private LocalDateTime updateTime;
  55 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  56 + private LocalDateTime createTime;
57 57  
58 58 // ========== 关联 role 信息 ==========
59 59  
60   - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
  60 + @Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png")
61 61 private String roleAvatar;
62 62  
  63 + @Schema(description = "角色名字", example = "小黄")
  64 + private String roleName;
  65 +
  66 + // ========== 仅在【对话管理】时加载 ==========
  67 +
  68 + @Schema(description = "消息数量", example = "20")
  69 + private Integer messageCount;
  70 +
63 71 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java
... ... @@ -4,15 +4,15 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotNull;
5 5 import lombok.Data;
6 6  
7   -@Schema(description = "管理后台 - AI 聊天会话更新【我的】 Request VO")
  7 +@Schema(description = "管理后台 - AI 聊天对话更新【我的】 Request VO")
8 8 @Data
9 9 public class AiChatConversationUpdateMyReqVO {
10 10  
11   - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
12   - @NotNull(message = "会话编号不能为空")
  11 + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "对话编号不能为空")
13 13 private Long id;
14 14  
15   - @Schema(description = "会话标题", example = "我是一个标题")
  15 + @Schema(description = "对话标题", example = "我是一个标题")
16 16 private String title;
17 17  
18 18 @Schema(description = "是否置顶", example = "true")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java
... ... @@ -12,7 +12,7 @@ public class AiChatMessageRespVO {
12 12 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
13 13 private Long id;
14 14  
15   - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  15 + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
16 16 private Long conversationId;
17 17  
18 18 @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
... ... @@ -13,7 +13,7 @@ import java.time.LocalDateTime;
13 13 import java.util.Date;
14 14  
15 15 /**
16   - * AI Chat 会话 DO
  16 + * AI Chat 对话 DO
17 17 *
18 18 * 用户每次发起 Chat 聊天时,会创建一个 {@link AiChatConversationDO} 对象,将它的消息关联在一起
19 19 *
... ... @@ -45,7 +45,7 @@ public class AiChatConversationDO extends BaseDO {
45 45 private Long userId;
46 46  
47 47 /**
48   - * 会话标题
  48 + * 对话标题
49 49 *
50 50 * 默认由系统自动生成,可用户手动修改
51 51 */
... ... @@ -79,7 +79,7 @@ public class AiChatConversationDO extends BaseDO {
79 79 */
80 80 private String model;
81 81  
82   - // ========== 会话配置 ==========
  82 + // ========== 对话配置 ==========
83 83  
84 84 /**
85 85 * 角色设定
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
... ... @@ -32,7 +32,7 @@ public class AiChatMessageDO extends BaseDO {
32 32 private Long id;
33 33  
34 34 /**
35   - * 会话编号
  35 + * 对话编号
36 36 *
37 37 * 关联 {@link AiChatConversationDO#getId()} 字段
38 38 */
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java
... ... @@ -62,7 +62,7 @@ public class AiChatModelDO extends BaseDO {
62 62 */
63 63 private Integer status;
64 64  
65   - // ========== 会话配置 ==========
  65 + // ========== 对话配置 ==========
66 66  
67 67 /**
68 68 * 温度参数
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java
1 1 package cn.iocoder.yudao.module.ai.dal.mysql.chat;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
5 7 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
6 8 import org.apache.ibatis.annotations.Mapper;
7 9  
... ... @@ -22,4 +24,18 @@ public interface AiChatConversationMapper extends BaseMapperX&lt;AiChatConversation
22 24 .orderByAsc(AiChatConversationDO::getCreateTime));
23 25 }
24 26  
  27 + default List<AiChatConversationDO> selectListByUserIdAndPinned(Long userId, boolean pinned) {
  28 + return selectList(new LambdaQueryWrapperX<AiChatConversationDO>()
  29 + .eq(AiChatConversationDO::getUserId, userId)
  30 + .eq(AiChatConversationDO::getPinned, pinned));
  31 + }
  32 +
  33 + default PageResult<AiChatConversationDO> selectChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
  34 + return selectPage(pageReqVO, new LambdaQueryWrapperX<AiChatConversationDO>()
  35 + .eqIfPresent(AiChatConversationDO::getUserId, pageReqVO.getUserId())
  36 + .likeIfPresent(AiChatConversationDO::getTitle, pageReqVO.getTitle())
  37 + .betweenIfPresent(AiChatConversationDO::getCreateTime, pageReqVO.getCreateTime())
  38 + .orderByDesc(AiChatConversationDO::getId));
  39 + }
  40 +
25 41 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java
1 1 package cn.iocoder.yudao.module.ai.dal.mysql.chat;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
3 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 8 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 10 import org.apache.ibatis.annotations.Mapper;
7 11  
  12 +import java.util.Collection;
  13 +import java.util.Collections;
8 14 import java.util.List;
  15 +import java.util.Map;
9 16  
10 17 /**
11 18 * AI 聊天对话 Mapper
... ... @@ -21,4 +28,19 @@ public interface AiChatMessageMapper extends BaseMapperX&lt;AiChatMessageDO&gt; {
21 28 .orderByAsc(AiChatMessageDO::getId));
22 29 }
23 30  
  31 + default Map<Long, Integer> selectCountMapByConversationId(Collection<Long> conversationIds) {
  32 + // SQL count 查询
  33 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<AiChatMessageDO>()
  34 + .select("COUNT(id) AS count, conversation_id AS conversationId")
  35 + .in("conversation_id", conversationIds)
  36 + .groupBy("conversation_id"));
  37 + if (CollUtil.isEmpty(result)) {
  38 + return Collections.emptyMap();
  39 + }
  40 + // 转换数据
  41 + return CollectionUtils.convertMap(result,
  42 + record -> MapUtil.getLong(record, "conversationId"),
  43 + record -> MapUtil.getInt(record, "count" ));
  44 + }
  45 +
24 46 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java
1 1 package cn.iocoder.yudao.module.ai.service.chat;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
4 6 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
5 7 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
6 8  
... ... @@ -14,7 +16,7 @@ import java.util.List;
14 16 public interface AiChatConversationService {
15 17  
16 18 /**
17   - * 创建【我的】聊天会话
  19 + * 创建【我的】聊天对话
18 20 *
19 21 * @param createReqVO 创建信息
20 22 * @param userId 用户编号
... ... @@ -23,7 +25,7 @@ public interface AiChatConversationService {
23 25 Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId);
24 26  
25 27 /**
26   - * 更新【我的】聊天会话
  28 + * 更新【我的】聊天对话
27 29 *
28 30 * @param updateReqVO 更新信息
29 31 * @param userId 用户编号
... ... @@ -31,23 +33,23 @@ public interface AiChatConversationService {
31 33 void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId);
32 34  
33 35 /**
34   - * 获得【我的】聊天会话列表
  36 + * 获得【我的】聊天对话列表
35 37 *
36 38 * @param userId 用户编号
37   - * @return 聊天会话列表
  39 + * @return 聊天对话列表
38 40 */
39 41 List<AiChatConversationDO> getChatConversationListByUserId(Long userId);
40 42  
41 43 /**
42   - * 获得聊天会话
  44 + * 获得聊天对话
43 45 *
44 46 * @param id 编号
45   - * @return 聊天会话
  47 + * @return 聊天对话
46 48 */
47 49 AiChatConversationDO getChatConversation(Long id);
48 50  
49 51 /**
50   - * 删除【我的】聊天会话
  52 + * 删除【我的】聊天对话
51 53 *
52 54 * @param id 编号
53 55 * @param userId 用户编号
... ... @@ -55,17 +57,20 @@ public interface AiChatConversationService {
55 57 void deleteChatConversationMy(Long id, Long userId);
56 58  
57 59 /**
58   - * 校验 - 是否存在
  60 + * 校验聊天对话是否存在
59 61 *
60   - * @param id
61   - * @return
  62 + * @param id 编号
  63 + * @return 聊天对话
62 64 */
63   - AiChatConversationDO validateExists(Long id);
  65 + AiChatConversationDO validateChatConversationExists(Long id);
64 66  
65 67 /**
66   - * 删除 - 所有对话,置顶除外
  68 + * 删除【我的】 + 非置顶的聊天对话
67 69 *
68   - * @param loginUserId
  70 + * @param userId 用户编号
69 71 */
70   - void deleteMyAllExceptPinned(Long loginUserId);
  72 + void deleteChatConversationMyByUnpinned(Long userId);
  73 +
  74 + PageResult<AiChatConversationDO> getChatConversationPage(AiChatConversationPageReqVO pageReqVO);
  75 +
71 76 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
1 1 package cn.iocoder.yudao.module.ai.service.chat;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import cn.hutool.core.util.ObjUtil;
5 6 import cn.hutool.core.util.ObjectUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 9 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO;
  10 +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
9 11 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO;
10 12 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
11 13 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
... ... @@ -22,6 +24,8 @@ import java.time.LocalDateTime;
22 24 import java.util.List;
23 25  
24 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
25 29 import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_MODEL_ERROR;
26 30 import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
27 31  
... ... @@ -69,7 +73,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
69 73 @Override
70 74 public void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId) {
71 75 // 1.1 校验对话是否存在
72   - AiChatConversationDO conversation = validateExists(updateReqVO.getId());
  76 + AiChatConversationDO conversation = validateChatConversationExists(updateReqVO.getId());
73 77 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
74 78 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
75 79 }
... ... @@ -103,7 +107,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
103 107 @Override
104 108 public void deleteChatConversationMy(Long id, Long userId) {
105 109 // 1. 校验对话是否存在
106   - AiChatConversationDO conversation = validateExists(id);
  110 + AiChatConversationDO conversation = validateChatConversationExists(id);
107 111 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
108 112 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
109 113 }
... ... @@ -119,7 +123,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
119 123 throw exception(CHAT_CONVERSATION_MODEL_ERROR);
120 124 }
121 125  
122   - public AiChatConversationDO validateExists(Long id) {
  126 + public AiChatConversationDO validateChatConversationExists(Long id) {
123 127 AiChatConversationDO conversation = chatConversationMapper.selectById(id);
124 128 if (conversation == null) {
125 129 throw exception(CHAT_CONVERSATION_NOT_EXISTS);
... ... @@ -128,12 +132,17 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
128 132 }
129 133  
130 134 @Override
131   - public void deleteMyAllExceptPinned(Long loginUserId) {
132   - chatConversationMapper.delete(
133   - new LambdaQueryWrapperX<AiChatConversationDO>()
134   - .eq(AiChatConversationDO::getUserId, loginUserId)
135   - .eq(AiChatConversationDO::getPinned, false)
136   - );
  135 + public void deleteChatConversationMyByUnpinned(Long userId) {
  136 + List<AiChatConversationDO> list = chatConversationMapper.selectListByUserIdAndPinned(userId, false);
  137 + if (CollUtil.isEmpty(list)) {
  138 + return;
  139 + }
  140 + chatConversationMapper.deleteBatchIds(convertList(list, AiChatConversationDO::getId));
  141 + }
  142 +
  143 + @Override
  144 + public PageResult<AiChatConversationDO> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
  145 + return chatConversationMapper.selectChatConversationPage(pageReqVO);
137 146 }
138 147  
139 148 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java
... ... @@ -5,7 +5,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
5 5 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
6 6 import reactor.core.publisher.Flux;
7 7  
  8 +import java.util.Collection;
8 9 import java.util.List;
  10 +import java.util.Map;
9 11  
10 12 /**
11 13 * AI 聊天消息 Service 接口
... ... @@ -32,9 +34,9 @@ public interface AiChatMessageService {
32 34 Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId);
33 35  
34 36 /**
35   - * 获得指定会话的消息列表
  37 + * 获得指定对话的消息列表
36 38 *
37   - * @param conversationId 会话编号
  39 + * @param conversationId 对话编号
38 40 * @return 消息列表
39 41 */
40 42 List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId);
... ... @@ -48,11 +50,19 @@ public interface AiChatMessageService {
48 50 void deleteChatMessage(Long id, Long userId);
49 51  
50 52 /**
51   - * 删除指定会话的消息
  53 + * 删除指定对话的消息
52 54 *
53   - * @param conversationId 会话编号
  55 + * @param conversationId 对话编号
54 56 * @param userId 用户编号
55 57 */
56 58 void deleteChatMessageByConversationId(Long conversationId, Long userId);
57 59  
  60 + /**
  61 + * 获得聊天对话的消息数量 Map
  62 + *
  63 + * @param conversationIds 对话编号数组
  64 + * @return 消息数量 Map
  65 + */
  66 + Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds);
  67 +
58 68 }
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
... ... @@ -5,7 +5,6 @@ import cn.hutool.core.util.ObjUtil;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
7 7 import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;
8   -import cn.iocoder.yudao.framework.common.exception.ErrorCode;
9 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
10 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 10 import cn.iocoder.yudao.module.ai.ErrorCodeConstants;
... ... @@ -15,7 +14,6 @@ import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
15 14 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
16 15 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
17 16 import jakarta.annotation.Resource;
18   -import org.reactivestreams.Publisher;
19 17 import org.springframework.ai.chat.ChatResponse;
20 18 import org.springframework.ai.chat.StreamingChatClient;
21 19 import org.springframework.ai.chat.messages.*;
... ... @@ -34,7 +32,6 @@ import lombok.extern.slf4j.Slf4j;
34 32 import org.springframework.stereotype.Service;
35 33 import org.springframework.transaction.annotation.Transactional;
36 34 import reactor.core.publisher.Flux;
37   -import reactor.core.scheduler.Schedulers;
38 35  
39 36 import java.time.LocalDateTime;
40 37 import java.util.*;
... ... @@ -115,7 +112,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
115 112 @Override
116 113 public Flux<CommonResult<AiChatMessageSendRespVO>> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) {
117 114 // 1.1 校验对话存在
118   - AiChatConversationDO conversation = chatConversationService.validateExists(sendReqVO.getConversationId());
  115 + AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId());
119 116 if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
120 117 throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接;
121 118 }
... ... @@ -189,7 +186,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
189 186 * n 组:指的是 user + assistant 形成一组
190 187 *
191 188 * @param messages 消息列表
192   - * @param conversation 会话
  189 + * @param conversation 对话
193 190 * @param sendReqVO 发送请求
194 191 * @return 消息上下文
195 192 */
... ... @@ -258,4 +255,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
258 255 chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
259 256 }
260 257  
  258 + @Override
  259 + public Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds) {
  260 + return chatMessageMapper.selectCountMapByConversationId(conversationIds);
  261 + }
  262 +
261 263 }
... ...