瀏覽代碼

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

YunaiV 11 月之前
父節點
當前提交
12b82b72e3

+ 9 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java

@@ -104,4 +104,13 @@ public class AiChatConversationController {
                 conversation -> conversation.setMessageCount(messageCountMap.getOrDefault(conversation.getId(), 0))));
     }
 
+    @Operation(summary = "管理员删除对话")
+    @DeleteMapping("/delete-by-admin")
+    @Parameter(name = "id", required = true, description = "对话编号", example = "1024")
+    @PreAuthorize("@ss.hasPermission('ai:chat-conversation:delete')")
+    public CommonResult<Boolean> deleteChatConversationByAdmin(@RequestParam("id") Long id) {
+        chatConversationService.deleteChatConversationByAdmin(id);
+        return success(true);
+    }
+
 }

+ 27 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java

@@ -3,8 +3,11 @@ 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.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+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.message.*;
 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
@@ -21,6 +24,7 @@ import jakarta.annotation.Resource;
 import jakarta.annotation.security.PermitAll;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import reactor.core.publisher.Flux;
@@ -30,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
@@ -103,6 +108,28 @@ public class AiChatMessageController {
 
     // ========== 对话管理 ==========
 
+    @GetMapping("/page")
+    @Operation(summary = "获得消息分页", description = "用于【对话管理】菜单")
+    @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
+    public CommonResult<PageResult<AiChatMessageRespVO>> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) {
+        PageResult<AiChatMessageDO> pageResult = chatMessageService.getChatMessagePage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+        // 拼接数据
+        Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(
+                convertSet(pageResult.getList(), AiChatMessageDO::getRoleId));
+        return success(BeanUtils.toBean(pageResult, AiChatMessageRespVO.class,
+                respVO -> MapUtils.findAndThen(roleMap, respVO.getRoleId(), role -> respVO.setRoleName(role.getName()))));
+    }
 
+    @Operation(summary = "管理员删除消息")
+    @DeleteMapping("/delete-by-admin")
+    @Parameter(name = "id", required = true, description = "消息编号", example = "1024")
+    @PreAuthorize("@ss.hasPermission('ai:chat-message:delete')")
+    public CommonResult<Boolean> deleteChatMessageByAdmin(@RequestParam("id") Long id) {
+        chatMessageService.deleteChatMessageByAdmin(id);
+        return success(true);
+    }
 
 }

+ 29 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessagePageReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message;
+
+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 AiChatMessagePageReqVO extends PageParam {
+
+    @Schema(description = "对话编号", example = "2048")
+    private Long conversationId;
+
+    @Schema(description = "用户编号", example = "1024")
+    private Long userId;
+
+    @Schema(description = "消息内容", example = "你好")
+    private String content;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 12 - 2
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java

@@ -15,6 +15,9 @@ public class AiChatMessageRespVO {
     @Schema(description = "对话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
     private Long conversationId;
 
+    @Schema(description = "回复消息编号", example = "1024")
+    private Long replyId;
+
     @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "role")
     private String type; // 参见 MessageType 枚举类
 
@@ -33,14 +36,21 @@ public class AiChatMessageRespVO {
     @Schema(description = "聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "你好,你好啊")
     private String content;
 
+    @Schema(description = "是否携带上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+    private Boolean useContext;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-05-12 12:51")
     private LocalDateTime createTime;
 
-    // ========= 扩展字段
+    // ========== 仅在【对话管理】时加载 ==========
 
     @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx")
     private String userAvatar;
 
-    @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx")
+    @Schema(description = "角色名字", example = "小黄")
+    private String roleName;
+
+    @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
     private String roleAvatar;
+
 }

+ 13 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatMessageMapper.java

@@ -2,9 +2,13 @@ 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.pojo.PageResult;
 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.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
+import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
@@ -43,4 +47,13 @@ public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
                 record -> MapUtil.getInt(record, "count" ));
     }
 
+    default PageResult<AiChatMessageDO> selectPage(AiChatMessagePageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<AiChatMessageDO>()
+                .eqIfPresent(AiChatMessageDO::getConversationId, pageReqVO.getConversationId())
+                .eqIfPresent(AiChatMessageDO::getUserId, pageReqVO.getUserId())
+                .likeIfPresent(AiChatMessageDO::getContent, pageReqVO.getContent())
+                .betweenIfPresent(AiChatMessageDO::getCreateTime, pageReqVO.getCreateTime())
+                .orderByDesc(AiChatMessageDO::getId));
+    }
+
 }

+ 14 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java

@@ -4,6 +4,7 @@ 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.controller.admin.chat.vo.message.AiChatMessageRespVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
 
 import java.util.List;
@@ -56,6 +57,13 @@ public interface AiChatConversationService {
      */
     void deleteChatConversationMy(Long id, Long userId);
 
+    /**
+     * 【管理员】删除聊天对话
+     *
+     * @param id 编号
+     */
+    void deleteChatConversationByAdmin(Long id);
+
     /**
      * 校验聊天对话是否存在
      *
@@ -71,6 +79,12 @@ public interface AiChatConversationService {
      */
     void deleteChatConversationMyByUnpinned(Long userId);
 
+    /**
+     * 获得聊天对话的分页列表
+     *
+     * @param pageReqVO 分页查询
+     * @return 聊天对话的分页列表
+     */
     PageResult<AiChatConversationDO> getChatConversationPage(AiChatConversationPageReqVO pageReqVO);
 
 }

+ 11 - 2
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java

@@ -25,7 +25,6 @@ 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;
 
@@ -108,10 +107,20 @@ public class AiChatConversationServiceImpl implements AiChatConversationService
     public void deleteChatConversationMy(Long id, Long userId) {
         // 1. 校验对话是否存在
         AiChatConversationDO conversation = validateChatConversationExists(id);
-        if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
+        if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), userId)) {
             throw exception(CHAT_CONVERSATION_NOT_EXISTS);
         }
+        // 2. 执行删除
+        chatConversationMapper.deleteById(id);
+    }
 
+    @Override
+    public void deleteChatConversationByAdmin(Long id) {
+        // 1. 校验对话是否存在
+        AiChatConversationDO conversation = validateChatConversationExists(id);
+        if (conversation == null) {
+            throw exception(CHAT_CONVERSATION_NOT_EXISTS);
+        }
         // 2. 执行删除
         chatConversationMapper.deleteById(id);
     }

+ 17 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.ai.service.chat;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
 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;
@@ -57,6 +59,13 @@ public interface AiChatMessageService {
      */
     void deleteChatMessageByConversationId(Long conversationId, Long userId);
 
+    /**
+     * 【管理员】删除消息
+     *
+     * @param id 消息编号
+     */
+    void deleteChatMessageByAdmin(Long id);
+
     /**
      * 获得聊天对话的消息数量 Map
      *
@@ -65,4 +74,12 @@ public interface AiChatMessageService {
      */
     Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds);
 
+    /**
+     * 获得聊天消息的分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 聊天消息的分页
+     */
+    PageResult<AiChatMessageDO> getChatMessagePage(AiChatMessagePageReqVO pageReqVO);
+
 }

+ 19 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java

@@ -6,8 +6,11 @@ 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.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.ErrorCodeConstants;
+import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationPageReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendRespVO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
 import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService;
@@ -255,9 +258,25 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
         chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
     }
 
+    @Override
+    public void deleteChatMessageByAdmin(Long id) {
+        // 1. 校验消息存在
+        AiChatMessageDO message = chatMessageMapper.selectById(id);
+        if (message == null) {
+            throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
+        }
+        // 2. 执行删除
+        chatMessageMapper.deleteById(id);
+    }
+
     @Override
     public Map<Long, Integer> getChatMessageCountMap(Collection<Long> conversationIds) {
         return chatMessageMapper.selectCountMapByConversationId(conversationIds);
     }
 
+    @Override
+    public PageResult<AiChatMessageDO> getChatMessagePage(AiChatMessagePageReqVO pageReqVO) {
+        return chatMessageMapper.selectPage(pageReqVO);
+    }
+
 }