集成ai部分

This commit is contained in:
sh
2026-01-05 15:39:01 +08:00
parent deb775ff5c
commit a31fc4cc72
23 changed files with 1885 additions and 1 deletions

View File

@@ -0,0 +1,296 @@
package com.ruoyi.cms.config;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.cms.domain.chat.ChatRequest;
import com.ruoyi.common.utils.StringUtils;
import lombok.var;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Component
public class ChatClient {
// 超时设置(单位:秒)
private static final int CONNECT_TIMEOUT = 30;
private static final int WRITE_TIMEOUT = 30;
private static final int READ_TIMEOUT = 300; // 流式响应不设置读取超时
// 单例 OkHttp 客户端(复用连接池)
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.build();
private static final String CHAT_ENDPOINT = "chat";
private static final String CHAT_HISTORY = "history";
public static final List<String> TEXT_FILE_EXTENSIONS= Arrays.asList(".txt", ".md", ".html", ".doc", ".docx", ".pdf", ".ppt", ".pptx", ".csv", ".xls", ".xlsx");
public static final List<String> IMAGE_FILE_EXTENSIONS=Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp");
private ChatConfig chatConfig;
@Autowired
public void setChatConfig(ChatConfig chatConfig) {
this.chatConfig = chatConfig;
}
@Value("${spring.profiles.active}")
private String env;
/**
* 发送流式聊天请求
* @param chatRequest 查询请求体
* @param callback 流式响应回调接口
*/
public void sendStreamingChat(ChatRequest chatRequest, StreamCallback callback) {
String url=chatConfig.getBaseUrl()+chatConfig.getChatUrl();
// 构建请求体
String jsonBody = buildChatRequestBody(chatRequest, CHAT_ENDPOINT);
// 构建请求
Request request = null;
try {
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),jsonBody);
request = new Request.Builder()
.url(url)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + chatConfig.getApiKey())
.post(body).build();
}catch (Exception e){
e.printStackTrace();
callback.onError(new RuntimeException("构建请求失败: " + e.getMessage(), e));
return;
}
// 发送异步请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onError(new RuntimeException("请求发送失败: " + e.getMessage(), e));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "无错误信息";
String errorMsg = String.format("API 响应错误: 状态码=%d, 错误信息=%s",
response.code(), errorBody);
System.err.println(errorMsg); // 打印详细错误
callback.onError(new RuntimeException(errorMsg));
return;
}
// 处理流式响应
ResponseBody body = response.body();
if (body == null) {
callback.onError(new RuntimeException("响应体为空"));
return;
}
// 逐行读取 SSE 格式的响应
try (var bufferedSource = body.source()) {
while (!bufferedSource.exhausted()) {
String chunk = bufferedSource.readUtf8Line();
if (chunk != null && !chunk.trim().isEmpty()) {
callback.onData(chunk);
}
}
}
// 通知流结束
callback.onComplete();
} catch (Exception e) {
String errorMsg = "处理响应失败: " + e.getMessage();
System.err.println(errorMsg);
callback.onError(new RuntimeException(errorMsg, e));
} finally {
response.close();
}
}
});
}
/**
* 构建聊天请求的 JSON 体
*/
/**
* 构建聊天请求的 JSON 体
* 根据文档 [cite: 162-172] 修改为 Vision API 兼容格式
*/
private String buildChatRequestBody(ChatRequest chatRequest, String key) {
JSONObject chatObject = new JSONObject();
// 1. 设置会话ID (如果有)
if(StringUtils.isNotEmpty(chatRequest.getSessionId())){
chatObject.put("chatId", chatRequest.getSessionId());
}
if("chat".equals(key)){
// 基础参数设置
chatObject.put("stream", true);
chatObject.put("model", chatConfig.getModel()); // 需确保为 "qd-job-turbo"
chatObject.put("user", chatRequest.getSessionId());
// 2. 获取历史消息列表,如果为空则初始化
JSONArray messages = chatRequest.getMessages();
if (messages == null) {
messages = new JSONArray();
}
// 3. 构建当前用户的多模态消息内容 (Multimodal Content) [cite: 162]
JSONArray contentArray = new JSONArray();
// 3.1 添加文本内容 [cite: 166, 172]
if(StringUtils.isNotEmpty(chatRequest.getData())){
JSONObject textPart = new JSONObject();
textPart.put("type", "text");
textPart.put("text", chatRequest.getData());
contentArray.add(textPart);
}
// 3.2 添加文件内容 (图片、PDF、Excel等) [cite: 174, 180, 232]
// 文档说明image_url 字段兼容 PDF, Excel, PPT 等所有 OCR 支持的文件
if(!CollectionUtils.isEmpty(chatRequest.getFileUrl())){
for(String url : chatRequest.getFileUrl()){
String finalUrl = "";
if(Objects.equals(env, "pro")){
finalUrl = url.replace("https://fw.rc.qingdao.gov.cn/rgpp-api/api/ng", "http://10.213.6.207:19010");
}else {
finalUrl = url;
}
// 处理内网/外网地址映射 (保留你原有的逻辑)
JSONObject filePart = new JSONObject();
filePart.put("type", "image_url"); // 固定为 image_url [cite: 174]
JSONObject imageUrlObj = new JSONObject();
imageUrlObj.put("url", finalUrl); // 文件地址 [cite: 172]
filePart.put("image_url", imageUrlObj);
contentArray.add(filePart);
}
}
// 4. 将当前消息封装为 User Message 对象并加入消息列表 [cite: 151, 163]
// 只有当有内容(文本或文件)时才添加
if (!contentArray.isEmpty()) {
JSONObject currentUserMessage = new JSONObject();
currentUserMessage.put("role", "user");
currentUserMessage.put("content", contentArray); // content 为数组格式
messages.add(currentUserMessage);
}
// 5. 将完整的消息列表放入请求体 [cite: 135]
chatObject.put("messages", messages);
} else {
// 非 chat 场景的逻辑保留
chatObject.put("appId", chatConfig.getAppId());
}
return chatObject.toJSONString();
}
/**
* 简单的 JSON 转义处理(防止特殊字符破坏 JSON 格式)
*/
private String escapeJson(String value) {
if (value == null) return "";
return value
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
/**
* 发送聊天请求并返回完整JSON响应
* @param chatRequest 用户输入的查询内容
* @return 完整的JSON响应字符串
* @throws IOException 网络请求异常
*/
public String sendChatGuest(ChatRequest chatRequest) throws IOException {
String url=chatConfig.getBaseUrl()+chatConfig.getGuestUrl();
JSONArray array = chatRequest.getMessages();
if(array==null||array.isEmpty()||array.size()==0){
array = new JSONArray();
}
JSONObject contentObject = new JSONObject();
contentObject.put("content","你是一个岗位招聘专家请根据用户的问题生成用户下一步想要提出的问题。需要以用户的口吻进行生成。结合上下文中用户提出的问题以及助手回复的答案需要猜测用户更进一步的需求例如期望的薪资期望的工作地点掌握的技能。生成的问题举例有没有薪资在9000以上的工作我的学历是本科。我希望找国企。注意不仅限于这些还要根据上下文。其次所有的问题应该限定在青岛。并且只生成3到4个。");
contentObject.put("role","system");
array.add(contentObject);
JSONObject jsonBody = new JSONObject();
jsonBody.put("stream",false);
jsonBody.put("model",chatConfig.getModel());
jsonBody.put("messages",array);
// 构建请求(使用非流式响应模式)
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), String.valueOf(jsonBody));
Request request = new Request.Builder()
.url(url)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + chatConfig.getApiKey())
.post(requestBody).build();
// 发送同步请求
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "无错误信息";
String errorMsg = String.format("API 响应错误: 状态码=%d, 错误信息=%s",
response.code(), errorBody);
throw new IOException(errorMsg);
}
ResponseBody body = response.body();
if (body == null) {
throw new IOException("响应体为空");
}
JSONObject object = JSONObject.parseObject(body.string());
String choices = object.getString("choices");
if (choices != null && !choices.trim().isEmpty()) {
JSONArray jsonArray = JSONArray.parseArray(choices);
object = JSONObject.parseObject(jsonArray.getString(0));
object = object.getJSONObject("message");
String content = object.getString("content");// 消息内容
return content;
}
return body.string();
}
}
/**
* 流式响应回调接口
*/
public interface StreamCallback {
/**
* 接收分片数据
* @param chunk SSE 格式的分片数据
*/
void onData(String chunk);
/**
* 响应结束
*/
void onComplete();
/**
* 发生错误
* @param e 异常信息
*/
void onError(Throwable e);
}
}

View File

@@ -0,0 +1,115 @@
package com.ruoyi.cms.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "chat")
public class ChatConfig {
private String baseUrl;
private String chatUrl;
private String chatDetailUrl;
private String chatHistoryUrl;
private String updateNameUrl;
private String delChatUrl;
private String delAllChatUrl;
private String guestUrl;
private String praiseUrl;
private String apiKey;
private String appId;
private String model;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getChatUrl() {
return chatUrl;
}
public void setChatUrl(String chatUrl) {
this.chatUrl = chatUrl;
}
public String getChatDetailUrl() {
return chatDetailUrl;
}
public void setChatDetailUrl(String chatDetailUrl) {
this.chatDetailUrl = chatDetailUrl;
}
public String getChatHistoryUrl() {
return chatHistoryUrl;
}
public void setChatHistoryUrl(String chatHistoryUrl) {
this.chatHistoryUrl = chatHistoryUrl;
}
public String getUpdateNameUrl() {
return updateNameUrl;
}
public void setUpdateNameUrl(String updateNameUrl) {
this.updateNameUrl = updateNameUrl;
}
public String getDelChatUrl() {
return delChatUrl;
}
public void setDelChatUrl(String delChatUrl) {
this.delChatUrl = delChatUrl;
}
public String getDelAllChatUrl() {
return delAllChatUrl;
}
public void setDelAllChatUrl(String delAllChatUrl) {
this.delAllChatUrl = delAllChatUrl;
}
public String getGuestUrl() {
return guestUrl;
}
public void setGuestUrl(String guestUrl) {
this.guestUrl = guestUrl;
}
public String getPraiseUrl() {
return praiseUrl;
}
public void setPraiseUrl(String praiseUrl) {
this.praiseUrl = praiseUrl;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}

View File

@@ -0,0 +1,235 @@
package com.ruoyi.cms.controller.app;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import com.ruoyi.cms.domain.chat.ChatRequest;
import com.ruoyi.cms.service.AiChatHistoryService;
import com.ruoyi.common.annotation.BussinessLog;
import com.ruoyi.cms.config.ChatClient;
import com.ruoyi.cms.config.ChatConfig;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import static com.ruoyi.common.enums.BusinessType.QUERY;
@RestController
@RequestMapping(value = "/app/chat")
@Slf4j
@Api(tags = "移动端:ai对话")
public class ChatController extends BaseController {
@Autowired
ChatClient chatClient;
@Autowired
ChatConfig chatConfig;
@Autowired
AiChatHistoryService aiChatHistoryService;
//private final ExecutorService executor = Executors.newCachedThreadPool();
// 可优化线程池配置,避免无限创建线程
private final ExecutorService executor = new ThreadPoolExecutor(
2, // 核心线程数
10, // 最大线程数
30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(50), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(避免任务丢失)
);
@BussinessLog(title = "查询用户的聊天历史记录",businessType = QUERY)
@ApiOperation("查询用户的聊天历史记录")
@GetMapping(value = "/getHistory")
public AjaxResult getChatHistoryList(AiChatHistory chatHistory) {
return AjaxResult.success(aiChatHistoryService.getList(chatHistory));
}
@BussinessLog(title = "查询用户的聊天详情",businessType = QUERY)
@ApiOperation("查询用户的聊天详情")
@GetMapping(value = "/detail")
public AjaxResult getChatDetail(ChatRequest request) {
return AjaxResult.success(aiChatHistoryService.getDetailList(request.getSessionId()));
}
// 处理前端聊天请求,返回 SSE 发射器
@ApiOperation("用户AI聊天")
@PostMapping("/chat")
@ResponseBody
@BussinessLog(title = "用户AI聊天",businessType = QUERY)
public SseEmitter chatStream(@RequestBody ChatRequest request) {
// 设置超时时间30分钟
SseEmitter emitter = new SseEmitter(1800000L);
long userId = 0;
if(ObjectUtil.isNotEmpty(request.getUserId())&&request.getUserId()!=0){
userId = request.getUserId();
}else{
try {
userId = SiteSecurityUtils.getLoginUser().getUserId();
}catch (Exception e) {
}
}
JSONObject contentObject = new JSONObject();
contentObject.put("content",request.getData());
contentObject.put("role","user");
JSONArray array = aiChatHistoryService.getChatHistoryData(request.getSessionId());
if(array==null||array.isEmpty()||array.size()==0){
array = new JSONArray();
}
array.add(contentObject);
request.setMessages(array);
List<String> answerList = new ArrayList<>();
long[] timeStart = {0};
// 异步处理请求并推送数据
executor.submit(() -> {
try {
// 2. 调用ChatClient的流式聊天方法正确传递参数
chatClient.sendStreamingChat(
request,
new ChatClient.StreamCallback() { // 使用内部类完整路径
@Override
public void onData(String chunk) {
try {
if(timeStart[0] == 0){
timeStart[0] = System.currentTimeMillis();
}
// 1. 处理SSE协议格式
String processedChunk = chunk.trim();
// 解析返回的chunk数据
String content = parseChatChunk(processedChunk);
if (StringUtils.isNotEmpty(content)) {
answerList.add(content);
}
emitter.send(processedChunk, MediaType.TEXT_EVENT_STREAM);
} catch (IOException e) {
emitter.completeWithError(e);
}
}
@Override
public void onComplete() {
emitter.complete();
}
@Override
public void onError(Throwable e) {
try {
emitter.send(SseEmitter.event()
.data("{\"status\":\"error\",\"message\":\"" + e.getMessage() + "\"}")
.name("error"));
} catch (IOException ex) {
// 记录日志
} finally {
emitter.completeWithError(e);
}
}
}
);
} catch (Exception e) {
emitter.completeWithError(e);
}
});
// 处理连接关闭
long finalUserId = userId;
emitter.onCompletion(() -> {
// 此处仅做资源清理,不关闭线程池
log.info("连接关闭,清理资源");
long timeEnd = System.currentTimeMillis();
double duration = (timeEnd - timeStart[0]) / 1000.0;
AiChatHistory chatHistory = new AiChatHistory();
chatHistory.setChatId(request.getSessionId());
chatHistory.setUserId((finalUserId==0)?null:finalUserId);
chatHistory.setAppId(chatConfig.getAppId());
chatHistory.setDataId(request.getDataId());
chatHistory.setTitle(request.getData());
chatHistory.setAnswerStringList(answerList);
chatHistory.setDurationSeconds(duration);
aiChatHistoryService.saveChatHistory(chatHistory);
});
return emitter;
}
@BussinessLog(title = "提供推测出的问询建议",businessType = QUERY)
@ApiOperation("提供推测出的问询建议")
@PostMapping(value = "/guest")
public AjaxResult getChatGuest(@RequestBody ChatRequest request) throws IOException {
JSONArray array = aiChatHistoryService.getChatHistoryData(request.getSessionId());
request.setMessages(array);
String result = chatClient.sendChatGuest(request);
try {
String[] strList = result.split("");
List<String> list = new ArrayList<>();
for(String str:strList){
if(StringUtils.isNotEmpty(str)){
str = str+"";
list.add(str);
}
}
return AjaxResult.success(list);
}catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
}
// 解析返回的流式数据
private String parseChatChunk(String chunk) {
try {
String processed = chunk.trim();
if (processed.startsWith("data:")) {
processed = processed.substring("data:".length()).trim();
}
if (processed.isEmpty() || "[DONE]".equals(processed)) {
return null;
}
JSONObject json = JSONObject.parseObject(processed);
String choices = json.getString("choices");
if (choices != null && !choices.trim().isEmpty()) {
JSONArray jsonArray = JSONArray.parseArray(choices);
json = JSONObject.parseObject(jsonArray.getString(0));
json = json.getJSONObject("delta");
String content = json.getString("content");// 消息内容
return content;
}
return null;
} catch (Exception e) {
return null;
}
}
@PreDestroy
public void destroy() {
executor.shutdown(); // 应用退出前关闭线程池
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制关闭未完成的任务
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
executor.shutdownNow();
log.error(e.getMessage());
}
}
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.cms.domain.ai;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@Data
@ApiModel("ai聊天详情")
@TableName("AI_CHAT_DETAIL")
public class AiChatDetail {
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@TableId(value = "id",type = IdType.AUTO)
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("会话id")
private String chatId;
@ApiModelProperty("数据id")
private String dataId;
@ApiModelProperty("会话类型Human用户AI大模型")
private String obj;
@ApiModelProperty("会话内容")
private String content;
@ApiModelProperty("会话时间")
private Date time;
@ApiModelProperty("耗时")
private Double durationSeconds;
}

View File

@@ -0,0 +1,53 @@
package com.ruoyi.cms.domain.ai;
import com.alibaba.fastjson2.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("ai聊天历史记录")
@TableName("AI_CHAT_HISTORY")
public class AiChatHistory{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@TableId(value = "id",type = IdType.AUTO)
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户id")
private Long userId;
@JSONField(name = "chatId")
@ApiModelProperty("会话id")
private String chatId;
@ApiModelProperty("应用id")
private String appId;
@JSONField(name = "title")
@ApiModelProperty("第一次的问题")
private String title;
@JSONField(name = "updateTime")
@ApiModelProperty("会话时间")
private Date updateTime;
@ApiModelProperty("是否删除")
private String delFlag;
@TableField(exist = false)
private List<String> answerStringList;
@TableField(exist = false)
private double durationSeconds;
@TableField(exist = false)
private String customTitle;
@TableField(exist = false)
private String dataId;
@TableField(exist = false)
private boolean top=false;
}

View File

@@ -0,0 +1,17 @@
package com.ruoyi.cms.domain.chat;
import com.alibaba.fastjson2.JSONArray;
import lombok.Data;
import java.util.List;
@Data
public class ChatRequest {
private String data;
private String dataId;
private String sessionId;
private long userId;
private List<String> fileUrl;
private JSONArray messages;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.ai.AiChatDetail;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AiChatDetailMapper extends BaseMapper<AiChatDetail> {
List<AiChatDetail> getList(AiChatDetail aiChatDetail);
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AiChatHistoryMapper extends BaseMapper<AiChatHistory> {
List<AiChatHistory> getList(AiChatHistory aiChatHistory);
}

View File

@@ -0,0 +1,18 @@
package com.ruoyi.cms.service;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.cms.domain.ai.AiChatHistory;
public interface AiChatHistoryService extends IService<AiChatHistory> {
JSONObject getList(AiChatHistory aiChatHistory);
void saveChatHistory(AiChatHistory aiChatHistory);
JSONObject getDetailList(String chatId);
JSONArray getChatHistoryData(String chatId);
}

View File

@@ -0,0 +1,191 @@
package com.ruoyi.cms.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cms.domain.ai.AiChatDetail;
import com.ruoyi.cms.domain.ai.AiChatHistory;
import com.ruoyi.cms.mapper.AiChatDetailMapper;
import com.ruoyi.cms.mapper.AiChatHistoryMapper;
import com.ruoyi.cms.service.AiChatHistoryService;
import com.ruoyi.common.utils.SiteSecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class AiChatHistoryServiceImpl extends ServiceImpl<AiChatHistoryMapper, AiChatHistory> implements AiChatHistoryService {
@Autowired
private AiChatHistoryMapper aiChatHistoryMapper;
@Autowired
private AiChatDetailMapper aiChatDetailMapper;
@Override
public JSONObject getList(AiChatHistory aiChatHistory) {
JSONObject object = new JSONObject();
JSONArray jsonArray = new JSONArray();
if(Objects.isNull(aiChatHistory.getUserId())){
try {
aiChatHistory.setUserId(SiteSecurityUtils.getUserId());
}catch (Exception e){
object.put("list", jsonArray);
return object;
}
}
List<AiChatHistory> list = aiChatHistoryMapper.getList(aiChatHistory);
if(list!=null&&!list.isEmpty()){
JSONObject jsonObject1 = new JSONObject();
for(AiChatHistory history:list){
jsonObject1 = JSONObject.parseObject(JSONObject.toJSONString(history));
jsonArray.add(jsonObject1);
}
}
object.put("list", jsonArray);
return object;
}
@Override
public void saveChatHistory(AiChatHistory aiChatHistory) {
try {
AiChatHistory history = new AiChatHistory();
history.setChatId(aiChatHistory.getChatId());
List<AiChatHistory> list = aiChatHistoryMapper.getList(history);
if(list!=null&&!list.isEmpty()){
history = list.get(0);
history.setUpdateTime(new Date());
aiChatHistoryMapper.updateById(history);
}else{
history.setTitle(aiChatHistory.getTitle());
history.setUserId(aiChatHistory.getUserId());
history.setAppId(aiChatHistory.getAppId());
history.setDelFlag("0");
history.setUpdateTime(new Date());
aiChatHistoryMapper.insert(history);
}
AiChatDetail chatDetail = new AiChatDetail();
chatDetail.setChatId(aiChatHistory.getChatId());
chatDetail.setObj("Human");
chatDetail.setTime(new Date());
chatDetail.setDataId(IdUtils.fastSimpleUUID());
chatDetail.setContent(aiChatHistory.getTitle());
aiChatDetailMapper.insert(chatDetail);
List<String> answerList = aiChatHistory.getAnswerStringList();
if(answerList!=null&&!answerList.isEmpty()){
chatDetail = new AiChatDetail();
chatDetail.setChatId(aiChatHistory.getChatId());
chatDetail.setObj("AI");
chatDetail.setTime(new Date());
chatDetail.setDataId(aiChatHistory.getDataId());
chatDetail.setContent(String.join("",answerList));
chatDetail.setDurationSeconds(aiChatHistory.getDurationSeconds());
aiChatDetailMapper.insert(chatDetail);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public JSONObject getDetailList(String chatId) {
JSONObject dataList = new JSONObject();
JSONArray array = new JSONArray();
AiChatDetail detail = new AiChatDetail();
detail.setChatId(chatId);
List<AiChatDetail> list = aiChatDetailMapper.getList(detail);
if(list!=null&&!list.isEmpty()){
JSONObject data = new JSONObject();
JSONArray value = new JSONArray();
JSONObject valueObject = new JSONObject();
String dataId = "";
List<String> contentList = new ArrayList<>();
List<AiChatDetail> details = new ArrayList<>();
for(AiChatDetail detail1:list){
details = list.stream().filter(fi->fi.getDataId().equals(detail1.getDataId())).collect(Collectors.toList());
if(details.size() > 1){
contentList = new ArrayList<>();
if(StringUtils.isNotEmpty(dataId)){
continue;
}
data = new JSONObject();
data.put("dataId", detail1.getDataId());
data.put("obj", detail1.getObj());
data.put("hideInUI",false);
data.put("customFeedbacks",new JSONArray());
data.put("time",detail1.getTime());
data.put("durationSeconds",detail1.getDurationSeconds());
value = new JSONArray();
dataId = detail1.getDataId();
for(AiChatDetail detail2:details){
if(detail2.getContent().contains("```")){
contentList.add(detail2.getContent());
}else{
if(contentList!=null&&!contentList.isEmpty()){
valueObject = new JSONObject();
valueObject.put("type","text");
valueObject.put("text",new JSONObject().fluentPut("content", String.join("",contentList)));
value.add(valueObject);
}
valueObject = new JSONObject();
valueObject.put("type","text");
valueObject.put("text",new JSONObject().fluentPut("content", detail2.getContent()));
value.add(valueObject);
}
}
data.put("value",value);
array.add(data);
}else{
dataId = "";
data = new JSONObject();
data.put("dataId", detail1.getDataId());
data.put("obj", detail1.getObj());
data.put("hideInUI",false);
data.put("customFeedbacks",new JSONArray());
data.put("time",detail1.getTime());
if("AI".equals(detail1.getObj())){
data.put("durationSeconds",detail1.getDurationSeconds());
}
valueObject = new JSONObject();
valueObject.put("type","text");
valueObject.put("text",new JSONObject().fluentPut("content", detail1.getContent()));
value = new JSONArray();
value.add(valueObject);
data.put("value",value);
array.add(data);
}
}
}
dataList.put("list",array);
return dataList;
}
@Override
public JSONArray getChatHistoryData(String chatId) {
JSONObject content = null;
JSONArray data = new JSONArray();
AiChatDetail detail = new AiChatDetail();
detail.setChatId(chatId);
List<AiChatDetail> list = aiChatDetailMapper.getList(detail);
if(list!=null&&!list.isEmpty()){
for(AiChatDetail d:list){
content = new JSONObject();
if("AI".equals(d.getObj())){
content.put("role","assistant");
}else{
content.put("role","user");
}
content.put("content",d.getContent());
data.add(content);
}
}
return data;
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.cms.mapper.AiChatDetailMapper">
<select id="getList" parameterType="com.ruoyi.cms.domain.ai.AiChatDetail" resultType="com.ruoyi.cms.domain.ai.AiChatDetail">
select * from ai_chat_detail where chat_id=#{chatId} order by time
</select>
</mapper>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.cms.mapper.AiChatHistoryMapper">
<select id="getList" parameterType="com.ruoyi.cms.domain.ai.AiChatHistory" resultType="com.ruoyi.cms.domain.ai.AiChatHistory">
select * from ai_chat_history where del_flag=0
<if test="userId != null and userId != ''">
and user_id = #{userId}
</if>
<if test="chatId != null and chatId != ''">
and chat_id = #{chatId}
</if>
<if test="appId != null and appId != ''">
and app_id = #{appId}
</if>
<if test="title != null and title != ''">
and title like CONCAT('%',#{title},'%')
</if>
<if test="updateTime != null">
and to_char(update_time,'yyyy-mm-dd') = to_char(#{updateTime},'yyyy-mm-dd')
</if>
</select>
</mapper>