Commit 5579620140c7036c35bf270b2ca40ba98a7de7b7
1 parent
708d66e8
【优化】聊天 event stream 改为 flex 返回更加的优雅
Showing
8 changed files
with
70 additions
and
108 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/Utf8SseEmitter.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.controller; | ||
| 2 | - | ||
| 3 | -import org.springframework.http.HttpHeaders; | ||
| 4 | -import org.springframework.http.MediaType; | ||
| 5 | -import org.springframework.http.server.ServerHttpResponse; | ||
| 6 | -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | ||
| 7 | - | ||
| 8 | -import java.nio.charset.StandardCharsets; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * 解决中文乱码 | ||
| 12 | - * | ||
| 13 | - * @author fansili | ||
| 14 | - * @time 2024/4/14 15:13 | ||
| 15 | - * @since 1.0 | ||
| 16 | - */ | ||
| 17 | -public class Utf8SseEmitter extends SseEmitter { | ||
| 18 | - | ||
| 19 | - @Override | ||
| 20 | - protected void extendResponse(ServerHttpResponse outputMessage) { | ||
| 21 | - super.extendResponse(outputMessage); | ||
| 22 | - | ||
| 23 | - HttpHeaders headers = outputMessage.getHeaders(); | ||
| 24 | - headers.setContentType(new MediaType(MediaType.TEXT_EVENT_STREAM, StandardCharsets.UTF_8)); | ||
| 25 | - } | ||
| 26 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.chat; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.chat; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | -import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | ||
| 5 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; | 4 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; |
| 6 | -import cn.iocoder.yudao.module.ai.service.AiChatService; | ||
| 7 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; | 5 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; |
| 6 | +import cn.iocoder.yudao.module.ai.service.AiChatService; | ||
| 8 | import io.swagger.v3.oas.annotations.Operation; | 7 | import io.swagger.v3.oas.annotations.Operation; |
| 9 | import io.swagger.v3.oas.annotations.Parameter; | 8 | import io.swagger.v3.oas.annotations.Parameter; |
| 10 | import io.swagger.v3.oas.annotations.tags.Tag; | 9 | import io.swagger.v3.oas.annotations.tags.Tag; |
| @@ -13,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; | @@ -13,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; | ||
| 13 | import org.springframework.http.MediaType; | 12 | import org.springframework.http.MediaType; |
| 14 | import org.springframework.validation.annotation.Validated; | 13 | import org.springframework.validation.annotation.Validated; |
| 15 | import org.springframework.web.bind.annotation.*; | 14 | import org.springframework.web.bind.annotation.*; |
| 16 | -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | 15 | +import reactor.core.publisher.Flux; |
| 17 | 16 | ||
| 18 | import java.util.List; | 17 | import java.util.List; |
| 19 | 18 | ||
| @@ -39,10 +38,8 @@ public class AiChatMessageController { | @@ -39,10 +38,8 @@ public class AiChatMessageController { | ||
| 39 | // TODO @fan:要不要使用 Flux 来返回;可以使用 Flux<AiChatMessageRespVO> | 38 | // TODO @fan:要不要使用 Flux 来返回;可以使用 Flux<AiChatMessageRespVO> |
| 40 | @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") | 39 | @Operation(summary = "发送消息(流式)", description = "流式返回,响应较快") |
| 41 | @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) | 40 | @PostMapping(value = "/send-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) |
| 42 | - public SseEmitter sendMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) { | ||
| 43 | - Utf8SseEmitter sseEmitter = new Utf8SseEmitter(); | ||
| 44 | - chatService.chatStream(sendReqVO, sseEmitter); | ||
| 45 | - return sseEmitter; | 41 | + public Flux<AiChatMessageRespVO> sendMessageStream(@Validated @RequestBody AiChatMessageSendReqVO sendReqVO) { |
| 42 | + return chatService.chatStream(sendReqVO); | ||
| 46 | } | 43 | } |
| 47 | 44 | ||
| 48 | @Operation(summary = "获得指定会话的消息列表") | 45 | @Operation(summary = "获得指定会话的消息列表") |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.image; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.image; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | -import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | ||
| 5 | -import cn.iocoder.yudao.module.ai.service.AiImageService; | ||
| 6 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReq; | 4 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReq; |
| 7 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReq; | 5 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReq; |
| 6 | +import cn.iocoder.yudao.module.ai.service.AiImageService; | ||
| 8 | import io.swagger.v3.oas.annotations.Operation; | 7 | import io.swagger.v3.oas.annotations.Operation; |
| 9 | import io.swagger.v3.oas.annotations.tags.Tag; | 8 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 10 | import lombok.AllArgsConstructor; | 9 | import lombok.AllArgsConstructor; |
| @@ -14,7 +13,6 @@ import org.springframework.web.bind.annotation.PostMapping; | @@ -14,7 +13,6 @@ import org.springframework.web.bind.annotation.PostMapping; | ||
| 14 | import org.springframework.web.bind.annotation.RequestBody; | 13 | import org.springframework.web.bind.annotation.RequestBody; |
| 15 | import org.springframework.web.bind.annotation.RequestMapping; | 14 | import org.springframework.web.bind.annotation.RequestMapping; |
| 16 | import org.springframework.web.bind.annotation.RestController; | 15 | import org.springframework.web.bind.annotation.RestController; |
| 17 | -import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; | ||
| 18 | 16 | ||
| 19 | // TODO @芋艿:整理接口定义 | 17 | // TODO @芋艿:整理接口定义 |
| 20 | /** | 18 | /** |
| @@ -35,10 +33,11 @@ public class AiImageController { | @@ -35,10 +33,11 @@ public class AiImageController { | ||
| 35 | 33 | ||
| 36 | @Operation(summary = "dall2/dall3绘画", description = "openAi dall3是付费的!") | 34 | @Operation(summary = "dall2/dall3绘画", description = "openAi dall3是付费的!") |
| 37 | @PostMapping("/dallDrawing") | 35 | @PostMapping("/dallDrawing") |
| 38 | - public SseEmitter dallDrawing(@Validated @RequestBody AiImageDallDrawingReq req) { | ||
| 39 | - Utf8SseEmitter sseEmitter = new Utf8SseEmitter(); | ||
| 40 | - aiImageService.dallDrawing(req, sseEmitter); | ||
| 41 | - return sseEmitter; | 36 | + public void dallDrawing(@Validated @RequestBody AiImageDallDrawingReq req) { |
| 37 | +// Utf8SseEmitter sseEmitter = new Utf8SseEmitter(); | ||
| 38 | +// aiImageService.dallDrawing(req, sseEmitter); | ||
| 39 | +// return sseEmitter; | ||
| 40 | + | ||
| 42 | } | 41 | } |
| 43 | 42 | ||
| 44 | @Operation(summary = "midjourney", description = "midjourney图片绘画流程:1、提交任务 2、获取完成的任务 3、选择对应功能 4、获取最终结果") | 43 | @Operation(summary = "midjourney", description = "midjourney图片绘画流程:1、提交任务 2、获取完成的任务 3、选择对应功能 4、获取最终结果") |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiChatService.java
| 1 | package cn.iocoder.yudao.module.ai.service; | 1 | package cn.iocoder.yudao.module.ai.service; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | ||
| 4 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; | 3 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; |
| 5 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; | 4 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; |
| 5 | +import reactor.core.publisher.Flux; | ||
| 6 | 6 | ||
| 7 | import java.util.List; | 7 | import java.util.List; |
| 8 | 8 | ||
| @@ -26,11 +26,10 @@ public interface AiChatService { | @@ -26,11 +26,10 @@ public interface AiChatService { | ||
| 26 | /** | 26 | /** |
| 27 | * chat stream | 27 | * chat stream |
| 28 | * | 28 | * |
| 29 | - * @param req | ||
| 30 | - * @param sseEmitter | 29 | + * @param sendReqVO |
| 31 | * @return | 30 | * @return |
| 32 | */ | 31 | */ |
| 33 | - void chatStream(AiChatMessageSendReqVO req, Utf8SseEmitter sseEmitter); | 32 | + Flux<AiChatMessageRespVO> chatStream(AiChatMessageSendReqVO sendReqVO); |
| 34 | 33 | ||
| 35 | /** | 34 | /** |
| 36 | * 获取 - 获取对话 message list | 35 | * 获取 - 获取对话 message list |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/AiImageService.java
| 1 | package cn.iocoder.yudao.module.ai.service; | 1 | package cn.iocoder.yudao.module.ai.service; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | ||
| 4 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReq; | 3 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReq; |
| 5 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReq; | 4 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReq; |
| 6 | 5 | ||
| @@ -17,9 +16,8 @@ public interface AiImageService { | @@ -17,9 +16,8 @@ public interface AiImageService { | ||
| 17 | * ai绘画 - dall2/dall3 绘画 | 16 | * ai绘画 - dall2/dall3 绘画 |
| 18 | * | 17 | * |
| 19 | * @param req | 18 | * @param req |
| 20 | - * @param sseEmitter | ||
| 21 | */ | 19 | */ |
| 22 | - void dallDrawing(AiImageDallDrawingReq req, Utf8SseEmitter sseEmitter); | 20 | + void dallDrawing(AiImageDallDrawingReq req); |
| 23 | 21 | ||
| 24 | /** | 22 | /** |
| 25 | * midjourney 图片生成 | 23 | * midjourney 图片生成 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiChatServiceImpl.java
| @@ -9,7 +9,6 @@ import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; | @@ -9,7 +9,6 @@ import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; | ||
| 9 | import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; | 9 | import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; |
| 10 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | 10 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 11 | import cn.iocoder.yudao.module.ai.config.AiChatClientFactory; | 11 | import cn.iocoder.yudao.module.ai.config.AiChatClientFactory; |
| 12 | -import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | ||
| 13 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO; | 12 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatConversationRespVO; |
| 14 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; | 13 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; |
| 15 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; | 14 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; |
| @@ -25,13 +24,12 @@ import cn.iocoder.yudao.module.ai.service.AiChatRoleService; | @@ -25,13 +24,12 @@ import cn.iocoder.yudao.module.ai.service.AiChatRoleService; | ||
| 25 | import cn.iocoder.yudao.module.ai.service.AiChatService; | 24 | import cn.iocoder.yudao.module.ai.service.AiChatService; |
| 26 | import lombok.AllArgsConstructor; | 25 | import lombok.AllArgsConstructor; |
| 27 | import lombok.extern.slf4j.Slf4j; | 26 | import lombok.extern.slf4j.Slf4j; |
| 28 | -import org.springframework.http.MediaType; | ||
| 29 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
| 30 | import org.springframework.transaction.annotation.Transactional; | 28 | import org.springframework.transaction.annotation.Transactional; |
| 31 | import reactor.core.publisher.Flux; | 29 | import reactor.core.publisher.Flux; |
| 32 | 30 | ||
| 33 | -import java.io.IOException; | ||
| 34 | import java.util.List; | 31 | import java.util.List; |
| 32 | +import java.util.concurrent.atomic.AtomicInteger; | ||
| 35 | import java.util.function.Consumer; | 33 | import java.util.function.Consumer; |
| 36 | 34 | ||
| 37 | /** | 35 | /** |
| @@ -76,6 +74,7 @@ public class AiChatServiceImpl implements AiChatService { | @@ -76,6 +74,7 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 76 | chatModal.getModel(), chatModal.getId(), req.getContent(), | 74 | chatModal.getModel(), chatModal.getId(), req.getContent(), |
| 77 | null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); | 75 | null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); |
| 78 | String content = null; | 76 | String content = null; |
| 77 | + int tokens = 0; | ||
| 79 | try { | 78 | try { |
| 80 | // 创建 chat 需要的 Prompt | 79 | // 创建 chat 需要的 Prompt |
| 81 | Prompt prompt = new Prompt(req.getContent()); | 80 | Prompt prompt = new Prompt(req.getContent()); |
| @@ -87,6 +86,7 @@ public class AiChatServiceImpl implements AiChatService { | @@ -87,6 +86,7 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 87 | ChatClient chatClient = aiChatClientFactory.getChatClient(platformEnum); | 86 | ChatClient chatClient = aiChatClientFactory.getChatClient(platformEnum); |
| 88 | ChatResponse call = chatClient.call(prompt); | 87 | ChatResponse call = chatClient.call(prompt); |
| 89 | content = call.getResult().getOutput().getContent(); | 88 | content = call.getResult().getOutput().getContent(); |
| 89 | + tokens = call.getResults().size(); | ||
| 90 | // 更新 conversation | 90 | // 更新 conversation |
| 91 | } catch (Exception e) { | 91 | } catch (Exception e) { |
| 92 | content = ExceptionUtil.getMessage(e); | 92 | content = ExceptionUtil.getMessage(e); |
| @@ -94,7 +94,7 @@ public class AiChatServiceImpl implements AiChatService { | @@ -94,7 +94,7 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 94 | // 保存 chat message | 94 | // 保存 chat message |
| 95 | insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), | 95 | insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), |
| 96 | chatModal.getModel(), chatModal.getId(), content, | 96 | chatModal.getModel(), chatModal.getId(), content, |
| 97 | - null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); | 97 | + tokens, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); |
| 98 | } | 98 | } |
| 99 | return new AiChatMessageRespVO().setContent(content); | 99 | return new AiChatMessageRespVO().setContent(content); |
| 100 | } | 100 | } |
| @@ -123,8 +123,7 @@ public class AiChatServiceImpl implements AiChatService { | @@ -123,8 +123,7 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 123 | return insertChatMessageDO; | 123 | return insertChatMessageDO; |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | - @Override | ||
| 127 | - public void chatStream(AiChatMessageSendReqVO req, Utf8SseEmitter sseEmitter) { | 126 | + public Flux<AiChatMessageRespVO> chatStream(AiChatMessageSendReqVO req) { |
| 128 | Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); | 127 | Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); |
| 129 | // 查询对话 | 128 | // 查询对话 |
| 130 | AiChatConversationRespVO conversation = chatConversationService.getConversationOfValidate(req.getConversationId()); | 129 | AiChatConversationRespVO conversation = chatConversationService.getConversationOfValidate(req.getConversationId()); |
| @@ -145,46 +144,42 @@ public class AiChatServiceImpl implements AiChatService { | @@ -145,46 +144,42 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 145 | // req.setTopP(req.getTopP()); | 144 | // req.setTopP(req.getTopP()); |
| 146 | // req.setTemperature(req.getTemperature()); | 145 | // req.setTemperature(req.getTemperature()); |
| 147 | // 保存 chat message | 146 | // 保存 chat message |
| 148 | - // 保存 chat message | ||
| 149 | insertChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(), | 147 | insertChatMessage(conversation.getId(), MessageType.USER, loginUserId, conversation.getRoleId(), |
| 150 | chatModal.getModel(), chatModal.getId(), req.getContent(), | 148 | chatModal.getModel(), chatModal.getId(), req.getContent(), |
| 151 | null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); | 149 | null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); |
| 152 | - | ||
| 153 | // 获取 client 类型 | 150 | // 获取 client 类型 |
| 154 | AiPlatformEnum platformEnum = AiPlatformEnum.valueOfPlatform(chatModal.getPlatform()); | 151 | AiPlatformEnum platformEnum = AiPlatformEnum.valueOfPlatform(chatModal.getPlatform()); |
| 155 | StreamingChatClient streamingChatClient = aiChatClientFactory.getStreamingChatClient(platformEnum); | 152 | StreamingChatClient streamingChatClient = aiChatClientFactory.getStreamingChatClient(platformEnum); |
| 156 | Flux<ChatResponse> streamResponse = streamingChatClient.stream(prompt); | 153 | Flux<ChatResponse> streamResponse = streamingChatClient.stream(prompt); |
| 157 | - | 154 | + // 转换 flex AiChatMessageRespVO |
| 158 | StringBuffer contentBuffer = new StringBuffer(); | 155 | StringBuffer contentBuffer = new StringBuffer(); |
| 159 | - streamResponse.subscribe( | ||
| 160 | - new Consumer<ChatResponse>() { | ||
| 161 | - @Override | ||
| 162 | - public void accept(ChatResponse chatResponse) { | ||
| 163 | - String content = chatResponse.getResults().get(0).getOutput().getContent(); | ||
| 164 | - try { | ||
| 165 | - contentBuffer.append(content); | ||
| 166 | - sseEmitter.send(new AiChatMessageRespVO().setContent(content), MediaType.APPLICATION_JSON); | ||
| 167 | - } catch (IOException e) { | ||
| 168 | - log.error("发送异常{}", ExceptionUtil.getMessage(e)); | ||
| 169 | - // 如果不是因为关闭而抛出异常,则重新连接 | ||
| 170 | - sseEmitter.completeWithError(e); | ||
| 171 | - } | ||
| 172 | - } | ||
| 173 | - }, | ||
| 174 | - error -> { | ||
| 175 | - // | ||
| 176 | - log.error("subscribe错误 {}", ExceptionUtil.getMessage(error)); | ||
| 177 | - }, | ||
| 178 | - () -> { | ||
| 179 | - log.info("发送完成!"); | ||
| 180 | - sseEmitter.complete(); | ||
| 181 | - // 保存 chat message | ||
| 182 | - insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), | ||
| 183 | - chatModal.getModel(), chatModal.getId(), contentBuffer.toString(), | ||
| 184 | - null, conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); | ||
| 185 | - | 156 | + AtomicInteger tokens = new AtomicInteger(0); |
| 157 | + return streamResponse.map(res -> { | ||
| 158 | + AiChatMessageRespVO aiChatMessageRespVO = new AiChatMessageRespVO(); | ||
| 159 | + aiChatMessageRespVO.setContent(res.getResult().getOutput().getContent()); | ||
| 160 | + contentBuffer.append(res.getResult().getOutput().getContent()); | ||
| 161 | + tokens.incrementAndGet(); | ||
| 162 | + return aiChatMessageRespVO; | ||
| 186 | } | 163 | } |
| 187 | - ); | 164 | + ).doOnComplete(new Runnable() { |
| 165 | + @Override | ||
| 166 | + public void run() { | ||
| 167 | + log.info("发送完成!"); | ||
| 168 | + // 保存 chat message | ||
| 169 | + insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), | ||
| 170 | + chatModal.getModel(), chatModal.getId(), contentBuffer.toString(), | ||
| 171 | + tokens.get(), conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); | ||
| 172 | + } | ||
| 173 | + }).doOnError(new Consumer<Throwable>() { | ||
| 174 | + @Override | ||
| 175 | + public void accept(Throwable throwable) { | ||
| 176 | + log.error("发送错误 {}!", throwable.getMessage()); | ||
| 177 | + // 保存 chat message | ||
| 178 | + insertChatMessage(conversation.getId(), MessageType.SYSTEM, loginUserId, conversation.getRoleId(), | ||
| 179 | + chatModal.getModel(), chatModal.getId(), throwable.getMessage(), | ||
| 180 | + tokens.get(), conversation.getTemperature(), conversation.getMaxTokens(), conversation.getMaxContexts()); | ||
| 181 | + } | ||
| 182 | + }); | ||
| 188 | } | 183 | } |
| 189 | 184 | ||
| 190 | @Override | 185 | @Override |
| @@ -194,7 +189,7 @@ public class AiChatServiceImpl implements AiChatService { | @@ -194,7 +189,7 @@ public class AiChatServiceImpl implements AiChatService { | ||
| 194 | // 获取对话所有 message | 189 | // 获取对话所有 message |
| 195 | List<AiChatMessageDO> aiChatMessageDOList = aiChatMessageMapper.selectByConversationId(conversationId); | 190 | List<AiChatMessageDO> aiChatMessageDOList = aiChatMessageMapper.selectByConversationId(conversationId); |
| 196 | // 转换 AiChatMessageRespVO | 191 | // 转换 AiChatMessageRespVO |
| 197 | - return AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVOList(aiChatMessageDOList); | 192 | + return AiChatMessageConvert.INSTANCE.convertAiChatMessageRespVOList(aiChatMessageDOList); |
| 198 | } | 193 | } |
| 199 | 194 | ||
| 200 | @Override | 195 | @Override |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/impl/AiImageServiceImpl.java
| @@ -5,8 +5,8 @@ import cn.iocoder.yudao.framework.ai.image.ImageGeneration; | @@ -5,8 +5,8 @@ import cn.iocoder.yudao.framework.ai.image.ImageGeneration; | ||
| 5 | import cn.iocoder.yudao.framework.ai.image.ImagePrompt; | 5 | import cn.iocoder.yudao.framework.ai.image.ImagePrompt; |
| 6 | import cn.iocoder.yudao.framework.ai.image.ImageResponse; | 6 | import cn.iocoder.yudao.framework.ai.image.ImageResponse; |
| 7 | import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageClient; | 7 | import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageClient; |
| 8 | -import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageModelEnum; | ||
| 9 | import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions; | 8 | import cn.iocoder.yudao.framework.ai.imageopenai.OpenAiImageOptions; |
| 9 | +import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageModelEnum; | ||
| 10 | import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageStyleEnum; | 10 | import cn.iocoder.yudao.framework.ai.imageopenai.enums.OpenAiImageStyleEnum; |
| 11 | import cn.iocoder.yudao.framework.ai.midjourney.api.MidjourneyInteractionsApi; | 11 | import cn.iocoder.yudao.framework.ai.midjourney.api.MidjourneyInteractionsApi; |
| 12 | import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyWebSocketStarter; | 12 | import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyWebSocketStarter; |
| @@ -14,22 +14,18 @@ import cn.iocoder.yudao.framework.ai.midjourney.webSocket.WssNotify; | @@ -14,22 +14,18 @@ import cn.iocoder.yudao.framework.ai.midjourney.webSocket.WssNotify; | ||
| 14 | import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; | 14 | import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; |
| 15 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | 15 | import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; |
| 16 | import cn.iocoder.yudao.module.ai.ErrorCodeConstants; | 16 | import cn.iocoder.yudao.module.ai.ErrorCodeConstants; |
| 17 | -import cn.iocoder.yudao.module.ai.controller.Utf8SseEmitter; | 17 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReq; |
| 18 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReq; | ||
| 18 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; | 19 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 19 | -import cn.iocoder.yudao.module.ai.enums.AiChatDrawingStatusEnum; | ||
| 20 | import cn.iocoder.yudao.module.ai.dal.mysql.AiImageMapper; | 20 | import cn.iocoder.yudao.module.ai.dal.mysql.AiImageMapper; |
| 21 | +import cn.iocoder.yudao.module.ai.enums.AiChatDrawingStatusEnum; | ||
| 21 | import cn.iocoder.yudao.module.ai.service.AiImageService; | 22 | import cn.iocoder.yudao.module.ai.service.AiImageService; |
| 22 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallDrawingReq; | ||
| 23 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReq; | ||
| 24 | import jakarta.annotation.PostConstruct; | 23 | import jakarta.annotation.PostConstruct; |
| 25 | import lombok.AllArgsConstructor; | 24 | import lombok.AllArgsConstructor; |
| 26 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
| 27 | -import org.springframework.http.MediaType; | ||
| 28 | import org.springframework.stereotype.Service; | 26 | import org.springframework.stereotype.Service; |
| 29 | import org.springframework.transaction.annotation.Transactional; | 27 | import org.springframework.transaction.annotation.Transactional; |
| 30 | 28 | ||
| 31 | -import java.io.IOException; | ||
| 32 | - | ||
| 33 | /** | 29 | /** |
| 34 | * ai 作图 | 30 | * ai 作图 |
| 35 | * | 31 | * |
| @@ -64,7 +60,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -64,7 +60,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 64 | } | 60 | } |
| 65 | 61 | ||
| 66 | @Override | 62 | @Override |
| 67 | - public void dallDrawing(AiImageDallDrawingReq req, Utf8SseEmitter sseEmitter) { | 63 | + public void dallDrawing(AiImageDallDrawingReq req) { |
| 68 | // 获取 model | 64 | // 获取 model |
| 69 | OpenAiImageModelEnum openAiImageModelEnum = OpenAiImageModelEnum.valueOfModel(req.getModal()); | 65 | OpenAiImageModelEnum openAiImageModelEnum = OpenAiImageModelEnum.valueOfModel(req.getModal()); |
| 70 | OpenAiImageStyleEnum openAiImageStyleEnum = OpenAiImageStyleEnum.valueOfStyle(req.getStyle()); | 66 | OpenAiImageStyleEnum openAiImageStyleEnum = OpenAiImageStyleEnum.valueOfStyle(req.getStyle()); |
| @@ -79,7 +75,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -79,7 +75,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 79 | // 发送 | 75 | // 发送 |
| 80 | ImageGeneration imageGeneration = imageResponse.getResult(); | 76 | ImageGeneration imageGeneration = imageResponse.getResult(); |
| 81 | // 发送信息 | 77 | // 发送信息 |
| 82 | - sendSseEmitter(sseEmitter, imageGeneration); | 78 | +// sendSseEmitter(sseEmitter, imageGeneration); |
| 83 | // 保存数据库 | 79 | // 保存数据库 |
| 84 | doSave(req.getPrompt(), req.getSize(), req.getModal(), | 80 | doSave(req.getPrompt(), req.getSize(), req.getModal(), |
| 85 | imageGeneration.getOutput().getUrl(), AiChatDrawingStatusEnum.COMPLETE, null); | 81 | imageGeneration.getOutput().getUrl(), AiChatDrawingStatusEnum.COMPLETE, null); |
| @@ -88,7 +84,7 @@ public class AiImageServiceImpl implements AiImageService { | @@ -88,7 +84,7 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 88 | doSave(req.getPrompt(), req.getSize(), req.getModal(), | 84 | doSave(req.getPrompt(), req.getSize(), req.getModal(), |
| 89 | null, AiChatDrawingStatusEnum.FAIL, aiException.getMessage()); | 85 | null, AiChatDrawingStatusEnum.FAIL, aiException.getMessage()); |
| 90 | // 发送错误信息 | 86 | // 发送错误信息 |
| 91 | - sendSseEmitter(sseEmitter, aiException.getMessage()); | 87 | +// sendSseEmitter(sseEmitter, aiException.getMessage()); |
| 92 | } | 88 | } |
| 93 | } | 89 | } |
| 94 | 90 | ||
| @@ -105,16 +101,16 @@ public class AiImageServiceImpl implements AiImageService { | @@ -105,16 +101,16 @@ public class AiImageServiceImpl implements AiImageService { | ||
| 105 | } | 101 | } |
| 106 | } | 102 | } |
| 107 | 103 | ||
| 108 | - private static void sendSseEmitter(Utf8SseEmitter sseEmitter, Object object) { | ||
| 109 | - try { | ||
| 110 | - sseEmitter.send(object, MediaType.APPLICATION_JSON); | ||
| 111 | - } catch (IOException e) { | ||
| 112 | - throw new RuntimeException(e); | ||
| 113 | - } finally { | ||
| 114 | - // 发送 complete | ||
| 115 | - sseEmitter.complete(); | ||
| 116 | - } | ||
| 117 | - } | 104 | +// private static void sendSseEmitter(Utf8SseEmitter sseEmitter, Object object) { |
| 105 | +// try { | ||
| 106 | +// sseEmitter.send(object, MediaType.APPLICATION_JSON); | ||
| 107 | +// } catch (IOException e) { | ||
| 108 | +// throw new RuntimeException(e); | ||
| 109 | +// } finally { | ||
| 110 | +// // 发送 complete | ||
| 111 | +// sseEmitter.complete(); | ||
| 112 | +// } | ||
| 113 | +// } | ||
| 118 | 114 | ||
| 119 | private AiImageDO doSave(String prompt, | 115 | private AiImageDO doSave(String prompt, |
| 120 | String size, | 116 | String size, |
yudao-server/src/main/resources/application-local.yaml
| @@ -2,7 +2,6 @@ server: | @@ -2,7 +2,6 @@ server: | ||
| 2 | port: 48080 | 2 | port: 48080 |
| 3 | 3 | ||
| 4 | --- #################### 数据库相关配置 #################### | 4 | --- #################### 数据库相关配置 #################### |
| 5 | - | ||
| 6 | spring: | 5 | spring: |
| 7 | # 数据源配置项 | 6 | # 数据源配置项 |
| 8 | autoconfigure: | 7 | autoconfigure: |
| @@ -79,7 +78,12 @@ spring: | @@ -79,7 +78,12 @@ spring: | ||
| 79 | port: 6379 # 端口 | 78 | port: 6379 # 端口 |
| 80 | database: 0 # 数据库索引 | 79 | database: 0 # 数据库索引 |
| 81 | # password: dev # 密码,建议生产环境开启 | 80 | # password: dev # 密码,建议生产环境开启 |
| 82 | - | 81 | +server: |
| 82 | + servlet: | ||
| 83 | + encoding: | ||
| 84 | + enabled: true | ||
| 85 | + charset: UTF-8 | ||
| 86 | + force: true | ||
| 83 | --- #################### 定时任务相关配置 #################### | 87 | --- #################### 定时任务相关配置 #################### |
| 84 | 88 | ||
| 85 | # Quartz 配置项,对应 QuartzProperties 配置类 | 89 | # Quartz 配置项,对应 QuartzProperties 配置类 |