Commit 098483d2beb7d72d44c2f7e349199d3ed9230673

Authored by YunaiV
1 parent 88142ed7

【代码优化】AI:MJ 生成图片的优化

Showing 23 changed files with 322 additions and 642 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -21,30 +21,21 @@ public interface ErrorCodeConstants { @@ -21,30 +21,21 @@ public interface ErrorCodeConstants {
21 // ========== API 聊天模型 1-040-002-000 ========== 21 // ========== API 聊天模型 1-040-002-000 ==========
22 ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在"); 22 ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在");
23 ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!"); 23 ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "聊天角色({})已禁用!");
24 - ErrorCode CHAT_ROLE_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "操作失败,找不到默认聊天角色");  
25 24
26 // ========== API 聊天会话 1-040-003-000 ========== 25 // ========== API 聊天会话 1-040-003-000 ==========
27 26
28 ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!"); 27 ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!");
29 ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整"); 28 ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "操作失败,该聊天模型的配置不完整");
30 - ErrorCode CHAT_CONVERSATION_UPDATE_MAX_TOKENS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Token 超过上限");  
31 - ErrorCode CHAT_CONVERSATION_UPDATE_MAX_CONTEXTS_ERROR = new ErrorCode(1_040_003_002, "更新对话失败,最大 Context 超过上限");  
32 29
33 // ========== API 聊天消息 1-040-004-000 ========== 30 // ========== API 聊天消息 1-040-004-000 ==========
  31 +
34 ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); 32 ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!");
35 ErrorCode AI_CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!"); 33 ErrorCode AI_CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "Stream 对话异常!");
36 34
37 - // midjourney  
38 -  
39 - ErrorCode AI_MIDJOURNEY_IMAGINE_FAIL = new ErrorCode(1_022_000_040, "midjourney imagine 操作失败!");  
40 - ErrorCode AI_MIDJOURNEY_OPERATION_NOT_EXISTS = new ErrorCode(1_022_000_040, "midjourney 操作不存在!");  
41 - ErrorCode AI_MIDJOURNEY_MESSAGE_ID_INCORRECT = new ErrorCode(1_022_000_040, "midjourney message id 不正确!");  
42 -  
43 // ========== API 绘画 1-040-005-000 ========== 35 // ========== API 绘画 1-040-005-000 ==========
44 36
45 - ErrorCode AI_IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "image 不存在!");  
46 - ErrorCode AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败! {}"); 37 + ErrorCode AI_IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "图片不存在!");
  38 + ErrorCode AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败!原因:{}");
47 ErrorCode AI_IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}"); 39 ErrorCode AI_IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");
48 - ErrorCode AI_IMAGE_SYSTEM_ACCOUNT_INSUFFICIENT_BALANCE = new ErrorCode(1_022_005_003, "Midjourney 系统账户余额不足!");  
49 40
50 } 41 }
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java
@@ -12,20 +12,20 @@ import lombok.Getter; @@ -12,20 +12,20 @@ import lombok.Getter;
12 @Getter 12 @Getter
13 public enum AiImageStatusEnum { 13 public enum AiImageStatusEnum {
14 14
15 - IN_PROGRESS("10", "进行中"),  
16 - SUCCESS("20", "完成"),  
17 - FAIL("30", "失败"); 15 + IN_PROGRESS(10, "进行中"),
  16 + SUCCESS(20, "完成"),
  17 + FAIL(30, "失败");
18 18
19 /** 19 /**
20 * 状态 20 * 状态
21 */ 21 */
22 - private final String status; 22 + private final Integer status;
23 /** 23 /**
24 * 状态名 24 * 状态名
25 */ 25 */
26 private final String name; 26 private final String name;
27 27
28 - public static AiImageStatusEnum valueOfStatus(String status) { 28 + public static AiImageStatusEnum valueOfStatus(Integer status) {
29 for (AiImageStatusEnum statusEnum : AiImageStatusEnum.values()) { 29 for (AiImageStatusEnum statusEnum : AiImageStatusEnum.values()) {
30 if (statusEnum.getStatus().equals(status)) { 30 if (statusEnum.getStatus().equals(status)) {
31 return statusEnum; 31 return statusEnum;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/YudaoMidjourneyConfiguration.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.ai.config; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.ai.config;
3 import cn.iocoder.yudao.framework.ai.core.model.midjourney.MidjourneyConfig; 3 import cn.iocoder.yudao.framework.ai.core.model.midjourney.MidjourneyConfig;
4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 4 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import org.springframework.boot.autoconfigure.AutoConfiguration;  
7 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Bean;
9 import org.springframework.context.annotation.Configuration; 8 import org.springframework.context.annotation.Configuration;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.http
@@ -29,12 +29,17 @@ Authorization: {{token}} @@ -29,12 +29,17 @@ Authorization: {{token}}
29 "style": "vivid" 29 "style": "vivid"
30 } 30 }
31 31
32 -### chat midjourney 32 +### 生成图片:生成图片
33 33
34 -POST {{baseUrl}}/admin-api/ai/image/midjourney 34 +POST {{baseUrl}}/ai/image/midjourney/imagine
35 Content-Type: application/json 35 Content-Type: application/json
36 Authorization: {{token}} 36 Authorization: {{token}}
37 37
38 { 38 {
39 - "prompt": "Cute cartoon style mobile game scene, a colorful camping car with an outdoor table and chairs next to it on the road in a spring forest, the simple structure of the camper van, soft lighting, C4D rendering, 3d model in the style of a cartoon, cute shape, a pastel color scheme, closeup view from the side angle, high resolution, bright colors, a happy atmosphere." 39 + "prompt": "中国旗袍",
  40 + "model": "midjourney",
  41 + "width": "1",
  42 + "height": "1",
  43 + "version": "6.0",
  44 + "base64Array": []
40 } 45 }
41 \ No newline at end of file 46 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
1 package cn.iocoder.yudao.module.ai.controller.admin.image; 1 package cn.iocoder.yudao.module.ai.controller.admin.image;
2 2
3 import cn.hutool.core.util.ObjUtil; 3 import cn.hutool.core.util.ObjUtil;
  4 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageParam; 6 import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO;  
9 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; 9 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
10 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;  
11 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; 10 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO;
  11 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiImageMidjourneyImagineReqVO;
12 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 12 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
13 import cn.iocoder.yudao.module.ai.service.image.AiImageService; 13 import cn.iocoder.yudao.module.ai.service.image.AiImageService;
14 import io.swagger.v3.oas.annotations.Operation; 14 import io.swagger.v3.oas.annotations.Operation;
@@ -63,19 +63,24 @@ public class AiImageController { @@ -63,19 +63,24 @@ public class AiImageController {
63 return success(true); 63 return success(true);
64 } 64 }
65 65
66 - // ================ midjourney 接口 ================ 66 + // ================ midjourney 专属 ================
67 67
68 - @Operation(summary = "Midjourney imagine(绘画)") 68 + @Operation(summary = "【Midjourney】生成图片")
69 @PostMapping("/midjourney/imagine") 69 @PostMapping("/midjourney/imagine")
70 - public CommonResult<Long> midjourneyImagine(@Validated @RequestBody AiImageMidjourneyImagineReqVO req) {  
71 - return success(imageService.midjourneyImagine(getLoginUserId(), req)); 70 + public CommonResult<Long> midjourneyImagine(@Validated @RequestBody AiImageMidjourneyImagineReqVO reqVO) {
  71 + if (true) {
  72 + imageService.midjourneySync();
  73 + return null;
  74 + }
  75 + Long imageId = imageService.midjourneyImagine(getLoginUserId(), reqVO);
  76 + return success(imageId);
72 } 77 }
73 78
74 - @Operation(summary = "Midjourney 回调通知", description = "由 Midjourney Proxy 回调") 79 + @Operation(summary = "Midjourney 生成图片的回调通知", description = "由 Midjourney Proxy 回调")
75 @PostMapping("/midjourney-notify") 80 @PostMapping("/midjourney-notify")
76 @PermitAll 81 @PermitAll
77 - public void midjourneyNotify(@RequestBody MidjourneyNotifyReqVO notifyReqVO) {  
78 - imageService.midjourneyNotify(notifyReqVO); 82 + public void midjourneyNotify(@RequestBody MidjourneyApi.Notify notify) {
  83 + imageService.midjourneyNotify(notify);
79 } 84 }
80 85
81 @Operation(summary = "Midjourney Action", description = "例如说:放大、缩小、U1、U2 等") 86 @Operation(summary = "Midjourney Action", description = "例如说:放大、缩小、U1、U2 等")
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyImagineReqVO.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.image.vo;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import jakarta.validation.constraints.NotNull;  
5 -import lombok.Data;  
6 -import lombok.experimental.Accessors;  
7 -  
8 -import java.util.List;  
9 -  
10 -// TODO @fan:待定  
11 -/**  
12 - * midjourney req  
13 - *  
14 - * @author fansili  
15 - * @time 2024/4/28 17:42  
16 - * @since 1.0  
17 - */  
18 -@Data  
19 -@Accessors(chain = true)  
20 -public class AiImageMidjourneyImagineReqVO {  
21 -  
22 - @Schema(description = "提示词")  
23 - @NotNull(message = "提示词不能为空!")  
24 - private String prompt;  
25 -  
26 - @Schema(description = "模型(midjourney、niji)")  
27 - private String model;  
28 -  
29 - @Schema(description = "图片宽度 --ar 设置")  
30 - private Integer width;  
31 -  
32 - @Schema(description = "图片高度 --ar 设置")  
33 - private Integer height;  
34 -  
35 - @Schema(description = "版本号 --v 设置")  
36 - private String version;  
37 -  
38 - @Schema(description = "垫图(参考图)base64数组")  
39 - private List<String> base64Array;  
40 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyOperationsVO.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.image.vo;  
2 -  
3 -import lombok.Data;  
4 -import lombok.experimental.Accessors;  
5 -  
6 -/**  
7 - * mj 保存 components 记录  
8 - *  
9 - * "components": [  
10 - * {  
11 - * "custom_id": "MJ::JOB::upsample::1::5d32f4e8-8d2f-4bef-82d8-bf517e3c3660",  
12 - * "style": 2,  
13 - * "label": "U1",  
14 - * "type": 2  
15 - * },  
16 - * ]  
17 - *  
18 - * @author fansili  
19 - * @time 2024/5/8 14:44  
20 - * @since 1.0  
21 - */  
22 -@Data  
23 -@Accessors(chain = true)  
24 -public class AiImageMidjourneyOperationsVO {  
25 -  
26 - private String custom_id;  
27 - private String style;  
28 - private String label;  
29 - private String type;  
30 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java
1 package cn.iocoder.yudao.module.ai.controller.admin.image.vo; 1 package cn.iocoder.yudao.module.ai.controller.admin.image.vo;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
3 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 5 import lombok.Data;
5 6
@@ -46,14 +47,11 @@ public class AiImageRespVO { @@ -46,14 +47,11 @@ public class AiImageRespVO {
46 @Schema(description = "绘制参数") 47 @Schema(description = "绘制参数")
47 private Map<String, String> options; 48 private Map<String, String> options;
48 49
49 - @Schema(description = "绘画 response")  
50 - private MidjourneyNotifyReqVO response;  
51 -  
52 // TODO @fan:进度是百分比,还是一个数字哈?感觉这个可以统一成通用字段; 50 // TODO @fan:进度是百分比,还是一个数字哈?感觉这个可以统一成通用字段;
53 @Schema(description = "mj 进度") 51 @Schema(description = "mj 进度")
54 private String progress; 52 private String progress;
55 53
56 @Schema(description = "mj buttons 按钮") 54 @Schema(description = "mj buttons 按钮")
57 - private List<MidjourneyNotifyReqVO.Button> buttons; 55 + private List<MidjourneyApi.Button> buttons;
58 56
59 } 57 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/MidjourneyImagineReqVO.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.image.vo;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import jakarta.validation.constraints.NotNull;  
5 -import lombok.Data;  
6 -  
7 -import java.util.List;  
8 -  
9 -// TODO @fan:待定  
10 -/**  
11 - * Midjourney:Imagine 请求  
12 - *  
13 - * @author fansili  
14 - * @time 2024/5/30 14:02  
15 - * @since 1.0  
16 - */  
17 -@Data  
18 -public class MidjourneyImagineReqVO {  
19 -  
20 - @Schema(description = "垫图(参考图)base64数组", required = false)  
21 - private List<String> base64Array;  
22 -  
23 - @Schema(description = "通知地址", required = false)  
24 - @NotNull(message = "回调地址不能为空!")  
25 - private String notifyHook;  
26 -  
27 - @Schema(description = "提示词", required = true)  
28 - @NotNull(message = "提示词不能为空!")  
29 - private String prompt;  
30 -  
31 - @Schema(description = "自定义参数", required = false)  
32 - private String state;  
33 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/MidjourneyNotifyReqVO.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.image.vo;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -  
6 -import java.util.List;  
7 -  
8 -/**  
9 - * Midjourney Proxy 通知回调  
10 - *  
11 - * - Midjourney Proxy:通知回调 bean 是 com.github.novicezk.midjourney.support.Task  
12 - * - 毫秒 api 通知回调文档地址:https://gpt-best.apifox.cn/doc-3530863  
13 - *  
14 - * @author fansili  
15 - * @time 2024/5/31 10:37  
16 - * @since 1.0  
17 - */  
18 -@Data  
19 -public class MidjourneyNotifyReqVO {  
20 -  
21 - @Schema(description = "job id")  
22 - private String id;  
23 -  
24 - @Schema(description = "任务类型 MidjourneyTaskActionEnum")  
25 - private String action;  
26 - @Schema(description = "任务状态 MidjourneyTaskStatusEnum")  
27 - private String status;  
28 -  
29 - @Schema(description = "提示词")  
30 - private String prompt;  
31 - @Schema(description = "提示词-英文")  
32 - private String promptEn;  
33 -  
34 - @Schema(description = "任务描述")  
35 - private String description;  
36 - @Schema(description = "自定义参数")  
37 - private String state;  
38 -  
39 - @Schema(description = "提交时间")  
40 - private Long submitTime;  
41 - @Schema(description = "开始执行时间")  
42 - private Long startTime;  
43 - @Schema(description = "结束时间")  
44 - private Long finishTime;  
45 -  
46 - @Schema(description = "图片url")  
47 - private String imageUrl;  
48 -  
49 - @Schema(description = "任务进度")  
50 - private String progress;  
51 - @Schema(description = "失败原因")  
52 - private String failReason;  
53 -  
54 - @Schema(description = "任务完成后的可执行按钮")  
55 - private List<Button> buttons;  
56 -  
57 - @Data  
58 - public static class Button {  
59 -  
60 - @Schema(description = "MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识")  
61 - private String customId;  
62 -  
63 - @Schema(description = "图标 emoji")  
64 - private String emoji;  
65 -  
66 - @Schema(description = "Make Variations 文本")  
67 - private String label;  
68 -  
69 - @Schema(description = "类型,系统内部使用")  
70 - private String type;  
71 -  
72 - @Schema(description = "样式: 2(Primary)、3(Green)")  
73 - private String style;  
74 - }  
75 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/MidjourneySubmitRespVO.java deleted
1 -package cn.iocoder.yudao.module.ai.controller.admin.image.vo;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -  
6 -import java.util.Map;  
7 -  
8 -// TODO @fan:待定  
9 -/**  
10 - * Midjourney:Imagine 请求  
11 - *  
12 - * @author fansili  
13 - * @time 2024/5/30 14:02  
14 - * @since 1.0  
15 - */  
16 -@Data  
17 -public class MidjourneySubmitRespVO {  
18 -  
19 - @Schema(description = "状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)")  
20 - private String code;  
21 -  
22 - @Schema(description = "描述")  
23 - private String description;  
24 -  
25 - @Schema(description = "扩展字段")  
26 - private Map<String, Object> properties;  
27 -  
28 - @Schema(description = "任务ID")  
29 - private String result;  
30 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/midjourney/AiImageMidjourneyImagineReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - 绘画生成(Midjourney) Request VO")
  11 +@Data
  12 +public class AiImageMidjourneyImagineReqVO {
  13 +
  14 + @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "中国神龙")
  15 + @NotEmpty(message = "提示词不能为空!")
  16 + private String prompt;
  17 +
  18 + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "midjourney")
  19 + @NotEmpty(message = "模型不能为空")
  20 + private String model; // 参考 MidjourneyApi.ModelEnum
  21 +
  22 + @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  23 + @NotNull(message = "图片宽度不能为空")
  24 + private Integer width;
  25 +
  26 + @Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  27 + @NotNull(message = "图片高度不能为空")
  28 + private Integer height;
  29 +
  30 + @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6.0")
  31 + @NotEmpty(message = "版本号不能为空")
  32 + private String version;
  33 +
  34 + // TODO @fan:参考图,建议用 referImageUrl。
  35 + @Schema(description = "垫图(参考图)base64数组")
  36 + private List<String> base64Array;
  37 +
  38 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/image/AiImageDO.java
1 package cn.iocoder.yudao.module.ai.dal.dataobject.image; 1 package cn.iocoder.yudao.module.ai.dal.dataobject.image;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO;  
6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; 6 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO;
7 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 7 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -28,8 +28,6 @@ import java.util.Map; @@ -28,8 +28,6 @@ import java.util.Map;
28 @Data 28 @Data
29 public class AiImageDO extends BaseDO { 29 public class AiImageDO extends BaseDO {
30 30
31 - // TODO @fan:1)使用 java 注释哈,不要注解。2)关联、枚举字段,要关联到对应类,参考 AiChatMessageDO 的注释  
32 -  
33 /** 31 /**
34 * 编号 32 * 编号
35 */ 33 */
@@ -76,7 +74,7 @@ public class AiImageDO extends BaseDO { @@ -76,7 +74,7 @@ public class AiImageDO extends BaseDO {
76 * 74 *
77 * 枚举 {@link AiImageStatusEnum} 75 * 枚举 {@link AiImageStatusEnum}
78 */ 76 */
79 - private String status; 77 + private Integer status;
80 78
81 /** 79 /**
82 * 图片地址 80 * 图片地址
@@ -97,22 +95,10 @@ public class AiImageDO extends BaseDO { @@ -97,22 +95,10 @@ public class AiImageDO extends BaseDO {
97 private Map<String, Object> options; 95 private Map<String, Object> options;
98 96
99 /** 97 /**
100 - * 绘画 response  
101 - */  
102 - @TableField(typeHandler = JacksonTypeHandler.class)  
103 - private MidjourneyNotifyReqVO response;  
104 -  
105 - // TODO @fan:这个建议 Double  
106 - /**  
107 - * mj 进度(10%、50%、100%)  
108 - */  
109 - private String progress;  
110 -  
111 - /**  
112 * mj buttons 按钮 98 * mj buttons 按钮
113 */ 99 */
114 @TableField(typeHandler = ButtonTypeHandler.class) 100 @TableField(typeHandler = ButtonTypeHandler.class)
115 - private List<MidjourneyNotifyReqVO.Button> buttons; 101 + private List<MidjourneyApi.Button> buttons;
116 102
117 /** 103 /**
118 * midjourney proxy 关联的 task id 104 * midjourney proxy 关联的 task id
@@ -124,12 +110,11 @@ public class AiImageDO extends BaseDO { @@ -124,12 +110,11 @@ public class AiImageDO extends BaseDO {
124 */ 110 */
125 private String errorMessage; 111 private String errorMessage;
126 112
127 - // TODO @芋艿:看看是不是 MidjourneyNotifyReqVO.Button 搞到 MJ API 那  
128 public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> { 113 public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> {
129 114
130 @Override 115 @Override
131 protected Object parse(String json) { 116 protected Object parse(String json) {
132 - return JsonUtils.parseArray(json, MidjourneyNotifyReqVO.Button.class); 117 + return JsonUtils.parseArray(json, MidjourneyApi.Button.class);
133 } 118 }
134 119
135 @Override 120 @Override
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
1 package cn.iocoder.yudao.module.ai.dal.mysql.image; 1 package cn.iocoder.yudao.module.ai.dal.mysql.image;
2 2
3 -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;  
4 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 7 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
9 -import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;  
10 -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;  
11 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
12 9
13 import java.util.List; 10 import java.util.List;
@@ -20,52 +17,19 @@ import java.util.List; @@ -20,52 +17,19 @@ import java.util.List;
20 @Mapper 17 @Mapper
21 public interface AiImageMapper extends BaseMapperX<AiImageDO> { 18 public interface AiImageMapper extends BaseMapperX<AiImageDO> {
22 19
23 - // TODO @fan:这个建议,直接使用 update,service 拼接要改的状态哈  
24 -  
25 - /**  
26 - * 更新 - 根据 messageId  
27 - *  
28 - * @param mjNonceId  
29 - * @param aiImageDO  
30 - */  
31 - default void updateByMjNonce(Long mjNonceId, AiImageDO aiImageDO) {  
32 -// this.update(aiImageDO, new LambdaQueryWrapperX<AiImageDO>().eq(AiImageDO::getMjNonceId, mjNonceId));  
33 - return;  
34 - }  
35 -  
36 - /**  
37 - * 查询 - 根据 job id  
38 - *  
39 - * @param id  
40 - * @return  
41 - */  
42 - default AiImageDO selectByJobId(String id) {  
43 - return this.selectOne(new LambdaQueryWrapperX<AiImageDO>().eq(AiImageDO::getTaskId, id)); 20 + default AiImageDO selectByTaskId(String taskId) {
  21 + return this.selectOne(AiImageDO::getTaskId, taskId);
44 } 22 }
45 23
46 - /**  
47 - * 查询 - page  
48 - *  
49 - * @param userId  
50 - * @param pageReqVO  
51 - * @return  
52 - */  
53 default PageResult<AiImageDO> selectPage(Long userId, PageParam pageReqVO) { 24 default PageResult<AiImageDO> selectPage(Long userId, PageParam pageReqVO) {
54 return selectPage(pageReqVO, new LambdaQueryWrapperX<AiImageDO>() 25 return selectPage(pageReqVO, new LambdaQueryWrapperX<AiImageDO>()
55 .eq(AiImageDO::getUserId, userId) 26 .eq(AiImageDO::getUserId, userId)
56 .orderByDesc(AiImageDO::getId)); 27 .orderByDesc(AiImageDO::getId));
57 } 28 }
58 29
59 - /**  
60 - * 查询 - 根据 status 和 platform  
61 - *  
62 - * @return  
63 - */  
64 - default List<AiImageDO> selectByStatusAndPlatform(AiImageStatusEnum statusEnum, AiPlatformEnum platformEnum) {  
65 - return this.selectList(new LambdaUpdateWrapper<AiImageDO>()  
66 - .eq(AiImageDO::getStatus, statusEnum.getStatus())  
67 - .eq(AiImageDO::getPlatform, platformEnum.getPlatform())  
68 - ); 30 + default List<AiImageDO> selectListByStatusAndPlatform(Integer status, String platform) {
  31 + return selectList(AiImageDO::getStatus, status,
  32 + AiImageDO::getPlatform, platform);
69 } 33 }
70 34
71 } 35 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/MidjourneyJob.java deleted
1 -package cn.iocoder.yudao.module.ai.job;  
2 -  
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;  
5 -import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;  
6 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
7 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
8 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
9 -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;  
10 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO;  
11 -import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;  
12 -import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;  
13 -import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;  
14 -import cn.iocoder.yudao.module.ai.service.image.AiImageService;  
15 -import lombok.extern.slf4j.Slf4j;  
16 -import org.springframework.beans.factory.annotation.Autowired;  
17 -import org.springframework.stereotype.Component;  
18 -  
19 -import java.util.ArrayList;  
20 -import java.util.List;  
21 -import java.util.Map;  
22 -  
23 -/**  
24 - * midjourney job 定时拉去 midjourney 绘制状态  
25 - *  
26 - * @author fansili  
27 - * @time 2024/6/5 14:55  
28 - * @since 1.0  
29 - */  
30 -@Component  
31 -@Slf4j  
32 -public class MidjourneyJob implements JobHandler {  
33 -  
34 - // TODO @fan:@Resource  
35 - @Autowired(required = false)  
36 - private MidjourneyApi midjourneyApi;  
37 - @Autowired  
38 - private AiImageMapper imageMapper;  
39 - @Autowired  
40 - private AiImageService imageService;  
41 -  
42 - // TODO @fan:这个方法,建议实现到 AiImageService,例如说 midjourneySync,返回 int 同步数量。  
43 - @Override  
44 - public String execute(String param) {  
45 - // 1、获取 midjourney 平台,状态在 “进行中” 的 image  
46 - List<AiImageDO> imageList = imageMapper.selectByStatusAndPlatform(AiImageStatusEnum.IN_PROGRESS, AiPlatformEnum.MIDJOURNEY);  
47 - log.info("Midjourney 同步 - 任务数量 {}!", imageList.size());  
48 - if (CollUtil.isEmpty(imageList)) {  
49 - return "Midjourney 同步 - 数量为空!";  
50 - }  
51 - log.info("Midjourney 同步 - 开始...");  
52 - // 2、批量拉去 task 信息  
53 - List<MidjourneyApi.NotifyRequest> taskList = midjourneyApi  
54 - .listByCondition(CollectionUtils.convertSet(imageList, AiImageDO::getTaskId));  
55 - Map<String, MidjourneyApi.NotifyRequest> taskIdMap  
56 - = CollectionUtils.convertMap(taskList, MidjourneyApi.NotifyRequest::id);  
57 - // 3、更新 image 状态  
58 - List<AiImageDO> updateImageList = new ArrayList<>();  
59 - for (AiImageDO aiImageDO : imageList) {  
60 - // 3.1 排除掉空的情况  
61 - if (!taskIdMap.containsKey(aiImageDO.getTaskId())) {  
62 - log.warn("Midjourney 同步 - {} 任务为空!", aiImageDO.getTaskId());  
63 - continue;  
64 - }  
65 - // TODO @ 3.1 和 3.2 是不是融合下;get,然后判空,continue;  
66 - // 3.2 获取通知对象  
67 - MidjourneyApi.NotifyRequest notifyRequest = taskIdMap.get(aiImageDO.getTaskId());  
68 - // 3.2 构建更新对象  
69 - // TODO @fan:建议 List<MidjourneyNotifyReqVO> 作为 imageService 去更新;  
70 - // TODO @芋艿 BeanUtils.toBean 转换为 null  
71 - updateImageList.add(imageService.buildUpdateImage(aiImageDO.getId(),  
72 - JsonUtils.parseObject(JsonUtils.toJsonString(notifyRequest), MidjourneyNotifyReqVO.class)));  
73 - }  
74 - // 4、批了更新 updateImageList  
75 - imageMapper.updateBatch(updateImageList);  
76 - return "Midjourney 同步 - ".concat(String.valueOf(updateImageList.size())).concat(" 任务!");  
77 - }  
78 -  
79 -}  
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/image/MidjourneySyncJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.job.image;
  2 +
  3 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  4 +import cn.iocoder.yudao.module.ai.service.image.AiImageService;
  5 +import jakarta.annotation.Resource;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * Midjourney 同步 Job:定时拉去 midjourney 绘制状态
  11 + *
  12 + * @author fansili
  13 + */
  14 +@Component
  15 +@Slf4j
  16 +public class MidjourneySyncJob implements JobHandler {
  17 +
  18 + @Resource
  19 + private AiImageService imageService;
  20 +
  21 + @Override
  22 + public String execute(String param) {
  23 + Integer count = imageService.midjourneySync();
  24 + log.info("[execute][同步 Midjourney ({}) 个]", count);
  25 + return String.format("同步 Midjourney %s 个", count);
  26 + }
  27 +
  28 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
1 package cn.iocoder.yudao.module.ai.service.image; 1 package cn.iocoder.yudao.module.ai.service.image;
2 2
  3 +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO;  
6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; 6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
7 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; 7 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiImageMidjourneyImagineReqVO;
8 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 8 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
9 9
10 /** 10 /**
@@ -41,38 +41,37 @@ public interface AiImageService { @@ -41,38 +41,37 @@ public interface AiImageService {
41 Long drawImage(Long userId, AiImageDrawReqVO drawReqVO); 41 Long drawImage(Long userId, AiImageDrawReqVO drawReqVO);
42 42
43 /** 43 /**
44 - * Midjourney imagine(绘画) 44 + * 删除【我的】绘画记录
45 * 45 *
  46 + * @param id 绘画编号
46 * @param userId 用户编号 47 * @param userId 用户编号
47 - * @param imagineReqVO 绘制请求  
48 - * @return 绘画编号  
49 */ 48 */
50 - Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO imagineReqVO); 49 + void deleteImageMy(Long id, Long userId);
  50 +
  51 + // ================ midjourney 专属 ================
51 52
52 /** 53 /**
53 - * 删除【我的】绘画记录 54 + * 【Midjourney】生成图片
54 * 55 *
55 - * @param id 绘画编号  
56 * @param userId 用户编号 56 * @param userId 用户编号
  57 + * @param reqVO 绘制请求
  58 + * @return 绘画编号
57 */ 59 */
58 - void deleteImageMy(Long id, Long userId); 60 + Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO reqVO);
59 61
60 /** 62 /**
61 - * midjourney proxy - 回调通知 63 + * 【Midjourney】同步图片进展
62 * 64 *
63 - * @param notifyReqVO  
64 - * @return 65 + * @return 同步成功数量
65 */ 66 */
66 - void midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO); 67 + Integer midjourneySync();
67 68
68 /** 69 /**
69 - * 构建 midjourney - 更新对象 70 + * 【Midjourney】通知图片进展
70 * 71 *
71 - * @param imageId  
72 - * @param notifyReqVO  
73 - * @return 72 + * @param notify 通知
74 */ 73 */
75 - AiImageDO buildUpdateImage(Long imageId, MidjourneyNotifyReqVO notifyReqVO); 74 + void midjourneyNotify(MidjourneyApi.Notify notify);
76 75
77 /** 76 /**
78 * midjourney - action(放大、缩小、U1、U2...) 77 * midjourney - action(放大、缩小、U1、U2...)
@@ -83,4 +82,5 @@ public interface AiImageService { @@ -83,4 +82,5 @@ public interface AiImageService {
83 * @return 82 * @return
84 */ 83 */
85 void midjourneyAction(Long loginUserId, Long imageId, String customId); 84 void midjourneyAction(Long loginUserId, Long imageId, String customId);
  85 +
86 } 86 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.ai.service.image; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.ai.service.image;
2 2
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.codec.Base64; 4 import cn.hutool.core.codec.Base64;
5 -import cn.hutool.core.exceptions.ExceptionUtil; 5 +import cn.hutool.core.collection.CollUtil;
6 import cn.hutool.core.map.MapUtil; 6 import cn.hutool.core.map.MapUtil;
7 import cn.hutool.core.util.ObjUtil; 7 import cn.hutool.core.util.ObjUtil;
8 import cn.hutool.core.util.StrUtil; 8 import cn.hutool.core.util.StrUtil;
@@ -14,8 +14,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; @@ -14,8 +14,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
14 import cn.iocoder.yudao.framework.common.pojo.PageResult; 14 import cn.iocoder.yudao.framework.common.pojo.PageResult;
15 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 15 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
16 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; 16 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
17 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;  
18 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO; 17 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiImageMidjourneyImagineReqVO;
19 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 18 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
20 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; 19 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
21 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 20 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
@@ -29,15 +28,17 @@ import org.springframework.ai.image.ImagePrompt; @@ -29,15 +28,17 @@ import org.springframework.ai.image.ImagePrompt;
29 import org.springframework.ai.image.ImageResponse; 28 import org.springframework.ai.image.ImageResponse;
30 import org.springframework.ai.openai.OpenAiImageOptions; 29 import org.springframework.ai.openai.OpenAiImageOptions;
31 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; 30 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
32 -import org.springframework.beans.factory.annotation.Autowired;  
33 import org.springframework.beans.factory.annotation.Value; 31 import org.springframework.beans.factory.annotation.Value;
34 import org.springframework.scheduling.annotation.Async; 32 import org.springframework.scheduling.annotation.Async;
35 import org.springframework.stereotype.Service; 33 import org.springframework.stereotype.Service;
36 import org.springframework.transaction.annotation.Transactional; 34 import org.springframework.transaction.annotation.Transactional;
37 35
38 import java.util.List; 36 import java.util.List;
  37 +import java.util.Map;
39 38
40 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 39 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
41 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; 42 import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
42 43
43 /** 44 /**
@@ -51,12 +52,16 @@ public class AiImageServiceImpl implements AiImageService { @@ -51,12 +52,16 @@ public class AiImageServiceImpl implements AiImageService {
51 52
52 @Resource 53 @Resource
53 private AiImageMapper imageMapper; 54 private AiImageMapper imageMapper;
  55 +
54 @Resource 56 @Resource
55 private FileApi fileApi; 57 private FileApi fileApi;
  58 +
56 @Resource 59 @Resource
57 private AiApiKeyService apiKeyService; 60 private AiApiKeyService apiKeyService;
58 - @Autowired(required = false) 61 +
  62 + @Resource
59 private MidjourneyApi midjourneyApi; 63 private MidjourneyApi midjourneyApi;
  64 +
60 @Value("${ai.midjourney-proxy.notifyUrl:http://127.0.0.1:48080/admin-api/ai/image/midjourney-notify}") 65 @Value("${ai.midjourney-proxy.notifyUrl:http://127.0.0.1:48080/admin-api/ai/image/midjourney-notify}")
61 private String midjourneyNotifyUrl; 66 private String midjourneyNotifyUrl;
62 67
@@ -74,7 +79,7 @@ public class AiImageServiceImpl implements AiImageService { @@ -74,7 +79,7 @@ public class AiImageServiceImpl implements AiImageService {
74 public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) { 79 public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) {
75 // 1. 保存数据库 80 // 1. 保存数据库
76 AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) 81 AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
77 - .setWidth(drawReqVO.getWidth()).setHeight(drawReqVO.getHeight()).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); 82 + .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
78 imageMapper.insert(image); 83 imageMapper.insert(image);
79 // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果 84 // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果
80 getSelf().executeDrawImage(image, drawReqVO); 85 getSelf().executeDrawImage(image, drawReqVO);
@@ -122,101 +127,121 @@ public class AiImageServiceImpl implements AiImageService { @@ -122,101 +127,121 @@ public class AiImageServiceImpl implements AiImageService {
122 } 127 }
123 128
124 @Override 129 @Override
125 - @Transactional(rollbackFor = Exception.class)  
126 - public Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO req) {  
127 - // 1、构建 AiImageDO 并 保存  
128 - AiImageDO image = new AiImageDO()  
129 - .setUserId(userId)  
130 - .setPrompt(req.getPrompt())  
131 - .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform())  
132 - .setModel(req.getModel())  
133 - .setWidth(req.getWidth())  
134 - .setHeight(req.getHeight())  
135 - .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());  
136 - imageMapper.insert(image); 130 + public void deleteImageMy(Long id, Long userId) {
  131 + // 1. 校验是否存在
  132 + AiImageDO image = validateImageExists(id);
  133 + if (ObjUtil.notEqual(image.getUserId(), userId)) {
  134 + throw exception(AI_IMAGE_NOT_EXISTS);
  135 + }
  136 + // 2. 删除记录
  137 + imageMapper.deleteById(id);
  138 + }
137 139
138 - // 3、调用 MidjourneyProxy 提交任务 140 + private AiImageDO validateImageExists(Long id) {
  141 + AiImageDO image = imageMapper.selectById(id);
  142 + if (image == null) {
  143 + throw exception(AI_IMAGE_NOT_EXISTS);
  144 + }
  145 + return image;
  146 + }
139 147
140 - // 3.1、设置 midjourney 扩展参数  
141 - MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest(null, midjourneyNotifyUrl, req.getPrompt(),  
142 - buildParams(req.getWidth(), req.getHeight(), req.getVersion(),  
143 - MidjourneyApi.ModelEnum.valueOfModel(req.getModel())));  
144 - // 3.2、提交绘画请求  
145 - // TODO @fan:5 这里,失败的情况,到底抛出异常,还是 RespVO,可以参考 OpenAI 的 API 封装  
146 - MidjourneyApi.SubmitResponse submitResponse = midjourneyApi.imagine(imagineRequest); 148 + // ================ midjourney 专属 ================
147 149
148 - // 4、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误))  
149 - if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(submitResponse.code())) {  
150 - if (submitResponse.description().contains("quota_not_enough")) {  
151 - throw exception(AI_IMAGE_SYSTEM_ACCOUNT_INSUFFICIENT_BALANCE, submitResponse.description());  
152 - }  
153 - throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, submitResponse.description()); 150 + @Override
  151 + @Transactional(rollbackFor = Exception.class)
  152 + public Long midjourneyImagine(Long userId, AiImageMidjourneyImagineReqVO reqVO) {
  153 + // 1. 保存数据库
  154 + AiImageDO image = BeanUtils.toBean(reqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false)
  155 + .setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus())
  156 + .setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform());
  157 + imageMapper.insert(image);
  158 +
  159 + // 2. 调用 Midjourney Proxy 提交任务
  160 + MidjourneyApi.ImagineRequest imagineRequest = new MidjourneyApi.ImagineRequest(
  161 + null, midjourneyNotifyUrl, reqVO.getPrompt(),
  162 + MidjourneyApi.ImagineRequest.buildState(reqVO.getWidth(), reqVO.getHeight(), reqVO.getVersion(), reqVO.getModel()));
  163 + MidjourneyApi.SubmitResponse imagineResponse = midjourneyApi.imagine(imagineRequest);
  164 +
  165 + // 3. 情况一【失败】:抛出业务异常
  166 + if (!MidjourneyApi.SubmitCodeEnum.SUCCESS_CODES.contains(imagineResponse.code())) {
  167 + String description = imagineResponse.description().contains("quota_not_enough") ?
  168 + "账户余额不足" : imagineResponse.description();
  169 + throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, description);
154 } 170 }
155 - // 4.1、更新 taskId 和参数 171 +
  172 + // 4. 情况二【成功】:更新 taskId 和参数
156 imageMapper.updateById(new AiImageDO() 173 imageMapper.updateById(new AiImageDO()
157 .setId(image.getId()) 174 .setId(image.getId())
158 - .setTaskId(submitResponse.result())  
159 - .setOptions(BeanUtil.beanToMap(req)) 175 + .setTaskId(imagineResponse.result())
  176 + .setOptions(BeanUtil.beanToMap(reqVO))
160 ); 177 );
161 return image.getId(); 178 return image.getId();
162 } 179 }
163 180
164 -  
165 @Override 181 @Override
166 - public void deleteImageMy(Long id, Long userId) {  
167 - // 1. 校验是否存在  
168 - AiImageDO image = validateImageExists(id);  
169 - if (ObjUtil.notEqual(image.getUserId(), userId)) {  
170 - throw exception(AI_IMAGE_NOT_EXISTS); 182 + public Integer midjourneySync() {
  183 + // 1.1 获取 Midjourney 平台,状态在 “进行中” 的 image
  184 + List<AiImageDO> imageList = imageMapper.selectListByStatusAndPlatform(
  185 + AiImageStatusEnum.IN_PROGRESS.getStatus(), AiPlatformEnum.MIDJOURNEY.getPlatform());
  186 + if (CollUtil.isEmpty(imageList)) {
  187 + return 0;
171 } 188 }
172 - // 2. 删除记录  
173 - imageMapper.deleteById(id); 189 + // 1.2 调用 Midjourney Proxy 获取任务进展
  190 + List<MidjourneyApi.Notify> taskList = midjourneyApi.getTaskList(convertSet(imageList, AiImageDO::getTaskId));
  191 + Map<String, MidjourneyApi.Notify> taskMap = convertMap(taskList, MidjourneyApi.Notify::id);
  192 +
  193 + // 2. 逐个处理,更新进展
  194 + int count = 0;
  195 + for (AiImageDO image : imageList) {
  196 + MidjourneyApi.Notify notify = taskMap.get(image.getTaskId());
  197 + if (notify == null) {
  198 + log.error("[midjourneySync][image({}) 查询不到进展]", image);
  199 + continue;
  200 + }
  201 + count++;
  202 + updateMidjourneyStatus(image, notify);
  203 + }
  204 + return count;
174 } 205 }
175 206
176 @Override 207 @Override
177 - public void midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO) {  
178 - // 1、根据 job id 查询关联的 image  
179 - AiImageDO image = imageMapper.selectByJobId(notifyReqVO.getId()); 208 + public void midjourneyNotify(MidjourneyApi.Notify notify) {
  209 + // 1. 校验 image 存在
  210 + AiImageDO image = imageMapper.selectByTaskId(notify.id());
180 if (image == null) { 211 if (image == null) {
181 - log.warn("midjourneyNotify 回调的 jobId 不存在! jobId: {}", notifyReqVO.getId()); 212 + log.warn("[midjourneyNotify][回调任务({}) 不存在]", notify.id());
  213 + return;
182 } 214 }
183 - // 2、转换状态  
184 - AiImageDO updateImage = buildUpdateImage(image.getId(), notifyReqVO);  
185 - // 3、更新 image 状态  
186 - imageMapper.updateById(updateImage); 215 + // 2. 更新状态
  216 + updateMidjourneyStatus(image, notify);
187 } 217 }
188 218
189 - public AiImageDO buildUpdateImage(Long imageId, MidjourneyNotifyReqVO notifyReqVO) {  
190 - // 1、转换状态  
191 - String imageStatus = null;  
192 - if (StrUtil.isNotBlank(notifyReqVO.getStatus())) {  
193 - MidjourneyApi.TaskStatusEnum taskStatusEnum = MidjourneyApi.TaskStatusEnum.valueOf(notifyReqVO.getStatus()); 219 + private void updateMidjourneyStatus(AiImageDO image, MidjourneyApi.Notify notify) {
  220 + // 1. 转换状态
  221 + Integer status = null;
  222 + if (StrUtil.isNotBlank(notify.status())) {
  223 + MidjourneyApi.TaskStatusEnum taskStatusEnum = MidjourneyApi.TaskStatusEnum.valueOf(notify.status());
194 if (MidjourneyApi.TaskStatusEnum.SUCCESS == taskStatusEnum) { 224 if (MidjourneyApi.TaskStatusEnum.SUCCESS == taskStatusEnum) {
195 - imageStatus = AiImageStatusEnum.SUCCESS.getStatus(); 225 + status = AiImageStatusEnum.SUCCESS.getStatus();
196 } else if (MidjourneyApi.TaskStatusEnum.FAILURE == taskStatusEnum) { 226 } else if (MidjourneyApi.TaskStatusEnum.FAILURE == taskStatusEnum) {
197 - imageStatus = AiImageStatusEnum.FAIL.getStatus(); 227 + status = AiImageStatusEnum.FAIL.getStatus();
198 } 228 }
199 } 229 }
200 230
201 - // 2、上传图片  
202 - String filePath = null;  
203 - if (!StrUtil.isBlank(notifyReqVO.getImageUrl())) { 231 + // 2. 上传图片
  232 + String picUrl = null;
  233 + if (StrUtil.isNotBlank(notify.imageUrl())) {
204 try { 234 try {
205 - filePath = fileApi.createFile(HttpUtil.downloadBytes(notifyReqVO.getImageUrl())); 235 + picUrl = fileApi.createFile(HttpUtil.downloadBytes(notify.imageUrl()));
206 } catch (Exception e) { 236 } catch (Exception e) {
207 - log.warn("midjourneyNotify 图片上传失败! {} 异常:{}", notifyReqVO.getImageUrl(), ExceptionUtil.getMessage(e)); 237 + picUrl = notify.imageUrl();
  238 + log.warn("[updateMidjourneyStatus][图片({}) 地址({}) 上传失败]", image.getId(), notify.imageUrl(), e);
208 } 239 }
209 } 240 }
210 241
211 - // 3、更新 image 状态  
212 - return new AiImageDO()  
213 - .setId(imageId)  
214 - .setStatus(imageStatus)  
215 - .setPicUrl(filePath)  
216 - .setProgress(notifyReqVO.getProgress())  
217 - .setResponse(notifyReqVO)  
218 - .setButtons(notifyReqVO.getButtons())  
219 - .setErrorMessage(notifyReqVO.getFailReason()); 242 + // 3. 更新 image 状态
  243 + imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(status)
  244 + .setPicUrl(picUrl).setButtons(notify.buttons()).setErrorMessage(notify.failReason()));
220 } 245 }
221 246
222 @Override 247 @Override
@@ -236,7 +261,6 @@ public class AiImageServiceImpl implements AiImageService { @@ -236,7 +261,6 @@ public class AiImageServiceImpl implements AiImageService {
236 261
237 // 5、新增 image 记录(根据 image 新增一个) 262 // 5、新增 image 记录(根据 image 新增一个)
238 AiImageDO newImage = new AiImageDO(); 263 AiImageDO newImage = new AiImageDO();
239 - newImage.setId(null);  
240 newImage.setUserId(image.getUserId()); 264 newImage.setUserId(image.getUserId());
241 newImage.setPrompt(image.getPrompt()); 265 newImage.setPrompt(image.getPrompt());
242 266
@@ -248,20 +272,15 @@ public class AiImageServiceImpl implements AiImageService { @@ -248,20 +272,15 @@ public class AiImageServiceImpl implements AiImageService {
248 newImage.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); 272 newImage.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
249 newImage.setPublicStatus(image.getPublicStatus()); 273 newImage.setPublicStatus(image.getPublicStatus());
250 274
251 - newImage.setPicUrl(null);  
252 - newImage.setProgress(null);  
253 - newImage.setButtons(null);  
254 newImage.setOptions(image.getOptions()); 275 newImage.setOptions(image.getOptions());
255 - newImage.setResponse(image.getResponse());  
256 newImage.setTaskId(submitResponse.result()); 276 newImage.setTaskId(submitResponse.result());
257 - newImage.setErrorMessage(null);  
258 imageMapper.insert(newImage); 277 imageMapper.insert(newImage);
259 } 278 }
260 279
261 - private static void validateCustomId(String customId, List<MidjourneyNotifyReqVO.Button> buttons) { 280 + private static void validateCustomId(String customId, List<MidjourneyApi.Button> buttons) {
262 boolean isTrue = false; 281 boolean isTrue = false;
263 - for (MidjourneyNotifyReqVO.Button button : buttons) {  
264 - if (button.getCustomId().equals(customId)) { 282 + for (MidjourneyApi.Button button : buttons) {
  283 + if (button.customId().equals(customId)) {
265 isTrue = true; 284 isTrue = true;
266 break; 285 break;
267 } 286 }
@@ -271,14 +290,6 @@ public class AiImageServiceImpl implements AiImageService { @@ -271,14 +290,6 @@ public class AiImageServiceImpl implements AiImageService {
271 } 290 }
272 } 291 }
273 292
274 - private AiImageDO validateImageExists(Long id) {  
275 - AiImageDO image = imageMapper.selectById(id);  
276 - if (image == null) {  
277 - throw exception(AI_IMAGE_NOT_EXISTS);  
278 - }  
279 - return image;  
280 - }  
281 -  
282 /** 293 /**
283 * 获得自身的代理对象,解决 AOP 生效问题 294 * 获得自身的代理对象,解决 AOP 生效问题
284 * 295 *
@@ -288,28 +299,4 @@ public class AiImageServiceImpl implements AiImageService { @@ -288,28 +299,4 @@ public class AiImageServiceImpl implements AiImageService {
288 return SpringUtil.getBean(getClass()); 299 return SpringUtil.getBean(getClass());
289 } 300 }
290 301
291 - // TODO @fan:这个是不是应该放在 MJ API 的封装里面搞哈?  
292 -  
293 - /**  
294 - * 构建 Midjourney 自定义参数  
295 - *  
296 - * @param width  
297 - * @param height  
298 - * @param version  
299 - * @param model  
300 - * @return  
301 - */  
302 - private String buildParams(Integer width, Integer height, String version, MidjourneyApi.ModelEnum model) {  
303 - StringBuilder params = new StringBuilder();  
304 - // --ar 来设置尺寸  
305 - params.append(String.format(" --ar %s:%s ", width, height));  
306 - // --niji 模型  
307 - if (MidjourneyApi.ModelEnum.NIJI == model) {  
308 - params.append(String.format(" --niji %s ", version));  
309 - } else {  
310 - // --v 版本  
311 - params.append(String.format(" --v %s ", version));  
312 - }  
313 - return params.toString();  
314 - }  
315 } 302 }
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -50,34 +50,6 @@ @@ -50,34 +50,6 @@
50 <artifactId>junit</artifactId> 50 <artifactId>junit</artifactId>
51 <scope>test</scope> 51 <scope>test</scope>
52 </dependency> 52 </dependency>
53 -  
54 - <!-- TODO fan:这里包,要进一步减少 -->  
55 -  
56 - <dependency>  
57 - <groupId>org.springframework.boot</groupId>  
58 - <artifactId>spring-boot-starter-websocket</artifactId>  
59 - </dependency>  
60 -  
61 - <!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->  
62 - <dependency>  
63 - <groupId>com.squareup.okhttp3</groupId>  
64 - <artifactId>okhttp</artifactId>  
65 - </dependency>  
66 - <dependency>  
67 - <groupId>io.projectreactor.netty</groupId>  
68 - <artifactId>reactor-netty</artifactId>  
69 - </dependency>  
70 - <dependency>  
71 - <groupId>net.dv8tion</groupId>  
72 - <artifactId>JDA</artifactId>  
73 - <version>5.0.0-beta.21</version>  
74 -<!-- <exclusions>-->  
75 -<!-- <exclusion>-->  
76 -<!-- <groupId>club.minnced</groupId>-->  
77 -<!-- <artifactId>opus-java</artifactId>-->  
78 -<!-- </exclusion>-->  
79 -<!-- </exclusions>-->  
80 - </dependency>  
81 </dependencies> 53 </dependencies>
82 54
83 </project> 55 </project>
84 \ No newline at end of file 56 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java
@@ -16,66 +16,54 @@ import java.util.List; @@ -16,66 +16,54 @@ import java.util.List;
16 import java.util.Map; 16 import java.util.Map;
17 17
18 /** 18 /**
19 - * Midjourney api 19 + * Midjourney API
20 * 20 *
21 * @author fansili 21 * @author fansili
22 - * @time 2024/6/11 15:46  
23 * @since 1.0 22 * @since 1.0
24 */ 23 */
25 @Slf4j 24 @Slf4j
26 public class MidjourneyApi { 25 public class MidjourneyApi {
27 26
28 - private static final String URI_IMAGINE = "/submit/imagine";  
29 - private static final String URI_ACTON = "/submit/action";  
30 - private static final String URI_LIST_BY_CONDITION = "/task/list-by-condition";  
31 private final WebClient webClient; 27 private final WebClient webClient;
32 - private final MidjourneyConfig midjourneyConfig;  
33 28
34 public MidjourneyApi(MidjourneyConfig midjourneyConfig) { 29 public MidjourneyApi(MidjourneyConfig midjourneyConfig) {
35 - this.midjourneyConfig = midjourneyConfig;  
36 this.webClient = WebClient.builder() 30 this.webClient = WebClient.builder()
37 .baseUrl(midjourneyConfig.getUrl()) 31 .baseUrl(midjourneyConfig.getUrl())
38 .defaultHeaders(ApiUtils.getJsonContentHeaders(midjourneyConfig.getKey())) 32 .defaultHeaders(ApiUtils.getJsonContentHeaders(midjourneyConfig.getKey()))
39 .build(); 33 .build();
40 } 34 }
41 35
42 -  
43 /** 36 /**
44 * imagine - 根据提示词提交绘画任务 37 * imagine - 根据提示词提交绘画任务
45 * 38 *
46 - * @param imagineReqVO  
47 - * @return 39 + * @param request 请求
  40 + * @return 提交结果
48 */ 41 */
49 - public SubmitResponse imagine(ImagineRequest imagineReqVO) {  
50 - // 1、发送 post 请求  
51 - String res = post(URI_IMAGINE, imagineReqVO);  
52 - // 2、转换 resp  
53 - return JsonUtils.parseObject(res, SubmitResponse.class); 42 + public SubmitResponse imagine(ImagineRequest request) {
  43 + String response = post("/submit/imagine", request);
  44 + return JsonUtils.parseObject(response, SubmitResponse.class);
54 } 45 }
55 46
56 /** 47 /**
57 * action - 放大、缩小、U1、U2... 48 * action - 放大、缩小、U1、U2...
58 * 49 *
59 - * @param actionReqVO 50 + * @param request 请求
  51 + * @return 提交结果
60 */ 52 */
61 - public SubmitResponse action(ActionRequest actionReqVO) {  
62 - // 1、发送 post 请求  
63 - String res = post(URI_ACTON, actionReqVO);  
64 - // 2、转换 resp 53 + public SubmitResponse action(ActionRequest request) {
  54 + String res = post("/submit/action", request);
65 return JsonUtils.parseObject(res, SubmitResponse.class); 55 return JsonUtils.parseObject(res, SubmitResponse.class);
66 } 56 }
67 57
68 /** 58 /**
69 * 批量查询 task 任务 59 * 批量查询 task 任务
70 * 60 *
71 - * @param taskIds  
72 - * @return 61 + * @param ids 任务编号数组
  62 + * @return task 任务
73 */ 63 */
74 - public List<NotifyRequest> listByCondition(Collection<String> taskIds) {  
75 - // 1、发送 post 请求  
76 - String res = post(URI_LIST_BY_CONDITION, ImmutableMap.of("ids", taskIds));  
77 - // 2、转换 对象  
78 - return JsonUtils.parseArray(res, NotifyRequest.class); 64 + public List<Notify> getTaskList(Collection<String> ids) {
  65 + String res = post("/task/list-by-condition", ImmutableMap.of("ids", ids));
  66 + return JsonUtils.parseArray(res, Notify.class);
79 } 67 }
80 68
81 private String post(String uri, Object body) { 69 private String post(String uri, Object body) {
@@ -94,12 +82,12 @@ public class MidjourneyApi { @@ -94,12 +82,12 @@ public class MidjourneyApi {
94 .block(); 82 .block();
95 } 83 }
96 84
97 - // ====== record 结构 85 + // ========== record 结构 ==========
98 86
99 /** 87 /**
100 - * Midjourney - Imagine 请求 88 + * Imagine 请求(生成图片)
101 * 89 *
102 - * @param base64Array 垫图(参考图)base64数组 90 + * @param base64Array 垫图(参考图) base64数 组
103 * @param notifyHook 通知地址 91 * @param notifyHook 通知地址
104 * @param prompt 提示词 92 * @param prompt 提示词
105 * @param state 自定义参数 93 * @param state 自定义参数
@@ -108,10 +96,24 @@ public class MidjourneyApi { @@ -108,10 +96,24 @@ public class MidjourneyApi {
108 String notifyHook, 96 String notifyHook,
109 String prompt, 97 String prompt,
110 String state) { 98 String state) {
  99 +
  100 + public static String buildState(Integer width, Integer height, String version, String model) {
  101 + StringBuilder params = new StringBuilder();
  102 + // --ar 来设置尺寸
  103 + params.append(String.format(" --ar %s:%s ", width, height));
  104 + // --niji 模型
  105 + if (MidjourneyApi.ModelEnum.NIJI.getModel().equals(model)) {
  106 + params.append(String.format(" --niji %s ", version));
  107 + } else {
  108 + params.append(String.format(" --v %s ", version));
  109 + }
  110 + return params.toString();
  111 + }
  112 +
111 } 113 }
112 114
113 /** 115 /**
114 - * Midjourney - Action 请求 116 + * Action 请求
115 * 117 *
116 * @param customId 操作按钮id 118 * @param customId 操作按钮id
117 * @param taskId 操作按钮id 119 * @param taskId 操作按钮id
@@ -124,7 +126,7 @@ public class MidjourneyApi { @@ -124,7 +126,7 @@ public class MidjourneyApi {
124 } 126 }
125 127
126 /** 128 /**
127 - * Midjourney - Submit 返回 129 + * Submit 统一返回
128 * 130 *
129 * @param code 状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误) 131 * @param code 状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)
130 * @param description 描述 132 * @param description 描述
@@ -138,7 +140,7 @@ public class MidjourneyApi { @@ -138,7 +140,7 @@ public class MidjourneyApi {
138 } 140 }
139 141
140 /** 142 /**
141 - * Midjourney - 通知 request 143 + * 通知 request
142 * 144 *
143 * @param id job id 145 * @param id job id
144 * @param action 任务类型 {@link TaskActionEnum} 146 * @param action 任务类型 {@link TaskActionEnum}
@@ -155,47 +157,47 @@ public class MidjourneyApi { @@ -155,47 +157,47 @@ public class MidjourneyApi {
155 * @param failReason 失败原因 157 * @param failReason 失败原因
156 * @param buttons 任务完成后的可执行按钮 158 * @param buttons 任务完成后的可执行按钮
157 */ 159 */
158 - public record NotifyRequest(String id,  
159 - String action,  
160 - String status, 160 + public record Notify(String id,
  161 + String action,
  162 + String status,
161 163
162 - String prompt,  
163 - String promptEn, 164 + String prompt,
  165 + String promptEn,
164 166
165 - String description,  
166 - String state, 167 + String description,
  168 + String state,
167 169
168 - Long submitTime,  
169 - Long startTime,  
170 - Long finishTime, 170 + Long submitTime,
  171 + Long startTime,
  172 + Long finishTime,
171 173
172 - String imageUrl,  
173 - String progress,  
174 - String failReason,  
175 - List<Button> buttons) { 174 + String imageUrl,
  175 + String progress,
  176 + String failReason,
  177 + List<Button> buttons) {
176 178
177 - /**  
178 - * button  
179 - *  
180 - * @param customId MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识  
181 - * @param emoji 图标 emoji  
182 - * @param label Make Variations 文本  
183 - * @param type 类型,系统内部使用  
184 - * @param style 样式: 2(Primary)、3(Green)  
185 - */  
186 - public record Button(String customId,  
187 - String emoji,  
188 - String label,  
189 - String type,  
190 - String style) { 179 + }
191 180
192 - } 181 + /**
  182 + * button
  183 + *
  184 + * @param customId MJ::JOB::upsample::1::85a4b4c1-8835-46c5-a15c-aea34fad1862 动作标识
  185 + * @param emoji 图标 emoji
  186 + * @param label Make Variations 文本
  187 + * @param type 类型,系统内部使用
  188 + * @param style 样式: 2(Primary)、3(Green)
  189 + */
  190 + public record Button(String customId,
  191 + String emoji,
  192 + String label,
  193 + String type,
  194 + String style) {
193 } 195 }
194 196
195 - // ====== enums 197 + // ============ enums ============
196 198
197 /** 199 /**
198 - * Midjourney - 模型 200 + * 模型枚举
199 */ 201 */
200 @AllArgsConstructor 202 @AllArgsConstructor
201 @Getter 203 @Getter
@@ -203,24 +205,15 @@ public class MidjourneyApi { @@ -203,24 +205,15 @@ public class MidjourneyApi {
203 205
204 MIDJOURNEY("midjourney", "midjourney"), 206 MIDJOURNEY("midjourney", "midjourney"),
205 NIJI("niji", "niji"), 207 NIJI("niji", "niji"),
206 -  
207 ; 208 ;
208 209
209 - private String model;  
210 - private String name; 210 + private final String model;
  211 + private final String name;
211 212
212 - public static ModelEnum valueOfModel(String model) {  
213 - for (ModelEnum itemEnum : ModelEnum.values()) {  
214 - if (itemEnum.getModel().equals(model)) {  
215 - return itemEnum;  
216 - }  
217 - }  
218 - throw new IllegalArgumentException("Invalid MessageType value: " + model);  
219 - }  
220 } 213 }
221 214
222 /** 215 /**
223 - * Midjourney - 提交返回的状态码 216 + * 提交返回的状态码的枚举
224 */ 217 */
225 @Getter 218 @Getter
226 @AllArgsConstructor 219 @AllArgsConstructor
@@ -239,64 +232,68 @@ public class MidjourneyApi { @@ -239,64 +232,68 @@ public class MidjourneyApi {
239 232
240 private final String code; 233 private final String code;
241 private final String name; 234 private final String name;
  235 +
242 } 236 }
243 237
244 /** 238 /**
245 - * Midjourney - action 239 + * Action 枚举
246 */ 240 */
247 @Getter 241 @Getter
248 @AllArgsConstructor 242 @AllArgsConstructor
249 public enum TaskActionEnum { 243 public enum TaskActionEnum {
  244 +
250 /** 245 /**
251 - * 生成图片. 246 + * 生成图片
252 */ 247 */
253 IMAGINE, 248 IMAGINE,
254 /** 249 /**
255 - * 选中放大. 250 + * 选中放大
256 */ 251 */
257 UPSCALE, 252 UPSCALE,
258 /** 253 /**
259 - * 选中其中的一张图,生成四张相似的. 254 + * 选中其中的一张图,生成四张相似的
260 */ 255 */
261 VARIATION, 256 VARIATION,
262 /** 257 /**
263 - * 重新执行. 258 + * 重新执行
264 */ 259 */
265 REROLL, 260 REROLL,
266 /** 261 /**
267 - * 图转prompt. 262 + * 图转 prompt
268 */ 263 */
269 DESCRIBE, 264 DESCRIBE,
270 /** 265 /**
271 - * 多图混合. 266 + * 多图混合
272 */ 267 */
273 BLEND 268 BLEND
  269 +
274 } 270 }
275 271
276 /** 272 /**
277 - * Midjourney - 任务状态 273 + * 任务状态枚举
278 */ 274 */
279 @Getter 275 @Getter
280 @AllArgsConstructor 276 @AllArgsConstructor
281 public enum TaskStatusEnum { 277 public enum TaskStatusEnum {
  278 +
282 /** 279 /**
283 - * 未启动. 280 + * 未启动
284 */ 281 */
285 NOT_START(0), 282 NOT_START(0),
286 /** 283 /**
287 - * 已提交. 284 + * 已提交
288 */ 285 */
289 SUBMITTED(1), 286 SUBMITTED(1),
290 /** 287 /**
291 - * 执行中. 288 + * 执行中
292 */ 289 */
293 IN_PROGRESS(3), 290 IN_PROGRESS(3),
294 /** 291 /**
295 - * 失败. 292 + * 失败
296 */ 293 */
297 FAILURE(4), 294 FAILURE(4),
298 /** 295 /**
299 - * 成功. 296 + * 成功
300 */ 297 */
301 SUCCESS(4); 298 SUCCESS(4);
302 299
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/package-info.java
@@ -2,11 +2,9 @@ @@ -2,11 +2,9 @@
2 * model 包,接入各种大模型,对标 https://github.com/spring-projects/spring-ai/tree/main/models 2 * model 包,接入各种大模型,对标 https://github.com/spring-projects/spring-ai/tree/main/models
3 * 3 *
4 * 1. yiyan 包:【百度】文心一言 4 * 1. yiyan 包:【百度】文心一言
5 - * 2. TODO 芋艿:  
6 - * tongyi 包:【阿里】通义千问,对标 spring-cloud-alibaba 提供的 ai 包  
7 - * 2.2  
8 - * 2.3 xinghuo 包:【讯飞】星火,自己实现  
9 - * 2.4 openai 包:【OpenAI】ChatGPT,拷贝 spring-ai 提供的 models/openai 包  
10 - * 2.5 midjourney 包:Midjourney,参考 https://github.com/novicezk/midjourney-proxy 实现 5 + * 2. tongyi 包:【阿里】通义千问,对标 spring-cloud-alibaba 提供的 ai 包 TODO 芋艿:未来直接使用它
  6 + * 3. xinghuo 包:【讯飞】星火,自己实现
  7 + * 4. midjourney 包:Midjourney,接入 https://github.com/novicezk/midjourney-proxy 实现
  8 + * 5. suno 包:TODO 芋艿:
11 */ 9 */
12 package cn.iocoder.yudao.framework.ai.core.model; 10 package cn.iocoder.yudao.framework.ai.core.model;
13 \ No newline at end of file 11 \ No newline at end of file
yudao-server/src/main/resources/application-local.yaml
@@ -76,13 +76,6 @@ server: @@ -76,13 +76,6 @@ server:
76 enabled: true 76 enabled: true
77 charset: UTF-8 77 charset: UTF-8
78 force: true 78 force: true
79 -# ai TODO @fan:这个融合到 yudao.ai 那好点哈  
80 -ai:  
81 - midjourney-proxy:  
82 - enable: true  
83 - url: https://api.holdai.top/mj  
84 - notifyUrl: http://61d61685.r21.cpolar.top/admin-api/ai/image/midjourney-notify  
85 - key: sk-c3qxUCVKsPfdQiYU8440E3Fc8dE5424d9cB124A4Ee2489E3  
86 79
87 --- #################### 定时任务相关配置 #################### 80 --- #################### 定时任务相关配置 ####################
88 81
yudao-server/src/main/resources/application.yaml
@@ -201,6 +201,13 @@ yudao.ai: @@ -201,6 +201,13 @@ yudao.ai:
201 enable: true 201 enable: true
202 base-url: https://suno-imrqwwui8-status2xxs-projects.vercel.app 202 base-url: https://suno-imrqwwui8-status2xxs-projects.vercel.app
203 203
  204 +ai:
  205 + midjourney-proxy:
  206 + enable: true
  207 + url: https://api.holdai.top/mj
  208 + notifyUrl: http://61d61685.r21.cpolar.top/admin-api/ai/image/midjourney-notify
  209 + key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf
  210 +
204 --- #################### 芋道相关配置 #################### 211 --- #################### 芋道相关配置 ####################
205 212
206 yudao: 213 yudao: