|
|
@@ -2,32 +2,44 @@ package com.storlead.knowledge.api;
|
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
import com.storlead.framework.common.result.Result;
|
|
|
import com.storlead.knowledge.config.DifyProperties;
|
|
|
+import com.storlead.knowledge.entity.AiMessageEntity;
|
|
|
import com.storlead.knowledge.pojo.dto.ChatDTO;
|
|
|
-import com.storlead.knowledge.pojo.dto.TagDTO;
|
|
|
+import com.storlead.knowledge.pojo.dto.QueryPageDTO;
|
|
|
+import com.storlead.knowledge.service.AiMessageService;
|
|
|
import com.storlead.knowledge.utils.HttpService;
|
|
|
import com.storlead.knowledge.utils.JacksonHolder;
|
|
|
import io.swagger.annotations.Api;
|
|
|
import io.swagger.annotations.ApiOperation;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.http.MediaType;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
import java.io.IOException;
|
|
|
+import java.util.Map;
|
|
|
|
|
|
|
|
|
@RestController
|
|
|
@RequestMapping("/chat/")
|
|
|
@Api(tags = "知识库: 对话及流程")
|
|
|
public class ChatController {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ChatController.class);
|
|
|
+
|
|
|
@Resource
|
|
|
private HttpService httpService;
|
|
|
|
|
|
@Resource
|
|
|
private DifyProperties difyProperties;
|
|
|
|
|
|
+ @Resource
|
|
|
+ private AiMessageService aiMessageService;
|
|
|
+
|
|
|
/**
|
|
|
* 创建聊天 blocking模式
|
|
|
* @param chatDTO
|
|
|
@@ -39,7 +51,18 @@ public class ChatController {
|
|
|
public Result<Object> chat(@RequestBody ChatDTO chatDTO) throws JsonProcessingException {
|
|
|
String url = difyProperties.getBaseUrl() + "chat-messages";
|
|
|
String body = JacksonHolder.OBJECT_MAPPER.writeValueAsString(chatDTO);
|
|
|
- return httpService.post(url, null, "Bearer "+difyProperties.getAppKeys().get(chatDTO.getAppId()), body, new TypeReference<>() {});
|
|
|
+ Result<Object> result = httpService.post(url, null, "Bearer "+chatDTO.getAppId(), body, new TypeReference<>() {});
|
|
|
+
|
|
|
+ // 异步保存聊天记录(不影响主流程)
|
|
|
+ if (result.isSuccess() && result.getResult() != null) {
|
|
|
+ try {
|
|
|
+ saveBlockingMessage(chatDTO, result.getResult());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存聊天记录失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -49,26 +72,110 @@ public class ChatController {
|
|
|
* @return
|
|
|
* @throws IOException
|
|
|
*/
|
|
|
- @PostMapping(value = "/chatStreaming", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
|
|
+ @PostMapping(value = "chatStreaming", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
|
|
+ @ApiOperation("创建聊天 流式返回")
|
|
|
public SseEmitter chatStreaming(@RequestBody ChatDTO chatDTO) throws IOException {
|
|
|
|
|
|
SseEmitter emitter = new SseEmitter(0L); // 0 = 不超时
|
|
|
|
|
|
String url = difyProperties.getBaseUrl() + "chat-messages";
|
|
|
|
|
|
- // 强制开启流式
|
|
|
- //chatDTO.setStream(true);
|
|
|
-
|
|
|
String body = JacksonHolder.OBJECT_MAPPER.writeValueAsString(chatDTO);
|
|
|
-
|
|
|
httpService.postStream(
|
|
|
url,
|
|
|
- "Bearer " + difyProperties.getAppKeys().get(chatDTO.getAppId()),
|
|
|
+ "Bearer " +chatDTO.getAppId(),
|
|
|
body,
|
|
|
- emitter
|
|
|
+ emitter,
|
|
|
+ (lastData) -> {
|
|
|
+ try {
|
|
|
+ saveStreamingMessage(chatDTO, lastData);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("保存流式聊天记录失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
);
|
|
|
|
|
|
return emitter;
|
|
|
}
|
|
|
|
|
|
+ @GetMapping("messages")
|
|
|
+ @ApiOperation("获取聊天记录(支持关键字搜索和分页)")
|
|
|
+ public Result<Object> listMessages(QueryPageDTO page) {
|
|
|
+ return Result.ok(aiMessageService.pageQuery(page));
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("history")
|
|
|
+ @ApiOperation("获取应用的对话历史")
|
|
|
+ public Result<Object> list(ChatDTO chatDTO) {
|
|
|
+ String url = difyProperties.getBaseUrl() + "conversations";
|
|
|
+ return httpService.get(
|
|
|
+ url,
|
|
|
+ Map.of("user", chatDTO.getUser()),
|
|
|
+ "Bearer "+difyProperties.getAppKeys().get(chatDTO.getAppId()),
|
|
|
+ new TypeReference<>() {}
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存 blocking 模式的聊天记录
|
|
|
+ */
|
|
|
+ private void saveBlockingMessage(ChatDTO chatDTO, Object responseData) {
|
|
|
+ try {
|
|
|
+ String json = JacksonHolder.OBJECT_MAPPER.writeValueAsString(responseData);
|
|
|
+ JsonNode root = JacksonHolder.OBJECT_MAPPER.readTree(json);
|
|
|
+ System.out.println(root);
|
|
|
+ AiMessageEntity entity = new AiMessageEntity();
|
|
|
+ entity.setTaskId(getJsonText(root, "task_id"));
|
|
|
+ entity.setMessageId(getJsonText(root, "message_id"));
|
|
|
+ entity.setConversationId(getJsonText(root, "conversation_id"));
|
|
|
+ entity.setAppId(chatDTO.getAppId());
|
|
|
+ entity.setInputs(chatDTO.getInputs() != null ? JacksonHolder.OBJECT_MAPPER.writeValueAsString(chatDTO.getInputs()) : null);
|
|
|
+ entity.setQuery(chatDTO.getQuery());
|
|
|
+ entity.setResponseMode(chatDTO.getResponse_mode());
|
|
|
+ entity.setUserId(chatDTO.getUser());
|
|
|
+ entity.setAnswer(getJsonText(root, "answer"));
|
|
|
+ entity.setCreatedAt(Integer.parseInt(getJsonText(root, "created_at")));
|
|
|
+
|
|
|
+ aiMessageService.save(entity);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析并保存blocking聊天记录失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存 streaming 模式的聊天记录(从 message_end 事件中解析)
|
|
|
+ */
|
|
|
+ private void saveStreamingMessage(ChatDTO chatDTO, String lastData) {
|
|
|
+ try {
|
|
|
+ // lastData 格式: {"event":"message_end","task_id":"...","message_id":"...","conversation_id":"...","metadata":{...}}
|
|
|
+ JsonNode root = JacksonHolder.OBJECT_MAPPER.readTree(lastData);
|
|
|
+
|
|
|
+ AiMessageEntity entity = new AiMessageEntity();
|
|
|
+ entity.setTaskId(getJsonText(root, "task_id"));
|
|
|
+ entity.setMessageId(getJsonText(root, "message_id"));
|
|
|
+ entity.setConversationId(getJsonText(root, "conversation_id"));
|
|
|
+ entity.setAppId(chatDTO.getAppId());
|
|
|
+ entity.setInputs(chatDTO.getInputs() != null ? JacksonHolder.OBJECT_MAPPER.writeValueAsString(chatDTO.getInputs()) : null);
|
|
|
+ entity.setQuery(chatDTO.getQuery());
|
|
|
+ entity.setResponseMode(chatDTO.getResponse_mode());
|
|
|
+ entity.setUserId(chatDTO.getUser());
|
|
|
+ // streaming 模式下 answer 在 metadata 中逐句返回,message_end 事件本身不含完整 answer,此处留空
|
|
|
+ entity.setAnswer(getJsonText(root, "answer"));
|
|
|
+ entity.setCreatedAt(Integer.parseInt(getJsonText(root, "created_at")));
|
|
|
+
|
|
|
+ aiMessageService.save(entity);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析并保存streaming聊天记录失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安全获取 JSON 节点文本值
|
|
|
+ */
|
|
|
+ private String getJsonText(JsonNode node, String field) {
|
|
|
+ JsonNode value = node.get(field);
|
|
|
+ return value != null && !value.isNull() ? value.asText() : null;
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
+
|