v3.9.1 发布(AI应用首页,加入 Chat2BI 和 AI绘图)

This commit is contained in:
JEECG
2026-01-28 15:22:43 +08:00
parent 020ba3443c
commit 9a43a8d41a
19 changed files with 691 additions and 115 deletions

View File

@ -56,7 +56,12 @@ public class AiAppConsts {
/**
* AI写作的应用id
* AI写作的流程id
*/
public static final String WRITER_APP_ID = "2010634128233779202";
public static final String ARTICLE_WRITER_FLOW_ID = "2011769909807579138";
/**
* AI写作redis请求前缀
*/
public static final String ARTICLE_WRITER_KEY = "airag:chat:article:write:{}";
}

View File

@ -168,4 +168,9 @@ public class Prompts {
*/
public static final String AI_REPLY_PROMPT = "请针对如下内容:[{}] 做个回复。回复内容参考:[{}], 回复格式:{},语气:{},语言:{},长度:{}。";
/**
* ai润色提提示词
*/
public static final String AI_TOUCHE_PROMPT = "请针对如下内容:[{}] 进行润色。 回复格式:{},语气:{},语言:{},长度:{}。";
}

View File

@ -16,11 +16,14 @@ import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.service.IAiragChatService;
import org.jeecg.modules.airag.app.vo.AiArticleWriteVersionVo;
import org.jeecg.modules.airag.app.vo.AppDebugParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
import jakarta.servlet.http.HttpServletRequest;
/**
@ -190,4 +193,31 @@ public class AiragAppController extends JeecgController<AiragApp, IAiragAppServi
return (SseEmitter) airagAppService.generateMemoryByAppId(variables, memoryId,false);
}
/**
* 写作保存
*/
@PostMapping("/save/article/write")
public Result<String> saveArticleWrite(@RequestBody AiArticleWriteVersionVo aiWriteVersionVo) {
airagAppService.saveArticleWrite(aiWriteVersionVo);
return Result.OK("保存成功!");
}
/**
* 写作删除
*/
@DeleteMapping("/delete/article/write")
public Result<String> deleteArticleWrite(@RequestParam(name = "version") String version) {
AssertUtils.assertNotEmpty("版本号不能为空", version);
airagAppService.deleteArticleWrite(version);
return Result.OK("删除成功!");
}
/**
* 写作查询
*/
@GetMapping("/list/article/write")
public Result<List<AiArticleWriteVersionVo>> listArticleWrite() {
List<AiArticleWriteVersionVo> list = airagAppService.listArticleWrite();
return Result.OK(list);
}
}

View File

@ -2,6 +2,8 @@ package org.jeecg.modules.airag.app.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.vo.AiArticleWriteVersionVo;
import java.util.List;
/**
* @Description: AI应用
@ -30,4 +32,26 @@ public interface IAiragAppService extends IService<AiragApp> {
* @return
*/
Object generateMemoryByAppId(String variables, String memoryId, boolean blocking);
/**
* 写作保存
*
* @param aiWriteVersionVo
*/
void saveArticleWrite(AiArticleWriteVersionVo aiWriteVersionVo);
/**
* 写作列表
*
* @return
*/
List<AiArticleWriteVersionVo> listArticleWrite();
/**
* 写作删除
*
* @param version
*/
void deleteArticleWrite(String version);
}

View File

@ -1,5 +1,6 @@
package org.jeecg.modules.airag.app.service.impl;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -10,15 +11,19 @@ import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.output.FinishReason;
import dev.langchain4j.service.TokenStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootBizTipException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.AssertUtils;
import org.jeecg.common.util.UUIDGenerator;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.airag.app.consts.AiAppConsts;
import org.jeecg.modules.airag.app.consts.Prompts;
import org.jeecg.modules.airag.app.entity.AiragApp;
import org.jeecg.modules.airag.app.mapper.AiragAppMapper;
import org.jeecg.modules.airag.app.service.IAiragAppService;
import org.jeecg.modules.airag.app.vo.AiArticleWriteVersionVo;
import org.jeecg.modules.airag.app.vo.AppVariableVo;
import org.jeecg.modules.airag.common.consts.AiragConsts;
import org.jeecg.modules.airag.common.handler.AIChatParams;
@ -30,14 +35,18 @@ import org.jeecg.modules.airag.common.vo.event.EventMessageData;
import org.jeecg.modules.airag.llm.entity.AiragKnowledge;
import org.jeecg.modules.airag.llm.service.IAiragKnowledgeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @Description: AI应用
@ -54,6 +63,9 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
@Autowired
private IAiragKnowledgeService airagKnowledgeService;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Object generatePrompt(String prompt, boolean blocking) {
@ -247,4 +259,68 @@ public class AiragAppServiceImpl extends ServiceImpl<AiragAppMapper, AiragApp> i
emitter.complete();
}
}
/**
* 写作列表
*/
@Override
public List<AiArticleWriteVersionVo> listArticleWrite() {
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String redisKey = StrUtil.format(AiAppConsts.ARTICLE_WRITER_KEY, loginUser.getUsername());
Object data = redisTemplate.opsForValue().get(redisKey);
if (data == null) {
return new ArrayList<>();
}
List<AiArticleWriteVersionVo> aiWriteViewVoList = (List<AiArticleWriteVersionVo>) data;
Collections.reverse(aiWriteViewVoList);
return aiWriteViewVoList;
}
/**
* 写作报错
*/
@Override
public void saveArticleWrite(AiArticleWriteVersionVo aiWriteVersionVo) {
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String redisKey = StrUtil.format(AiAppConsts.ARTICLE_WRITER_KEY, loginUser.getUsername());
//先查看redis中是否存在
Object data = redisTemplate.opsForValue().get(redisKey);
if(null != data){
List<AiArticleWriteVersionVo> aiWriteVersionVos = (List<AiArticleWriteVersionVo>) data;
aiWriteVersionVo.setVersion("V"+(aiWriteVersionVos.size() + 1));
aiWriteVersionVos.add(aiWriteVersionVo);
redisTemplate.opsForValue().set(redisKey, aiWriteVersionVos);
}else{
List<AiArticleWriteVersionVo> aiWriteVersionVos = new ArrayList<>();
aiWriteVersionVo.setVersion("V1");
aiWriteVersionVos.add(aiWriteVersionVo);
redisTemplate.opsForValue().set(redisKey, aiWriteVersionVos);
}
}
/**
* 写作删除
*/
@Override
public void deleteArticleWrite(String version) {
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
String redisKey = StrUtil.format(AiAppConsts.ARTICLE_WRITER_KEY, loginUser.getUsername());
Object data = redisTemplate.opsForValue().get(redisKey);
if (data == null) {
return;
}
List<AiArticleWriteVersionVo> aiWriteVersionVos = (List<AiArticleWriteVersionVo>) data;
if (aiWriteVersionVos.isEmpty()) {
return;
}
List<AiArticleWriteVersionVo> newList = aiWriteVersionVos.stream()
.filter(vo -> !version.equals(vo.getVersion()))
.collect(Collectors.toList());
if (newList.isEmpty()) {
redisTemplate.delete(redisKey);
} else {
redisTemplate.opsForValue().set(redisKey, newList);
}
}
}

View File

@ -933,21 +933,32 @@ public class AiragChatServiceImpl implements IAiragChatService {
private SseEmitter genImageChat(SseEmitter emitter, ChatSendParams sendParams, String requestId, List<ChatMessage> messages, ChatConversation chatConversation, String topicId) {
AssertUtils.assertNotEmpty("请选择绘画模型", sendParams.getDrawModelId());
AIChatParams aiChatParams = new AIChatParams();
try {
List<String> images = sendParams.getImages();
List<Map<String, Object>> imageList = new ArrayList<>();
if(CollectionUtils.isEmpty(images)) {
//生成图片
imageList = aiChatHandler.imageGenerate(sendParams.getDrawModelId(), sendParams.getContent(), aiChatParams);
} else {
//图生图
imageList = aiChatHandler.imageEdit(sendParams.getDrawModelId(), sendParams.getContent(), images, aiChatParams);
}
// 记录历史消息
String imageMarkdown = imageList.stream().map(map -> {
String newUrl = this.uploadImage(map);
return "![](" + newUrl + ")";
}).collect(Collectors.joining("\n"));
//update-begin---author:wangshuai---date:2026-01-26---for: 【QQYUN-14615】应用门户加入新工具取绘画id---
String drawModelId = sendParams.getDrawModelId();
if(oConvertUtils.isEmpty(sendParams.getDrawModelId())){
AiragApp app = chatConversation.getApp();
String metadata = app.getMetadata();
if(oConvertUtils.isNotEmpty(metadata) && metadata.contains("drawModelId")){
drawModelId = JSONObject.parseObject(drawModelId).getString("drawModelId");
}
}
try {
List<String> images = sendParams.getImages();
List<Map<String, Object>> imageList;
if(CollectionUtils.isEmpty(images)) {
//生成图片
imageList = aiChatHandler.imageGenerate(drawModelId, sendParams.getContent(), aiChatParams);
} else {
//图生图
imageList = aiChatHandler.imageEdit(drawModelId, sendParams.getContent(), images, aiChatParams);
}
// 记录历史消息
String imageMarkdown = imageList.stream().map(map -> {
String newUrl = this.uploadImage(map);
return "![](" + newUrl + ")";
}).collect(Collectors.joining("\n"));
//update-end---author:wangshuai---date:2026-01-26---for:【QQYUN-14615】应用门户加入新工具取绘画id---
AiMessage aiMessage = new AiMessage(imageMarkdown);
appendMessage(messages, aiMessage, chatConversation, topicId);
// 处理绘画结果并通过SSE返回给客户端
@ -1868,18 +1879,46 @@ public class AiragChatServiceImpl implements IAiragChatService {
@Override
public SseEmitter genAiWriter(AiWriteGenerateVo aiWriteGenerateVo) {
String activeMode = "compose";
String reply = "reply";
ChatSendParams sendParams = new ChatSendParams();
sendParams.setAppId(AiAppConsts.WRITER_APP_ID);
sendParams.setAppId(AiAppConsts.ARTICLE_WRITER_FLOW_ID);
String content = "";
//写作
if (activeMode.equals(aiWriteGenerateVo.getActiveMode())) {
content = StrUtil.format(Prompts.AI_WRITER_PROMPT, aiWriteGenerateVo.getPrompt(), aiWriteGenerateVo.getFormat(), aiWriteGenerateVo.getTone(), aiWriteGenerateVo.getLanguage(), aiWriteGenerateVo.getLength());
} else {
} else if(reply.equals(aiWriteGenerateVo.getActiveMode())){
//回复
content = StrUtil.format(Prompts.AI_REPLY_PROMPT, aiWriteGenerateVo.getPrompt(), aiWriteGenerateVo.getOriginalContent(), aiWriteGenerateVo.getFormat(), aiWriteGenerateVo.getTone(), aiWriteGenerateVo.getLanguage(), aiWriteGenerateVo.getLength());
} else {
content = StrUtil.format(Prompts.AI_TOUCHE_PROMPT, aiWriteGenerateVo.getPrompt(), aiWriteGenerateVo.getFormat(), aiWriteGenerateVo.getTone(), aiWriteGenerateVo.getLanguage(), aiWriteGenerateVo.getLength());
}
sendParams.setContent(content);
sendParams.setIzSaveSession(false);
return this.send(sendParams);
//组装会话
String requestId = UUIDGenerator.generate();
String topicId = UUIDGenerator.generate();
String conversationId = UUIDGenerator.generate();
ChatConversation chatConversation = new ChatConversation();
chatConversation.setId(conversationId);
chatConversation.setMessages(new ArrayList<>());
Map<String,Object> flowInputs = new HashMap<>();
flowInputs.put("type", aiWriteGenerateVo.getActiveMode());
flowInputs.put("version", "V1");
chatConversation.setFlowInputs(flowInputs);
SseEmitter emitter = createSSE(requestId);
// 缓存emitter
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE, requestId, emitter);
// 缓存开始发送时间
log.info("[AI-CHAT]开始发送消息,requestId:{}", requestId);
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_SEND_TIME, requestId, System.currentTimeMillis());
// 初始化历史消息缓存
AiragLocalCache.put(AiragConsts.CACHE_TYPE_SSE_HISTORY_MSG, requestId, new CopyOnWriteArrayList<>());
// 发送就绪消息
EventData eventRequestId = new EventData(requestId, null, EventData.EVENT_INIT_REQUEST_ID, chatConversation.getId(), topicId);
eventRequestId.setData(EventMessageData.builder().message("").build());
sendMessage2Client(emitter, eventRequestId);
sendWithFlow(requestId, AiAppConsts.ARTICLE_WRITER_FLOW_ID, chatConversation, topicId, new ArrayList<>(), sendParams);
return emitter;
}
}

View File

@ -0,0 +1,23 @@
package org.jeecg.modules.airag.app.vo;
import lombok.Data;
/**
* @Description: AI写作版本号
*
* @author: wangshuai
* @date: 2026/1/16 11:57
*/
@Data
public class AiArticleWriteVersionVo {
/**
* 当前版本号
*/
private String version;
/**
* 写作内容
*/
private String content;
}