ai
This commit is contained in:
@ -6,7 +6,6 @@ import org.dromara.ai.advisor.CustomLoggerAdvisor;
|
||||
import org.dromara.ai.chatmemory.FileBasedChatMemory;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
|
||||
@ -15,7 +14,6 @@ import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
@ -57,18 +55,12 @@ public class DashScopeChat {
|
||||
* @param chatId 会话id
|
||||
* @return 流式输出结果
|
||||
*/
|
||||
public Flux<String> doChatStream(String message, String chatId) {
|
||||
public Flux<String> doChatStream(String message, String chatId, Boolean isFirst) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
boolean isFirst = StringUtils.isBlank(chatId);
|
||||
if (StringUtils.isBlank(chatId)) {
|
||||
// 构建新的会话id
|
||||
chatId = UUID.randomUUID().toString();
|
||||
}
|
||||
String finalChatId = chatId;
|
||||
return chatClient
|
||||
.prompt()
|
||||
.user(message)
|
||||
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, finalChatId))
|
||||
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId))
|
||||
.stream()
|
||||
.content()// 收集所有 token,生成完整回复
|
||||
.collectList()
|
||||
@ -76,7 +68,7 @@ public class DashScopeChat {
|
||||
String aiResponse = String.join("", tokens);
|
||||
if (isFirst) {
|
||||
// 异步生成标题
|
||||
generateChatTitleAsync(finalChatId, message, aiResponse, userId);
|
||||
generateChatTitleAsync(chatId, message, aiResponse, userId);
|
||||
}
|
||||
// 返回完整的流结果
|
||||
return Flux.fromIterable(tokens);
|
||||
@ -94,6 +86,14 @@ public class DashScopeChat {
|
||||
private void generateChatTitleAsync(String chatId, String userMessage, String aiResponse, Long userId) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// 先判断一下当前聊天是否存在
|
||||
Long count = chatMemoryService.lambdaQuery()
|
||||
.eq(AIChatMemory::getUserId, userId)
|
||||
.eq(AIChatMemory::getFileName, chatId)
|
||||
.count();
|
||||
if (count > 0) {
|
||||
return;
|
||||
}
|
||||
// 构建生成标题的提示词
|
||||
String prompt = String.format("""
|
||||
请为下面这段用户与AI的对话生成一个简短的标题(不超过10个字):
|
||||
|
||||
@ -48,7 +48,9 @@ public class FileBasedChatMemory implements ChatMemory {
|
||||
|
||||
@Override
|
||||
public List<Message> get(String conversationId) {
|
||||
return getOrCreateConversation(conversationId);
|
||||
List<Message> messages = getOrCreateConversation(conversationId);
|
||||
log.info("获取对话:{}", messages);
|
||||
return messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -5,8 +5,8 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.ai.chat.DashScopeChat;
|
||||
import org.dromara.ai.chatmemory.FileBasedChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatReq;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@ -33,9 +33,9 @@ public class AIChatController {
|
||||
* ChatClient 流式调用
|
||||
*/
|
||||
@GetMapping("/stream")
|
||||
public Flux<String> streamChat(String query, String chatId, HttpServletResponse response) {
|
||||
public Flux<String> streamChat(@Validated AIChatReq req, HttpServletResponse response) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
return dashScopeChat.doChatStream(query, chatId);
|
||||
return dashScopeChat.doChatStream(req.getQuery(), req.getChatId(), req.getIsFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -6,10 +6,12 @@ import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
@ -66,6 +68,17 @@ public class AIChatMemoryController extends BaseController {
|
||||
return R.ok(aiChatMemoryService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*/
|
||||
@SaCheckPermission("ai:chatMemory:edit")
|
||||
@Log(title = "AI 对话记录信息", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> updateAIChatMemory(@Validated AIChatMemoryUpdateReq req) {
|
||||
return toAjax(aiChatMemoryService.updateByReq(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除AI 对话记录信息
|
||||
*
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-05 11:28
|
||||
*/
|
||||
@Data
|
||||
public class AIChatMemoryUpdateReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 6541297164616819137L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "主键不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 第一条问题
|
||||
*/
|
||||
private String firstQuestion;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package org.dromara.ai.domain.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-11-05 09:31
|
||||
*/
|
||||
@Data
|
||||
public class AIChatReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -4669223630531267889L;
|
||||
|
||||
/**
|
||||
* 聊天内容
|
||||
*/
|
||||
@NotBlank(message = "请输入内容")
|
||||
private String query;
|
||||
|
||||
/**
|
||||
* 会话id
|
||||
*/
|
||||
@NotBlank(message = "请输入会话id")
|
||||
private String chatId;
|
||||
|
||||
/**
|
||||
* 是否首次对话
|
||||
*/
|
||||
@NotNull(message = "请选择是否首次对话")
|
||||
private Boolean isFirst;
|
||||
}
|
||||
@ -3,6 +3,7 @@ package org.dromara.ai.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
@ -43,6 +44,14 @@ public interface IAIChatMemoryService extends IService<AIChatMemory>{
|
||||
*/
|
||||
List<AIChatMemoryVo> queryList(AIChatMemoryQueryReq bo);
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*
|
||||
* @param req 修改参数
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByReq(AIChatMemoryUpdateReq req);
|
||||
|
||||
/**
|
||||
* 校验并批量删除AI 对话记录信息信息
|
||||
*
|
||||
|
||||
@ -6,12 +6,16 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.dromara.ai.domain.AIChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.ai.mapper.AIChatMemoryMapper;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
@ -64,6 +68,23 @@ public class AIChatMemoryServiceImpl extends ServiceImpl<AIChatMemoryMapper, AIC
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*
|
||||
* @param req 修改参数
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByReq(AIChatMemoryUpdateReq req) {
|
||||
AIChatMemory oldChatMemory = this.getById(req.getId());
|
||||
if (oldChatMemory == null) {
|
||||
throw new ServiceException("数据不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
AIChatMemory update = new AIChatMemory();
|
||||
BeanUtils.copyProperties(req, update);
|
||||
return this.updateById(update);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<AIChatMemory> buildQueryWrapper(AIChatMemoryQueryReq req) {
|
||||
LambdaQueryWrapper<AIChatMemory> lqw = Wrappers.lambdaQuery();
|
||||
lqw.orderByDesc(AIChatMemory::getId);
|
||||
|
||||
@ -5,7 +5,18 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.ai.chat.DashScopeChat;
|
||||
import org.dromara.ai.chatmemory.FileBasedChatMemory;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryQueryReq;
|
||||
import org.dromara.ai.domain.dto.AIChatMemoryUpdateReq;
|
||||
import org.dromara.ai.domain.dto.AIChatReq;
|
||||
import org.dromara.ai.domain.vo.AIChatMemoryVo;
|
||||
import org.dromara.ai.service.IAIChatMemoryService;
|
||||
import org.dromara.bigscreen.domain.dto.BusBwlBo;
|
||||
import org.dromara.bigscreen.domain.dto.TaskInfoDto;
|
||||
import org.dromara.bigscreen.domain.vo.BusBwlVo;
|
||||
@ -14,6 +25,9 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.domain.dto.UserDTO;
|
||||
import org.dromara.common.core.enums.BusinessStatusEnum;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
@ -30,11 +44,11 @@ import org.dromara.warm.flow.orm.mapper.FlowDefinitionMapper;
|
||||
import org.dromara.workflow.domain.bo.FlowTaskBo;
|
||||
import org.dromara.workflow.domain.vo.FlowTaskVo;
|
||||
import org.dromara.workflow.mapper.FlwTaskMapper;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
@ -63,8 +77,82 @@ public class PersonalHomeController extends BaseController {
|
||||
|
||||
private final IBusBwlService busBwlService;
|
||||
|
||||
@Resource
|
||||
private DashScopeChat dashScopeChat;
|
||||
|
||||
@Resource
|
||||
private IAIChatMemoryService aiChatMemoryService;
|
||||
|
||||
// region AI 模块
|
||||
|
||||
/**
|
||||
* 获取新 AI 对话聊天id
|
||||
*/
|
||||
@GetMapping("/ai/chat/new")
|
||||
public R<String> getNewAIChat() {
|
||||
return R.ok(UUID.randomUUID().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 对话流式调用
|
||||
*/
|
||||
@GetMapping("/ai/chat/stream")
|
||||
public Flux<String> streamChat(@Validated AIChatReq req, HttpServletResponse response) {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
return dashScopeChat.doChatStream(req.getQuery(), req.getChatId(), req.getIsFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 对话记录
|
||||
*/
|
||||
@GetMapping("/ai/chat/history")
|
||||
public R<List<Message>> getChatHistory(String chatId) {
|
||||
FileBasedChatMemory memory = new FileBasedChatMemory(System.getProperty("user.dir") + "/chat-memory");
|
||||
return R.ok(memory.get(chatId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 AI 对话记录信息列表
|
||||
*/
|
||||
@GetMapping("/ai/chatMemory/list")
|
||||
public TableDataInfo<AIChatMemoryVo> listAIChatMemory(AIChatMemoryQueryReq req, PageQuery pageQuery) {
|
||||
return aiChatMemoryService.queryPageList(req, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 对话记录信息详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@GetMapping("/ai/chatMemory/{id}")
|
||||
public R<AIChatMemoryVo> getAIChatMemoryInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(aiChatMemoryService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改 AI 对话记录信息
|
||||
*/
|
||||
@Log(title = "AI 对话记录信息", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/ai/chatMemory")
|
||||
public R<Void> updateAIChatMemory(@Validated AIChatMemoryUpdateReq req) {
|
||||
return toAjax(aiChatMemoryService.updateByReq(req));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 AI 对话记录信息
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@Log(title = "AI 对话记录信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/ai/chatMemory/{ids}")
|
||||
public R<Void> removeAIChatMemory(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(aiChatMemoryService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 查询派单列表
|
||||
|
||||
Reference in New Issue
Block a user