mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-26 09:11:52 +08:00
【优化】AI:聊天消息的 Service 实现
This commit is contained in:
parent
3b02bcf4f8
commit
a482876113
@ -30,8 +30,8 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode CHAT_CONVERSATION_UPDATE_MAX_TOKENS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Token 超过上限");
|
||||
ErrorCode CHAT_CONVERSATION_UPDATE_MAX_CONTEXTS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Context 超过上限");
|
||||
|
||||
// chat
|
||||
ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_022_000_100, "提问的 MessageId 不存在!");
|
||||
// ========== API 聊天消息 1-040-004-000 ==========
|
||||
ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
|
||||
|
||||
// midjourney
|
||||
|
||||
|
@ -1,8 +1,19 @@
|
||||
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.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
|
||||
import cn.iocoder.yudao.module.ai.service.AiChatService;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -14,9 +25,12 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Collections;
|
||||
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.convertSet;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
@Tag(name = "管理后台 - 聊天消息")
|
||||
@ -26,39 +40,65 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
|
||||
public class AiChatMessageController {
|
||||
|
||||
@Resource
|
||||
private AiChatService chatService;
|
||||
private AiChatMessageService chatMessageService;
|
||||
@Resource
|
||||
private AiChatConversationService chatConversationService;
|
||||
@Resource
|
||||
private AiChatRoleService chatRoleService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Operation(summary = "发送消息(段式)", description = "一次性返回,响应较慢")
|
||||
@PostMapping("/send")
|
||||
public CommonResult<AiChatMessageRespVO> sendMessage(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) {
|
||||
return success(chatService.chat(sendReqVO));
|
||||
return success(chatMessageService.sendMessage(sendReqVO));
|
||||
}
|
||||
|
||||
@Operation(summary = "发送消息(流式)", description = "流式返回,响应较快")
|
||||
@PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
@PermitAll // 解决 SSE 最终响应的时候,会被 Access Denied 拦截的问题
|
||||
public Flux<AiChatMessageSendRespVO> sendChatMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) {
|
||||
return chatService.sendChatMessageStream(sendReqVO, getLoginUserId());
|
||||
return chatMessageService.sendChatMessageStream(sendReqVO, getLoginUserId());
|
||||
}
|
||||
|
||||
@Operation(summary = "获得指定会话的消息列表")
|
||||
@GetMapping("/list-by-conversation-id")
|
||||
@Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
|
||||
public CommonResult<List<AiChatMessageRespVO>> getMessageListByConversationId(@RequestParam("conversationId") Long conversationId) {
|
||||
return success(chatService.getMessageListByConversationId(conversationId));
|
||||
public CommonResult<List<AiChatMessageRespVO>> getChatMessageListByConversationId(
|
||||
@RequestParam("conversationId") Long conversationId) {
|
||||
AiChatConversationDO conversation = chatConversationService.getChatConversation(conversationId);
|
||||
if (conversation == null || ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
List<AiChatMessageDO> messageList = chatMessageService.getChatMessageListByConversationId(conversationId);
|
||||
if (CollUtil.isEmpty(messageList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// 拼接数据
|
||||
Map<Long, AiChatRoleDO> roleMap = chatRoleService.getChatRoleMap(convertSet(messageList, AiChatMessageDO::getRoleId));
|
||||
AdminUserRespDTO user = adminUserApi.getUser(getLoginUserId());
|
||||
return success(BeanUtils.toBean(messageList, AiChatMessageRespVO.class, respVO -> {
|
||||
MapUtils.findAndThen(roleMap, respVO.getRoleId(), role -> respVO.setRoleAvatar(role.getAvatar()));
|
||||
respVO.setUserAvatar(user.getAvatar());
|
||||
}));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除消息")
|
||||
@DeleteMapping("/delete")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteMessage(@RequestParam("id") Long id) {
|
||||
return success(chatService.deleteMessage(id));
|
||||
public CommonResult<Boolean> deleteChatMessage(@RequestParam("id") Long id) {
|
||||
chatMessageService.deleteMessage(id, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除消息-对于对话全部消息")
|
||||
@Operation(summary = "删除指定会话的消息")
|
||||
@DeleteMapping("/delete-by-conversation-id")
|
||||
@Parameter(name = "id", required = true, description = "消息编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteByConversationId(@RequestParam("conversationId") Long conversationId) {
|
||||
return success(chatService.deleteByConversationId(conversationId));
|
||||
@Parameter(name = "conversationId", required = true, description = "会话编号", example = "1024")
|
||||
public CommonResult<Boolean> deleteChatMessageByConversationId(@RequestParam("conversationId") Long conversationId) {
|
||||
chatMessageService.deleteChatMessageByConversationId(conversationId, getLoginUserId());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,13 +31,14 @@ public class AiChatMessageSendRespVO {
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// ========= 扩展字段
|
||||
// ========== 扩展字段 ==========
|
||||
|
||||
@Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx")
|
||||
@Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/1.png")
|
||||
private String userAvatar;
|
||||
|
||||
@Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://xxx")
|
||||
@Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://iocoder.cn/2.png")
|
||||
private String roleAvatar;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,30 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.convert;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 聊天 对话 convert
|
||||
*
|
||||
* @author fansili
|
||||
* @time 2024/4/18 16:39
|
||||
* @since 1.0
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiChatMessageConvert {
|
||||
|
||||
AiChatMessageConvert INSTANCE = Mappers.getMapper(AiChatMessageConvert.class);
|
||||
|
||||
/**
|
||||
* 转换 AiChatMessageRespVO
|
||||
*
|
||||
* @param aiChatMessageDOList
|
||||
* @return
|
||||
*/
|
||||
List<AiChatMessageRespVO> convertAiChatMessageRespVOList(List<AiChatMessageDO> aiChatMessageDOList);
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.dal.mysql;
|
||||
|
||||
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.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* message mapper
|
||||
*
|
||||
* @fansili
|
||||
* @since v1.0
|
||||
*/
|
||||
@Repository
|
||||
@Mapper
|
||||
public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
|
||||
|
||||
/**
|
||||
* 查询 - 根据 对话id查询
|
||||
*
|
||||
* @param conversationId
|
||||
*/
|
||||
default List<AiChatMessageDO> selectByConversationId(Long conversationId) {
|
||||
return this.selectList(
|
||||
new LambdaQueryWrapperX<AiChatMessageDO>()
|
||||
.eq(AiChatMessageDO::getConversationId, conversationId)
|
||||
.orderByAsc(AiChatMessageDO::getId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 - 根据 conversationId
|
||||
*
|
||||
* @param conversationId
|
||||
*/
|
||||
default int deleteByConversationId(Long conversationId) {
|
||||
return this.delete(new LambdaQueryWrapperX<AiChatMessageDO>().eq(AiChatMessageDO::getConversationId, conversationId));
|
||||
}
|
||||
}
|
@ -1,18 +1,13 @@
|
||||
package cn.iocoder.yudao.module.ai.dal.mysql;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
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.framework.mybatis.core.query.QueryWrapperX;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatModel.AiChatModelPageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@ -42,7 +37,6 @@ public interface AiChatModelMapper extends BaseMapperX<AiChatModelDO> {
|
||||
return this.selectList(new LambdaQueryWrapperX<AiChatModelDO>().eq(AiChatModelDO::getId, modalIds));
|
||||
}
|
||||
|
||||
|
||||
default PageResult<AiChatModelDO> selectPage(AiChatModelPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<AiChatModelDO>()
|
||||
.likeIfPresent(AiChatModelDO::getName, reqVO.getName())
|
||||
|
@ -13,7 +13,6 @@ import org.springframework.stereotype.Repository;
|
||||
* @time 2024/4/28 14:01
|
||||
* @since 1.0
|
||||
*/
|
||||
@Repository
|
||||
@Mapper
|
||||
public interface AiImageMapper extends BaseMapperX<AiImageDO> {
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.ai.dal.mysql.chat;
|
||||
|
||||
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 org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 聊天对话 Mapper
|
||||
*
|
||||
* @author fansili
|
||||
*/
|
||||
@Mapper
|
||||
public interface AiChatMessageMapper extends BaseMapperX<AiChatMessageDO> {
|
||||
|
||||
default List<AiChatMessageDO> selectListByConversationId(Long conversationId) {
|
||||
return selectList(new LambdaQueryWrapperX<AiChatMessageDO>()
|
||||
.eq(AiChatMessageDO::getConversationId, conversationId)
|
||||
.orderByAsc(AiChatMessageDO::getId));
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.service;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 聊天 chat
|
||||
*
|
||||
* @author fansili
|
||||
* @time 2024/4/14 15:55
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface AiChatService {
|
||||
|
||||
/**
|
||||
* chat
|
||||
*
|
||||
* @param sendReqVO
|
||||
* @return
|
||||
*/
|
||||
AiChatMessageRespVO chat(AiChatMessageSendReqVO sendReqVO);
|
||||
|
||||
/**
|
||||
* 获取 - 获取对话 message list
|
||||
*
|
||||
* @param conversationId
|
||||
* @return
|
||||
*/
|
||||
List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId);
|
||||
|
||||
/**
|
||||
* 删除 - 删除message
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
Boolean deleteMessage(Long id);
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param sendReqVO
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Flux<AiChatMessageSendRespVO> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId);
|
||||
|
||||
/**
|
||||
* 删除消息-对于对话全部消息
|
||||
*
|
||||
* @param conversationId
|
||||
* @return
|
||||
*/
|
||||
Boolean deleteByConversationId(Long conversationId);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package cn.iocoder.yudao.module.ai.service.chat;
|
||||
|
||||
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.List;
|
||||
|
||||
/**
|
||||
* AI 聊天消息 Service 接口
|
||||
*
|
||||
* @author fansili
|
||||
*/
|
||||
public interface AiChatMessageService {
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param sendReqVO 发送信息
|
||||
* @return 发送结果
|
||||
*/
|
||||
AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO sendReqVO);
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param sendReqVO 发送信息
|
||||
* @param userId 用户编号
|
||||
* @return 发送结果
|
||||
*/
|
||||
Flux<AiChatMessageSendRespVO> sendChatMessageStream(AiChatMessageSendReqVO sendReqVO, Long userId);
|
||||
|
||||
/**
|
||||
* 获得指定会话的消息列表
|
||||
*
|
||||
* @param conversationId 会话编号
|
||||
* @return 消息列表
|
||||
*/
|
||||
List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId);
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
*
|
||||
* @param id 消息编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void deleteMessage(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 删除指定会话的消息
|
||||
*
|
||||
* @param conversationId 会话编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void deleteChatMessageByConversationId(Long conversationId, Long userId);
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.ai.service.impl;
|
||||
package cn.iocoder.yudao.module.ai.service.chat;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
@ -20,13 +20,10 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO;
|
||||
import cn.iocoder.yudao.module.ai.convert.AiChatMessageConvert;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatMessageDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.mysql.AiChatMessageMapper;
|
||||
import cn.iocoder.yudao.module.ai.service.AiChatService;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatModelService;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -37,21 +34,20 @@ import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.module.ai.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST;
|
||||
import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 聊天 service
|
||||
* AI 聊天消息 Service 实现类
|
||||
*
|
||||
* @author fansili
|
||||
* @time 2024/4/14 15:55
|
||||
* @since 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AiChatServiceImpl implements AiChatService {
|
||||
@Slf4j
|
||||
public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
|
||||
@Resource
|
||||
private AiChatMessageMapper chatMessageMapper;
|
||||
@ -72,7 +68,7 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AiChatMessageRespVO chat(AiChatMessageSendReqVO req) {
|
||||
public AiChatMessageRespVO sendMessage(AiChatMessageSendReqVO req) {
|
||||
return null; // TODO 芋艿:一起改
|
||||
// Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
// // 查询对话
|
||||
@ -117,10 +113,13 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
if (ObjUtil.notEqual(conversation.getUserId(), userId)) {
|
||||
throw exception(CHAT_CONVERSATION_NOT_EXISTS); // TODO 芋艿:异常情况的对接;
|
||||
}
|
||||
List<AiChatMessageDO> historyMessages = chatMessageMapper.selectByConversationId(conversation.getId());
|
||||
List<AiChatMessageDO> historyMessages = chatMessageMapper.selectListByConversationId(conversation.getId());
|
||||
// 1.2 校验模型
|
||||
AiChatModelDO model = chatModalService.validateChatModel(conversation.getModelId());
|
||||
StreamingChatClient chatClient = apiKeyService.getStreamingChatClient(model.getKeyId());
|
||||
// 1.3 获取用户头像、角色头像
|
||||
AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId());
|
||||
AiChatRoleDO role = conversation.getRoleId() != null ? chatRoleService.getChatRole(conversation.getRoleId()) : null;
|
||||
|
||||
// 2. 插入 user 发送消息
|
||||
AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model,
|
||||
@ -136,19 +135,17 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
|
||||
// 3.3 流式返回
|
||||
// 注意:Schedulers.immediate() 目的是,避免默认 Schedulers.parallel() 并发消费 chunk 导致 SSE 响应前端会乱序问题
|
||||
|
||||
// 3.4 获取用户头像、角色头像
|
||||
AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId());
|
||||
AiChatRoleDO chatRole = chatRoleService.getChatRole(assistantMessage.getRoleId());
|
||||
|
||||
StringBuffer contentBuffer = new StringBuffer();
|
||||
return streamResponse.publishOn(Schedulers.single()).map(chunk -> {
|
||||
String newContent = chunk.getResult() != null ? chunk.getResult().getOutput().getContent() : null;
|
||||
newContent = StrUtil.nullToDefault(newContent, ""); // 避免 null 的 情况
|
||||
contentBuffer.append(newContent);
|
||||
// 响应结果
|
||||
return new AiChatMessageSendRespVO().setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class).setUserAvatar(user.getAvatar()))
|
||||
.setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class).setContent(newContent).setRoleAvatar(chatRole == null ? null : chatRole.getAvatar()));
|
||||
AiChatMessageSendRespVO.Message send = BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class,
|
||||
o -> o.setUserAvatar(user.getAvatar()));
|
||||
AiChatMessageSendRespVO.Message receive = BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class,
|
||||
o -> o.setRoleAvatar(role != null ? role.getAvatar() : null)).setContent(newContent);
|
||||
return new AiChatMessageSendRespVO().setSend(send).setReceive(receive);
|
||||
}).doOnComplete(() -> {
|
||||
chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()).setContent(contentBuffer.toString()));
|
||||
}).doOnError(throwable -> {
|
||||
@ -157,11 +154,6 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteByConversationId(Long conversationId) {
|
||||
return chatMessageMapper.deleteByConversationId(conversationId) > 0;
|
||||
}
|
||||
|
||||
private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages,
|
||||
AiChatModelDO model, AiChatMessageSendReqVO sendReqVO) {
|
||||
// 1. 构建 Prompt Message 列表
|
||||
@ -174,12 +166,11 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
// 1.3 user message 新发送消息
|
||||
chatMessages.add(new UserMessage(sendReqVO.getContent()));
|
||||
|
||||
// 2. 构建 ChatOptions 对象 TODO 芋艿:临时注释掉;等文心一言兼容了;
|
||||
// 2. 构建 ChatOptions 对象
|
||||
AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
|
||||
ChatOptions chatOptions = clientFactory.buildChatOptions(platform, model.getModel(),
|
||||
conversation.getTemperature(), conversation.getMaxTokens());
|
||||
return new Prompt(chatMessages, chatOptions);
|
||||
// return new Prompt(chatMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,42 +222,30 @@ public class AiChatServiceImpl implements AiChatService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AiChatMessageRespVO> getMessageListByConversationId(Long conversationId) {
|
||||
// 校验对话是否存在
|
||||
chatConversationService.validateExists(conversationId);
|
||||
// 获取对话所有 message
|
||||
List<AiChatMessageDO> aiChatMessageDOList = chatMessageMapper.selectByConversationId(conversationId);
|
||||
// 获取模型信息
|
||||
Set<Long> roleIds = aiChatMessageDOList.stream().map(AiChatMessageDO::getRoleId).collect(Collectors.toSet());
|
||||
List<AiChatRoleDO> roleList;
|
||||
if (!CollUtil.isEmpty(roleIds)) {
|
||||
roleList = chatRoleService.getChatRoles(roleIds);
|
||||
} else {
|
||||
roleList = Collections.emptyList();
|
||||
}
|
||||
Map<Long, AiChatRoleDO> roleMap = roleList.stream().collect(Collectors.toMap(AiChatRoleDO::getId, o -> o));
|
||||
// 转换 AiChatMessageRespVO
|
||||
List<AiChatMessageRespVO> aiChatMessageRespList = AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVOList(aiChatMessageDOList);
|
||||
// 获取用户信息
|
||||
AdminUserRespDTO user = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId());
|
||||
// 设置用户头像 和 模型头像
|
||||
return aiChatMessageRespList.stream().map(item -> {
|
||||
// 设置 role 头像
|
||||
if (roleMap.containsKey(item.getRoleId())) {
|
||||
AiChatRoleDO role = roleMap.get(item.getRoleId());
|
||||
item.setRoleAvatar(role.getAvatar());
|
||||
}
|
||||
// 设置 user 头像
|
||||
if (user != null) {
|
||||
item.setUserAvatar(user.getAvatar());
|
||||
}
|
||||
return item;
|
||||
}).collect(Collectors.toList());
|
||||
public List<AiChatMessageDO> getChatMessageListByConversationId(Long conversationId) {
|
||||
return chatMessageMapper.selectListByConversationId(conversationId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteMessage(Long id) {
|
||||
return chatMessageMapper.deleteById(id) > 0;
|
||||
public void deleteMessage(Long id, Long userId) {
|
||||
// 1. 校验消息存在
|
||||
AiChatMessageDO message = chatMessageMapper.selectById(id);
|
||||
if (message == null || ObjUtil.notEqual(message.getUserId(), userId)) {
|
||||
throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
|
||||
}
|
||||
// 2. 执行删除
|
||||
chatMessageMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteChatMessageByConversationId(Long conversationId, Long userId) {
|
||||
// 1. 校验消息存在
|
||||
List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId);
|
||||
if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) {
|
||||
throw exception(AI_CHAT_MESSAGE_NOT_EXIST);
|
||||
}
|
||||
// 2. 执行删除
|
||||
chatMessageMapper.deleteBatchIds(convertList(messages, AiChatMessageDO::getId));
|
||||
}
|
||||
|
||||
}
|
@ -4,12 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRolePageReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveMyReqVO;
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
/**
|
||||
* AI 聊天角色 Service 接口
|
||||
@ -74,12 +76,16 @@ public interface AiChatRoleService {
|
||||
AiChatRoleDO getChatRole(Long id);
|
||||
|
||||
/**
|
||||
* 获得聊天角色 - 根据 ids
|
||||
* 获得聊天角色列表
|
||||
*
|
||||
* @param roleIds
|
||||
* @return
|
||||
* @param ids 编号数组
|
||||
* @return 聊天角色列表
|
||||
*/
|
||||
List<AiChatRoleDO> getChatRoles(Set<Long> roleIds);
|
||||
List<AiChatRoleDO> getChatRoleList(Collection<Long> ids);
|
||||
|
||||
default Map<Long, AiChatRoleDO> getChatRoleMap(Collection<Long> ids) {
|
||||
return convertMap(getChatRoleList(ids), AiChatRoleDO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验聊天角色是否合法
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.service.model;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
@ -14,12 +15,9 @@ import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
@ -107,8 +105,11 @@ public class AiChatRoleServiceImpl implements AiChatRoleService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AiChatRoleDO> getChatRoles(Set<Long> roleIds) {
|
||||
return chatRoleMapper.selectBatchIds(roleIds);
|
||||
public List<AiChatRoleDO> getChatRoleList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return chatRoleMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user