Commit 098483d2beb7d72d44c2f7e349199d3ed9230673
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 | 21 | // ========== API 聊天模型 1-040-002-000 ========== |
| 22 | 22 | ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "聊天角色不存在"); |
| 23 | 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 | 25 | // ========== API 聊天会话 1-040-003-000 ========== |
| 27 | 26 | |
| 28 | 27 | ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对话不存在!"); |
| 29 | 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 | 30 | // ========== API 聊天消息 1-040-004-000 ========== |
| 31 | + | |
| 34 | 32 | ErrorCode AI_CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "消息不存在!"); |
| 35 | 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 | 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 | 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 | 12 | @Getter |
| 13 | 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 | 26 | private final String name; |
| 27 | 27 | |
| 28 | - public static AiImageStatusEnum valueOfStatus(String status) { | |
| 28 | + public static AiImageStatusEnum valueOfStatus(Integer status) { | |
| 29 | 29 | for (AiImageStatusEnum statusEnum : AiImageStatusEnum.values()) { |
| 30 | 30 | if (statusEnum.getStatus().equals(status)) { |
| 31 | 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 | 3 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.MidjourneyConfig; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | -import org.springframework.boot.autoconfigure.AutoConfiguration; | |
| 7 | 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 8 | 7 | import org.springframework.context.annotation.Bean; |
| 9 | 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 | 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 | 35 | Content-Type: application/json |
| 36 | 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 | 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 | 1 | package cn.iocoder.yudao.module.ai.controller.admin.image; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.ObjUtil; |
| 4 | +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 6 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 8 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO; | |
| 9 | 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 | 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 | 12 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 13 | 13 | import cn.iocoder.yudao.module.ai.service.image.AiImageService; |
| 14 | 14 | import io.swagger.v3.oas.annotations.Operation; |
| ... | ... | @@ -63,19 +63,24 @@ public class AiImageController { |
| 63 | 63 | return success(true); |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | - // ================ midjourney 接口 ================ | |
| 66 | + // ================ midjourney 专属 ================ | |
| 67 | 67 | |
| 68 | - @Operation(summary = "Midjourney imagine(绘画)") | |
| 68 | + @Operation(summary = "【Midjourney】生成图片") | |
| 69 | 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 | 80 | @PostMapping("/midjourney-notify") |
| 76 | 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 | 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 | 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 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 5 | import lombok.Data; |
| 5 | 6 | |
| ... | ... | @@ -46,14 +47,11 @@ public class AiImageRespVO { |
| 46 | 47 | @Schema(description = "绘制参数") |
| 47 | 48 | private Map<String, String> options; |
| 48 | 49 | |
| 49 | - @Schema(description = "绘画 response") | |
| 50 | - private MidjourneyNotifyReqVO response; | |
| 51 | - | |
| 52 | 50 | // TODO @fan:进度是百分比,还是一个数字哈?感觉这个可以统一成通用字段; |
| 53 | 51 | @Schema(description = "mj 进度") |
| 54 | 52 | private String progress; |
| 55 | 53 | |
| 56 | 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 | 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 | 4 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 4 | 5 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 5 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO; | |
| 6 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 7 | 7 | import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; |
| 8 | 8 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| ... | ... | @@ -28,8 +28,6 @@ import java.util.Map; |
| 28 | 28 | @Data |
| 29 | 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 | 74 | * |
| 77 | 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 | 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 | 98 | * mj buttons 按钮 |
| 113 | 99 | */ |
| 114 | 100 | @TableField(typeHandler = ButtonTypeHandler.class) |
| 115 | - private List<MidjourneyNotifyReqVO.Button> buttons; | |
| 101 | + private List<MidjourneyApi.Button> buttons; | |
| 116 | 102 | |
| 117 | 103 | /** |
| 118 | 104 | * midjourney proxy 关联的 task id |
| ... | ... | @@ -124,12 +110,11 @@ public class AiImageDO extends BaseDO { |
| 124 | 110 | */ |
| 125 | 111 | private String errorMessage; |
| 126 | 112 | |
| 127 | - // TODO @芋艿:看看是不是 MidjourneyNotifyReqVO.Button 搞到 MJ API 那 | |
| 128 | 113 | public static class ButtonTypeHandler extends AbstractJsonTypeHandler<Object> { |
| 129 | 114 | |
| 130 | 115 | @Override |
| 131 | 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 | 120 | @Override | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.dal.mysql.image; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | |
| 4 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 5 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | 5 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 7 | 6 | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 8 | 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 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 12 | 9 | |
| 13 | 10 | import java.util.List; |
| ... | ... | @@ -20,52 +17,19 @@ import java.util.List; |
| 20 | 17 | @Mapper |
| 21 | 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 | 24 | default PageResult<AiImageDO> selectPage(Long userId, PageParam pageReqVO) { |
| 54 | 25 | return selectPage(pageReqVO, new LambdaQueryWrapperX<AiImageDO>() |
| 55 | 26 | .eq(AiImageDO::getUserId, userId) |
| 56 | 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 | 1 | package cn.iocoder.yudao.module.ai.service.image; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.MidjourneyNotifyReqVO; | |
| 6 | 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 | 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 9 | 9 | |
| 10 | 10 | /** |
| ... | ... | @@ -41,38 +41,37 @@ public interface AiImageService { |
| 41 | 41 | Long drawImage(Long userId, AiImageDrawReqVO drawReqVO); |
| 42 | 42 | |
| 43 | 43 | /** |
| 44 | - * Midjourney imagine(绘画) | |
| 44 | + * 删除【我的】绘画记录 | |
| 45 | 45 | * |
| 46 | + * @param id 绘画编号 | |
| 46 | 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 | 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 | 77 | * midjourney - action(放大、缩小、U1、U2...) |
| ... | ... | @@ -83,4 +82,5 @@ public interface AiImageService { |
| 83 | 82 | * @return |
| 84 | 83 | */ |
| 85 | 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 | 2 | |
| 3 | 3 | import cn.hutool.core.bean.BeanUtil; |
| 4 | 4 | import cn.hutool.core.codec.Base64; |
| 5 | -import cn.hutool.core.exceptions.ExceptionUtil; | |
| 5 | +import cn.hutool.core.collection.CollUtil; | |
| 6 | 6 | import cn.hutool.core.map.MapUtil; |
| 7 | 7 | import cn.hutool.core.util.ObjUtil; |
| 8 | 8 | import cn.hutool.core.util.StrUtil; |
| ... | ... | @@ -14,8 +14,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 14 | 14 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 15 | 15 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 16 | 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 | 18 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 20 | 19 | import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; |
| 21 | 20 | import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; |
| ... | ... | @@ -29,15 +28,17 @@ import org.springframework.ai.image.ImagePrompt; |
| 29 | 28 | import org.springframework.ai.image.ImageResponse; |
| 30 | 29 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 31 | 30 | import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; |
| 32 | -import org.springframework.beans.factory.annotation.Autowired; | |
| 33 | 31 | import org.springframework.beans.factory.annotation.Value; |
| 34 | 32 | import org.springframework.scheduling.annotation.Async; |
| 35 | 33 | import org.springframework.stereotype.Service; |
| 36 | 34 | import org.springframework.transaction.annotation.Transactional; |
| 37 | 35 | |
| 38 | 36 | import java.util.List; |
| 37 | +import java.util.Map; | |
| 39 | 38 | |
| 40 | 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 | 42 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; |
| 42 | 43 | |
| 43 | 44 | /** |
| ... | ... | @@ -51,12 +52,16 @@ public class AiImageServiceImpl implements AiImageService { |
| 51 | 52 | |
| 52 | 53 | @Resource |
| 53 | 54 | private AiImageMapper imageMapper; |
| 55 | + | |
| 54 | 56 | @Resource |
| 55 | 57 | private FileApi fileApi; |
| 58 | + | |
| 56 | 59 | @Resource |
| 57 | 60 | private AiApiKeyService apiKeyService; |
| 58 | - @Autowired(required = false) | |
| 61 | + | |
| 62 | + @Resource | |
| 59 | 63 | private MidjourneyApi midjourneyApi; |
| 64 | + | |
| 60 | 65 | @Value("${ai.midjourney-proxy.notifyUrl:http://127.0.0.1:48080/admin-api/ai/image/midjourney-notify}") |
| 61 | 66 | private String midjourneyNotifyUrl; |
| 62 | 67 | |
| ... | ... | @@ -74,7 +79,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 74 | 79 | public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) { |
| 75 | 80 | // 1. 保存数据库 |
| 76 | 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 | 83 | imageMapper.insert(image); |
| 79 | 84 | // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果 |
| 80 | 85 | getSelf().executeDrawImage(image, drawReqVO); |
| ... | ... | @@ -122,101 +127,121 @@ public class AiImageServiceImpl implements AiImageService { |
| 122 | 127 | } |
| 123 | 128 | |
| 124 | 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 | 173 | imageMapper.updateById(new AiImageDO() |
| 157 | 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 | 178 | return image.getId(); |
| 162 | 179 | } |
| 163 | 180 | |
| 164 | - | |
| 165 | 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 | 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 | 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 | 224 | if (MidjourneyApi.TaskStatusEnum.SUCCESS == taskStatusEnum) { |
| 195 | - imageStatus = AiImageStatusEnum.SUCCESS.getStatus(); | |
| 225 | + status = AiImageStatusEnum.SUCCESS.getStatus(); | |
| 196 | 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 | 234 | try { |
| 205 | - filePath = fileApi.createFile(HttpUtil.downloadBytes(notifyReqVO.getImageUrl())); | |
| 235 | + picUrl = fileApi.createFile(HttpUtil.downloadBytes(notify.imageUrl())); | |
| 206 | 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 | 247 | @Override |
| ... | ... | @@ -236,7 +261,6 @@ public class AiImageServiceImpl implements AiImageService { |
| 236 | 261 | |
| 237 | 262 | // 5、新增 image 记录(根据 image 新增一个) |
| 238 | 263 | AiImageDO newImage = new AiImageDO(); |
| 239 | - newImage.setId(null); | |
| 240 | 264 | newImage.setUserId(image.getUserId()); |
| 241 | 265 | newImage.setPrompt(image.getPrompt()); |
| 242 | 266 | |
| ... | ... | @@ -248,20 +272,15 @@ public class AiImageServiceImpl implements AiImageService { |
| 248 | 272 | newImage.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); |
| 249 | 273 | newImage.setPublicStatus(image.getPublicStatus()); |
| 250 | 274 | |
| 251 | - newImage.setPicUrl(null); | |
| 252 | - newImage.setProgress(null); | |
| 253 | - newImage.setButtons(null); | |
| 254 | 275 | newImage.setOptions(image.getOptions()); |
| 255 | - newImage.setResponse(image.getResponse()); | |
| 256 | 276 | newImage.setTaskId(submitResponse.result()); |
| 257 | - newImage.setErrorMessage(null); | |
| 258 | 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 | 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 | 284 | isTrue = true; |
| 266 | 285 | break; |
| 267 | 286 | } |
| ... | ... | @@ -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 | 294 | * 获得自身的代理对象,解决 AOP 生效问题 |
| 284 | 295 | * |
| ... | ... | @@ -288,28 +299,4 @@ public class AiImageServiceImpl implements AiImageService { |
| 288 | 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 | 50 | <artifactId>junit</artifactId> |
| 51 | 51 | <scope>test</scope> |
| 52 | 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 | 53 | </dependencies> |
| 82 | 54 | |
| 83 | 55 | </project> |
| 84 | 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 | 16 | import java.util.Map; |
| 17 | 17 | |
| 18 | 18 | /** |
| 19 | - * Midjourney api | |
| 19 | + * Midjourney API | |
| 20 | 20 | * |
| 21 | 21 | * @author fansili |
| 22 | - * @time 2024/6/11 15:46 | |
| 23 | 22 | * @since 1.0 |
| 24 | 23 | */ |
| 25 | 24 | @Slf4j |
| 26 | 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 | 27 | private final WebClient webClient; |
| 32 | - private final MidjourneyConfig midjourneyConfig; | |
| 33 | 28 | |
| 34 | 29 | public MidjourneyApi(MidjourneyConfig midjourneyConfig) { |
| 35 | - this.midjourneyConfig = midjourneyConfig; | |
| 36 | 30 | this.webClient = WebClient.builder() |
| 37 | 31 | .baseUrl(midjourneyConfig.getUrl()) |
| 38 | 32 | .defaultHeaders(ApiUtils.getJsonContentHeaders(midjourneyConfig.getKey())) |
| 39 | 33 | .build(); |
| 40 | 34 | } |
| 41 | 35 | |
| 42 | - | |
| 43 | 36 | /** |
| 44 | 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 | 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 | 55 | return JsonUtils.parseObject(res, SubmitResponse.class); |
| 66 | 56 | } |
| 67 | 57 | |
| 68 | 58 | /** |
| 69 | 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 | 69 | private String post(String uri, Object body) { |
| ... | ... | @@ -94,12 +82,12 @@ public class MidjourneyApi { |
| 94 | 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 | 91 | * @param notifyHook 通知地址 |
| 104 | 92 | * @param prompt 提示词 |
| 105 | 93 | * @param state 自定义参数 |
| ... | ... | @@ -108,10 +96,24 @@ public class MidjourneyApi { |
| 108 | 96 | String notifyHook, |
| 109 | 97 | String prompt, |
| 110 | 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 | 118 | * @param customId 操作按钮id |
| 117 | 119 | * @param taskId 操作按钮id |
| ... | ... | @@ -124,7 +126,7 @@ public class MidjourneyApi { |
| 124 | 126 | } |
| 125 | 127 | |
| 126 | 128 | /** |
| 127 | - * Midjourney - Submit 返回 | |
| 129 | + * Submit 统一返回 | |
| 128 | 130 | * |
| 129 | 131 | * @param code 状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误) |
| 130 | 132 | * @param description 描述 |
| ... | ... | @@ -138,7 +140,7 @@ public class MidjourneyApi { |
| 138 | 140 | } |
| 139 | 141 | |
| 140 | 142 | /** |
| 141 | - * Midjourney - 通知 request | |
| 143 | + * 通知 request | |
| 142 | 144 | * |
| 143 | 145 | * @param id job id |
| 144 | 146 | * @param action 任务类型 {@link TaskActionEnum} |
| ... | ... | @@ -155,47 +157,47 @@ public class MidjourneyApi { |
| 155 | 157 | * @param failReason 失败原因 |
| 156 | 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 | 202 | @AllArgsConstructor |
| 201 | 203 | @Getter |
| ... | ... | @@ -203,24 +205,15 @@ public class MidjourneyApi { |
| 203 | 205 | |
| 204 | 206 | MIDJOURNEY("midjourney", "midjourney"), |
| 205 | 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 | 218 | @Getter |
| 226 | 219 | @AllArgsConstructor |
| ... | ... | @@ -239,64 +232,68 @@ public class MidjourneyApi { |
| 239 | 232 | |
| 240 | 233 | private final String code; |
| 241 | 234 | private final String name; |
| 235 | + | |
| 242 | 236 | } |
| 243 | 237 | |
| 244 | 238 | /** |
| 245 | - * Midjourney - action | |
| 239 | + * Action 枚举 | |
| 246 | 240 | */ |
| 247 | 241 | @Getter |
| 248 | 242 | @AllArgsConstructor |
| 249 | 243 | public enum TaskActionEnum { |
| 244 | + | |
| 250 | 245 | /** |
| 251 | - * 生成图片. | |
| 246 | + * 生成图片 | |
| 252 | 247 | */ |
| 253 | 248 | IMAGINE, |
| 254 | 249 | /** |
| 255 | - * 选中放大. | |
| 250 | + * 选中放大 | |
| 256 | 251 | */ |
| 257 | 252 | UPSCALE, |
| 258 | 253 | /** |
| 259 | - * 选中其中的一张图,生成四张相似的. | |
| 254 | + * 选中其中的一张图,生成四张相似的 | |
| 260 | 255 | */ |
| 261 | 256 | VARIATION, |
| 262 | 257 | /** |
| 263 | - * 重新执行. | |
| 258 | + * 重新执行 | |
| 264 | 259 | */ |
| 265 | 260 | REROLL, |
| 266 | 261 | /** |
| 267 | - * 图转prompt. | |
| 262 | + * 图转 prompt | |
| 268 | 263 | */ |
| 269 | 264 | DESCRIBE, |
| 270 | 265 | /** |
| 271 | - * 多图混合. | |
| 266 | + * 多图混合 | |
| 272 | 267 | */ |
| 273 | 268 | BLEND |
| 269 | + | |
| 274 | 270 | } |
| 275 | 271 | |
| 276 | 272 | /** |
| 277 | - * Midjourney - 任务状态 | |
| 273 | + * 任务状态枚举 | |
| 278 | 274 | */ |
| 279 | 275 | @Getter |
| 280 | 276 | @AllArgsConstructor |
| 281 | 277 | public enum TaskStatusEnum { |
| 278 | + | |
| 282 | 279 | /** |
| 283 | - * 未启动. | |
| 280 | + * 未启动 | |
| 284 | 281 | */ |
| 285 | 282 | NOT_START(0), |
| 286 | 283 | /** |
| 287 | - * 已提交. | |
| 284 | + * 已提交 | |
| 288 | 285 | */ |
| 289 | 286 | SUBMITTED(1), |
| 290 | 287 | /** |
| 291 | - * 执行中. | |
| 288 | + * 执行中 | |
| 292 | 289 | */ |
| 293 | 290 | IN_PROGRESS(3), |
| 294 | 291 | /** |
| 295 | - * 失败. | |
| 292 | + * 失败 | |
| 296 | 293 | */ |
| 297 | 294 | FAILURE(4), |
| 298 | 295 | /** |
| 299 | - * 成功. | |
| 296 | + * 成功 | |
| 300 | 297 | */ |
| 301 | 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 | 2 | * model 包,接入各种大模型,对标 https://github.com/spring-projects/spring-ai/tree/main/models |
| 3 | 3 | * |
| 4 | 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 | 10 | package cn.iocoder.yudao.framework.ai.core.model; |
| 13 | 11 | \ No newline at end of file | ... | ... |
yudao-server/src/main/resources/application-local.yaml
| ... | ... | @@ -76,13 +76,6 @@ server: |
| 76 | 76 | enabled: true |
| 77 | 77 | charset: UTF-8 |
| 78 | 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 | 201 | enable: true |
| 202 | 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 | 213 | yudao: | ... | ... |