|
@@ -4,7 +4,9 @@ import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.util.ObjUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import cn.hutool.extra.spring.SpringUtil;
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
|
|
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
|
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
|
|
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
|
|
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
|
@@ -19,6 +21,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMe
|
|
|
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
|
|
|
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
|
|
|
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
|
|
|
+import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.DifyResponse;
|
|
|
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
|
|
|
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
|
|
|
import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper;
|
|
@@ -26,17 +29,17 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
|
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
|
|
import jakarta.annotation.Resource;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
import org.springframework.scheduling.annotation.Async;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
+import org.springframework.web.reactive.function.client.WebClient;
|
|
|
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|
|
import reactor.core.publisher.Flux;
|
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.List;
|
|
|
-import java.util.Map;
|
|
|
+import java.util.*;
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
|
@@ -53,7 +56,9 @@ import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConsta
|
|
|
@Validated
|
|
|
@Slf4j
|
|
|
public class KeFuMessageServiceImpl implements KeFuMessageService {
|
|
|
-
|
|
|
+
|
|
|
+ private final WebClient webClient;
|
|
|
+ ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_040_07_001, "写作生成异常!");
|
|
|
@Resource
|
|
|
private KeFuMessageMapper keFuMessageMapper;
|
|
|
@Resource
|
|
@@ -66,6 +71,11 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
|
|
|
private WebSocketSenderApi webSocketSenderApi;
|
|
|
@Resource
|
|
|
private AiApi aiApi;
|
|
|
+
|
|
|
+ public KeFuMessageServiceImpl(WebClient.Builder webClientBuilder) {
|
|
|
+ this.webClient = webClientBuilder.baseUrl("http://42.194.163.46:9502").build();
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) {
|
|
@@ -89,7 +99,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
|
|
|
getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_TYPE, message);
|
|
|
return kefuMessage.getId();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@Override
|
|
|
public Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO) {
|
|
|
// 1.1 设置会话编号
|
|
@@ -142,26 +152,31 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
|
|
|
// 1.1 设置会话编号
|
|
|
String apiKey = DictFrameworkUtils.parseDictDataValue("ai_key", "多轮对话");
|
|
|
Map<String, Object> inputs = new HashMap<>();
|
|
|
+ String type = "";
|
|
|
if (sendReqVO.getRelUserId() == 1L) {
|
|
|
inputs.put("type", "系统客服");
|
|
|
+ type = "系统客服";
|
|
|
} else if (sendReqVO.getRelUserId() == 2L) {
|
|
|
inputs.put("type", "贷款专家");
|
|
|
+ type = "贷款专家";
|
|
|
} else if (sendReqVO.getRelUserId() == 3L) {
|
|
|
inputs.put("type", "律师专家");
|
|
|
+ type = "律师专家";
|
|
|
} else {
|
|
|
inputs.put("type", "其他");
|
|
|
+ type = "其他";
|
|
|
}
|
|
|
Long relUserId = sendReqVO.getRelUserId();
|
|
|
inputs.put("token", sendReqVO.getToken());
|
|
|
- String conversationId;
|
|
|
+ String difyconversationId;
|
|
|
KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class);
|
|
|
if (ObjUtil.isNull(kefuMessage.getConversationId())) {
|
|
|
KeFuConversationDO conversation = conversationService.getOrCreateConversation(sendReqVO.getSenderId(),
|
|
|
sendReqVO.getRelUserId());
|
|
|
kefuMessage.setConversationId(conversation.getId());
|
|
|
sendReqVO.setConversationId(conversation.getId());
|
|
|
- conversationId = conversation.getDifyConversationId();
|
|
|
- } else {conversationId = "";}
|
|
|
+ difyconversationId = conversation.getDifyConversationId();
|
|
|
+ } else {difyconversationId = "";}
|
|
|
sendMessage(sendReqVO);
|
|
|
|
|
|
|
|
@@ -171,55 +186,154 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
|
|
|
aiSendReqVO.setContentType(22);
|
|
|
aiSendReqVO.setSenderType(22);
|
|
|
Long aiId = sendMessage(aiSendReqVO);
|
|
|
- StringBuffer contentBuffer = new StringBuffer();
|
|
|
- StringBuffer contentBufferCon = new StringBuffer();
|
|
|
+
|
|
|
KeFuMessageRespVO message = BeanUtils.toBean(aiSendReqVO, KeFuMessageRespVO.class);
|
|
|
- return aiApi.getDifyMessageStreaming(inputs,
|
|
|
- SecurityFrameworkUtils.getLoginUserId().toString(),
|
|
|
- apiKey, sendReqVO.getContent(), conversationId)
|
|
|
- .flatMap(response -> {
|
|
|
- log.info("流式结果:" + response.toString());
|
|
|
- if (response.getEvent().equals("message")) {
|
|
|
- String answer = response.getAnswer(); // 完整答案
|
|
|
- log.info("进入workflow_finished阶段:" + answer);
|
|
|
- if (StrUtil.isNotBlank(answer)) {
|
|
|
- contentBuffer.append(answer);
|
|
|
- message.setContent(answer);
|
|
|
- message.setMessageId(response.getMessage_id());
|
|
|
- return Flux.just(message);
|
|
|
- }
|
|
|
- }
|
|
|
- if (response.getEvent().equals("message_end")) {
|
|
|
- log.info("进入message_end");
|
|
|
- contentBufferCon.append(response.getConversation_id());
|
|
|
+ message.setId(aiId);
|
|
|
+ message.setDifyConversationId(difyconversationId);
|
|
|
+ return getEmitterFromDify(type, message, apiKey, sendReqVO, difyconversationId);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取发射器dify
|
|
|
+ *
|
|
|
+ * @param reqVO 请求vo
|
|
|
+ * @return {@link SseEmitter }
|
|
|
+ */
|
|
|
+ private Flux<KeFuMessageRespVO> getEmitterFromDify(String type, KeFuMessageRespVO message, String apiKey,
|
|
|
+ AppKeFuMessageSendReqVO reqVO, String difyconversationId) {
|
|
|
+
|
|
|
+ Map<String, Object> inputs = new HashMap<>();
|
|
|
+
|
|
|
+ JSONObject requestBody = new JSONObject();
|
|
|
+ inputs.put("token", reqVO.getToken());
|
|
|
+ inputs.put("type", type);
|
|
|
+
|
|
|
+ requestBody.set("response_mode", "streaming");
|
|
|
+ requestBody.set("user", SecurityFrameworkUtils.getLoginUserId());
|
|
|
+ // 如果difyConversationId为0 则为 开场白
|
|
|
+ if (StrUtil.isNotEmpty(difyconversationId) && !StrUtil.equals(difyconversationId,
|
|
|
+ "0")) {
|
|
|
+
|
|
|
+ requestBody.set("conversation_id", difyconversationId);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (CollUtil.isNotEmpty(reqVO.getFileUrls())) {
|
|
|
+ inputs.put("fileUrls", StrUtil.join(",", reqVO.getFileUrls()));
|
|
|
+ if (CollUtil.isNotEmpty(reqVO.getFileUrls())) {
|
|
|
+ List<Map<String, Object>> docs = new ArrayList<>();
|
|
|
+ for (String image : reqVO.getFileUrls()) {
|
|
|
+ Map<String, Object> variableValue = new HashMap<>();
|
|
|
+ variableValue.put("transfer_method", "remote_url");
|
|
|
+ variableValue.put("url", image);
|
|
|
+ if (StrUtil.containsAny(image, ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx")) {
|
|
|
+ variableValue.put("type", "document");
|
|
|
+ } else {
|
|
|
+ variableValue.put("type", "image");
|
|
|
}
|
|
|
- return Flux.empty(); // 如果不是 workflow_finished 或 message_end,返回空 Flux
|
|
|
- }).doOnComplete(() -> {
|
|
|
- KeFuMessageDO aiMessage = new KeFuMessageDO();
|
|
|
- aiMessage.setId(aiId);
|
|
|
- aiMessage.setContent(contentBuffer.toString());
|
|
|
- // 1.2 保存消息
|
|
|
- LocalDateTime current = LocalDateTime.now();
|
|
|
- aiMessage.setCreateTime(current);
|
|
|
- TenantUtils.executeIgnore(() ->
|
|
|
- keFuMessageMapper.updateById(aiMessage));
|
|
|
- if (conversationId == null) {
|
|
|
- KeFuConversationRespVO updatePinnedReqVO = new KeFuConversationRespVO();
|
|
|
- updatePinnedReqVO.setId(sendReqVO.getConversationId());
|
|
|
- updatePinnedReqVO.setDifyConversationId(contentBufferCon.toString());
|
|
|
-
|
|
|
- TenantUtils.executeIgnore(() ->
|
|
|
- conversationService.updateConversation(updatePinnedReqVO));
|
|
|
+
|
|
|
+ docs.add(variableValue);
|
|
|
+ }
|
|
|
+ requestBody.set("files", docs);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ requestBody.set("inputs", inputs);
|
|
|
+ requestBody.set("query", reqVO.getContent());
|
|
|
+ // 使用 WebClient 发起非阻塞请求
|
|
|
+
|
|
|
+ Flux<KeFuMessageRespVO> streamResponse = sendMessageToDify(apiKey, requestBody, message);
|
|
|
+
|
|
|
+ // 3.2 流式返回
|
|
|
+ StringBuffer contentBuffer = new StringBuffer();
|
|
|
+ StringBuffer difyId = new StringBuffer();
|
|
|
+ return streamResponse.map(chunk -> {
|
|
|
+ String newContent = chunk.getContent();
|
|
|
+ contentBuffer.append(newContent);
|
|
|
+ if (chunk.getEvent().equals("message_end")) {
|
|
|
+ difyId.append(chunk.getDifyConversationId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 响应结果
|
|
|
+ return chunk;
|
|
|
+ }).doOnComplete(() -> {
|
|
|
+ // 忽略租户,因为 Flux 异步无法透传租户
|
|
|
+ message.setContent(contentBuffer.toString());
|
|
|
+ // 1.2 保存消息
|
|
|
+ TenantUtils.executeIgnore(() ->
|
|
|
+ keFuMessageMapper.updateById(BeanUtils.toBean(message, KeFuMessageDO.class)));
|
|
|
+ if (StrUtil.isBlank(message.getDifyConversationId())) {
|
|
|
+ KeFuConversationRespVO updatePinnedReqVO = new KeFuConversationRespVO();
|
|
|
+ updatePinnedReqVO.setId(message.getConversationId());
|
|
|
+ updatePinnedReqVO.setDifyConversationId(difyId.toString());
|
|
|
+ LocalDateTime current = LocalDateTime.now();
|
|
|
+ updatePinnedReqVO.setCreateTime(current);
|
|
|
+ TenantUtils.executeIgnore(() ->
|
|
|
+ conversationService.updateConversation(updatePinnedReqVO));
|
|
|
+
|
|
|
+ }
|
|
|
+ }).doOnError(throwable -> {
|
|
|
+ log.error("[generateWriteCon-tent][generateReqVO({}) 发生异常]", null, throwable);
|
|
|
+ // 忽略租户,因为 Flux 异步无法透传租户
|
|
|
+
|
|
|
+ }).onErrorResume(error -> Flux.just(null));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public Flux<KeFuMessageRespVO> sendMessageToDify(String apiKey, JSONObject requestBody,
|
|
|
+ KeFuMessageRespVO message) {
|
|
|
+ List<KeFuMessageRespVO> collectedMessages = new ArrayList<>();
|
|
|
+ return webClient.post()
|
|
|
+ .uri("/v1/chat-messages") // 假设的API路径,根据实际情况修改
|
|
|
+ .headers(httpHeaders -> {
|
|
|
+ httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ httpHeaders.setBearerAuth(apiKey);
|
|
|
+ })
|
|
|
+ .bodyValue(requestBody)
|
|
|
+ .retrieve()
|
|
|
+ .bodyToFlux(DifyResponse.class)
|
|
|
+ .map(difyResponse -> convertToKeFuMessage(difyResponse, message))
|
|
|
+ .filter(this::shouldInclude)
|
|
|
+ .doOnNext(collectedMessages::add)
|
|
|
+ .doOnComplete(() -> {
|
|
|
+ if (!collectedMessages.isEmpty()) {
|
|
|
|
|
|
+
|
|
|
}
|
|
|
})
|
|
|
- .onErrorResume(e -> {
|
|
|
- log.error("流式处理出错:", e);
|
|
|
- return Flux.error(new RuntimeException("流式处理失败,请稍后再试"));
|
|
|
- });
|
|
|
+ .doOnTerminate(() -> System.out.println("请求结束"));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private boolean shouldInclude(KeFuMessageRespVO streamResponse) {
|
|
|
+ // 示例:只要message节点的数据和message_end节点的数据
|
|
|
+ if (streamResponse.getEvent().equals("message")
|
|
|
+ || streamResponse.getEvent().equals("message_end")) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private KeFuMessageRespVO convertToKeFuMessage(DifyResponse response, KeFuMessageRespVO message) {
|
|
|
+ // 实现 DifyResponse 到 KeFuMessageRespVO 的转换逻辑
|
|
|
+ // 设置其他必要的字段...
|
|
|
+ StringBuffer contentBuffer = new StringBuffer();
|
|
|
+ if (StrUtil.equals("message", response.getEvent())) {
|
|
|
+ message.setContent(response.getAnswer());
|
|
|
+ message.setMessageId(response.getMessage_id());
|
|
|
+ message.setDifyConversationId(response.getConversation_id());
|
|
|
+ contentBuffer.append(response.getAnswer());
|
|
|
+ message.setMessageLast(contentBuffer.toString());
|
|
|
+ } else if (StrUtil.equals("message_end", response.getEvent())) {
|
|
|
|
|
|
+ } else {
|
|
|
+
|
|
|
+ }
|
|
|
+ message.setEvent(response.getEvent());
|
|
|
+ return message;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public void updateKeFuMessageReadStatus(Long conversationId, Long userId, Integer userType) {
|