Commit 2d11f085c8a1d65d3e7af6e1f196acc3936d1ac8
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
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java
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<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<AiChatMessageDO> { |
| 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 | } | ... | ... |