From 2d11f085c8a1d65d3e7af6e1f196acc3936d1ac8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 25 May 2024 00:08:27 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E6=96=B0=E5=A2=9E=E3=80=91AI=EF=BC=9A?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E7=AE=A1=E7=90=86=2050%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/AiChatConversationController.java | 50 ++++++++++++++----- .../admin/chat/AiChatMessageController.java | 12 +++-- .../AiChatConversationCreateMyReqVO.java | 2 +- .../AiChatConversationPageReqVO.java | 26 ++++++++++ .../AiChatConversationRespVO.java | 24 ++++++--- .../AiChatConversationUpdateMyReqVO.java | 8 +-- .../chat/vo/message/AiChatMessageRespVO.java | 2 +- .../dataobject/chat/AiChatConversationDO.java | 6 +-- .../dal/dataobject/chat/AiChatMessageDO.java | 2 +- .../dal/dataobject/model/AiChatModelDO.java | 2 +- .../mysql/chat/AiChatConversationMapper.java | 16 ++++++ .../dal/mysql/chat/AiChatMessageMapper.java | 22 ++++++++ .../chat/AiChatConversationService.java | 33 ++++++------ .../chat/AiChatConversationServiceImpl.java | 29 +++++++---- .../ai/service/chat/AiChatMessageService.java | 18 +++++-- .../chat/AiChatMessageServiceImpl.java | 12 +++-- 16 files changed, 196 insertions(+), 68 deletions(-) create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java index 9d0cd74c8..ad57460ca 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java @@ -1,27 +1,35 @@ package cn.iocoder.yudao.module.ai.controller.admin.chat; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; +import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -@Tag(name = "管理后台 - AI 聊天会话") +@Tag(name = "管理后台 - AI 聊天对话") @RestController @RequestMapping("/ai/chat/conversation") @Validated @@ -29,30 +37,32 @@ public class AiChatConversationController { @Resource private AiChatConversationService chatConversationService; + @Resource + private AiChatMessageService chatMessageService; @PostMapping("/create-my") - @Operation(summary = "创建【我的】聊天会话") + @Operation(summary = "创建【我的】聊天对话") public CommonResult createChatConversationMy(@RequestBody @Valid AiChatConversationCreateMyReqVO createReqVO) { return success(chatConversationService.createChatConversationMy(createReqVO, getLoginUserId())); } @PutMapping("/update-my") - @Operation(summary = "更新【我的】聊天会话") + @Operation(summary = "更新【我的】聊天对话") public CommonResult updateChatConversationMy(@RequestBody @Valid AiChatConversationUpdateMyReqVO updateReqVO) { chatConversationService.updateChatConversationMy(updateReqVO, getLoginUserId()); return success(true); } @GetMapping("/my-list") - @Operation(summary = "获得【我的】聊天会话列表") + @Operation(summary = "获得【我的】聊天对话列表") public CommonResult> getChatConversationMyList() { List list = chatConversationService.getChatConversationListByUserId(getLoginUserId()); return success(BeanUtils.toBean(list, AiChatConversationRespVO.class)); } @GetMapping("/get-my") - @Operation(summary = "获得【我的】聊天会话") - @Parameter(name = "id", required = true, description = "会话编号", example = "1024") + @Operation(summary = "获得【我的】聊天对话") + @Parameter(name = "id", required = true, description = "对话编号", example = "1024") public CommonResult getChatConversationMy(@RequestParam("id") Long id) { AiChatConversationDO conversation = chatConversationService.getChatConversation(id); if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) { @@ -62,20 +72,36 @@ public class AiChatConversationController { } @DeleteMapping("/delete-my") - @Operation(summary = "删除聊天会话") - @Parameter(name = "id", required = true, description = "会话编号", example = "1024") + @Operation(summary = "删除聊天对话") + @Parameter(name = "id", required = true, description = "对话编号", example = "1024") public CommonResult deleteChatConversationMy(@RequestParam("id") Long id) { chatConversationService.deleteChatConversationMy(id, getLoginUserId()); return success(true); } + // TODO 芋艿:这个 url 可以改下 @DeleteMapping("/delete-my-all-except-pinned") @Operation(summary = "删除所有对话(置顶除外)") - @Parameter(name = "id", required = true, description = "会话编号", example = "1024") - public CommonResult deleteMyAllExceptPinned() { - chatConversationService.deleteMyAllExceptPinned(getLoginUserId()); + public CommonResult deleteChatConversationMyByUnpinned() { + chatConversationService.deleteChatConversationMyByUnpinned(getLoginUserId()); return success(true); } - // ========== 会话管理 ========== + + // ========== 对话管理 ========== + + @GetMapping("/page") + @Operation(summary = "获得对话分页", description = "用于【对话管理】菜单") + @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')") + public CommonResult> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) { + PageResult pageResult = chatConversationService.getChatConversationPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + // 拼接关联数据 + Map messageCountMap = chatMessageService.getChatMessageCountMap( + convertList(pageResult.getList(), AiChatConversationDO::getId)); + return success(BeanUtils.toBean(pageResult, AiChatConversationRespVO.class, + conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0)))); + } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java index 7211f0664..68559c776 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java @@ -62,9 +62,9 @@ public class AiChatMessageController { return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId()); } - @Operation(summary = "获得指定会话的消息列表") + @Operation(summary = "获得指定对话的消息列表") @GetMapping("/list-by-conversation-id") - @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024") + @Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024") public CommonResult> getChatMessageListByConversationId( @RequestParam("conversationId") Long conversationId) { AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId); @@ -93,12 +93,16 @@ public class AiChatMessageController { return success(true); } - @Operation(summary = "删除指定会话的消息") + @Operation(summary = "删除指定对话的消息") @DeleteMapping("/delete-by-conversation-id") - @Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024") + @Parameter(name = "conversationId", required = true, description = "对话编号", example = "1024") public CommonResult deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) { chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId()); return success(true); } + // ========== 对话管理 ========== + + + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java index d64ea7f61..c13200b6a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationCreateMyReqVO.java +++ b/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; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@Schema(description = "管理后台 - AI 聊天会话创建【我的】 Request VO") +@Schema(description = "管理后台 - AI 聊天对话创建【我的】 Request VO") @Data public class AiChatConversationCreateMyReqVO { diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java new file mode 100644 index 000000000..967e866ea --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationPageReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - AI 聊天对话的分页 Request VO") +@Data +public class AiChatConversationPageReqVO extends PageParam { + + @Schema(description = "用户编号", example = "1024") + private Long userId; + + @Schema(description = "对话标题", example = "你好") + private String title; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java index e670ef9f9..66eb24db5 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java +++ b/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; import java.time.LocalDateTime; -@Schema(description = "管理后台 - AI 聊天会话 Response VO") +@Schema(description = "管理后台 - AI 聊天对话 Response VO") @Data public class AiChatConversationRespVO implements VO { - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private Long id; @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") private Long userId; - @Schema(description = "会话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题") + @Schema(description = "对话标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是一个标题") private String title; @Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean pinned; - @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1") - @Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = "avatar", ref = "roleAvatar") + @Schema(description = "角色编号", example = "1") + @Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = {"name", "avatar"}, refs = {"roleName", "roleAvatar"}) private Long roleId; @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @@ -52,12 +52,20 @@ public class AiChatConversationRespVO implements VO { @Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private Integer maxContexts; - @Schema(description = "最后更新时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime updateTime; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; // ========== 关联 role 信息 ========== - @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") + @Schema(description = "角色头像", example = "https://www.iocoder.cn/1.png") private String roleAvatar; + @Schema(description = "角色名字", example = "小黄") + private String roleName; + + // ========== 仅在【对话管理】时加载 ========== + + @Schema(description = "消息数量", example = "20") + private Integer messageCount; + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java index 36e95d298..f9ce64bae 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationUpdateMyReqVO.java +++ b/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; import jakarta.validation.constraints.NotNull; import lombok.Data; -@Schema(description = "管理后台 - AI 聊天会话更新【我的】 Request VO") +@Schema(description = "管理后台 - AI 聊天对话更新【我的】 Request VO") @Data public class AiChatConversationUpdateMyReqVO { - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "会话编号不能为空") + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "对话编号不能为空") private Long id; - @Schema(description = "会话标题", example = "我是一个标题") + @Schema(description = "对话标题", example = "我是一个标题") private String title; @Schema(description = "是否置顶", example = "true") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java index e4e878e40..2cb0f9c5c 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java +++ b/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 { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") private Long id; - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") private Long conversationId; @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role") diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java index b92e66143..8e582f621 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java +++ b/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; import java.util.Date; /** - * AI Chat 会话 DO + * AI Chat 对话 DO * * 用户每次发起 Chat 聊天时,会创建一个 {@link AiChatConversationDO} 对象,将它的消息关联在一起 * @@ -45,7 +45,7 @@ public class AiChatConversationDO extends BaseDO { private Long userId; /** - * 会话标题 + * 对话标题 * * 默认由系统自动生成,可用户手动修改 */ @@ -79,7 +79,7 @@ public class AiChatConversationDO extends BaseDO { */ private String model; - // ========== 会话配置 ========== + // ========== 对话配置 ========== /** * 角色设定 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java index 1c915ed7c..455a428a7 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java +++ b/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 { private Long id; /** - * 会话编号 + * 对话编号 * * 关联 {@link AiChatConversationDO#getId()} 字段 */ diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java index c943f7ea6..7197f8b58 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/model/AiChatModelDO.java +++ b/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 { */ private Integer status; - // ========== 会话配置 ========== + // ========== 对话配置 ========== /** * 温度参数 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java index dd4901bdf..6400297d8 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.ai.dal.mysql.chat; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; import org.apache.ibatis.annotations.Mapper; @@ -22,4 +24,18 @@ public interface AiChatConversationMapper extends BaseMapperX selectListByUserIdAndPinned(Long userId, boolean pinned) { + return selectList(new LambdaQueryWrapperX() + .eq(AiChatConversationDO::getUserId, userId) + .eq(AiChatConversationDO::getPinned, pinned)); + } + + default PageResult selectChatConversationPage(AiChatConversationPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eqIfPresent(AiChatConversationDO::getUserId, pageReqVO.getUserId()) + .likeIfPresent(AiChatConversationDO::getTitle, pageReqVO.getTitle()) + .betweenIfPresent(AiChatConversationDO::getCreateTime, pageReqVO.getCreateTime()) + .orderByDesc(AiChatConversationDO::getId)); + } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java index 7b3e9c493..76db130be 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java @@ -1,11 +1,18 @@ package cn.iocoder.yudao.module.ai.dal.mysql.chat; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.ibatis.annotations.Mapper; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * AI 聊天对话 Mapper @@ -21,4 +28,19 @@ public interface AiChatMessageMapper extends BaseMapperX { .orderByAsc(AiChatMessageDO::getId)); } + default Map selectCountMapByConversationId(Collection conversationIds) { + // SQL count 查询 + List> result = selectMaps(new QueryWrapper() + .select("COUNT(id) AS count, conversation_id AS conversationId") + .in("conversation_id", conversationIds) + .groupBy("conversation_id")); + if (CollUtil.isEmpty(result)) { + return Collections.emptyMap(); + } + // 转换数据 + return CollectionUtils.convertMap(result, + record -> MapUtil.getLong(record, "conversationId"), + record -> MapUtil.getInt(record, "count" )); + } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java index f93572e66..42678a2f9 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.ai.service.chat; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; @@ -14,7 +16,7 @@ import java.util.List; public interface AiChatConversationService { /** - * 创建【我的】聊天会话 + * 创建【我的】聊天对话 * * @param createReqVO 创建信息 * @param userId 用户编号 @@ -23,7 +25,7 @@ public interface AiChatConversationService { Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId); /** - * 更新【我的】聊天会话 + * 更新【我的】聊天对话 * * @param updateReqVO 更新信息 * @param userId 用户编号 @@ -31,23 +33,23 @@ public interface AiChatConversationService { void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId); /** - * 获得【我的】聊天会话列表 + * 获得【我的】聊天对话列表 * * @param userId 用户编号 - * @return 聊天会话列表 + * @return 聊天对话列表 */ List getChatConversationListByUserId(Long userId); /** - * 获得聊天会话 + * 获得聊天对话 * * @param id 编号 - * @return 聊天会话 + * @return 聊天对话 */ AiChatConversationDO getChatConversation(Long id); /** - * 删除【我的】聊天会话 + * 删除【我的】聊天对话 * * @param id 编号 * @param userId 用户编号 @@ -55,17 +57,20 @@ public interface AiChatConversationService { void deleteChatConversationMy(Long id, Long userId); /** - * 校验 - 是否存在 + * 校验聊天对话是否存在 * - * @param id - * @return + * @param id 编号 + * @return 聊天对话 */ - AiChatConversationDO validateExists(Long id); + AiChatConversationDO validateChatConversationExists(Long id); /** - * 删除 - 所有对话,置顶除外 + * 删除【我的】 + 非置顶的聊天对话 * - * @param loginUserId + * @param userId 用户编号 */ - void deleteMyAllExceptPinned(Long loginUserId); + void deleteChatConversationMyByUnpinned(Long userId); + + PageResult getChatConversationPage(AiChatConversationPageReqVO pageReqVO); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java index 3a3b16aea..41417fbcd 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.module.ai.service.chat; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO; import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; @@ -22,6 +24,8 @@ import java.time.LocalDateTime; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_MODEL_ERROR; import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; @@ -69,7 +73,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @Override public void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId) { // 1.1 校验对话是否存在 - AiChatConversationDO conversation = validateExists(updateReqVO.getId()); + AiChatConversationDO conversation = validateChatConversationExists(updateReqVO.getId()); if (ObjUtil.notEqual(conversation.getUserId(), userId)) { throw exception(CHAT_CONVERSATION_NOT_EXISTS); } @@ -103,7 +107,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService @Override public void deleteChatConversationMy(Long id, Long userId) { // 1. 校验对话是否存在 - AiChatConversationDO conversation = validateExists(id); + AiChatConversationDO conversation = validateChatConversationExists(id); if (ObjUtil.notEqual(conversation.getUserId(), userId)) { throw exception(CHAT_CONVERSATION_NOT_EXISTS); } @@ -119,7 +123,7 @@ public class AiChatConversationServiceImpl implements AiChatConversationService throw exception(CHAT_CONVERSATION_MODEL_ERROR); } - public AiChatConversationDO validateExists(Long id) { + public AiChatConversationDO validateChatConversationExists(Long id) { AiChatConversationDO conversation = chatConversationMapper.selectById(id); if (conversation == null) { throw exception(CHAT_CONVERSATION_NOT_EXISTS); @@ -128,12 +132,17 @@ public class AiChatConversationServiceImpl implements AiChatConversationService } @Override - public void deleteMyAllExceptPinned(Long loginUserId) { - chatConversationMapper.delete( - new LambdaQueryWrapperX() - .eq(AiChatConversationDO::getUserId, loginUserId) - .eq(AiChatConversationDO::getPinned, false) - ); + public void deleteChatConversationMyByUnpinned(Long userId) { + List list = chatConversationMapper.selectListByUserIdAndPinned(userId, false); + if (CollUtil.isEmpty(list)) { + return; + } + chatConversationMapper.deleteBatchIds(convertList(list, AiChatConversationDO::getId)); + } + + @Override + public PageResult getChatConversationPage(AiChatConversationPageReqVO pageReqVO) { + return chatConversationMapper.selectChatConversationPage(pageReqVO); } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java index d7f3e8078..329e2cb1b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java @@ -5,7 +5,9 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*; import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; import reactor.core.publisher.Flux; +import java.util.Collection; import java.util.List; +import java.util.Map; /** * AI 聊天消息 Service 接口 @@ -32,9 +34,9 @@ public interface AiChatMessageService { Flux> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId); /** - * 获得指定会话的消息列表 + * 获得指定对话的消息列表 * - * @param conversationId 会话编号 + * @param conversationId 对话编号 * @return 消息列表 */ List getChatMessageListByConversationId(Long conversationId); @@ -48,11 +50,19 @@ public interface AiChatMessageService { void deleteChatMessage(Long id, Long userId); /** - * 删除指定会话的消息 + * 删除指定对话的消息 * - * @param conversationId 会话编号 + * @param conversationId 对话编号 * @param userId 用户编号 */ void deleteChatMessageByConversationId(Long conversationId, Long userId); + /** + * 获得聊天对话的消息数量 Map + * + * @param conversationIds 对话编号数组 + * @return 消息数量 Map + */ + Map getChatMessageCountMap(Collection conversationIds); + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 8da9110ba..55ae7a7a3 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -5,7 +5,6 @@ import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; -import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ai.ErrorCodeConstants; @@ -15,7 +14,6 @@ import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; -import org.reactivestreams.Publisher; import org.springframework.ai.chat.ChatResponse; import org.springframework.ai.chat.StreamingChatClient; import org.springframework.ai.chat.messages.*; @@ -34,7 +32,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; import java.time.LocalDateTime; import java.util.*; @@ -115,7 +112,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @Override public Flux> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId) { // 1.1 校验对话存在 - AiChatConversationDO conversation = chatConversationService.validateExists(sendReqVO.getConversationId()); + AiChatConversationDO conversation = chatConversationService.validateChatConversationExists(sendReqVO.getConversationId()); if (ObjUtil.notEqual(conversation.getUserId(), userId)) { throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接; } @@ -189,7 +186,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { * n 组:指的是 user + assistant 形成一组 * * @param messages 消息列表 - * @param conversation 会话 + * @param conversation 对话 * @param sendReqVO 发送请求 * @return 消息上下文 */ @@ -258,4 +255,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId)); } + @Override + public Map getChatMessageCountMap(Collection conversationIds) { + return chatMessageMapper.selectCountMapByConversationId(conversationIds); + } + }