فهرست منبع

Merge branch 'master-jdk21-ai' of https://gitee.com/cherishsince/ruoyi-vue-pro into master-jdk21-ai

cherishsince 1 سال پیش
والد
کامیت
7d0f5fdb7b
26فایلهای تغییر یافته به همراه322 افزوده شده و 60 حذف شده
  1. 29 0
      yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
  2. 8 5
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java
  3. 4 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java
  4. 0 47
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjClient.java
  5. 119 7
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java
  6. 20 1
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java
  7. 22 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/Attachments.java
  8. 26 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/Describe.java
  9. 6 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/ReRoll.java
  10. 36 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/UploadAttachmentsRes.java
  11. 28 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body/describe.json
  12. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/1.json
  13. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/10.json
  14. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/11.json
  15. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/12.json
  16. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/2.json
  17. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/3.json
  18. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/4.json
  19. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/5.json
  20. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/6.json
  21. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/7.json
  22. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/8.json
  23. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/9.json
  24. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/interactions_type2.json
  25. 0 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/interactions_type3.json
  26. 24 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java

+ 29 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml

@@ -120,6 +120,35 @@
             <version>4.5.14</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <!-- JSON 处理库,如 Jackson -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.dv8tion</groupId>
+            <artifactId>JDA</artifactId>
+            <version>5.0.0-beta.21</version>
+<!--            <exclusions>-->
+<!--                <exclusion>-->
+<!--                    <groupId>club.minnced</groupId>-->
+<!--                    <artifactId>opus-java</artifactId>-->
+<!--                </exclusion>-->
+<!--            </exclusions>-->
+        </dependency>
     </dependencies>
 
 </project>

+ 8 - 5
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyConfig.java

@@ -42,14 +42,20 @@ public class MidjourneyConfig {
      * 发送命令
      */
     private String apiInteractions = "api/v9/interactions";
-
+    /**
+     * 附件
+     */
+    private String apiAttachments = "/api/v9/channels/%s/attachments";
+    /**
+     * 文件上传
+     */
+    private String apiAttachmentsUpload = "https://discord-attachments-uploads-prd.storage.googleapis.com/";
 
     //
     // 浏览器配置
 
     private String userAage = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
 
-
     //
     // 请求 json 文件
 
@@ -65,9 +71,6 @@ public class MidjourneyConfig {
         this.token = token;
         this.guildId = guildId;
         this.channelId = channelId;
-        this.serverUrl = serverUrl;
-        this.apiInteractions = apiInteractions;
-        this.userAage = userAage;
         this.requestTemplates = requestTemplates;
 
         // 生成 session id

+ 4 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjConstants.java

@@ -34,4 +34,8 @@ public final class MjConstants {
 	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";
 }

+ 0 - 47
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjClient.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.framework.ai.midjourney.interactions;
-
-import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Map;
-
-/**
- * mj client
- * <p>
- * author: fansili
- * time: 2024/4/3 17:37
- */
-public class MjClient {
-
-    private static RestTemplate restTemplate = new RestTemplate();
-    private static HttpHeaders headers = new HttpHeaders();
-
-    private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s";
-
-    static {
-        headers.setContentType(MediaType.APPLICATION_JSON); // 设置内容类型为JSON
-        headers.set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36");
-        headers.set("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");
-    }
-
-    public static String post(String url, MidjourneyConfig config, String body) {
-        // 设置 header
-        headers.set("Referer", String.format(HEADER_REFERER, config.getGuildId(), config.getChannelId()));
-        headers.set("Authorization", config.getToken());
-        // 封装请求体和头部信息
-        HttpEntity<String> requestEntity = new HttpEntity<>(body, headers);
-        // 发送请求
-        return restTemplate.postForObject(url, requestEntity, String.class);
-    }
-
-
-    public static String setParams(String requestTemplate, Map<String, String> requestParams) {
-        for (Map.Entry<String, String> entry : requestParams.entrySet()) {
-            requestTemplate = requestTemplate.replace("$".concat(entry.getKey()), entry.getValue());
-        }
-        return requestTemplate;
-    }
-}

+ 119 - 7
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/interactions/MjInteractions.java

@@ -3,10 +3,25 @@ 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;
 
 /**
@@ -18,9 +33,12 @@ import java.util.HashMap;
 @Slf4j
 public class MjInteractions {
 
-    private MidjourneyConfig midjourneyConfig;
 
-    private String url;
+    private final String url;
+    private final MidjourneyConfig midjourneyConfig;
+    private final RestTemplate restTemplate = new RestTemplate();
+    private static final String HEADER_REFERER = "https://discord.com/channels/%s/%s";
+
 
     public MjInteractions(MidjourneyConfig midjourneyConfig) {
         this.midjourneyConfig = midjourneyConfig;
@@ -37,10 +55,13 @@ public class MjInteractions {
         requestParams.put("session_id", midjourneyConfig.getSessionId());
         requestParams.put("nonce", String.valueOf(IdUtil.getSnowflakeNextId()));
         requestParams.put("prompt", prompt);
-        // 设置参数
-        String requestBody = MjClient.setParams(requestTemplate, requestParams);
+        // 解析 template 参数占位符
+        String requestBody = MjUtil.parseTemplate(requestTemplate, requestParams);
+        // 获取 header
+        HttpHeaders httpHeaders = getHttpHeaders();
         // 发送请求
-        String res = MjClient.post(url, midjourneyConfig, requestBody);
+        HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, httpHeaders);
+        String res = restTemplate.postForObject(url, requestEntity, String.class);
         // 这个 res 只要不返回值,就是成功!
         boolean isSuccess = StrUtil.isBlank(res);
         if (isSuccess) {
@@ -50,6 +71,8 @@ public class MjInteractions {
         return isSuccess;
     }
 
+
+
     public Boolean reRoll(ReRoll reRoll) {
         // 获取请求模板
         String requestTemplate = midjourneyConfig.getRequestTemplates().get("reroll");
@@ -61,10 +84,88 @@ public class MjInteractions {
         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<String> 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;
+    }
+
+
+    public UploadAttachmentsRes uploadAttachments(Attachments attachments) {
+        // file
+        JSONObject fileObj = new JSONObject();
+        fileObj.put("id", "0");
+        fileObj.put("filename", attachments.getFileSystemResource().getFilename());
+        try {
+            fileObj.put("file_size", attachments.getFileSystemResource().contentLength());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        // 创建用于存放表单数据的MultiValueMap
+        MultiValueMap<String, Object> multipartRequest = new LinkedMultiValueMap<>();
+        multipartRequest.put("files", Lists.newArrayList(fileObj));
+        // 设置header值
+        HttpHeaders httpHeaders = new HttpHeaders();
+        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<MultiValueMap<String, Object>> 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<FileSystemResource> fileSystemResourceHttpEntity = new HttpEntity<>(attachments.getFileSystemResource(), httpHeaders);
+        ResponseEntity<String> 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");
         // 设置参数
-        String requestBody = MjClient.setParams(requestTemplate, requestParams);
+        HashMap<String, String> 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<String, String> formData = new LinkedMultiValueMap<>();
+        formData.add("payload_json", requestBody);
         // 发送请求
-        String res = MjClient.post(url, midjourneyConfig, requestBody);
+        HttpEntity<MultiValueMap<String, String>> multiValueMapHttpEntity = new HttpEntity<>(formData, httpHeaders);
+        String res = restTemplate.postForObject(url, multiValueMapHttpEntity, String.class);
         // 这个 res 只要不返回值,就是成功!
         boolean isSuccess = StrUtil.isBlank(res);
         if (isSuccess) {
@@ -73,4 +174,15 @@ public class MjInteractions {
         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;
+    }
 }

+ 20 - 1
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/util/MjUtil.java

@@ -3,6 +3,7 @@ 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;
 
@@ -19,7 +20,12 @@ public class MjUtil {
     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) {
         // 有三种格式。
         // 南极应该是什么样子?
@@ -61,4 +67,17 @@ public class MjUtil {
         return mjContent;
     }
 
+    /**
+     * 设置 params
+     *
+     * @param requestTemplate
+     * @param requestParams
+     * @return
+     */
+    public static String parseTemplate(String requestTemplate, Map<String, String> requestParams) {
+        for (Map.Entry<String, String> entry : requestParams.entrySet()) {
+            requestTemplate = requestTemplate.replace("$".concat(entry.getKey()), entry.getValue());
+        }
+        return requestTemplate;
+    }
 }

+ 22 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/Attachments.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.framework.ai.midjourney.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.core.io.FileSystemResource;
+
+/**
+ * 附件
+ * <p>
+ * author: fansili
+ * time: 2024/4/7 17:18
+ */
+@Data
+@Accessors(chain = true)
+public class Attachments {
+
+    /**
+     * 创建文件系统资源对象
+     */
+    private FileSystemResource fileSystemResource;
+
+}

+ 26 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/Describe.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.framework.ai.midjourney.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.File;
+
+/**
+ * describe
+ *
+ * author: fansili
+ * time: 2024/4/7 12:30
+ */
+@Data
+@Accessors(chain = true)
+public class Describe {
+
+    /**
+     * 文件名字
+     */
+    private String fileName;
+    /**
+     * UploadAttachmentsRes 里面的 finalFileName
+     */
+    private String finalFileName;
+}

+ 6 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/ReRoll.java

@@ -11,6 +11,12 @@ import lombok.experimental.Accessors;
 @Accessors(chain = true)
 public class ReRoll {
 
+    /**
+     * socket 消息里面收到的 messageId
+     */
     private String messageId;
+    /**
+     * socket 消息里面的,操作按钮id(MJ::JOB::upsample::3::2aeefbef-43e2-4057-bcf1-43b5f39ab6f7)
+     */
     private String customId;
 }

+ 36 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/vo/UploadAttachmentsRes.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.framework.ai.midjourney.vo;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * 上传附件 - res
+ *
+ * author: fansili
+ * time: 2024/4/8 13:32
+ */
+@Data
+@Accessors(chain = true)
+public class UploadAttachmentsRes {
+
+    private List<Attachment> attachments;
+
+    @Data
+    @Accessors(chain = true)
+    public static class Attachment {
+        /**
+         * 附件的ID。
+         */
+        private int id;
+        /**
+         * 附件的上传URL。
+         */
+        private String uploadUrl;
+        /**
+         * 上传到服务器的文件名。
+         */
+        private String uploadFilename;
+    }
+}

+ 28 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body/describe.json

@@ -0,0 +1,28 @@
+{
+  "type": 2,
+  "guild_id": "$guild_id",
+  "channel_id": "$channel_id",
+  "application_id": "936929561302675456",
+  "session_id": "$session_id",
+  "nonce": "$nonce",
+  "data": {
+    "version": "1204231436023111690",
+    "id": "1092492867185950852",
+    "name": "describe",
+    "type": 1,
+    "options": [
+      {
+        "type": 11,
+        "name": "image",
+        "value": 0
+      }
+    ],
+    "attachments": [
+      {
+        "id": "0",
+        "filename": "$file_name",
+        "uploaded_filename": "$final_file_name"
+      }
+    ]
+  }
+}

+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/1.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/1.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/10.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/10.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/11.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/11.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/12.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/12.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/2.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/2.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/3.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/3.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/4.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/4.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/5.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/5.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/6.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/6.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/7.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/7.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/8.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/8.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/图片消息过程/9.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/imageGenerationProcess/9.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body/interactions_type2.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/interactions_type2.json


+ 0 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/http-body/interactions_type3.json → yudao-module-ai/yudao-spring-boot-starter-ai/src/main/resources/requestTestJson/interactions_type3.json


+ 24 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/mj/MjInteractionsTests.java

@@ -3,9 +3,14 @@ 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;
@@ -46,4 +51,23 @@ public class MjInteractionsTests {
                 .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")
+        );
+    }
 }