From 32bc632947783ac18dd8618d57e216ac6174892c Mon Sep 17 00:00:00 2001 From: cherishsince Date: Tue, 9 Apr 2024 20:12:41 +0800 Subject: [PATCH] 处理todo --- yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java | 28 ---------------------------- yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/OpenAiModelEnum.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/ChatController.java | 41 +++++++---------------------------------- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/vo/AiChatReqVO.java | 20 +++++++------------- yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml | 4 ++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java | 64 ---------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java | 1 + yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java | 3 ++- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MjMessage.java | 125 ----------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java | 41 +++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java | 28 ++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java | 26 ++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java | 14 ++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java | 41 ----------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjGennerateStatusEnum.java | 30 ------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjInteractionsEnum.java | 28 ---------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjMessageTypeEnum.java | 26 -------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjNotifyCode.java | 14 -------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java | 193 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java | 83 ----------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java | 219 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MjWebSocketStarter.java | 220 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MjWebSocketHandler.java | 281 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MjMessageListener.java | 83 ----------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/util/JacksonUtil.java | 80 -------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java | 3 +-- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java | 22 ++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java | 73 ------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjUtilTests.java | 22 ---------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjWebSocketTests.java | 51 --------------------------------------------------- 39 files changed, 1389 insertions(+), 1492 deletions(-) delete mode 100644 yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java create mode 100644 yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/OpenAiModelEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MjMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjGennerateStatusEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjInteractionsEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjMessageTypeEnum.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjNotifyCode.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MjWebSocketStarter.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MjWebSocketHandler.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MjMessageListener.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/util/JacksonUtil.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjUtilTests.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjWebSocketTests.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java deleted file mode 100644 index 7eac2b6..0000000 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.ai.enums; - -import lombok.Getter; - -// TODO @fansili:1)类注释要加下;2)author 和 time 用 javadoc,@author 和 @since;3)@AllArgsConstructor 使用这个注解,去掉构造方法;4)value 改成 model 字段,然后注释都写下哈;5)message 改成 name,然后注释都写下哈 -/** - * author: fansili - * time: 2024/3/4 12:36 - */ -@Getter -public enum AiModelEnum { - - OPEN_AI_GPT_3_5("gpt-3.5-turbo", "GPT3.5"), - OPEN_AI_GPT_4("gpt-4-turbo", "GPT4") - - ; - - AiModelEnum(String value, String message) { - this.value = value; - this.message = message; - } - - // TODO @fan - private String value; - - private String message; - -} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/OpenAiModelEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/OpenAiModelEnum.java new file mode 100644 index 0000000..3daa3f5 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/OpenAiModelEnum.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.ai.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +// TODO done @fansili:1)类注释要加下;2)author 和 time 用 javadoc,@author 和 @since;3)@AllArgsConstructor 使用这个注解,去掉构造方法;4)value 改成 model 字段,然后注释都写下哈;5)message 改成 name,然后注释都写下哈 +/** + * @author: fansili + * @time: 2024/3/4 12:36 + */ +@Getter +@AllArgsConstructor +public enum OpenAiModelEnum { + + /** + * open ai 3.5模型 + */ + OPEN_AI_GPT_3_5("gpt-3.5-turbo", "GPT3.5"), + /** + * open ai 4.0 收费模型 + */ + OPEN_AI_GPT_4("gpt-4-turbo", "GPT4") + + ; + + /** + * 模型 - 用于参数传递 + */ + private String model; + /** + * 模型名字 - 用于展示 + */ + private String name; + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/ChatController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/ChatController.java index a6300a5..e9f8ab6 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/ChatController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/ChatController.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.ai.ErrorCodeConstants; import cn.iocoder.yudao.module.ai.controller.admin.vo.AiChatReqVO; -import cn.iocoder.yudao.module.ai.enums.AiModelEnum; +import cn.iocoder.yudao.module.ai.enums.OpenAiModelEnum; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; @@ -13,7 +13,6 @@ import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.openai.OpenAiChatClient; -import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.validation.annotation.Validated; @@ -23,16 +22,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; -import java.util.Scanner; import java.util.function.Consumer; -// TODO @fansili:有了 swagger 注释,就不用类注释了 -/** - * AI模块 - * - * author: fansili - * time: 2024/3/3 20:28 - */ +// TODO done @fansili:有了 swagger 注释,就不用类注释了 @Tag(name = "AI模块") @RestController @RequestMapping("/ai-api") @@ -48,7 +40,7 @@ public class ChatController { ChatClient chatClient = getChatClient(reqVO.getAiModel()); String res; try { - res = chatClient.call(reqVO.getInputText()); + res = chatClient.call(reqVO.getPrompt()); } catch (Exception e) { res = e.getMessage(); } @@ -59,33 +51,14 @@ public class ChatController { @Operation(summary = "对话聊天chatStream", description = "简单的ai聊天") public CommonResult chatStream(HttpServletResponse response, @RequestBody @Validated AiChatReqVO reqVO) throws InterruptedException { OpenAiChatClient chatClient = applicationContext.getBean(OpenAiChatClient.class); - Flux chatResponse = chatClient.stream(new Prompt(reqVO.getInputText())); + Flux chatResponse = chatClient.stream(new Prompt(reqVO.getPrompt())); chatResponse.subscribe(new Consumer() { @Override public void accept(ChatResponse chatResponse) { System.err.println(chatResponse.getResults().get(0).getOutput().getContent()); } }); - return CommonResult.success("1"); - } - - public static void main(String[] args) { - OpenAiChatClient openAiChatClient = new OpenAiChatClient(new OpenAiApi("openkey")); - Flux responseFlux = openAiChatClient.stream(new Prompt("最好的编程语言!")); - long now = System.currentTimeMillis(); - responseFlux.subscribe(new Consumer() { - @Override - public void accept(ChatResponse chatResponse) { - if (chatResponse.getResults().get(0).getOutput() == null) { - return; - } - System.err.println(chatResponse.getResults().get(0).getOutput().getContent()); - } - }); - - // 阻止退出 - Scanner scanner = new Scanner(System.in); - scanner.nextLine(); + return CommonResult.success(null); } /** @@ -94,8 +67,8 @@ public class ChatController { * @param aiModelEnum * @return */ - private ChatClient getChatClient(AiModelEnum aiModelEnum) { - if (AiModelEnum.OPEN_AI_GPT_3_5 == aiModelEnum) { + private ChatClient getChatClient(OpenAiModelEnum aiModelEnum) { + if (OpenAiModelEnum.OPEN_AI_GPT_3_5 == aiModelEnum) { return applicationContext.getBean(OpenAiChatClient.class); } // AI模型暂不支持 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/vo/AiChatReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/vo/AiChatReqVO.java index 187f517..643b660 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/vo/AiChatReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/vo/AiChatReqVO.java @@ -1,27 +1,21 @@ package cn.iocoder.yudao.module.ai.controller.admin.vo; -import cn.iocoder.yudao.module.ai.enums.AiModelEnum; +import cn.iocoder.yudao.module.ai.enums.OpenAiModelEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; -// TODO @fansili 1)swagger 注释不太对;2)有了 swagger 注释,就不用类注释了 -/** - * ai 聊天 req - * - * author: fansili - * time: 2024/3/4 12:33 - */ -@Schema(description = "用户 App - 上传文件 Request VO") +// TODO done @fansili 1)swagger 注释不太对;2)有了 swagger 注释,就不用类注释了 @Data +@Schema(description = "用户 App - 上传文件 Request VO") public class AiChatReqVO { - @Schema(description = "输入内容", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "输入内容不能为空") - private String inputText; + @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "提示词不能为空!") + private String prompt; @Schema(description = "AI模型", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "AI模型不能为空") - private AiModelEnum aiModel; + private OpenAiModelEnum aiModel; } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index 6e3f2b2..81d303a 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -149,6 +149,10 @@ + + cn.iocoder.boot + yudao-common + \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java deleted file mode 100644 index 77d4411..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java +++ /dev/null @@ -1,64 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatqianwen; - -import com.aliyun.broadscope.bailian.sdk.AccessTokenClient; -import com.aliyun.broadscope.bailian.sdk.ApplicationClient; -import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest; -import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; -import lombok.Getter; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import reactor.core.publisher.Flux; - -// TODO @fansili:是不是挪到 api 包里?按照 spring ai 的结构;根目录只放 client 和 options -/** - * 阿里 通义千问 - * - * https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all - * - * author: fansili - * time: 2024/3/13 21:09 - */ -@Getter -public class QianWenApi { - - /** - * accessKeyId、accessKeySecret、agentKey、appId 获取方式如下链接 - * https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp - */ - private String accessKeyId; - private String accessKeySecret; - private String agentKey; - private String appId; - private String endpoint = "bailian.cn-beijing.aliyuncs.com"; - private String token; - private ApplicationClient client; - - public QianWenApi(String accessKeyId, String accessKeySecret, String agentKey, String endpoint) { - this.accessKeyId = accessKeyId; - this.accessKeySecret = accessKeySecret; - this.agentKey = agentKey; - - if (endpoint != null) { - this.endpoint = endpoint; - } - - // 获取token - AccessTokenClient accessTokenClient = new AccessTokenClient(accessKeyId, accessKeySecret, agentKey); - token = accessTokenClient.getToken(); - // 构建client - client = ApplicationClient.builder() - .token(token) - .build(); - } - - public ResponseEntity chatCompletionEntity(CompletionsRequest request) { - // 发送请求 - CompletionsResponse response = client.completions(request); - // 阿里云的这个 http code 随便设置,外面判断是否成功用的 CompletionsResponse.isSuccess - return new ResponseEntity<>(response, HttpStatusCode.valueOf(200)); - } - - public Flux chatCompletionStream(CompletionsRequest request) { - return client.streamCompletions(request); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java index 62c9820..a8fb910 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.ai.chat.*; import cn.iocoder.yudao.framework.ai.chat.messages.MessageType; import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; import com.aliyun.broadscope.bailian.sdk.models.*; import lombok.extern.slf4j.Slf4j; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java new file mode 100644 index 0000000..c8170af --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.framework.ai.chatqianwen.api; + +import com.aliyun.broadscope.bailian.sdk.AccessTokenClient; +import com.aliyun.broadscope.bailian.sdk.ApplicationClient; +import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest; +import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; +import lombok.Getter; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Flux; + +// TODO done @fansili:是不是挪到 api 包里?按照 spring ai 的结构;根目录只放 client 和 options +/** + * 阿里 通义千问 + * + * https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all + * + * author: fansili + * time: 2024/3/13 21:09 + */ +@Getter +public class QianWenApi { + + /** + * accessKeyId、accessKeySecret、agentKey、appId 获取方式如下链接 + * https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp + */ + private String accessKeyId; + private String accessKeySecret; + private String agentKey; + private String appId; + private String endpoint = "bailian.cn-beijing.aliyuncs.com"; + private String token; + private ApplicationClient client; + + public QianWenApi(String accessKeyId, String accessKeySecret, String agentKey, String endpoint) { + this.accessKeyId = accessKeyId; + this.accessKeySecret = accessKeySecret; + this.agentKey = agentKey; + + if (endpoint != null) { + this.endpoint = endpoint; + } + + // 获取token + AccessTokenClient accessTokenClient = new AccessTokenClient(accessKeyId, accessKeySecret, agentKey); + token = accessTokenClient.getToken(); + // 构建client + client = ApplicationClient.builder() + .token(token) + .build(); + } + + public ResponseEntity chatCompletionEntity(CompletionsRequest request) { + // 发送请求 + CompletionsResponse response = client.completions(request); + // 阿里云的这个 http code 随便设置,外面判断是否成功用的 CompletionsResponse.isSuccess + return new ResponseEntity<>(response, HttpStatusCode.valueOf(200)); + } + + public Flux chatCompletionStream(CompletionsRequest request) { + return client.streamCompletions(request); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java index 0f651ac..6c5c8db 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/imageopenai/OpenAiImageApi.java @@ -4,6 +4,7 @@ import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageRequest; import cn.iocoder.yudao.framework.ai.imageopenai.api.OpenAiImageResponse; import cn.iocoder.yudao.framework.ai.util.JacksonUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import io.netty.channel.ChannelOption; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; @@ -55,7 +56,7 @@ public class OpenAiImageApi { httpPost.setURI(URI.create(DEFAULT_BASE_URL.concat("/v1/images/generations"))); httpPost.setHeader("Content-Type", "application/json"); httpPost.setHeader("Authorization", "Bearer " + apiKey); - httpPost.setEntity(new StringEntity(JacksonUtil.toJson(request), "UTF-8")); + httpPost.setEntity(new StringEntity(JsonUtils.toJsonString(request), "UTF-8")); CloseableHttpResponse response= null; try { diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java new file mode 100644 index 0000000..f52ba93 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyMessage.java @@ -0,0 +1,126 @@ +package cn.iocoder.yudao.framework.ai.midjourney; + +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyGennerateStatusEnum; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +@Data +@Accessors(chain = true) +public class MidjourneyMessage { + + /** + * id是一个重要的字段,在同时生成多个的时候,可以区分生成信息 + */ + private String id; + /** + * 现在已知: + * 0:我们发送的消息,和指令 + * 20: mj生成图片发送过程中 + * 19: 选择了某一张图片后的通知 + */ + private Integer type; + /** + * content + */ + private Content content; + /** + * 图片生成完成才有 + */ + private List components; + /** + * 生成过程中如果有,预展示图片,这里会有 + */ + private List attachments; + /** + * 原始数据(discard 返回的原始数据) + */ + private String rawData; + /** + * 生成状态(用于区分生成状态) + * 1、等待 + * 2、进行中 + * 3、完成 + * {@link MidjourneyGennerateStatusEnum} + */ + private String generateStatus; + + @Data + @Accessors(chain = true) + public static class ComponentType { + + private int type; + + private List components; + } + + @Data + @Accessors(chain = true) + public static class Component { + /** + * 自定义ID,用于唯一标识特定交互动作及其上下文信息。 + */ + private String customId; + + /** + * 样式编号,用于确定按钮的样式外观。 + * 在某些应用中,例如Discord,2可能表示一种特定的颜色或形状的按钮。 + */ + private int style; + + /** + * 按钮的标签文本,用户可见的内容。 + */ + private String label; + + /** + * 组件类型,此处为2可能表示这是一种特定类型的交互组件, + * 如在Discord API中,类型2对应的是一个可点击的按钮组件。 + */ + private int type; + + } + + @Data + @Accessors(chain = true) + public static class Attachment { + // 文件名 + private String filename; + + // 附件大小(字节) + private int size; + + // 内容类型(例如:image/webp) + private String contentType; + + // 图像宽度(像素) + private int width; + + // 占位符版本号 + private int placeholderVersion; + + // 代理URL,用于访问附件资源 + private String proxyUrl; + + // 占位符标识符 + private String placeholder; + + // 附件ID + private String id; + + // 直接访问附件资源的URL + private String url; + + // 图像高度(像素) + private int height; + } + + @Data + @Accessors(chain = true) + public static class Content { + private String prompt; + private String progress; + private String status; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MjMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MjMessage.java deleted file mode 100644 index 2160e8e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MjMessage.java +++ /dev/null @@ -1,125 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney; - -import lombok.Data; -import lombok.experimental.Accessors; - -import java.util.List; - -@Data -@Accessors(chain = true) -public class MjMessage { - - /** - * id是一个重要的字段,在同时生成多个的时候,可以区分生成信息 - */ - private String id; - /** - * 现在已知: - * 0:我们发送的消息,和指令 - * 20: mj生成图片发送过程中 - * 19: 选择了某一张图片后的通知 - */ - private Integer type; - /** - * content - */ - private Content content; - /** - * 图片生成完成才有 - */ - private List components; - /** - * 生成过程中如果有,预展示图片,这里会有 - */ - private List attachments; - /** - * 原始数据(discard 返回的原始数据) - */ - private String rawData; - /** - * 生成状态(用于区分生成状态) - * 1、等待 - * 2、进行中 - * 3、完成 - * {@link cn.iocoder.yudao.framework.ai.midjourney.constants.MjGennerateStatusEnum} - */ - private String generateStatus; - - @Data - @Accessors(chain = true) - public static class ComponentType { - - private int type; - - private List components; - } - - @Data - @Accessors(chain = true) - public static class Component { - /** - * 自定义ID,用于唯一标识特定交互动作及其上下文信息。 - */ - private String customId; - - /** - * 样式编号,用于确定按钮的样式外观。 - * 在某些应用中,例如Discord,2可能表示一种特定的颜色或形状的按钮。 - */ - private int style; - - /** - * 按钮的标签文本,用户可见的内容。 - */ - private String label; - - /** - * 组件类型,此处为2可能表示这是一种特定类型的交互组件, - * 如在Discord API中,类型2对应的是一个可点击的按钮组件。 - */ - private int type; - - } - - @Data - @Accessors(chain = true) - public static class Attachment { - // 文件名 - private String filename; - - // 附件大小(字节) - private int size; - - // 内容类型(例如:image/webp) - private String contentType; - - // 图像宽度(像素) - private int width; - - // 占位符版本号 - private int placeholderVersion; - - // 代理URL,用于访问附件资源 - private String proxyUrl; - - // 占位符标识符 - private String placeholder; - - // 附件ID - private String id; - - // 直接访问附件资源的URL - private String url; - - // 图像高度(像素) - private int height; - } - - @Data - @Accessors(chain = true) - public static class Content { - private String prompt; - private String progress; - private String status; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java new file mode 100644 index 0000000..ee180a0 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyConstants.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.framework.ai.midjourney.constants; + +public final class MidjourneyConstants { + + /** + * 消息 - 编号 + */ + public static final String MSG_ID = "id"; + /** + * 消息 - 类型 + * 现在已知: + * 0:我们发送的消息,和指令 + * 20: mj生成图片发送过程中 + * 19: 选择了某一张图片后的通知 + */ + public static final String MSG_TYPE = "type"; + /** + * 平道id + */ + public static final String MSG_CHANNEL_ID = "channel_id"; + /** + * 内容 + * + * "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", + */ + public static final String MSG_CONTENT = "content"; + /** + * 组件(图片生成好之后才有) + */ + public static final String MSG_COMPONENTS = "components"; + /** + * 附件(生成中比较模糊的图片) + */ + public static final String MSG_ATTACHMENTS = "attachments"; + + + // + // + + public static final String HTTP_COOKIE = "__dcfduid=6ca536c0e3fa11eeb7cbe34c31b49caf; __sdcfduid=6ca536c1e3fa11eeb7cbe34c31b49caf52cce5ffd8983d2a052cf6aba75fe5fe566f2c265902e283ce30dbf98b8c9c93; _gcl_au=1.1.245923998.1710853617; _ga=GA1.1.111061823.1710853617; __cfruid=6385bb3f48345a006b25992db7dcf984e395736d-1712124666; _cfuvid=O09la5ms0ypNptiG0iD8A6BKWlTxz1LG0WR7qRStD7o-1712124666575-0.0.1.1-604800000; locale=zh-CN; cf_clearance=l_YGod1_SUtYxpDVeZXiX7DLLPl1DYrquZe8WVltvYs-1712124668-1.0.1.1-Hl2.fToel23EpF2HCu9J20rB4D7OhhCzoajPSdo.9Up.wPxhvq22DP9RHzEBKuIUlKyH.kJLxXJfAt2N.LD5WQ; OptanonConsent=isIABGlobal=false&datestamp=Wed+Apr+03+2024+14%3A11%3A15+GMT%2B0800+(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&version=6.33.0&hosts=&landingPath=https%3A%2F%2Fdiscord.com%2F&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1; _ga_Q149DFWHT7=GS1.1.1712124668.4.1.1712124679.0.0.0"; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java new file mode 100644 index 0000000..ef7147b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyGennerateStatusEnum.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.framework.ai.midjourney.constants; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +// TODO done @fansili:1)Mj 缩写,还是搞成全称。。虽然长一点,但是感觉会相对清晰一些哈;2)lombok 相关的注解,可以用用哈;3)value 改 status; +/** + * mj 生成状态 + * + * author: fansili + * time: 2024/4/6 21:07 + */ +@Getter +@AllArgsConstructor +public enum MidjourneyGennerateStatusEnum { + + WAITING("waiting", "等待..."), + IN_PROGRESS("in_progress", "进行中"), + COMPLETED("completed", "完成"), + + ; + + /** + * 状态 + */ + private String status; + /** + * 状态信息 + */ + private String message; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java new file mode 100644 index 0000000..08832e7 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyInteractionsEnum.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.framework.ai.midjourney.constants; + +import lombok.Getter; + +/** + * MJ 命令 + */ +@Getter +public enum MidjourneyInteractionsEnum { + + IMAGINE("imagine", "生成图片"), + DESCRIBE("describe", "生成描述"), + FAST("fast", "快速生成"), + SETTINGS("settings", "设置"), + ASK("ask", "提问"), + BLEND("blend", "融合"), + + ; + + MidjourneyInteractionsEnum(String value, String message) { + this.value =value; + this.message =message; + } + + private String value; + private String message; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java new file mode 100644 index 0000000..9c8eb9e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyMessageTypeEnum.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.framework.ai.midjourney.constants; + + +public enum MidjourneyMessageTypeEnum { + /** + * 创建. + */ + CREATE, + /** + * 修改. + */ + UPDATE, + /** + * 删除. + */ + DELETE; + + public static MidjourneyMessageTypeEnum of(String type) { + return switch (type) { + case "MESSAGE_CREATE" -> CREATE; + case "MESSAGE_UPDATE" -> UPDATE; + case "MESSAGE_DELETE" -> DELETE; + default -> null; + }; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java new file mode 100644 index 0000000..1dccb8a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MidjourneyNotifyCode.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.framework.ai.midjourney.constants; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public final class MidjourneyNotifyCode { + /** + * 成功. + */ + public static final int SUCCESS = 1; + + + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java deleted file mode 100644 index 751652b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -public final class MjConstants { - - /** - * 消息 - 编号 - */ - public static final String MSG_ID = "id"; - /** - * 消息 - 类型 - * 现在已知: - * 0:我们发送的消息,和指令 - * 20: mj生成图片发送过程中 - * 19: 选择了某一张图片后的通知 - */ - public static final String MSG_TYPE = "type"; - /** - * 平道id - */ - public static final String MSG_CHANNEL_ID = "channel_id"; - /** - * 内容 - * - * "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", - */ - public static final String MSG_CONTENT = "content"; - /** - * 组件(图片生成好之后才有) - */ - public static final String MSG_COMPONENTS = "components"; - /** - * 附件(生成中比较模糊的图片) - */ - public static final String MSG_ATTACHMENTS = "attachments"; - - - // - // - - public static final String HTTP_COOKIE = "__dcfduid=6ca536c0e3fa11eeb7cbe34c31b49caf; __sdcfduid=6ca536c1e3fa11eeb7cbe34c31b49caf52cce5ffd8983d2a052cf6aba75fe5fe566f2c265902e283ce30dbf98b8c9c93; _gcl_au=1.1.245923998.1710853617; _ga=GA1.1.111061823.1710853617; __cfruid=6385bb3f48345a006b25992db7dcf984e395736d-1712124666; _cfuvid=O09la5ms0ypNptiG0iD8A6BKWlTxz1LG0WR7qRStD7o-1712124666575-0.0.1.1-604800000; locale=zh-CN; cf_clearance=l_YGod1_SUtYxpDVeZXiX7DLLPl1DYrquZe8WVltvYs-1712124668-1.0.1.1-Hl2.fToel23EpF2HCu9J20rB4D7OhhCzoajPSdo.9Up.wPxhvq22DP9RHzEBKuIUlKyH.kJLxXJfAt2N.LD5WQ; OptanonConsent=isIABGlobal=false&datestamp=Wed+Apr+03+2024+14%3A11%3A15+GMT%2B0800+(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&version=6.33.0&hosts=&landingPath=https%3A%2F%2Fdiscord.com%2F&groups=C0001%3A1%2CC0002%3A1%2CC0003%3A1; _ga_Q149DFWHT7=GS1.1.1712124668.4.1.1712124679.0.0.0"; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjGennerateStatusEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjGennerateStatusEnum.java deleted file mode 100644 index 1e8bc6f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjGennerateStatusEnum.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -import lombok.Getter; - -// TODO @fansili:1)Mj 缩写,还是搞成全称。。虽然长一点,但是感觉会相对清晰一些哈;2)lombok 相关的注解,可以用用哈;3)value 改 status; -/** - * mj 生成状态 - * - * author: fansili - * time: 2024/4/6 21:07 - */ -@Getter -public enum MjGennerateStatusEnum { - - - WAITING("waiting", "等待..."), - IN_PROGRESS("in_progress", "进行中"), - COMPLETED("completed", "完成"), - - ; - - MjGennerateStatusEnum(String value, String message) { - this.value = value; - this.message = message; - } - - private String value; - - private String message; -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjInteractionsEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjInteractionsEnum.java deleted file mode 100644 index 124bc83..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjInteractionsEnum.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -import lombok.Getter; - -/** - * MJ 命令 - */ -@Getter -public enum MjInteractionsEnum { - - IMAGINE("imagine", "生成图片"), - DESCRIBE("describe", "生成描述"), - FAST("fast", "快速生成"), - SETTINGS("settings", "设置"), - ASK("ask", "提问"), - BLEND("blend", "融合"), - - ; - - MjInteractionsEnum(String value, String message) { - this.value =value; - this.message =message; - } - - private String value; - private String message; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjMessageTypeEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjMessageTypeEnum.java deleted file mode 100644 index b47199e..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjMessageTypeEnum.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - - -public enum MjMessageTypeEnum { - /** - * 创建. - */ - CREATE, - /** - * 修改. - */ - UPDATE, - /** - * 删除. - */ - DELETE; - - public static MjMessageTypeEnum of(String type) { - return switch (type) { - case "MESSAGE_CREATE" -> CREATE; - case "MESSAGE_UPDATE" -> UPDATE; - case "MESSAGE_DELETE" -> DELETE; - default -> null; - }; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjNotifyCode.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjNotifyCode.java deleted file mode 100644 index 103bd6f..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjNotifyCode.java +++ /dev/null @@ -1,14 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.constants; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public final class MjNotifyCode { - /** - * 成功. - */ - public static final int SUCCESS = 1; - - - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java new file mode 100644 index 0000000..104e1dd --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MidjourneyInteractions.java @@ -0,0 +1,192 @@ +package cn.iocoder.yudao.framework.ai.midjourney.interactions; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyConstants; +import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; +import cn.iocoder.yudao.framework.ai.midjourney.vo.Attachments; +import cn.iocoder.yudao.framework.ai.midjourney.vo.Describe; +import cn.iocoder.yudao.framework.ai.midjourney.vo.ReRoll; +import cn.iocoder.yudao.framework.ai.midjourney.vo.UploadAttachmentsRes; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.HashMap; + +// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi +/** + * 图片生成 + * + * author: fansili + * time: 2024/4/3 17:36 + */ +@Slf4j +public class MidjourneyInteractions { + + // TODO done @fansili:静态变量,放在最前面哈; + private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s"; + + private final String url; + private final MidjourneyConfig midjourneyConfig; + private final RestTemplate restTemplate = new RestTemplate(); // TODO @fansili:优先级低:后续搞到统一的管理 + + + public MidjourneyInteractions(MidjourneyConfig midjourneyConfig) { + this.midjourneyConfig = midjourneyConfig; + this.url = midjourneyConfig.getServerUrl().concat(midjourneyConfig.getApiInteractions()); + } + + public Boolean imagine(String prompt) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("imagine"); + // 设置参数 + HashMap requestParams = Maps.newHashMap(); + // TODO @fansili:感觉参数的组装,可以搞成一个公用的方法;就是 config + 入参的感觉; + requestParams.put("guild_id", midjourneyConfig.getGuildId()); + requestParams.put("channel_id", midjourneyConfig.getChannelId()); + requestParams.put("session_id", midjourneyConfig.getSessionId()); + requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); // TODO @fansili:建议用 uuid 之类的;nextId 跨进程未必合适哈; + requestParams.put("prompt", prompt); + // 解析 template 参数占位符 + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 获取 header + HttpHeaders httpHeaders = getHttpHeaders(); + // 发送请求 + HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); + String res = restTemplate.postForObject(url, requestEntity, String.class); + // 这个 res 只要不返回值,就是成功! + // TODO @fansili:可以直接 if (StrUtil.isBlank(res)) + if (StrUtil.isBlank(res)) { + return true; + } else { + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return false; + } + } + // TODO done @fansili:方法和方法之间,空一行哈; + + + public Boolean reRoll(ReRoll reRoll) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll"); + // 设置参数 + HashMap requestParams = Maps.newHashMap(); + requestParams.put("guild_id", midjourneyConfig.getGuildId()); + requestParams.put("channel_id", midjourneyConfig.getChannelId()); + requestParams.put("session_id", midjourneyConfig.getSessionId()); + requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); + requestParams.put("custom_id", reRoll.getCustomId()); + requestParams.put("message_id", reRoll.getMessageId()); + // 获取 header + HttpHeaders httpHeaders = getHttpHeaders(); + // 设置参数 + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 发送请求 + HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); + String res = restTemplate.postForObject(url, requestEntity, String.class); + // 这个 res 只要不返回值,就是成功! + boolean isSuccess = StrUtil.isBlank(res); + if (isSuccess) { + return true; + } + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return isSuccess; + } + + // TODO @fansili:搞成私有方法,可能会好点; + public UploadAttachmentsRes uploadAttachments(Attachments attachments) { + // file + JSONObject fileObj = new JSONObject(); + fileObj.put("id", "0"); + fileObj.put("filename", attachments.getFileSystemResource().getFilename()); + // TODO @fansili:这块用 lombok 哪个异常处理,简化下代码; + try { + fileObj.put("file_size", attachments.getFileSystemResource().contentLength()); + } catch (IOException e) { + throw new RuntimeException(e); + } + // 创建用于存放表单数据的MultiValueMap + MultiValueMap multipartRequest = new LinkedMultiValueMap<>(); + multipartRequest.put("files", Lists.newArrayList(fileObj)); + // 设置header值 + HttpHeaders httpHeaders = new HttpHeaders(); + // TODO @fansili:通用的 header 构建,抽一个方法哈; + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + // 创建HttpEntity对象,包含表单数据和头部信息 + HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(multipartRequest, httpHeaders); + // 发送POST请求并接收响应 + String uri = String.format(midjourneyConfig.getApiAttachments(), midjourneyConfig.getChannelId()); + String response = restTemplate.postForObject(midjourneyConfig.getServerUrl().concat(uri), multiValueMapHttpEntity, String.class); + UploadAttachmentsRes uploadAttachmentsRes = JSON.parseObject(response, UploadAttachmentsRes.class); + + // + // 上传文件 + String uploadUrl = uploadAttachmentsRes.getAttachments().getFirst().getUploadUrl(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpEntity fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders); + ResponseEntity exchange = restTemplate.exchange(uploadUrl, HttpMethod.PUT, fileSystemResourceHttpEntity, String.class); + String uploadRes = exchange.getBody(); + return uploadAttachmentsRes; + } + + public Boolean describe(Describe describe) { + // 获取请求模板 + String requestTemplate = midjourneyConfig.getRequestTemplates().get("describe"); + // 设置参数 + HashMap requestParams = Maps.newHashMap(); + requestParams.put("guild_id", midjourneyConfig.getGuildId()); + requestParams.put("channel_id", midjourneyConfig.getChannelId()); + requestParams.put("session_id", midjourneyConfig.getSessionId()); + requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); + requestParams.put("file_name", describe.getFileName()); + requestParams.put("final_file_name", describe.getFinalFileName()); + // 设置 header + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); // 设置内容类型为JSON + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + String requestBody = MidjourneyUtil.parseTemplate(requestTemplate, requestParams); + // 创建表单数据 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("payload_json", requestBody); + // 发送请求 + HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders); + String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class); + // 这个 res 只要不返回值,就是成功! + boolean isSuccess = StrUtil.isBlank(res); + if (isSuccess) { + return true; + } + log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); + return isSuccess; + } + + @NotNull + private HttpHeaders getHttpHeaders() { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); // 设置内容类型为JSON + httpHeaders.set("Authorization", midjourneyConfig.getToken()); + httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); + httpHeaders.set("Cookie", MidjourneyConstants.HTTP_COOKIE); + httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); + return httpHeaders; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java deleted file mode 100644 index f0bd74d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java +++ /dev/null @@ -1,193 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.interactions; - -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MjConstants; -import cn.iocoder.yudao.framework.ai.midjourney.util.MjUtil; -import cn.iocoder.yudao.framework.ai.midjourney.vo.Attachments; -import cn.iocoder.yudao.framework.ai.midjourney.vo.Describe; -import cn.iocoder.yudao.framework.ai.midjourney.vo.ReRoll; -import cn.iocoder.yudao.framework.ai.midjourney.vo.UploadAttachmentsRes; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.core.io.FileSystemResource; -import org.springframework.http.*; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -import java.io.IOException; -import java.util.HashMap; - -// TODO @fansili:按照 spring ai 的封装习惯,这个类是不是 MidjourneyApi -/** - * 图片生成 - * - * author: fansili - * time: 2024/4/3 17:36 - */ -@Slf4j -public class MjInteractions { - - private final String url; - private final MidjourneyConfig midjourneyConfig; - private final RestTemplate restTemplate = new RestTemplate(); // TODO @fansili:优先级低:后续搞到统一的管理 - // TODO @fansili:静态变量,放在最前面哈; - private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s"; - - public MjInteractions(MidjourneyConfig midjourneyConfig) { - this.midjourneyConfig = midjourneyConfig; - this.url = midjourneyConfig.getServerUrl().concat(midjourneyConfig.getApiInteractions()); - } - - public Boolean imagine(String prompt) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("imagine"); - // 设置参数 - HashMap requestParams = Maps.newHashMap(); - // TODO @fansili:感觉参数的组装,可以搞成一个公用的方法;就是 config + 入参的感觉; - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); // TODO @fansili:建议用 uuid 之类的;nextId 跨进程未必合适哈; - requestParams.put("prompt", prompt); - // 解析 template 参数占位符 - String requestBody = MjUtil.parseTemplate(requestTemplate, requestParams); - // 获取 header - HttpHeaders httpHeaders = getHttpHeaders(); - // 发送请求 - HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); - String res = restTemplate.postForObject(url, requestEntity, String.class); - // 这个 res 只要不返回值,就是成功! - // TODO @fansili:可以直接 if (StrUtil.isBlank(res)) - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - // TODO @fansili:方法和方法之间,空一行哈; - - - public Boolean reRoll(ReRoll reRoll) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll"); - // 设置参数 - HashMap requestParams = Maps.newHashMap(); - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); - requestParams.put("custom_id", reRoll.getCustomId()); - requestParams.put("message_id", reRoll.getMessageId()); - // 获取 header - HttpHeaders httpHeaders = getHttpHeaders(); - // 设置参数 - String requestBody = MjUtil.parseTemplate(requestTemplate, requestParams); - // 发送请求 - HttpEntity requestEntity = new HttpEntity<>(requestBody, httpHeaders); - String res = restTemplate.postForObject(url, requestEntity, String.class); - // 这个 res 只要不返回值,就是成功! - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - - // TODO @fansili:搞成私有方法,可能会好点; - public UploadAttachmentsRes uploadAttachments(Attachments attachments) { - // file - JSONObject fileObj = new JSONObject(); - fileObj.put("id", "0"); - fileObj.put("filename", attachments.getFileSystemResource().getFilename()); - // TODO @fansili:这块用 lombok 哪个异常处理,简化下代码; - try { - fileObj.put("file_size", attachments.getFileSystemResource().contentLength()); - } catch (IOException e) { - throw new RuntimeException(e); - } - // 创建用于存放表单数据的MultiValueMap - MultiValueMap multipartRequest = new LinkedMultiValueMap<>(); - multipartRequest.put("files", Lists.newArrayList(fileObj)); - // 设置header值 - HttpHeaders httpHeaders = new HttpHeaders(); - // TODO @fansili:通用的 header 构建,抽一个方法哈; - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MjConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - // 创建HttpEntity对象,包含表单数据和头部信息 - HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(multipartRequest, httpHeaders); - // 发送POST请求并接收响应 - String uri = String.format(midjourneyConfig.getApiAttachments(), midjourneyConfig.getChannelId()); - String response = restTemplate.postForObject(midjourneyConfig.getServerUrl().concat(uri), multiValueMapHttpEntity, String.class); - UploadAttachmentsRes uploadAttachmentsRes = JSON.parseObject(response, UploadAttachmentsRes.class); - - - // - // 上传文件 - String uploadUrl = uploadAttachmentsRes.getAttachments().getFirst().getUploadUrl(); - String uploadAttachmentsUrl = midjourneyConfig.getApiAttachmentsUpload().concat(uploadUrl); - httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); - HttpEntity fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders); - ResponseEntity exchange = restTemplate.exchange(uploadUrl, HttpMethod.PUT, fileSystemResourceHttpEntity, String.class); - String uploadRes = exchange.getBody(); - - return uploadAttachmentsRes; - } - - public Boolean describe(Describe describe) { - // 获取请求模板 - String requestTemplate = midjourneyConfig.getRequestTemplates().get("describe"); - // 设置参数 - HashMap requestParams = Maps.newHashMap(); - requestParams.put("guild_id", midjourneyConfig.getGuildId()); - requestParams.put("channel_id", midjourneyConfig.getChannelId()); - requestParams.put("session_id", midjourneyConfig.getSessionId()); - requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId())); - requestParams.put("file_name", describe.getFileName()); - requestParams.put("final_file_name", describe.getFinalFileName()); - // 设置 header - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); // 设置内容类型为JSON - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MjConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - String requestBody = MjUtil.parseTemplate(requestTemplate, requestParams); - // 创建表单数据 - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("payload_json", requestBody); - // 发送请求 - HttpEntity> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders); - String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class); - // 这个 res 只要不返回值,就是成功! - boolean isSuccess = StrUtil.isBlank(res); - if (isSuccess) { - return true; - } - log.error("请求失败! 请求参数:{} 返回结果! {}", requestBody, res); - return isSuccess; - } - - @NotNull - private HttpHeaders getHttpHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setContentType(MediaType.APPLICATION_JSON); // 设置内容类型为JSON - httpHeaders.set("Authorization", midjourneyConfig.getToken()); - httpHeaders.set("User-Agent", midjourneyConfig.getUserAage()); - httpHeaders.set("Cookie", MjConstants.HTTP_COOKIE); - httpHeaders.set("Referer", String.format(HEADER_REFERER, midjourneyConfig.getGuildId(), midjourneyConfig.getChannelId())); - return httpHeaders; - } - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java new file mode 100644 index 0000000..92c7d42 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MidjourneyUtil.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.framework.ai.midjourney.util; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * mj util + * + * author: fansili + * time: 2024/4/6 19:00 + */ +public class MidjourneyUtil { + /** + * content正则匹配prompt和进度. + */ + public static final String CONTENT_REGEX = ".*?\\*\\*(.*?)\\*\\*.+<@\\d+> \\((.*?)\\)"; + public static final String CONTENT_PROGRESS_REGEX = "\\(([^)]*)\\)"; + + /** + * 解析 content 参数 + * + * @param content + * @return + */ + public static MidjourneyMessage.Content parseContent(String content) { + // 有三种格式。 + // 南极应该是什么样子? + // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", + // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (fast, stealth)" + MidjourneyMessage.Content mjContent = new MidjourneyMessage.Content(); + if (CharSequenceUtil.isBlank(content)) { + return null; + } + if (!content.contains("<@")) { + return mjContent.setPrompt(content); + } + int rawIndex = content.indexOf("<@") - 3; + String prompt = content.substring(0, rawIndex).trim(); + String contentTail = content.substring(rawIndex).trim(); + // 检查是否存在进度条 + Pattern pattern = Pattern.compile(CONTENT_PROGRESS_REGEX); + Matcher matcher = pattern.matcher(contentTail); + + if (contentTail.contains("%")) { + if (matcher.find()) { + // 获取第一个(也是此处唯一的)捕获组的内容 + String progress = matcher.group(1); + mjContent.setProgress(progress); + } + if (matcher.find()) { + String status = matcher.group(1); + mjContent.setStatus(status); + } + } else { + if (matcher.find()) { + // 获取第一个(也是此处唯一的)捕获组的内容 + String status = matcher.group(1); + mjContent.setStatus(status); + } + } + mjContent.setPrompt(prompt); + // tip:contentArray + return mjContent; + } + + /** + * 设置 params + * + * @param requestTemplate + * @param requestParams + * @return + */ + public static String parseTemplate(String requestTemplate, Map requestParams) { + for (Map.Entry entry : requestParams.entrySet()) { + requestTemplate = requestTemplate.replace("$".concat(entry.getKey()), entry.getValue()); + } + return requestTemplate; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java deleted file mode 100644 index 751a79b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.util; - -import cn.hutool.core.text.CharSequenceUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MjMessage; - -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * mj util - * - * author: fansili - * time: 2024/4/6 19:00 - */ -public class MjUtil { - /** - * content正则匹配prompt和进度. - */ - public static final String CONTENT_REGEX = ".*?\\*\\*(.*?)\\*\\*.+<@\\d+> \\((.*?)\\)"; - public static final String CONTENT_PROGRESS_REGEX = "\\(([^)]*)\\)"; - - /** - * 解析 content 参数 - * - * @param content - * @return - */ - public static MjMessage.Content parseContent(String content) { - // 有三种格式。 - // 南极应该是什么样子? - // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)", - // "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (fast, stealth)" - MjMessage.Content mjContent = new MjMessage.Content(); - if (CharSequenceUtil.isBlank(content)) { - return null; - } - if (!content.contains("<@")) { - return mjContent.setPrompt(content); - } - int rawIndex = content.indexOf("<@") - 3; - String prompt = content.substring(0, rawIndex).trim(); - String contentTail = content.substring(rawIndex).trim(); - // 检查是否存在进度条 - Pattern pattern = Pattern.compile(CONTENT_PROGRESS_REGEX); - Matcher matcher = pattern.matcher(contentTail); - - if (contentTail.contains("%")) { - if (matcher.find()) { - // 获取第一个(也是此处唯一的)捕获组的内容 - String progress = matcher.group(1); - mjContent.setProgress(progress); - } - if (matcher.find()) { - String status = matcher.group(1); - mjContent.setStatus(status); - } - } else { - if (matcher.find()) { - // 获取第一个(也是此处唯一的)捕获组的内容 - String status = matcher.group(1); - mjContent.setStatus(status); - } - } - mjContent.setPrompt(prompt); - // tip:contentArray - return mjContent; - } - - /** - * 设置 params - * - * @param requestTemplate - * @param requestParams - * @return - */ - public static String parseTemplate(String requestTemplate, Map requestParams) { - for (Map.Entry entry : requestParams.entrySet()) { - requestTemplate = requestTemplate.replace("$".concat(entry.getKey()), entry.getValue()); - } - return requestTemplate; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java new file mode 100644 index 0000000..e7ff41c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MidjourneyWebSocketStarter.java @@ -0,0 +1,219 @@ +package cn.iocoder.yudao.framework.ai.midjourney.webSocket; + + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyNotifyCode; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.handler.MidjourneyWebSocketHandler; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.tomcat.websocket.Constants; +import org.jetbrains.annotations.NotNull; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; + +import java.io.IOException; +import java.net.URI; + +// TODO @fansili:mj 这块 websocket 有点小复杂,虽然代码量 400 多行;感觉可以考虑,有没第三方 sdk,通过它透明接入 mj +@Slf4j +public class MidjourneyWebSocketStarter implements WebSocketStarter { + /** + * 链接重试次数 + */ + private static final int CONNECT_RETRY_LIMIT = 5; + /** + * mj 配置文件 + */ + private final MidjourneyConfig midjourneyConfig; + /** + * mj 监听(所有message 都会 callback到这里) + */ + private final MidjourneyMessageListener userMessageListener; + /** + * wss 服务器 + */ + private final String wssServer; + /** + * + */ + private final String resumeWss; + /** + * + */ + private ResumeData resumeData = null; + /** + * 是否运行成功 + */ + private boolean running = false; + /** + * 链接成功的 session + */ + private WebSocketSession webSocketSession = null; + + public MidjourneyWebSocketStarter(String wssServer, + String resumeWss, + MidjourneyConfig midjourneyConfig, + MidjourneyMessageListener userMessageListener) { + this.wssServer = wssServer; + this.resumeWss = resumeWss; + this.midjourneyConfig = midjourneyConfig; + this.userMessageListener = userMessageListener; + } + + @Override + public void start() { + start(false); + } + + private void start(boolean reconnect) { + // 设置header + WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); + headers.add("Accept-Encoding", "gzip, deflate, br"); + headers.add("Accept-Language", "zh-CN,zh;q=0.9"); + headers.add("Cache-Control", "no-cache"); + headers.add("Pragma", "no-cache"); + headers.add("Sec-Websocket-Extensions", "permessage-deflate; client_max_window_bits"); + headers.add("User-Agent", this.midjourneyConfig.getUserAage()); + // 创建 mjHeader + MidjourneyWebSocketHandler mjWebSocketHandler = new MidjourneyWebSocketHandler( + this.midjourneyConfig, this.userMessageListener, this::onSocketSuccess, this::onSocketFailure); + // + String gatewayUrl; + if (reconnect) { + gatewayUrl = getGatewayServer(this.resumeData.getResumeGatewayUrl()) + "/?encoding=json&v=9&compress=zlib-stream"; + mjWebSocketHandler.setSessionId(this.resumeData.getSessionId()); + mjWebSocketHandler.setSequence(this.resumeData.getSequence()); + mjWebSocketHandler.setResumeGatewayUrl(this.resumeData.getResumeGatewayUrl()); + } else { + gatewayUrl = getGatewayServer(null) + "/?encoding=json&v=9&compress=zlib-stream"; + } + // 创建 StandardWebSocketClient + StandardWebSocketClient webSocketClient = new StandardWebSocketClient(); + // 设置 io timeout 时间 + webSocketClient.getUserProperties().put(Constants.IO_TIMEOUT_MS_PROPERTY, "10000"); + // + ListenableFuture socketSessionFuture = webSocketClient.doHandshake(mjWebSocketHandler, headers, URI.create(gatewayUrl)); + // 添加 callback 进行回调 + socketSessionFuture.addCallback(new ListenableFutureCallback<>() { + @Override + public void onFailure(@NotNull Throwable e) { + onSocketFailure(MidjourneyWebSocketHandler.CLOSE_CODE_EXCEPTION, e.getMessage()); + } + + @Override + public void onSuccess(WebSocketSession session) { + MidjourneyWebSocketStarter.this.webSocketSession = session; + } + }); + } + + private void onSocketSuccess(String sessionId, Object sequence, String resumeGatewayUrl) { + this.resumeData = new ResumeData(sessionId, sequence, resumeGatewayUrl); + this.running = true; + notifyWssLock(MidjourneyNotifyCode.SUCCESS, ""); + } + + private void onSocketFailure(int code, String reason) { + // 1001异常可以忽略 + if (code == 1001) { + return; + } + // 关闭 socket + closeSocketSessionWhenIsOpen(); + // 没有运行通知 + if (!this.running) { + notifyWssLock(code, reason); + return; + } + // 已经运行先设置为false,发起 + this.running = false; + if (code >= 4000) { + log.warn("[wss-{}] Can't reconnect! Account disabled. Closed by {}({}).", this.midjourneyConfig.getChannelId(), code, reason); + } else if (code == 2001) { + log.warn("[wss-{}] Closed by {}({}). Try reconnect...", this.midjourneyConfig.getChannelId(), code, reason); + tryReconnect(); + } else { + log.warn("[wss-{}] Closed by {}({}). Try new connection...", this.midjourneyConfig.getChannelId(), code, reason); + tryNewConnect(); + } + } + + /** + * 重连 + */ + private void tryReconnect() { + try { + tryStart(true); + } catch (Exception e) { + log.warn("[wss-{}] Reconnect fail: {}, Try new connection...", this.midjourneyConfig.getChannelId(), e.getMessage()); + ThreadUtil.sleep(1000); + tryNewConnect(); + } + } + + private void tryNewConnect() { + // 链接重试次数5 + for (int i = 1; i <= CONNECT_RETRY_LIMIT; i++) { + try { + tryStart(false); + return; + } catch (Exception e) { + log.warn("[wss-{}] New connect fail ({}): {}", this.midjourneyConfig.getChannelId(), i, e.getMessage()); + ThreadUtil.sleep(5000); + } + } + log.error("[wss-{}] Account disabled", this.midjourneyConfig.getChannelId()); + } + + public void tryStart(boolean reconnect) { + start(reconnect); + } + + private void notifyWssLock(int code, String reason) { + System.err.println("notifyWssLock: " + code + " - " + reason); + } + + /** + * 关闭 socket session + */ + private void closeSocketSessionWhenIsOpen() { + try { + if (this.webSocketSession != null && this.webSocketSession.isOpen()) { + this.webSocketSession.close(CloseStatus.GOING_AWAY); + } + } catch (IOException e) { + // do nothing + } + } + + private String getGatewayServer(String resumeGatewayUrl) { + if (CharSequenceUtil.isNotBlank(resumeGatewayUrl)) { + return CharSequenceUtil.isBlank(this.resumeWss) ? resumeGatewayUrl : this.resumeWss; + } + return this.wssServer; + } + + @Getter + public static class ResumeData { + + public ResumeData(String sessionId, Object sequence, String resumeGatewayUrl) { + this.sessionId = sessionId; + this.sequence = sequence; + this.resumeGatewayUrl = resumeGatewayUrl; + } + + /** + * socket session + */ + private final String sessionId; + private final Object sequence; + private final String resumeGatewayUrl; + } +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MjWebSocketStarter.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MjWebSocketStarter.java deleted file mode 100644 index b01c4ad..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/MjWebSocketStarter.java +++ /dev/null @@ -1,220 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket; - - -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MjNotifyCode; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.handler.MjWebSocketHandler; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MjMessageListener; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.apache.tomcat.websocket.Constants; -import org.jetbrains.annotations.NotNull; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketHttpHeaders; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; - -import java.io.IOException; -import java.net.URI; -import java.util.concurrent.TimeoutException; - -// TODO @fansili:mj 这块 websocket 有点小复杂,虽然代码量 400 多行;感觉可以考虑,有没第三方 sdk,通过它透明接入 mj -@Slf4j -public class MjWebSocketStarter implements WebSocketStarter { - /** - * 链接重试次数 - */ - private static final int CONNECT_RETRY_LIMIT = 5; - /** - * mj 配置文件 - */ - private final MidjourneyConfig midjourneyConfig; - /** - * mj 监听(所有message 都会 callback到这里) - */ - private final MjMessageListener userMessageListener; - /** - * wss 服务器 - */ - private final String wssServer; - /** - * - */ - private final String resumeWss; - /** - * - */ - private ResumeData resumeData = null; - /** - * 是否运行成功 - */ - private boolean running = false; - /** - * 链接成功的 session - */ - private WebSocketSession webSocketSession = null; - - public MjWebSocketStarter(String wssServer, - String resumeWss, - MidjourneyConfig midjourneyConfig, - MjMessageListener userMessageListener) { - this.wssServer = wssServer; - this.resumeWss = resumeWss; - this.midjourneyConfig = midjourneyConfig; - this.userMessageListener = userMessageListener; - } - - @Override - public void start() { - start(false); - } - - private void start(boolean reconnect) { - // 设置header - WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); - headers.add("Accept-Encoding", "gzip, deflate, br"); - headers.add("Accept-Language", "zh-CN,zh;q=0.9"); - headers.add("Cache-Control", "no-cache"); - headers.add("Pragma", "no-cache"); - headers.add("Sec-Websocket-Extensions", "permessage-deflate; client_max_window_bits"); - headers.add("User-Agent", this.midjourneyConfig.getUserAage()); - // 创建 mjHeader - MjWebSocketHandler mjWebSocketHandler = new MjWebSocketHandler( - this.midjourneyConfig, this.userMessageListener, this::onSocketSuccess, this::onSocketFailure); - // - String gatewayUrl; - if (reconnect) { - gatewayUrl = getGatewayServer(this.resumeData.getResumeGatewayUrl()) + "/?encoding=json&v=9&compress=zlib-stream"; - mjWebSocketHandler.setSessionId(this.resumeData.getSessionId()); - mjWebSocketHandler.setSequence(this.resumeData.getSequence()); - mjWebSocketHandler.setResumeGatewayUrl(this.resumeData.getResumeGatewayUrl()); - } else { - gatewayUrl = getGatewayServer(null) + "/?encoding=json&v=9&compress=zlib-stream"; - } - // 创建 StandardWebSocketClient - StandardWebSocketClient webSocketClient = new StandardWebSocketClient(); - // 设置 io timeout 时间 - webSocketClient.getUserProperties().put(Constants.IO_TIMEOUT_MS_PROPERTY, "10000"); - // - ListenableFuture socketSessionFuture = webSocketClient.doHandshake(mjWebSocketHandler, headers, URI.create(gatewayUrl)); - // 添加 callback 进行回调 - socketSessionFuture.addCallback(new ListenableFutureCallback<>() { - @Override - public void onFailure(@NotNull Throwable e) { - onSocketFailure(MjWebSocketHandler.CLOSE_CODE_EXCEPTION, e.getMessage()); - } - - @Override - public void onSuccess(WebSocketSession session) { - MjWebSocketStarter.this.webSocketSession = session; - } - }); - } - - private void onSocketSuccess(String sessionId, Object sequence, String resumeGatewayUrl) { - this.resumeData = new ResumeData(sessionId, sequence, resumeGatewayUrl); - this.running = true; - notifyWssLock(MjNotifyCode.SUCCESS, ""); - } - - private void onSocketFailure(int code, String reason) { - // 1001异常可以忽略 - if (code == 1001) { - return; - } - // 关闭 socket - closeSocketSessionWhenIsOpen(); - // 没有运行通知 - if (!this.running) { - notifyWssLock(code, reason); - return; - } - // 已经运行先设置为false,发起 - this.running = false; - if (code >= 4000) { - log.warn("[wss-{}] Can't reconnect! Account disabled. Closed by {}({}).", this.midjourneyConfig.getChannelId(), code, reason); - } else if (code == 2001) { - log.warn("[wss-{}] Closed by {}({}). Try reconnect...", this.midjourneyConfig.getChannelId(), code, reason); - tryReconnect(); - } else { - log.warn("[wss-{}] Closed by {}({}). Try new connection...", this.midjourneyConfig.getChannelId(), code, reason); - tryNewConnect(); - } - } - - /** - * 重连 - */ - private void tryReconnect() { - try { - tryStart(true); - } catch (Exception e) { - log.warn("[wss-{}] Reconnect fail: {}, Try new connection...", this.midjourneyConfig.getChannelId(), e.getMessage()); - ThreadUtil.sleep(1000); - tryNewConnect(); - } - } - - private void tryNewConnect() { - // 链接重试次数5 - for (int i = 1; i <= CONNECT_RETRY_LIMIT; i++) { - try { - tryStart(false); - return; - } catch (Exception e) { - log.warn("[wss-{}] New connect fail ({}): {}", this.midjourneyConfig.getChannelId(), i, e.getMessage()); - ThreadUtil.sleep(5000); - } - } - log.error("[wss-{}] Account disabled", this.midjourneyConfig.getChannelId()); - } - - public void tryStart(boolean reconnect) { - start(reconnect); - } - - private void notifyWssLock(int code, String reason) { - System.err.println("notifyWssLock: " + code + " - " + reason); - } - - /** - * 关闭 socket session - */ - private void closeSocketSessionWhenIsOpen() { - try { - if (this.webSocketSession != null && this.webSocketSession.isOpen()) { - this.webSocketSession.close(CloseStatus.GOING_AWAY); - } - } catch (IOException e) { - // do nothing - } - } - - private String getGatewayServer(String resumeGatewayUrl) { - if (CharSequenceUtil.isNotBlank(resumeGatewayUrl)) { - return CharSequenceUtil.isBlank(this.resumeWss) ? resumeGatewayUrl : this.resumeWss; - } - return this.wssServer; - } - - @Getter - public static class ResumeData { - - public ResumeData(String sessionId, Object sequence, String resumeGatewayUrl) { - this.sessionId = sessionId; - this.sequence = sequence; - this.resumeGatewayUrl = resumeGatewayUrl; - } - - /** - * socket session - */ - private final String sessionId; - private final Object sequence; - private final String resumeGatewayUrl; - } -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java new file mode 100644 index 0000000..b6fbf5c --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MidjourneyWebSocketHandler.java @@ -0,0 +1,281 @@ +package cn.iocoder.yudao.framework.ai.midjourney.webSocket.handler; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.FailureCallback; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.SuccessCallback; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.utils.data.DataArray; +import net.dv8tion.jda.api.utils.data.DataObject; +import net.dv8tion.jda.api.utils.data.DataType; +import net.dv8tion.jda.internal.requests.WebSocketCode; +import net.dv8tion.jda.internal.utils.compress.Decompressor; +import net.dv8tion.jda.internal.utils.compress.ZlibDecompressor; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.socket.*; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class MidjourneyWebSocketHandler implements WebSocketHandler { + /** + * close 错误码:重连 + */ + public static final int CLOSE_CODE_RECONNECT = 2001; + /** + * close 错误码:无效、作废 + */ + public static final int CLOSE_CODE_INVALIDATE = 1009; + /** + * close 错误码:异常 + */ + public static final int CLOSE_CODE_EXCEPTION = 1011; + /** + * mj配置文件 + */ + private final MidjourneyConfig midjourneyConfig; + /** + * mj 消息监听 + */ + private final MidjourneyMessageListener userMessageListener; + /** + * 成功回调 + */ + private final SuccessCallback successCallback; + /** + * 失败回调 + */ + private final FailureCallback failureCallback; + /** + * 心跳执行器 + */ + private final ScheduledExecutorService heartExecutor; + /** + * auth数据 + */ + private final DataObject authData; + + @Setter + private String sessionId = null; + @Setter + private Object sequence = null; + @Setter + private String resumeGatewayUrl = null; + + private long interval = 41250; + private boolean heartbeatAck = false; + + private Future heartbeatInterval; + private Future heartbeatTimeout; + + /** + * 处理 message 消息的 Decompressor + */ + private final Decompressor decompressor = new ZlibDecompressor(2048); + + public MidjourneyWebSocketHandler(MidjourneyConfig account, + MidjourneyMessageListener userMessageListener, + SuccessCallback successCallback, + FailureCallback failureCallback) { + this.midjourneyConfig = account; + this.userMessageListener = userMessageListener; + this.successCallback = successCallback; + this.failureCallback = failureCallback; + this.heartExecutor = Executors.newSingleThreadScheduledExecutor(); + this.authData = createAuthData(); + } + + @Override + public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception { + // do nothing + } + + @Override + public void handleTransportError(@NotNull WebSocketSession session, @NotNull Throwable e) throws Exception { + log.error("[wss-{}] Transport error", this.midjourneyConfig.getChannelId(), e); + // 通知链接异常 + onFailure(CLOSE_CODE_EXCEPTION, "transport error"); + } + + @Override + public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus closeStatus) throws Exception { + // 链接关闭 + onFailure(closeStatus.getCode(), closeStatus.getReason()); + } + + @Override + public boolean supportsPartialMessages() { + return true; + } + + @Override + public void handleMessage(@NotNull WebSocketSession session, WebSocketMessage message) throws Exception { + // 获取 message 消息 + ByteBuffer buffer = (ByteBuffer) message.getPayload(); + // 解析 message + byte[] decompressed = decompressor.decompress(buffer.array()); + if (decompressed == null) { + return; + } + // 转换 json + String json = new String(decompressed, StandardCharsets.UTF_8); + // 转换 jda 自带的 dataObject(和json object 差不多) + DataObject data = DataObject.fromJson(json); + // 获取消息类型 + int opCode = data.getInt("op"); + switch (opCode) { + case WebSocketCode.HEARTBEAT -> handleHeartbeat(session); + case WebSocketCode.HEARTBEAT_ACK -> { + this.heartbeatAck = true; + clearHeartbeatTimeout(); + } + case WebSocketCode.HELLO -> { + handleHello(session, data); + doResumeOrIdentify(session); + } + case WebSocketCode.RESUME -> onSuccess(); + case WebSocketCode.RECONNECT -> onFailure(CLOSE_CODE_RECONNECT, "receive server reconnect"); + case WebSocketCode.INVALIDATE_SESSION -> onFailure(CLOSE_CODE_INVALIDATE, "receive session invalid"); + case WebSocketCode.DISPATCH -> handleDispatch(data); + default -> log.debug("[wss-{}] Receive unknown code: {}.", midjourneyConfig.getChannelId(), data); + } + } + + private void handleDispatch(DataObject raw) { + this.sequence = raw.opt("s").orElse(null); + if (!raw.isType("d", DataType.OBJECT)) { + return; + } + DataObject content = raw.getObject("d"); + String t = raw.getString("t", null); + if ("READY".equals(t)) { + this.sessionId = content.getString("session_id"); + this.resumeGatewayUrl = content.getString("resume_gateway_url"); + onSuccess(); + } else if ("RESUMED".equals(t)) { + onSuccess(); + } else { + try { + this.userMessageListener.onMessage(raw); + } catch (Exception e) { + log.error("[wss-{}] Handle message error", this.midjourneyConfig.getChannelId(), e); + } + } + } + + private void handleHeartbeat(WebSocketSession session) { + sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); + this.heartbeatTimeout = ThreadUtil.execAsync(() -> { + ThreadUtil.sleep(this.interval); + onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack"); + }); + } + + private void handleHello(WebSocketSession session, DataObject data) { + clearHeartbeatInterval(); + this.interval = data.getObject("d").getLong("heartbeat_interval"); + this.heartbeatAck = true; + this.heartbeatInterval = this.heartExecutor.scheduleAtFixedRate(() -> { + if (this.heartbeatAck) { + this.heartbeatAck = false; + sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); + } else { + onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack interval"); + } + }, (long) Math.floor(RandomUtil.randomDouble(0, 1) * this.interval), this.interval, TimeUnit.MILLISECONDS); + } + + private void doResumeOrIdentify(WebSocketSession session) { + if (CharSequenceUtil.isBlank(this.sessionId)) { + sendMessage(session, WebSocketCode.IDENTIFY, this.authData); + } else { + var data = DataObject.empty().put("token", this.midjourneyConfig.getToken()) + .put("session_id", this.sessionId).put("seq", this.sequence); + sendMessage(session, WebSocketCode.RESUME, data); + } + } + + private void sendMessage(WebSocketSession session, int op, Object d) { + var data = DataObject.empty().put("op", op).put("d", d); + try { + session.sendMessage(new TextMessage(data.toString())); + } catch (IOException e) { + log.error("[wss-{}] Send message error", this.midjourneyConfig.getChannelId(), e); + onFailure(CLOSE_CODE_EXCEPTION, "send message error"); + } + } + + private void onSuccess() { + ThreadUtil.execute(() -> this.successCallback.onSuccess(this.sessionId, this.sequence, this.resumeGatewayUrl)); + } + + private void onFailure(int code, String reason) { + clearHeartbeatTimeout(); + clearHeartbeatInterval(); + ThreadUtil.execute(() -> this.failureCallback.onFailure(code, reason)); + } + + private void clearHeartbeatTimeout() { + if (this.heartbeatTimeout != null) { + this.heartbeatTimeout.cancel(true); + this.heartbeatTimeout = null; + } + } + + private void clearHeartbeatInterval() { + if (this.heartbeatInterval != null) { + this.heartbeatInterval.cancel(true); + this.heartbeatInterval = null; + } + } + + private DataObject createAuthData() { + UserAgent userAgent = UserAgentUtil.parse(this.midjourneyConfig.getUserAage()); + DataObject connectionProperties = DataObject.empty() + .put("browser", userAgent.getBrowser().getName()) + .put("browser_user_agent", this.midjourneyConfig.getUserAage()) + .put("browser_version", userAgent.getVersion()) + .put("client_build_number", 222963) + .put("client_event_source", null) + .put("device", "") + .put("os", userAgent.getOs().getName()) + .put("referer", "https://www.midjourney.com") + .put("referrer_current", "") + .put("referring_domain", "www.midjourney.com") + .put("referring_domain_current", "") + .put("release_channel", "stable") + .put("system_locale", "zh-CN"); + DataObject presence = DataObject.empty() + .put("activities", DataArray.empty()) + .put("afk", false) + .put("since", 0) + .put("status", "online"); + DataObject clientState = DataObject.empty() + .put("api_code_version", 0) + .put("guild_versions", DataObject.empty()) + .put("highest_last_message_id", "0") + .put("private_channels_version", "0") + .put("read_state_version", 0) + .put("user_guild_settings_version", -1) + .put("user_settings_version", -1); + return DataObject.empty() + .put("capabilities", 16381) + .put("client_state", clientState) + .put("compress", false) + .put("presence", presence) + .put("properties", connectionProperties) + .put("token", this.midjourneyConfig.getToken()); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MjWebSocketHandler.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MjWebSocketHandler.java deleted file mode 100644 index 55fc7e4..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/handler/MjWebSocketHandler.java +++ /dev/null @@ -1,281 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket.handler; - -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.http.useragent.UserAgent; -import cn.hutool.http.useragent.UserAgentUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.FailureCallback; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.SuccessCallback; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MjMessageListener; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import net.dv8tion.jda.api.utils.data.DataArray; -import net.dv8tion.jda.api.utils.data.DataObject; -import net.dv8tion.jda.api.utils.data.DataType; -import net.dv8tion.jda.internal.requests.WebSocketCode; -import net.dv8tion.jda.internal.utils.compress.Decompressor; -import net.dv8tion.jda.internal.utils.compress.ZlibDecompressor; -import org.jetbrains.annotations.NotNull; -import org.springframework.web.socket.*; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -@Slf4j -public class MjWebSocketHandler implements WebSocketHandler { - /** - * close 错误码:重连 - */ - public static final int CLOSE_CODE_RECONNECT = 2001; - /** - * close 错误码:无效、作废 - */ - public static final int CLOSE_CODE_INVALIDATE = 1009; - /** - * close 错误码:异常 - */ - public static final int CLOSE_CODE_EXCEPTION = 1011; - /** - * mj配置文件 - */ - private final MidjourneyConfig midjourneyConfig; - /** - * mj 消息监听 - */ - private final MjMessageListener userMessageListener; - /** - * 成功回调 - */ - private final SuccessCallback successCallback; - /** - * 失败回调 - */ - private final FailureCallback failureCallback; - /** - * 心跳执行器 - */ - private final ScheduledExecutorService heartExecutor; - /** - * auth数据 - */ - private final DataObject authData; - - @Setter - private String sessionId = null; - @Setter - private Object sequence = null; - @Setter - private String resumeGatewayUrl = null; - - private long interval = 41250; - private boolean heartbeatAck = false; - - private Future heartbeatInterval; - private Future heartbeatTimeout; - - /** - * 处理 message 消息的 Decompressor - */ - private final Decompressor decompressor = new ZlibDecompressor(2048); - - public MjWebSocketHandler(MidjourneyConfig account, - MjMessageListener userMessageListener, - SuccessCallback successCallback, - FailureCallback failureCallback) { - this.midjourneyConfig = account; - this.userMessageListener = userMessageListener; - this.successCallback = successCallback; - this.failureCallback = failureCallback; - this.heartExecutor = Executors.newSingleThreadScheduledExecutor(); - this.authData = createAuthData(); - } - - @Override - public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception { - // do nothing - } - - @Override - public void handleTransportError(@NotNull WebSocketSession session, @NotNull Throwable e) throws Exception { - log.error("[wss-{}] Transport error", this.midjourneyConfig.getChannelId(), e); - // 通知链接异常 - onFailure(CLOSE_CODE_EXCEPTION, "transport error"); - } - - @Override - public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus closeStatus) throws Exception { - // 链接关闭 - onFailure(closeStatus.getCode(), closeStatus.getReason()); - } - - @Override - public boolean supportsPartialMessages() { - return true; - } - - @Override - public void handleMessage(@NotNull WebSocketSession session, WebSocketMessage message) throws Exception { - // 获取 message 消息 - ByteBuffer buffer = (ByteBuffer) message.getPayload(); - // 解析 message - byte[] decompressed = decompressor.decompress(buffer.array()); - if (decompressed == null) { - return; - } - // 转换 json - String json = new String(decompressed, StandardCharsets.UTF_8); - // 转换 jda 自带的 dataObject(和json object 差不多) - DataObject data = DataObject.fromJson(json); - // 获取消息类型 - int opCode = data.getInt("op"); - switch (opCode) { - case WebSocketCode.HEARTBEAT -> handleHeartbeat(session); - case WebSocketCode.HEARTBEAT_ACK -> { - this.heartbeatAck = true; - clearHeartbeatTimeout(); - } - case WebSocketCode.HELLO -> { - handleHello(session, data); - doResumeOrIdentify(session); - } - case WebSocketCode.RESUME -> onSuccess(); - case WebSocketCode.RECONNECT -> onFailure(CLOSE_CODE_RECONNECT, "receive server reconnect"); - case WebSocketCode.INVALIDATE_SESSION -> onFailure(CLOSE_CODE_INVALIDATE, "receive session invalid"); - case WebSocketCode.DISPATCH -> handleDispatch(data); - default -> log.debug("[wss-{}] Receive unknown code: {}.", midjourneyConfig.getChannelId(), data); - } - } - - private void handleDispatch(DataObject raw) { - this.sequence = raw.opt("s").orElse(null); - if (!raw.isType("d", DataType.OBJECT)) { - return; - } - DataObject content = raw.getObject("d"); - String t = raw.getString("t", null); - if ("READY".equals(t)) { - this.sessionId = content.getString("session_id"); - this.resumeGatewayUrl = content.getString("resume_gateway_url"); - onSuccess(); - } else if ("RESUMED".equals(t)) { - onSuccess(); - } else { - try { - this.userMessageListener.onMessage(raw); - } catch (Exception e) { - log.error("[wss-{}] Handle message error", this.midjourneyConfig.getChannelId(), e); - } - } - } - - private void handleHeartbeat(WebSocketSession session) { - sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); - this.heartbeatTimeout = ThreadUtil.execAsync(() -> { - ThreadUtil.sleep(this.interval); - onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack"); - }); - } - - private void handleHello(WebSocketSession session, DataObject data) { - clearHeartbeatInterval(); - this.interval = data.getObject("d").getLong("heartbeat_interval"); - this.heartbeatAck = true; - this.heartbeatInterval = this.heartExecutor.scheduleAtFixedRate(() -> { - if (this.heartbeatAck) { - this.heartbeatAck = false; - sendMessage(session, WebSocketCode.HEARTBEAT, this.sequence); - } else { - onFailure(CLOSE_CODE_RECONNECT, "heartbeat has not ack interval"); - } - }, (long) Math.floor(RandomUtil.randomDouble(0, 1) * this.interval), this.interval, TimeUnit.MILLISECONDS); - } - - private void doResumeOrIdentify(WebSocketSession session) { - if (CharSequenceUtil.isBlank(this.sessionId)) { - sendMessage(session, WebSocketCode.IDENTIFY, this.authData); - } else { - var data = DataObject.empty().put("token", this.midjourneyConfig.getToken()) - .put("session_id", this.sessionId).put("seq", this.sequence); - sendMessage(session, WebSocketCode.RESUME, data); - } - } - - private void sendMessage(WebSocketSession session, int op, Object d) { - var data = DataObject.empty().put("op", op).put("d", d); - try { - session.sendMessage(new TextMessage(data.toString())); - } catch (IOException e) { - log.error("[wss-{}] Send message error", this.midjourneyConfig.getChannelId(), e); - onFailure(CLOSE_CODE_EXCEPTION, "send message error"); - } - } - - private void onSuccess() { - ThreadUtil.execute(() -> this.successCallback.onSuccess(this.sessionId, this.sequence, this.resumeGatewayUrl)); - } - - private void onFailure(int code, String reason) { - clearHeartbeatTimeout(); - clearHeartbeatInterval(); - ThreadUtil.execute(() -> this.failureCallback.onFailure(code, reason)); - } - - private void clearHeartbeatTimeout() { - if (this.heartbeatTimeout != null) { - this.heartbeatTimeout.cancel(true); - this.heartbeatTimeout = null; - } - } - - private void clearHeartbeatInterval() { - if (this.heartbeatInterval != null) { - this.heartbeatInterval.cancel(true); - this.heartbeatInterval = null; - } - } - - private DataObject createAuthData() { - UserAgent userAgent = UserAgentUtil.parse(this.midjourneyConfig.getUserAage()); - DataObject connectionProperties = DataObject.empty() - .put("browser", userAgent.getBrowser().getName()) - .put("browser_user_agent", this.midjourneyConfig.getUserAage()) - .put("browser_version", userAgent.getVersion()) - .put("client_build_number", 222963) - .put("client_event_source", null) - .put("device", "") - .put("os", userAgent.getOs().getName()) - .put("referer", "https://www.midjourney.com") - .put("referrer_current", "") - .put("referring_domain", "www.midjourney.com") - .put("referring_domain_current", "") - .put("release_channel", "stable") - .put("system_locale", "zh-CN"); - DataObject presence = DataObject.empty() - .put("activities", DataArray.empty()) - .put("afk", false) - .put("since", 0) - .put("status", "online"); - DataObject clientState = DataObject.empty() - .put("api_code_version", 0) - .put("guild_versions", DataObject.empty()) - .put("highest_last_message_id", "0") - .put("private_channels_version", "0") - .put("read_state_version", 0) - .put("user_guild_settings_version", -1) - .put("user_settings_version", -1); - return DataObject.empty() - .put("capabilities", 16381) - .put("client_state", clientState) - .put("compress", false) - .put("presence", presence) - .put("properties", connectionProperties) - .put("token", this.midjourneyConfig.getToken()); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java new file mode 100644 index 0000000..31bbe0a --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MidjourneyMessageListener.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener; + + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; +import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyMessage; +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyConstants; +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyGennerateStatusEnum; +import cn.iocoder.yudao.framework.ai.midjourney.constants.MidjourneyMessageTypeEnum; +import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import net.dv8tion.jda.api.utils.data.DataObject; + +import java.util.List; + +@Slf4j +public class MidjourneyMessageListener { + + private MidjourneyConfig midjourneyConfig; + + public MidjourneyMessageListener(MidjourneyConfig midjourneyConfig) { + this.midjourneyConfig = midjourneyConfig; + } + + public void onMessage(DataObject raw) { + MidjourneyMessageTypeEnum messageType = MidjourneyMessageTypeEnum.of(raw.getString("t")); + if (messageType == null || MidjourneyMessageTypeEnum.DELETE == messageType) { + return; + } + DataObject data = raw.getObject("d"); + if (ignoreAndLogMessage(data, messageType)) { + return; + } + + // 转换几个重要的信息 + MidjourneyMessage mjMessage = new MidjourneyMessage(); + mjMessage.setId(data.getString(MidjourneyConstants.MSG_ID)); + mjMessage.setType(data.getInt(MidjourneyConstants.MSG_TYPE)); + mjMessage.setRawData(StrUtil.str(raw.toJson(), "UTF-8")); + mjMessage.setContent(MidjourneyUtil.parseContent(data.getString(MidjourneyConstants.MSG_CONTENT))); + // 转换 components + if (!data.getArray(MidjourneyConstants.MSG_COMPONENTS).isEmpty()) { + String componentsJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_COMPONENTS).toJson(), "UTF-8"); + List components = JSON.parseArray(componentsJson, MidjourneyMessage.ComponentType.class); + mjMessage.setComponents(components); + } + // 转换附件 + if (!data.getArray(MidjourneyConstants.MSG_ATTACHMENTS).isEmpty()) { + String attachmentsJson = StrUtil.str(data.getArray(MidjourneyConstants.MSG_ATTACHMENTS).toJson(), "UTF-8"); + List attachments = JSON.parseArray(attachmentsJson, MidjourneyMessage.Attachment.class); + mjMessage.setAttachments(attachments); + } + // 转换状态 + convertGenerateStatus(mjMessage); + // + log.info("message 信息 {}", JSONUtil.toJsonPrettyStr(mjMessage)); + System.err.println(JSONUtil.toJsonPrettyStr(mjMessage)); + } + + private void convertGenerateStatus(MidjourneyMessage mjMessage) { + if (mjMessage.getType() == 20 && mjMessage.getContent().getStatus().contains("Waiting")) { + mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.WAITING.getValue()); + } else if (mjMessage.getType() == 20 && !StrUtil.isBlank(mjMessage.getContent().getProgress())) { + mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.IN_PROGRESS.getValue()); + } else if (mjMessage.getType() == 0 && !CollUtil.isEmpty(mjMessage.getComponents())) { + mjMessage.setGenerateStatus(MidjourneyGennerateStatusEnum.COMPLETED.getValue()); + } + } + + private boolean ignoreAndLogMessage(DataObject data, MidjourneyMessageTypeEnum messageType) { + String channelId = data.getString(MidjourneyConstants.MSG_CHANNEL_ID); + if (!CharSequenceUtil.equals(channelId, midjourneyConfig.getChannelId())) { + return true; + } + String authorName = data.optObject("author").map(a -> a.getString("username")).orElse("System"); + log.debug("{} - {} - {}: {}", midjourneyConfig.getChannelId(), messageType.name(), authorName, data.opt("content").orElse("")); + return false; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MjMessageListener.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MjMessageListener.java deleted file mode 100644 index 4893e7b..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MjMessageListener.java +++ /dev/null @@ -1,83 +0,0 @@ -package cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener; - - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.CharSequenceUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.MjMessage; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MjConstants; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MjGennerateStatusEnum; -import cn.iocoder.yudao.framework.ai.midjourney.constants.MjMessageTypeEnum; -import cn.iocoder.yudao.framework.ai.midjourney.util.MjUtil; -import com.alibaba.fastjson.JSON; -import lombok.extern.slf4j.Slf4j; -import net.dv8tion.jda.api.utils.data.DataObject; - -import java.util.List; - -@Slf4j -public class MjMessageListener { - - private MidjourneyConfig midjourneyConfig; - - public MjMessageListener(MidjourneyConfig midjourneyConfig) { - this.midjourneyConfig = midjourneyConfig; - } - - public void onMessage(DataObject raw) { - MjMessageTypeEnum messageType = MjMessageTypeEnum.of(raw.getString("t")); - if (messageType == null || MjMessageTypeEnum.DELETE == messageType) { - return; - } - DataObject data = raw.getObject("d"); - if (ignoreAndLogMessage(data, messageType)) { - return; - } - - // 转换几个重要的信息 - MjMessage mjMessage = new MjMessage(); - mjMessage.setId(data.getString(MjConstants.MSG_ID)); - mjMessage.setType(data.getInt(MjConstants.MSG_TYPE)); - mjMessage.setRawData(StrUtil.str(raw.toJson(), "UTF-8")); - mjMessage.setContent(MjUtil.parseContent(data.getString(MjConstants.MSG_CONTENT))); - // 转换 components - if (!data.getArray(MjConstants.MSG_COMPONENTS).isEmpty()) { - String componentsJson = StrUtil.str(data.getArray(MjConstants.MSG_COMPONENTS).toJson(), "UTF-8"); - List components = JSON.parseArray(componentsJson, MjMessage.ComponentType.class); - mjMessage.setComponents(components); - } - // 转换附件 - if (!data.getArray(MjConstants.MSG_ATTACHMENTS).isEmpty()) { - String attachmentsJson = StrUtil.str(data.getArray(MjConstants.MSG_ATTACHMENTS).toJson(), "UTF-8"); - List attachments = JSON.parseArray(attachmentsJson, MjMessage.Attachment.class); - mjMessage.setAttachments(attachments); - } - // 转换状态 - convertGenerateStatus(mjMessage); - // - log.info("message 信息 {}", JSONUtil.toJsonPrettyStr(mjMessage)); - System.err.println(JSONUtil.toJsonPrettyStr(mjMessage)); - } - - private void convertGenerateStatus(MjMessage mjMessage) { - if (mjMessage.getType() == 20 && mjMessage.getContent().getStatus().contains("Waiting")) { - mjMessage.setGenerateStatus(MjGennerateStatusEnum.WAITING.getValue()); - } else if (mjMessage.getType() == 20 && !StrUtil.isBlank(mjMessage.getContent().getProgress())) { - mjMessage.setGenerateStatus(MjGennerateStatusEnum.IN_PROGRESS.getValue()); - } else if (mjMessage.getType() == 0 && !CollUtil.isEmpty(mjMessage.getComponents())) { - mjMessage.setGenerateStatus(MjGennerateStatusEnum.COMPLETED.getValue()); - } - } - - private boolean ignoreAndLogMessage(DataObject data, MjMessageTypeEnum messageType) { - String channelId = data.getString(MjConstants.MSG_CHANNEL_ID); - if (!CharSequenceUtil.equals(channelId, midjourneyConfig.getChannelId())) { - return true; - } - String authorName = data.optObject("author").map(a -> a.getString("username")).orElse("System"); - log.debug("{} - {} - {}: {}", midjourneyConfig.getChannelId(), messageType.name(), authorName, data.opt("content").orElse("")); - return false; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/util/JacksonUtil.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/util/JacksonUtil.java deleted file mode 100644 index b5c96c9..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/util/JacksonUtil.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.iocoder.yudao.framework.ai.util; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; - -import java.io.IOException; - -// TODO @fansili:看看能不能用 JsonUtils -/** - * Jackson工具类 - * - * author: fansili - * time: 2024/3/17 10:13 - */ -public class JacksonUtil { - - private static final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * 初始化 ObjectMapper 以美化输出(即格式化JSON内容) - */ - static { - // 美化输出(缩进) - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - // 忽略值为 null 的属性 - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - // 配置一个模块来将 Long 类型转换为 String 类型 - SimpleModule module = new SimpleModule(); - module.addSerializer(Long.class, ToStringSerializer.instance); - objectMapper.registerModule(module); - } - - /** - * 将对象转换为 JSON 字符串 - * - * @param obj 需要序列化的Java对象 - * @return 序列化后的 JSON 字符串 - * @throws JsonProcessingException 当 JSON 序列化过程中出现错误时抛出异常 - */ - public static String toJson(Object obj) { - try { - return objectMapper.writeValueAsString(obj); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - /** - * 将 JSON 字符串反序列化为指定类型的对象 - * - * @param json JSON 字符串 - * @param clazz 目标类型 Class 对象 - * @param 泛型类型参数 - * @return 反序列化后的 Java 对象 - * @throws IOException 当 JSON 解析过程中出现错误时抛出异常 - */ - public static T fromJson(String json, Class clazz) { - try { - return objectMapper.readValue(json, clazz); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - - /** - * 将对象转换为格式化的 JSON 字符串(已启用 INDENT_OUTPUT 功能,所以所有方法都会返回格式化后的 JSON) - * - * @param obj 需要序列化的Java对象 - * @return 格式化后的 JSON 字符串 - * @throws JsonProcessingException 当 JSON 序列化过程中出现错误时抛出异常 - */ - public static String toFormattedJson(Object obj) { - // 已在类初始化时设置了 SerializationFeature.INDENT_OUTPUT,此处无需额外操作 - return toJson(obj); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java index 14b8397..0a74866 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java @@ -1,10 +1,9 @@ package cn.iocoder.yudao.framework.ai.chat; import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; -import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenApi; +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenOptions; -import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest; import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Flux; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java new file mode 100644 index 0000000..10c9a09 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.framework.ai.midjourney; + +import cn.hutool.core.io.FileUtil; +import cn.iocoder.yudao.framework.ai.midjourney.interactions.MidjourneyInteractions; +import cn.iocoder.yudao.framework.ai.midjourney.vo.Attachments; +import cn.iocoder.yudao.framework.ai.midjourney.vo.Describe; +import cn.iocoder.yudao.framework.ai.midjourney.vo.ReRoll; +import cn.iocoder.yudao.framework.ai.midjourney.vo.UploadAttachmentsRes; +import com.alibaba.fastjson.JSON; +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.FileSystemResource; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * mj + * + * author: fansili + * time: 2024/4/4 18:59 + */ +public class MidjourneyInteractionsTests { + + private MidjourneyConfig midjourneyConfig; + @Before + public void setup() { + String token = "OTcyNzIxMzA0ODkxNDUzNDUw.G_vMOz.BO_Q0sXAD80u5ZKIHPNYDTRX_FgeKL3cKFc53I"; + Map requestTemplates = new HashMap<>(); + List files = FileUtil.loopFiles("/Users/fansili/projects/github/ruoyi-vue-pro/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body"); + for (File file : files) { + requestTemplates.put(file.getName().replace(".json", ""), FileUtil.readUtf8String(file)); + } + this.midjourneyConfig = new MidjourneyConfig(token, "1225608134878302329", "1225608134878302332", requestTemplates); + } + + @Test + public void mjImageTest() { + MidjourneyInteractions mjImagineInteractions = new MidjourneyInteractions(midjourneyConfig); + mjImagineInteractions.imagine("童话里应该是什么样子?"); + } + + + @Test + public void reRollTest() { + MidjourneyInteractions mjImagineInteractions = new MidjourneyInteractions(midjourneyConfig); + mjImagineInteractions.reRoll(new ReRoll() + .setMessageId("1226165117448753243") + .setCustomId("MJ::JOB::upsample::3::2aeefbef-43e2-4057-bcf1-43b5f39ab6f7")); + } + + @Test + public void uploadAttachmentsTest() { + MidjourneyInteractions mjImagineInteractions = new MidjourneyInteractions(midjourneyConfig); + UploadAttachmentsRes res = mjImagineInteractions.uploadAttachments( + new Attachments().setFileSystemResource( + new FileSystemResource(new File("/Users/fansili/Downloads/DSC01402.JPG"))) + ); + System.err.println(JSON.toJSONString(res)); + } + + @Test + public void describeTest() { + MidjourneyInteractions mjImagineInteractions = new MidjourneyInteractions(midjourneyConfig); + mjImagineInteractions.describe(new Describe() + .setFileName("DSC01402.JPG") + .setFinalFileName("16826931-2873-45ec-8cfb-0ad81f1a075f/DSC01402.JPG") + ); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java new file mode 100644 index 0000000..39d6493 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyUtilTests.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.framework.ai.midjourney; + +import cn.iocoder.yudao.framework.ai.midjourney.util.MidjourneyUtil; +import org.junit.Test; + +/** + * mj util + * + * author: fansili + * time: 2024/4/6 21:57 + */ +public class MidjourneyUtilTests { + + @Test + public void parseContentTest() { + String content1 = "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)"; + String content2 = "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (fast, stealth)"; + + System.err.println(MidjourneyUtil.parseContent(content1)); + System.err.println(MidjourneyUtil.parseContent(content2)); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java new file mode 100644 index 0000000..a0d6728 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyWebSocketTests.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.framework.ai.midjourney; + +import cn.hutool.core.io.FileUtil; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MidjourneyMessageListener; +import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MidjourneyWebSocketStarter; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; + +/** + * author: fansili + * time: 2024/4/3 16:40 + */ +public class MidjourneyWebSocketTests { + + private MidjourneyConfig midjourneyConfig; + + @Before + public void setup() { + String token = "OTcyNzIxMzA0ODkxNDUzNDUw.G_vMOz.BO_Q0sXAD80u5ZKIHPNYDTRX_FgeKL3cKFc53I"; + Map requestTemplates = new HashMap<>(); + List files = FileUtil.loopFiles("/Users/fansili/projects/github/ruoyi-vue-pro/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body"); + for (File file : files) { + requestTemplates.put(file.getName().replace(".json", ""), FileUtil.readUtf8String(file)); + } + this.midjourneyConfig = new MidjourneyConfig(token, "1225608134878302329", "1225608134878302332", requestTemplates); + } + + @Test + public void startSocketTest() { + String wssUrl = "wss://gateway.discord.gg"; + var messageListener = new MidjourneyMessageListener(midjourneyConfig); + var webSocketStarter = new MidjourneyWebSocketStarter(wssUrl, null, midjourneyConfig, messageListener); + + try { + webSocketStarter.start(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + + Scanner scanner = new Scanner(System.in); + scanner.nextLine(); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java deleted file mode 100644 index 636e9d2..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java +++ /dev/null @@ -1,73 +0,0 @@ -package cn.iocoder.yudao.framework.ai.mj; - -import cn.hutool.core.io.FileUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.interactions.MjInteractions; -import cn.iocoder.yudao.framework.ai.midjourney.vo.Attachments; -import cn.iocoder.yudao.framework.ai.midjourney.vo.Describe; -import cn.iocoder.yudao.framework.ai.midjourney.vo.ReRoll; -import cn.iocoder.yudao.framework.ai.midjourney.vo.UploadAttachmentsRes; -import com.alibaba.fastjson.JSON; -import org.junit.Before; -import org.junit.Test; -import org.springframework.core.io.FileSystemResource; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * mj - * - * author: fansili - * time: 2024/4/4 18:59 - */ -public class MjInteractionsTests { - - private MidjourneyConfig midjourneyConfig; - @Before - public void setup() { - String token = "OTcyNzIxMzA0ODkxNDUzNDUw.G_vMOz.BO_Q0sXAD80u5ZKIHPNYDTRX_FgeKL3cKFc53I"; - Map requestTemplates = new HashMap<>(); - List files = FileUtil.loopFiles("/Users/fansili/projects/github/ruoyi-vue-pro/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body"); - for (File file : files) { - requestTemplates.put(file.getName().replace(".json", ""), FileUtil.readUtf8String(file)); - } - this.midjourneyConfig = new MidjourneyConfig(token, "1225608134878302329", "1225608134878302332", requestTemplates); - } - - @Test - public void mjImageTest() { - MjInteractions mjImagineInteractions = new MjInteractions(midjourneyConfig); - mjImagineInteractions.imagine("童话里应该是什么样子?"); - } - - - @Test - public void reRollTest() { - MjInteractions mjImagineInteractions = new MjInteractions(midjourneyConfig); - mjImagineInteractions.reRoll(new ReRoll() - .setMessageId("1226165117448753243") - .setCustomId("MJ::JOB::upsample::3::2aeefbef-43e2-4057-bcf1-43b5f39ab6f7")); - } - - @Test - public void uploadAttachmentsTest() { - MjInteractions mjImagineInteractions = new MjInteractions(midjourneyConfig); - UploadAttachmentsRes res = mjImagineInteractions.uploadAttachments( - new Attachments().setFileSystemResource( - new FileSystemResource(new File("/Users/fansili/Downloads/DSC01402.JPG"))) - ); - System.err.println(JSON.toJSONString(res)); - } - - @Test - public void describeTest() { - MjInteractions mjImagineInteractions = new MjInteractions(midjourneyConfig); - mjImagineInteractions.describe(new Describe() - .setFileName("DSC01402.JPG") - .setFinalFileName("16826931-2873-45ec-8cfb-0ad81f1a075f/DSC01402.JPG") - ); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjUtilTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjUtilTests.java deleted file mode 100644 index 521e9b5..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjUtilTests.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.framework.ai.mj; - -import cn.iocoder.yudao.framework.ai.midjourney.util.MjUtil; -import org.junit.Test; - -/** - * mj util - * - * author: fansili - * time: 2024/4/6 21:57 - */ -public class MjUtilTests { - - @Test - public void parseContentTest() { - String content1 = "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (32%) (fast, stealth)"; - String content2 = "**南极应该是什么样子? --v 6.0 --style raw** - <@972721304891453450> (fast, stealth)"; - - System.err.println(MjUtil.parseContent(content1)); - System.err.println(MjUtil.parseContent(content2)); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjWebSocketTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjWebSocketTests.java deleted file mode 100644 index 9076923..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjWebSocketTests.java +++ /dev/null @@ -1,51 +0,0 @@ -package cn.iocoder.yudao.framework.ai.mj; - -import cn.hutool.core.io.FileUtil; -import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener.MjMessageListener; -import cn.iocoder.yudao.framework.ai.midjourney.webSocket.MjWebSocketStarter; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; - -/** - * author: fansili - * time: 2024/4/3 16:40 - */ -public class MjWebSocketTests { - - private MidjourneyConfig midjourneyConfig; - - @Before - public void setup() { - String token = "OTcyNzIxMzA0ODkxNDUzNDUw.G_vMOz.BO_Q0sXAD80u5ZKIHPNYDTRX_FgeKL3cKFc53I"; - Map requestTemplates = new HashMap<>(); - List files = FileUtil.loopFiles("/Users/fansili/projects/github/ruoyi-vue-pro/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body"); - for (File file : files) { - requestTemplates.put(file.getName().replace(".json", ""), FileUtil.readUtf8String(file)); - } - this.midjourneyConfig = new MidjourneyConfig(token, "1225608134878302329", "1225608134878302332", requestTemplates); - } - - @Test - public void startSocketTest() { - String wssUrl = "wss://gateway.discord.gg"; - var messageListener = new MjMessageListener(midjourneyConfig); - var webSocketStarter = new MjWebSocketStarter(wssUrl, null, midjourneyConfig, messageListener); - - try { - webSocketStarter.start(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - - Scanner scanner = new Scanner(System.in); - scanner.nextLine(); - } -} -- libgit2 0.21.4