Commit a1f738dd81dac8149a0004cc1003c76bb3236297

Authored by cherishsince
1 parent 782f1085

【增加】增加 midjourney 提交任务

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/client/vo/MidjourneySubmitCodeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.client.vo;
  2 +
  3 +import com.google.common.collect.Lists;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * Midjourney 提交任务 code 枚举
  11 + *
  12 + * @author fansili
  13 + * @time 2024/5/30 14:33
  14 + * @since 1.0
  15 + */
  16 +@Getter
  17 +@AllArgsConstructor
  18 +public enum MidjourneySubmitCodeEnum {
  19 +
  20 + // 状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)
  21 + SUBMIT_SUCCESS("1", "提交成功"),
  22 + ALREADY_EXISTS("1", "已存在"),
  23 + QUEUING("22", "排队中"),
  24 +
  25 + ;
  26 +
  27 + public static final List<String> SUCCESS_CODES = Lists.newArrayList(
  28 + SUBMIT_SUCCESS.code,
  29 + ALREADY_EXISTS.code,
  30 + QUEUING.code
  31 + );
  32 +
  33 + private String code;
  34 + private String name;
  35 +
  36 +}
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/client/vo/MidjourneySubmitRespVO.java
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.ai.client.vo; @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.ai.client.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
  6 +import java.util.Map;
  7 +
6 /** 8 /**
7 * Midjourney:Imagine 请求 9 * Midjourney:Imagine 请求
8 * 10 *
@@ -20,7 +22,7 @@ public class MidjourneySubmitRespVO { @@ -20,7 +22,7 @@ public class MidjourneySubmitRespVO {
20 private String description; 22 private String description;
21 23
22 @Schema(description = "扩展字段") 24 @Schema(description = "扩展字段")
23 - private String properties; 25 + private Map<String, Object> properties;
24 26
25 @Schema(description = "任务ID") 27 @Schema(description = "任务ID")
26 private String result; 28 private String result;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
@@ -3,13 +3,17 @@ package cn.iocoder.yudao.module.ai.controller.admin.image; @@ -3,13 +3,17 @@ package cn.iocoder.yudao.module.ai.controller.admin.image;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*; 6 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO;
  7 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO;
  8 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;
  9 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageMyRespVO;
7 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 10 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
8 import cn.iocoder.yudao.module.ai.service.image.AiImageService; 11 import cn.iocoder.yudao.module.ai.service.image.AiImageService;
9 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
10 import io.swagger.v3.oas.annotations.Parameter; 13 import io.swagger.v3.oas.annotations.Parameter;
11 import io.swagger.v3.oas.annotations.tags.Tag; 14 import io.swagger.v3.oas.annotations.tags.Tag;
12 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
  16 +import jakarta.servlet.http.HttpServletRequest;
13 import lombok.extern.slf4j.Slf4j; 17 import lombok.extern.slf4j.Slf4j;
14 import org.springframework.validation.annotation.Validated; 18 import org.springframework.validation.annotation.Validated;
15 import org.springframework.web.bind.annotation.*; 19 import org.springframework.web.bind.annotation.*;
@@ -49,32 +53,17 @@ public class AiImageController { @@ -49,32 +53,17 @@ public class AiImageController {
49 } 53 }
50 54
51 // TODO @fan:建议把 dallDrawing、midjourney 融合成一个 draw 接口,异步绘制;然后返回一个 id 给前端;前端通过 get 接口轮询,直到获取到生成成功 55 // TODO @fan:建议把 dallDrawing、midjourney 融合成一个 draw 接口,异步绘制;然后返回一个 id 给前端;前端通过 get 接口轮询,直到获取到生成成功
  56 + // TODO @芋艿: 参数差异较大
52 @Operation(summary = "dall2/dall3绘画", description = "openAi dall3是付费的!") 57 @Operation(summary = "dall2/dall3绘画", description = "openAi dall3是付费的!")
53 @PostMapping("/dall") 58 @PostMapping("/dall")
54 public CommonResult<Long> dall(@Validated @RequestBody AiImageDallReqVO req) { 59 public CommonResult<Long> dall(@Validated @RequestBody AiImageDallReqVO req) {
55 return success(aiImageService.dall(getLoginUserId(), req)); 60 return success(aiImageService.dall(getLoginUserId(), req));
56 } 61 }
57 62
58 - @Operation(summary = "midjourney绘画", description = "midjourney图片绘画流程:1、提交任务 2、获取完成的任务 3、选择对应功能 4、获取最终结果")  
59 - @PostMapping("/midjourney")  
60 - public CommonResult<Void> midjourney(@Validated @RequestBody AiImageMidjourneyReqVO req) {  
61 - aiImageService.midjourney(req);  
62 - return success(null);  
63 - }  
64 -  
65 - @Operation(summary = "midjourney绘画操作", description = "一般有选择图片、放大、换一批...")  
66 - @PostMapping("/midjourney-operate")  
67 - public CommonResult<Void> midjourneyOperate(@Validated @RequestBody AiImageMidjourneyOperateReqVO req) {  
68 - aiImageService.midjourneyOperate(req);  
69 - return success(null);  
70 - }  
71 -  
72 - // TODO @fan:要不先不要 midjourneyOperate、cancelMidjourney 接口哈  
73 - @Operation(summary = "取消 midjourney 绘画", description = "取消 midjourney 绘画")  
74 - @PostMapping("/cancel-midjourney")  
75 - public CommonResult<Void> cancelMidjourney(@RequestParam("id") Long id) {  
76 - // @范 这里实现mj取消逻辑  
77 - return success(null); 63 + @Operation(summary = "midjourney-imagine 绘画", description = "...")
  64 + @PostMapping("/midjourney/imagine")
  65 + public CommonResult<Long> midjourneyImagine(@Validated @RequestBody AiImageMidjourneyImagineReqVO req) {
  66 + return success(aiImageService.midjourneyImagine(getLoginUserId(), req));
78 } 67 }
79 68
80 @Operation(summary = "删除【我的】绘画记录") 69 @Operation(summary = "删除【我的】绘画记录")
@@ -83,4 +72,10 @@ public class AiImageController { @@ -83,4 +72,10 @@ public class AiImageController {
83 public CommonResult<Boolean> deleteIdMy(@RequestParam("id") Long id) { 72 public CommonResult<Boolean> deleteIdMy(@RequestParam("id") Long id) {
84 return success(aiImageService.deleteIdMy(id, getLoginUserId())); 73 return success(aiImageService.deleteIdMy(id, getLoginUserId()));
85 } 74 }
  75 +
  76 + @Operation(summary = "删除【我的】绘画记录")
  77 + @RequestMapping("/midjourney-notify")
  78 + public CommonResult<Boolean> midjourneyNotify(HttpServletRequest request) {
  79 + return success(true);
  80 + }
86 } 81 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyReqVO.java renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyImagineReqVO.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 io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
4 import lombok.Data; 5 import lombok.Data;
5 import lombok.experimental.Accessors; 6 import lombok.experimental.Accessors;
6 7
  8 +import java.util.List;
  9 +
7 /** 10 /**
8 * midjourney req 11 * midjourney req
9 * 12 *
@@ -13,17 +16,15 @@ import lombok.experimental.Accessors; @@ -13,17 +16,15 @@ import lombok.experimental.Accessors;
13 */ 16 */
14 @Data 17 @Data
15 @Accessors(chain = true) 18 @Accessors(chain = true)
16 -public class AiImageMidjourneyReqVO { 19 +public class AiImageMidjourneyImagineReqVO {
17 20
18 @Schema(description = "提示词") 21 @Schema(description = "提示词")
  22 + @NotNull(message = "提示词不能为空!")
19 private String prompt; 23 private String prompt;
20 24
21 - @Schema(description = "绘画比例 1:1、3:4、4:3、9:16、16:9")  
22 - private String size;  
23 -  
24 - @Schema(description = "风格")  
25 - private String style; 25 + @Schema(description = "模型(midjourney、niji)")
  26 + private String model;
26 27
27 - @Schema(description = "参考图")  
28 - private String referImage; 28 + @Schema(description = "垫图(参考图)base64数组")
  29 + private List<String> base64Array;
29 } 30 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.ai.service.image; @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.ai.service.image;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; 4 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO;
5 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO; 5 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageListReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO;
6 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperateReqVO; 7 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperateReqVO;
7 -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyReqVO;  
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 /**
@@ -44,10 +44,11 @@ public interface AiImageService { @@ -44,10 +44,11 @@ public interface AiImageService {
44 /** 44 /**
45 * midjourney 图片生成 45 * midjourney 图片生成
46 * 46 *
  47 + * @param loginUserId
47 * @param req 48 * @param req
48 * @return 49 * @return
49 */ 50 */
50 - void midjourney(AiImageMidjourneyReqVO req); 51 + Long midjourneyImagine(Long loginUserId, AiImageMidjourneyImagineReqVO req);
51 52
52 /** 53 /**
53 * midjourney 操作(u1、u2、放大、换一批...) 54 * midjourney 操作(u1、u2、放大、换一批...)
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
1 package cn.iocoder.yudao.module.ai.service.image; 1 package cn.iocoder.yudao.module.ai.service.image;
2 2
3 -import cn.hutool.core.util.IdUtil;  
4 import cn.hutool.http.HttpUtil; 3 import cn.hutool.http.HttpUtil;
  4 +import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
5 import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; 5 import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum;
6 import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum;
7 import cn.iocoder.yudao.framework.ai.core.exception.AiException; 7 import cn.iocoder.yudao.framework.ai.core.exception.AiException;
@@ -11,6 +11,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -11,6 +11,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 11 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
12 import cn.iocoder.yudao.module.ai.AiCommonConstants; 12 import cn.iocoder.yudao.module.ai.AiCommonConstants;
13 import cn.iocoder.yudao.module.ai.ErrorCodeConstants; 13 import cn.iocoder.yudao.module.ai.ErrorCodeConstants;
  14 +import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient;
  15 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO;
  16 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitCodeEnum;
  17 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO;
14 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*; 18 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
15 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; 19 import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
16 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; 20 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
@@ -18,21 +22,22 @@ import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum; @@ -18,21 +22,22 @@ import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum;
18 import cn.iocoder.yudao.module.ai.enums.AiImageStatusEnum; 22 import cn.iocoder.yudao.module.ai.enums.AiImageStatusEnum;
19 import cn.iocoder.yudao.module.infra.api.file.FileApi; 23 import cn.iocoder.yudao.module.infra.api.file.FileApi;
20 import com.google.common.collect.ImmutableMap; 24 import com.google.common.collect.ImmutableMap;
21 -import jakarta.annotation.PostConstruct;  
22 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
23 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
24 import org.springframework.ai.image.ImageGeneration; 27 import org.springframework.ai.image.ImageGeneration;
25 import org.springframework.ai.image.ImagePrompt; 28 import org.springframework.ai.image.ImagePrompt;
26 import org.springframework.ai.image.ImageResponse; 29 import org.springframework.ai.image.ImageResponse;
27 -import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi;  
28 -import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter;  
29 import org.springframework.ai.openai.OpenAiImageClient; 30 import org.springframework.ai.openai.OpenAiImageClient;
30 import org.springframework.ai.openai.OpenAiImageOptions; 31 import org.springframework.ai.openai.OpenAiImageOptions;
  32 +import org.springframework.beans.factory.annotation.Autowired;
  33 +import org.springframework.beans.factory.annotation.Value;
31 import org.springframework.scheduling.annotation.Async; 34 import org.springframework.scheduling.annotation.Async;
32 import org.springframework.stereotype.Service; 35 import org.springframework.stereotype.Service;
33 import org.springframework.transaction.annotation.Transactional; 36 import org.springframework.transaction.annotation.Transactional;
34 37
  38 +import java.util.HashMap;
35 import java.util.List; 39 import java.util.List;
  40 +import java.util.Map;
36 41
37 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
38 43
@@ -59,28 +64,11 @@ public class AiImageServiceImpl implements AiImageService { @@ -59,28 +64,11 @@ public class AiImageServiceImpl implements AiImageService {
59 private FileApi fileApi; 64 private FileApi fileApi;
60 @Resource 65 @Resource
61 private OpenAiImageClient openAiImageClient; 66 private OpenAiImageClient openAiImageClient;
62 - @Resource  
63 - private MidjourneyWebSocketStarter midjourneyWebSocketStarter;  
64 - @Resource  
65 - private MidjourneyInteractionsApi midjourneyInteractionsApi;  
66 -  
67 - // TODO @fan:接 mj proxy  
68 - @PostConstruct  
69 - public void startMidjourney() {  
70 - // todo @fan 暂时注释掉  
71 -// log.info("midjourney web socket starter...");  
72 -// midjourneyWebSocketStarter.start(new WssNotify() {  
73 -// @Override  
74 -// public void notify(int code, String message) {  
75 -// log.info("code: {}, message: {}", code, message);  
76 -// if (message.contains("Authentication failed")) {  
77 -// // TODO 芋艿,这里看怎么处理,token无效的时候会认证失败!  
78 -// // 认证失败  
79 -// log.error("midjourney socket 认证失败,检查token是否失效!");  
80 -// }  
81 -// }  
82 -// });  
83 - } 67 + @Autowired
  68 + private MidjourneyProxyClient midjourneyProxyClient;
  69 +
  70 + @Value("${ai.midjourney-proxy.notifyUrl:http://127.0.0.1:48080/admin-api/ai/image/midjourney-notify}")
  71 + private String midjourneyNotifyUrl;
84 72
85 @Override 73 @Override
86 public PageResult<AiImageDO> getImagePageMy(Long loginUserId, AiImageListReqVO req) { 74 public PageResult<AiImageDO> getImagePageMy(Long loginUserId, AiImageListReqVO req) {
@@ -143,18 +131,53 @@ public class AiImageServiceImpl implements AiImageService { @@ -143,18 +131,53 @@ public class AiImageServiceImpl implements AiImageService {
143 131
144 @Override 132 @Override
145 @Transactional(rollbackFor = Exception.class) 133 @Transactional(rollbackFor = Exception.class)
146 - public void midjourney(AiImageMidjourneyReqVO req) {  
147 - // 保存数据库  
148 - String messageId = String.valueOf(IdUtil.getSnowflakeNextId());  
149 - // todo  
150 -// AiImageDO aiImageDO = doSave(req.getPrompt(), null, "midjoureny",  
151 -// null, null, AiImageStatusEnum.SUBMIT, null,  
152 -// messageId, null, null);  
153 - // 提交 midjourney 任务  
154 - Boolean imagine = midjourneyInteractionsApi.imagine(messageId, req.getPrompt());  
155 - if (!imagine) {  
156 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.AI_MIDJOURNEY_IMAGINE_FAIL); 134 + public Long midjourneyImagine(Long loginUserId, AiImageMidjourneyImagineReqVO req) {
  135 +
  136 + // 1、构建 AiImageDO
  137 + AiImageDO aiImageDO = new AiImageDO();
  138 + aiImageDO.setId(null);
  139 + aiImageDO.setUserId(loginUserId);
  140 + aiImageDO.setPrompt(req.getPrompt());
  141 + aiImageDO.setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform());
  142 + // todo @范 平台需要转换(mj 模型一般分版本)
  143 + aiImageDO.setModel(null);
  144 + aiImageDO.setWidth(null);
  145 + aiImageDO.setHeight(null);
  146 + aiImageDO.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
  147 + aiImageDO.setPublicStatus(AiImagePublicStatusEnum.PRIVATE.getStatus());
  148 + aiImageDO.setPicUrl(null);
  149 + aiImageDO.setOriginalPicUrl(null);
  150 + aiImageDO.setDrawRequest(null);
  151 + aiImageDO.setDrawResponse(null);
  152 + aiImageDO.setErrorMessage(null);
  153 +
  154 + // 2、保存 image
  155 + imageMapper.insert(aiImageDO);
  156 +
  157 + // 3、调用 MidjourneyProxy 提交任务
  158 + MidjourneyImagineReqVO imagineReqVO = BeanUtils.toBean(req, MidjourneyImagineReqVO.class);
  159 + imagineReqVO.setNotifyHook(midjourneyNotifyUrl);
  160 + imagineReqVO.setState(String.valueOf(aiImageDO.getId()));
  161 + MidjourneySubmitRespVO submitRespVO = midjourneyProxyClient.imagine(imagineReqVO);
  162 +
  163 + // 4、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误))
  164 + String updateStatus = null;
  165 + String errorMessage = null;
  166 + Map<String, Object> drawResponse = new HashMap<>();
  167 +
  168 + if (!MidjourneySubmitCodeEnum.SUCCESS_CODES.contains(submitRespVO.getCode())) {
  169 + updateStatus = AiImageStatusEnum.FAIL.getStatus();
  170 + errorMessage = submitRespVO.getDescription();
  171 + } else {
  172 + drawResponse.put("jobId", submitRespVO.getResult());
157 } 173 }
  174 + imageMapper.updateById(new AiImageDO()
  175 + .setId(aiImageDO.getId())
  176 + .setStatus(updateStatus)
  177 + .setErrorMessage(errorMessage)
  178 + .setDrawResponse(drawResponse)
  179 + );
  180 + return aiImageDO.getId();
158 } 181 }
159 182
160 @Transactional(rollbackFor = Exception.class) 183 @Transactional(rollbackFor = Exception.class)