Commit 69fa98792ce116458095ea79cb134682d33120c2
1 parent
1fa799f6
【新增】AI:聊天对话的新建(80%)
Showing
9 changed files
with
78 additions
and
89 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java
| ... | ... | @@ -25,7 +25,8 @@ public interface ErrorCodeConstants { |
| 25 | 25 | |
| 26 | 26 | // ========== API 聊天会话 1-040-003-000 ========== |
| 27 | 27 | |
| 28 | - ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "AI 对话不存在!");; | |
| 28 | + ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "AI 对话不存在!"); | |
| 29 | + ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整"); | |
| 29 | 30 | |
| 30 | 31 | // chat |
| 31 | 32 | ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_022_000_100, "AI 提问的 MessageId 不存在!"); | ... | ... |
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 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 4 | 5 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; |
| 5 | 6 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO; |
| 6 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationUpdateMyReqVO; |
| 8 | +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | |
| 7 | 9 | import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; |
| 8 | 10 | import io.swagger.v3.oas.annotations.Operation; |
| 9 | 11 | import io.swagger.v3.oas.annotations.Parameter; |
| ... | ... | @@ -40,11 +42,11 @@ public class AiChatConversationController { |
| 40 | 42 | return success(true); |
| 41 | 43 | } |
| 42 | 44 | |
| 43 | - // TODO done @fan:实现一下 | |
| 44 | 45 | @GetMapping("/my-list") |
| 45 | - @Operation(summary = "获得聊天会话列表") | |
| 46 | - public CommonResult<List<AiChatConversationRespVO>> getConversationList() { | |
| 47 | - return success(chatConversationService.listConversation()); | |
| 46 | + @Operation(summary = "获得【我的】聊天会话列表") | |
| 47 | + public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() { | |
| 48 | + List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId()); | |
| 49 | + return success(BeanUtils.toBean(list, AiChatConversationRespVO.class)); | |
| 48 | 50 | } |
| 49 | 51 | |
| 50 | 52 | // TODO @fan:实现一下 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/conversation/AiChatConversationRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelRespVO; | |
| 4 | +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | |
| 5 | +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; | |
| 6 | +import com.fhs.core.trans.anno.Trans; | |
| 7 | +import com.fhs.core.trans.constant.TransType; | |
| 8 | +import com.fhs.core.trans.vo.VO; | |
| 3 | 9 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 10 | import jakarta.validation.constraints.NotNull; |
| 5 | 11 | import lombok.Data; |
| ... | ... | @@ -7,7 +13,7 @@ import lombok.experimental.Accessors; |
| 7 | 13 | |
| 8 | 14 | @Schema(description = "管理后台 - AI 聊天会话 Response VO") |
| 9 | 15 | @Data |
| 10 | -public class AiChatConversationRespVO { | |
| 16 | +public class AiChatConversationRespVO implements VO { | |
| 11 | 17 | |
| 12 | 18 | @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| 13 | 19 | private Long id; |
| ... | ... | @@ -22,9 +28,12 @@ public class AiChatConversationRespVO { |
| 22 | 28 | private Boolean pinned; |
| 23 | 29 | |
| 24 | 30 | @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1") |
| 31 | + @Trans(type = TransType.SIMPLE, target = AiChatRoleDO.class, fields = "avatar", ref = "roleAvatar") | |
| 25 | 32 | private Long roleId; |
| 26 | 33 | |
| 27 | 34 | @Schema(description = "模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| 35 | + @Trans(type = TransType.SIMPLE, target = AiChatModelDO.class, fields = {"maxTokens", "maxContexts"}, | |
| 36 | + refs = {"modelMaxTokens", "modelMaxContexts"}) | |
| 28 | 37 | private Long modelId; |
| 29 | 38 | |
| 30 | 39 | @Schema(description = "模型标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ERNIE-Bot-turbo-0922") |
| ... | ... | @@ -39,4 +48,17 @@ public class AiChatConversationRespVO { |
| 39 | 48 | @Schema(description = "上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| 40 | 49 | private Integer maxContexts; |
| 41 | 50 | |
| 51 | + // ========== 关联 role 信息 ========== | |
| 52 | + | |
| 53 | + @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png") | |
| 54 | + private String roleAvatar; | |
| 55 | + | |
| 56 | + // ========== 关联 model 信息 ========== | |
| 57 | + | |
| 58 | + @Schema(description = "模型的单条回复的最大 Token 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096") | |
| 59 | + private Integer modelMaxTokens; | |
| 60 | + | |
| 61 | + @Schema(description = "模型的上下文的最大 Message 数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") | |
| 62 | + private Integer modelMaxContexts; | |
| 63 | + | |
| 42 | 64 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatConversationMapper.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.dal.mysql; | |
| 2 | - | |
| 3 | -import cn.hutool.core.collection.CollUtil; | |
| 4 | -import cn.hutool.core.util.StrUtil; | |
| 5 | -import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 6 | -import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 7 | -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 8 | -import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | |
| 9 | -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
| 10 | -import org.apache.ibatis.annotations.Mapper; | |
| 11 | -import org.apache.ibatis.annotations.Param; | |
| 12 | -import org.apache.ibatis.annotations.Update; | |
| 13 | -import org.springframework.stereotype.Repository; | |
| 14 | - | |
| 15 | -import java.util.List; | |
| 16 | - | |
| 17 | -/** | |
| 18 | - * message mapper | |
| 19 | - * | |
| 20 | - * @fansili | |
| 21 | - * @since v1.0 | |
| 22 | - */ | |
| 23 | -@Repository | |
| 24 | -@Mapper | |
| 25 | -public interface AiChatConversationMapper extends BaseMapperX<AiChatConversationDO> { | |
| 26 | - | |
| 27 | - /** | |
| 28 | - * 更新 - chat count | |
| 29 | - * | |
| 30 | - * @param id | |
| 31 | - */ | |
| 32 | - @Update("update ai_chat_conversation set chat_count = chat_count + 1 where id = #{id}") | |
| 33 | - void updateIncrChatCount(@Param("id") Long id); | |
| 34 | - | |
| 35 | - /** | |
| 36 | - * 查询 - 最新的对话 | |
| 37 | - * | |
| 38 | - * @param loginUserId | |
| 39 | - */ | |
| 40 | - default AiChatConversationDO selectLatestConversation(Long loginUserId) { | |
| 41 | - PageResult<AiChatConversationDO> pageResult = selectPage(new PageParam().setPageNo(1).setPageSize(1), | |
| 42 | - new LambdaQueryWrapper<AiChatConversationDO>() | |
| 43 | - .eq(AiChatConversationDO::getUserId, loginUserId) | |
| 44 | - .orderByDesc(AiChatConversationDO::getId)); | |
| 45 | - if (CollUtil.isEmpty(pageResult.getList())) { | |
| 46 | - return null; | |
| 47 | - } | |
| 48 | - return pageResult.getList().get(0); | |
| 49 | - } | |
| 50 | - | |
| 51 | - /** | |
| 52 | - * 查询 - 前100 | |
| 53 | - * | |
| 54 | - * @param search | |
| 55 | - */ | |
| 56 | - default List<AiChatConversationDO> selectTop100Conversation(Long loginUserId, String search) { | |
| 57 | - LambdaQueryWrapper<AiChatConversationDO> queryWrapper | |
| 58 | - = new LambdaQueryWrapper<AiChatConversationDO>().eq(AiChatConversationDO::getUserId, loginUserId); | |
| 59 | - if (!StrUtil.isBlank(search)) { | |
| 60 | - queryWrapper.like(AiChatConversationDO::getTitle, search); | |
| 61 | - } | |
| 62 | - queryWrapper.orderByDesc(AiChatConversationDO::getId); | |
| 63 | - return selectPage(new PageParam().setPageNo(1).setPageSize(100), queryWrapper).getList(); | |
| 64 | - } | |
| 65 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/AiChatMessageMapper.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.dal.mysql; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 4 | 4 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | |
| 5 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; |
| 6 | 7 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 8 | import org.springframework.stereotype.Repository; |
| ... | ... | @@ -30,4 +31,5 @@ public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> { |
| 30 | 31 | .orderByAsc(AiChatMessageDO::getId) |
| 31 | 32 | ); |
| 32 | 33 | } |
| 34 | + | |
| 33 | 35 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatConversationMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.dal.mysql.chat; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 4 | +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; | |
| 5 | +import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; | |
| 6 | +import org.apache.ibatis.annotations.Mapper; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * AI 聊天对话 Mapper | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Mapper | |
| 16 | +public interface AiChatConversationMapper extends BaseMapperX<AiChatConversationDO> { | |
| 17 | + | |
| 18 | + default List<AiChatConversationDO> selectListByUserId(Long userId) { | |
| 19 | + return selectList(AiChatConversationDO::getUserId, userId); | |
| 20 | + } | |
| 21 | + | |
| 22 | +} | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationService.java
| ... | ... | @@ -19,7 +19,7 @@ public interface AiChatConversationService { |
| 19 | 19 | * |
| 20 | 20 | * @param createReqVO 创建信息 |
| 21 | 21 | * @param userId 用户编号 |
| 22 | - * @return 聊天会话 | |
| 22 | + * @return 编号 | |
| 23 | 23 | */ |
| 24 | 24 | Long createChatConversationMy(AiChatConversationCreateMyReqVO createReqVO, Long userId); |
| 25 | 25 | |
| ... | ... | @@ -32,11 +32,12 @@ public interface AiChatConversationService { |
| 32 | 32 | void updateChatConversationMy(AiChatConversationUpdateMyReqVO updateReqVO, Long userId); |
| 33 | 33 | |
| 34 | 34 | /** |
| 35 | - * 获取 - 对话列表 | |
| 35 | + * 获得【我的】聊天会话列表 | |
| 36 | 36 | * |
| 37 | - * @return | |
| 37 | + * @param userId 用户编号 | |
| 38 | + * @return 聊天会话列表 | |
| 38 | 39 | */ |
| 39 | - List<AiChatConversationRespVO> listConversation(); | |
| 40 | + List<AiChatConversationDO> getChatConversationListByUserId(Long userId); | |
| 40 | 41 | |
| 41 | 42 | /** |
| 42 | 43 | * 获取 - 对话 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
| ... | ... | @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.ai.service.chat; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | +import cn.hutool.core.util.ObjectUtil; | |
| 5 | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | 7 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 7 | 8 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationCreateMyReqVO; |
| ... | ... | @@ -11,7 +12,7 @@ import cn.iocoder.yudao.module.ai.convert.AiChatConversationConvert; |
| 11 | 12 | import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; |
| 12 | 13 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 13 | 14 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 14 | -import cn.iocoder.yudao.module.ai.dal.mysql.AiChatConversationMapper; | |
| 15 | +import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; | |
| 15 | 16 | import cn.iocoder.yudao.module.ai.service.model.AiChatModelService; |
| 16 | 17 | import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; |
| 17 | 18 | import jakarta.annotation.Resource; |
| ... | ... | @@ -22,6 +23,7 @@ import org.springframework.validation.annotation.Validated; |
| 22 | 23 | import java.util.List; |
| 23 | 24 | |
| 24 | 25 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*; |
| 26 | +import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_MODEL_ERROR; | |
| 25 | 27 | import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; |
| 26 | 28 | |
| 27 | 29 | /** |
| ... | ... | @@ -49,9 +51,10 @@ public class AiChatConversationServiceImpl implements AiChatConversationService |
| 49 | 51 | : chatRoleService.getRequiredDefaultChatRole(); |
| 50 | 52 | Assert.notNull(role, "必须找到聊天角色"); |
| 51 | 53 | // 1.2 获得 AiChatModelDO 聊天模型 |
| 52 | - AiChatModelDO model = role.getModelId() != null ? chatModalService.validateChatModel(role.getId()) | |
| 54 | + AiChatModelDO model = role.getModelId() != null ? chatModalService.validateChatModel(role.getModelId()) | |
| 53 | 55 | : chatModalService.getRequiredDefaultChatModel(); |
| 54 | - Assert.notNull(role, "必须找到默认模型"); | |
| 56 | + Assert.notNull(model, "必须找到默认模型"); | |
| 57 | + validateChatModel(model); | |
| 55 | 58 | |
| 56 | 59 | // 2. 创建 AiChatConversationDO 聊天对话 |
| 57 | 60 | AiChatConversationDO conversation = new AiChatConversationDO() |
| ... | ... | @@ -70,9 +73,10 @@ public class AiChatConversationServiceImpl implements AiChatConversationService |
| 70 | 73 | throw exception(CHAT_CONVERSATION_NOT_EXISTS); |
| 71 | 74 | } |
| 72 | 75 | // 1.2 校验模型是否存在 |
| 73 | - AiChatModelDO model = null; | |
| 76 | + AiChatModelDO model; | |
| 74 | 77 | if (updateReqVO.getModelId() != null) { |
| 75 | 78 | model = chatModalService.validateChatModel(updateReqVO.getModelId()); |
| 79 | + Assert.notNull(model, "必须找到默认模型"); | |
| 76 | 80 | } |
| 77 | 81 | // 1.3 校验温度参数、Token 数量、消息数量 TODO |
| 78 | 82 | |
| ... | ... | @@ -81,13 +85,15 @@ public class AiChatConversationServiceImpl implements AiChatConversationService |
| 81 | 85 | } |
| 82 | 86 | |
| 83 | 87 | @Override |
| 84 | - public List<AiChatConversationRespVO> listConversation() { | |
| 85 | - // 获取用户id | |
| 86 | - Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); | |
| 87 | - // 查询前100对话 | |
| 88 | - List<AiChatConversationDO> top100Conversation | |
| 89 | - = chatConversationMapper.selectTop100Conversation(loginUserId, null); | |
| 90 | - return AiChatConversationConvert.INSTANCE.covnertChatConversationResList(top100Conversation); | |
| 88 | + public List<AiChatConversationDO> getChatConversationListByUserId(Long userId) { | |
| 89 | + return chatConversationMapper.selectListByUserId(userId); | |
| 90 | + } | |
| 91 | + | |
| 92 | + private void validateChatModel(AiChatModelDO model) { | |
| 93 | + if (ObjectUtil.isAllNotEmpty(model.getTemperature(), model.getMaxTokens(), model.getMaxContexts())) { | |
| 94 | + return; | |
| 95 | + } | |
| 96 | + throw exception(CHAT_CONVERSATION_MODEL_ERROR); | |
| 91 | 97 | } |
| 92 | 98 | |
| 93 | 99 | @Override | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java
| ... | ... | @@ -20,7 +20,7 @@ import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert; |
| 20 | 20 | import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO; |
| 21 | 21 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 22 | 22 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 23 | -import cn.iocoder.yudao.module.ai.dal.mysql.AiChatConversationMapper; | |
| 23 | +import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatConversationMapper; | |
| 24 | 24 | import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper; |
| 25 | 25 | import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; |
| 26 | 26 | import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; |
| ... | ... | @@ -119,8 +119,6 @@ public class AiChatServiceImpl implements AiChatService { |
| 119 | 119 | .setMaxContexts(maxContexts); |
| 120 | 120 | // 增加 chat message 记录 |
| 121 | 121 | aiChatMessageMapper.insert(insertChatMessageDO); |
| 122 | - // chat count 先+1 | |
| 123 | - aiChatConversationMapper.updateIncrChatCount(conversationId); | |
| 124 | 122 | return insertChatMessageDO; |
| 125 | 123 | } |
| 126 | 124 | ... | ... |