diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/MidjourneyProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/MidjourneyProperties.java new file mode 100644 index 000000000..fc381a1c4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/MidjourneyProperties.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney; + +import lombok.Data; + +/** + * Midjourney 属性 + * + * @author fansili + * @time 2024/6/5 15:02 + * @since 1.0 + */ +@Data +public class MidjourneyProperties { + + private String key; + private String url; + private String notifyUrl; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java new file mode 100644 index 000000000..a501652b8 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/api/MidjourneyApi.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.api; + +import cn.iocoder.yudao.framework.ai.core.model.midjourney.MidjourneyProperties; +import cn.iocoder.yudao.framework.ai.core.model.midjourney.vo.MidjourneyActionRequest; +import cn.iocoder.yudao.framework.ai.core.model.midjourney.vo.MidjourneyImagineRequest; +import cn.iocoder.yudao.framework.ai.core.model.midjourney.vo.MidjourneyNotifyRequest; +import cn.iocoder.yudao.framework.ai.core.model.midjourney.vo.MidjourneySubmitResponse; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.google.common.collect.ImmutableMap; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.openai.api.ApiUtils; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Collection; +import java.util.List; + +/** + * Midjourney api + * + * @author fansili + * @time 2024/6/11 15:46 + * @since 1.0 + */ +@Slf4j +public class MidjourneyApi { + + private static final String URI_IMAGINE = "/submit/imagine"; + private static final String URI_ACTON = "/submit/action"; + private static final String URI_LIST_BY_CONDITION = "/task/list-by-condition"; + private final WebClient webClient; + private final MidjourneyProperties midjourneyProperties; + + public MidjourneyApi(MidjourneyProperties midjourneyProperties) { + this.midjourneyProperties = midjourneyProperties; + this.webClient = WebClient.builder() + .baseUrl(midjourneyProperties.getUrl()) + .defaultHeaders(ApiUtils.getJsonContentHeaders(midjourneyProperties.getKey())) + .build(); + } + + + /** + * imagine - 根据提示词提交绘画任务 + * + * @param imagineReqVO + * @return + */ + public MidjourneySubmitResponse imagine(MidjourneyImagineRequest imagineReqVO) { + // 1、发送 post 请求 + String res = post(URI_IMAGINE, imagineReqVO); + // 2、转换 resp + return JsonUtils.parseObject(res, MidjourneySubmitResponse.class); + } + + /** + * action - 放大、缩小、U1、U2... + * + * @param actionReqVO + */ + public MidjourneySubmitResponse action(MidjourneyActionRequest actionReqVO) { + // 1、发送 post 请求 + String res = post(URI_ACTON, actionReqVO); + // 2、转换 resp + return JsonUtils.parseObject(res, MidjourneySubmitResponse.class); + } + + /** + * 批量查询 task 任务 + * + * @param taskIds + * @return + */ + public List listByCondition(Collection taskIds) { + // 1、发送 post 请求 + String res = post(URI_LIST_BY_CONDITION, ImmutableMap.of("ids", taskIds)); + // 2、转换 对象 + return JsonUtils.parseArray(res, MidjourneyNotifyRequest.class); + } + + private String post(String uri, Object body) { + // 1、发送 post 请求 + return webClient.post() + .uri(uri) + .body(Mono.just(JsonUtils.toJsonString(body)), String.class) + .retrieve() + .onStatus(status -> !status.is2xxSuccessful(), + response -> response.bodyToMono(String.class) + .handle((respBody, sink) -> { + log.error("【Midjourney api】调用失败!resp: 【{}】", respBody); + sink.error(new IllegalStateException("【Midjourney api】调用失败!")); + })) + .bodyToMono(String.class) + .block(); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyModelEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyModelEnum.java new file mode 100644 index 000000000..68db7694d --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyModelEnum.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 来源于 midjourney-proxy + */ +@Getter +@AllArgsConstructor +public enum MidjourneyModelEnum { + + MIDJOURNEY("midjourney", "midjourney"), + NIJI("Niji", "Niji"), + + ; + + private String model; + private String name; + + public static MidjourneyModelEnum valueOfModel(String model) { + for (MidjourneyModelEnum itemEnum : MidjourneyModelEnum.values()) { + if (itemEnum.getModel().equals(model)) { + return itemEnum; + } + } + throw new IllegalArgumentException("Invalid MessageType value: " + model); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneySubmitCodeEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneySubmitCodeEnum.java new file mode 100644 index 000000000..5097f07a7 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneySubmitCodeEnum.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.enums; + +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +// TODO @fan:待定 +/** + * Midjourney 提交任务 code 枚举 + * + * @author fansili + */ +@Getter +@AllArgsConstructor +public enum MidjourneySubmitCodeEnum { + + SUBMIT_SUCCESS("1", "提交成功"), + ALREADY_EXISTS("21", "已存在"), + QUEUING("22", "排队中"), + ; + + public static final List SUCCESS_CODES = Lists.newArrayList( + SUBMIT_SUCCESS.code, + ALREADY_EXISTS.code, + QUEUING.code + ); + + private final String code; + private final String name; + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyTaskActionEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyTaskActionEnum.java new file mode 100644 index 000000000..054b67b3e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyTaskActionEnum.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.enums; + +import lombok.Getter; + +/** + * 来源于 midjourney-proxy + */ +@Getter +public enum MidjourneyTaskActionEnum { + /** + * 生成图片. + */ + IMAGINE, + /** + * 选中放大. + */ + UPSCALE, + /** + * 选中其中的一张图,生成四张相似的. + */ + VARIATION, + /** + * 重新执行. + */ + REROLL, + /** + * 图转prompt. + */ + DESCRIBE, + /** + * 多图混合. + */ + BLEND + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyTaskStatusEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyTaskStatusEnum.java new file mode 100644 index 000000000..08841fb91 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/enums/MidjourneyTaskStatusEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.enums; + + +import lombok.Getter; + +/** + * 来源于 midjourney-proxy + */ +public enum MidjourneyTaskStatusEnum { + /** + * 未启动. + */ + NOT_START(0), + /** + * 已提交. + */ + SUBMITTED(1), + /** + * 执行中. + */ + IN_PROGRESS(3), + /** + * 失败. + */ + FAILURE(4), + /** + * 成功. + */ + SUCCESS(4); + + @Getter + private final int order; + + MidjourneyTaskStatusEnum(int order) { + this.order = order; + } + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyActionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyActionRequest.java new file mode 100644 index 000000000..47f3597f2 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyActionRequest.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * Midjourney:action 请求 + * + * @author fansili + * @time 2024/5/30 14:02 + * @since 1.0 + */ +@Data +public class MidjourneyActionRequest { + + @Schema(description = "操作按钮id", required = true) + private String customId; + + @Schema(description = "操作按钮id", required = true) + private String taskId; + + @Schema(description = "通知地址", required = false) + private String notifyHook; + + @Schema(description = "自定义参数", required = false) + private String state; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyImagineRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyImagineRequest.java new file mode 100644 index 000000000..94aee7e18 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyImagineRequest.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +// TODO @fan:待定 +/** + * Midjourney:Imagine 请求 + * + * @author fansili + * @time 2024/5/30 14:02 + * @since 1.0 + */ +@Data +public class MidjourneyImagineRequest { + + @Schema(description = "垫图(参考图)base64数组", required = false) + private List base64Array; + + @Schema(description = "通知地址", required = false) + private String notifyHook; + + @Schema(description = "提示词", required = true) + private String prompt; + + @Schema(description = "自定义参数", required = false) + private String state; +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyNotifyRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyNotifyRequest.java new file mode 100644 index 000000000..e56e519c0 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/midjourney/vo/MidjourneyNotifyRequest.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.framework.ai.core.model.midjourney.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * Midjourney Proxy 通知回调 + * + * - Midjourney Proxy:通知回调 bean 是 com.github.novicezk.midjourney.support.Task + * - 毫秒 api 通知回调文档地址:https://gpt-best.apifox.cn/doc-3530863 + * + * @author fansili + * @time 2024/5/31 10:37 + * @since 1.0 + */ +@Data +public class MidjourneyNotifyRequest { + + @Schema(description = "job id") + private String id; + + @Schema(description = "任务类型 MidjourneyTaskActionEnum") + private String action; + @Schema(description = "任务状态 MidjourneyTaskStatusEnum") + private String status; + + @Schema(description = "提示词") + private String prompt; + @Schema(description = "提示词-英文") + private String promptEn; + + @Schema(description = "任务描述") + private String description; + @Schema(description = "自定义参数") + private String state; + + @Schema(description = "提交时间") + private Long submitTime; + @Schema(description = "开始执行时间") + private Long startTime; + @Schema(description = "结束时间") + private Long finishTime; + + @Schema(description = "图片url") + private String imageUrl; + + @Schema(description = "任务进度") + private String progress; + @Schema(description = "失败原因") + private String failReason; + + @Schema(description = "任务完成后的可执行按钮") + private List