Commit 776d6e4e1e1758641e973125424eb06b4a99e45e

Authored by cherishsince
1 parent 79a094be

【增加】AI Image mj 增加 action 操作

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
@@ -44,5 +44,6 @@ public interface ErrorCodeConstants { @@ -44,5 +44,6 @@ public interface ErrorCodeConstants {
44 44
45 ErrorCode AI_IMAGE_NOT_EXISTS = new ErrorCode(1_022_005_000, "image 不存在!"); 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 提交失败! {}"); 46 ErrorCode AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_022_005_001, "Midjourney 提交失败! {}");
  47 + ErrorCode AI_IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_022_005_002, "Midjourney 按钮 customId 不存在! {}");
47 48
48 } 49 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
@@ -74,8 +74,14 @@ public class AiImageController { @@ -74,8 +74,14 @@ public class AiImageController {
74 @Operation(summary = "midjourney proxy - 回调通知") 74 @Operation(summary = "midjourney proxy - 回调通知")
75 @PostMapping("/midjourney-notify") 75 @PostMapping("/midjourney-notify")
76 @PermitAll 76 @PermitAll
77 - public CommonResult<Boolean> midjourneyNotify( @RequestBody MidjourneyNotifyReqVO notifyReqVO) {  
78 - return success(imageService.midjourneyNotify(getLoginUserId(), notifyReqVO)); 77 + public CommonResult<Boolean> midjourneyNotify(@RequestBody MidjourneyNotifyReqVO notifyReqVO) {
  78 + return success(imageService.midjourneyNotify(notifyReqVO));
79 } 79 }
80 80
  81 + @Operation(summary = "midjourney - action(放大、缩小、U1、U2...)")
  82 + @PostMapping("/midjourney/action")
  83 + public CommonResult<Boolean> midjourneyAction(@RequestParam("id") Long imageId,
  84 + @RequestParam("customId") String customId) {
  85 + return success(imageService.midjourneyAction(getLoginUserId(), imageId, customId));
  86 + }
81 } 87 }
82 \ No newline at end of file 88 \ No newline at end of file
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
@@ -59,10 +59,18 @@ public interface AiImageService { @@ -59,10 +59,18 @@ public interface AiImageService {
59 /** 59 /**
60 * midjourney proxy - 回调通知 60 * midjourney proxy - 回调通知
61 * 61 *
62 - * @param loginUserId  
63 * @param notifyReqVO 62 * @param notifyReqVO
64 * @return 63 * @return
65 */ 64 */
66 - Boolean midjourneyNotify(Long loginUserId, MidjourneyNotifyReqVO notifyReqVO); 65 + Boolean midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO);
67 66
  67 + /**
  68 + * midjourney - action(放大、缩小、U1、U2...)
  69 + *
  70 + * @param loginUserId
  71 + * @param imageId
  72 + * @param customId
  73 + * @return
  74 + */
  75 + Boolean midjourneyAction(Long loginUserId, Long imageId, String customId);
68 } 76 }
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient; @@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient;
15 import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum; 15 import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum;
16 import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum; 16 import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum;
17 import cn.iocoder.yudao.module.ai.client.enums.MidjourneyTaskStatusEnum; 17 import cn.iocoder.yudao.module.ai.client.enums.MidjourneyTaskStatusEnum;
  18 +import cn.iocoder.yudao.module.ai.client.vo.MidjourneyActionReqVO;
18 import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO; 19 import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO;
19 import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; 20 import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO;
20 import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO; 21 import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO;
@@ -39,9 +40,10 @@ import org.springframework.scheduling.annotation.Async; @@ -39,9 +40,10 @@ import org.springframework.scheduling.annotation.Async;
39 import org.springframework.stereotype.Service; 40 import org.springframework.stereotype.Service;
40 import org.springframework.transaction.annotation.Transactional; 41 import org.springframework.transaction.annotation.Transactional;
41 42
  43 +import java.util.List;
  44 +
42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
43 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL;  
44 -import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_IMAGE_NOT_EXISTS; 46 +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*;
45 47
46 /** 48 /**
47 * AI 绘画 Service 实现类 49 * AI 绘画 Service 实现类
@@ -136,30 +138,21 @@ public class AiImageServiceImpl implements AiImageService { @@ -136,30 +138,21 @@ public class AiImageServiceImpl implements AiImageService {
136 aiImageDO.setUserId(loginUserId); 138 aiImageDO.setUserId(loginUserId);
137 aiImageDO.setPrompt(req.getPrompt()); 139 aiImageDO.setPrompt(req.getPrompt());
138 aiImageDO.setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()); 140 aiImageDO.setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform());
139 - // todo @范 平台需要转换(mj 模型一般分版本)  
140 aiImageDO.setModel(null); 141 aiImageDO.setModel(null);
141 aiImageDO.setWidth(null); 142 aiImageDO.setWidth(null);
142 aiImageDO.setHeight(null); 143 aiImageDO.setHeight(null);
143 aiImageDO.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); 144 aiImageDO.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus());
144 -  
145 // 2、保存 image 145 // 2、保存 image
146 imageMapper.insert(aiImageDO); 146 imageMapper.insert(aiImageDO);
147 -  
148 // 3、调用 MidjourneyProxy 提交任务 147 // 3、调用 MidjourneyProxy 提交任务
149 MidjourneyImagineReqVO imagineReqVO = BeanUtils.toBean(req, MidjourneyImagineReqVO.class); 148 MidjourneyImagineReqVO imagineReqVO = BeanUtils.toBean(req, MidjourneyImagineReqVO.class);
150 imagineReqVO.setNotifyHook(midjourneyNotifyUrl); 149 imagineReqVO.setNotifyHook(midjourneyNotifyUrl);
151 - // 设置 midjourney 扩展参数  
152 - // --ar 来设置尺寸  
153 - String midjourneySizeParam = String.format(" --ar %s:%s ", req.getWidth(), req.getHeight());  
154 - // --v 版本  
155 - String midjourneyVersionParam = String.format(" --v %s ", req.getVersion());  
156 - // --niji 模型  
157 - MidjourneyModelEnum midjourneyModelEnum = MidjourneyModelEnum.valueOfModel(req.getModel());  
158 - String midjourneyNijiParam = MidjourneyModelEnum.NIJI == midjourneyModelEnum ? " --niji " : "";  
159 - // 设置参数  
160 - imagineReqVO.setState(midjourneySizeParam.concat(midjourneyVersionParam).concat(midjourneyNijiParam)); 150 + // 4、设置 midjourney 扩展参数
  151 + imagineReqVO.setState(buildParams(req.getWidth(),
  152 + req.getHeight(), req.getVersion(), MidjourneyModelEnum.valueOfModel(req.getModel())));
  153 + // 5、提交绘画请求
161 MidjourneySubmitRespVO submitRespVO = midjourneyProxyClient.imagine(imagineReqVO); 154 MidjourneySubmitRespVO submitRespVO = midjourneyProxyClient.imagine(imagineReqVO);
162 - // 4、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)) 155 + // 6、保存任务 id (状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误))
163 if (!MidjourneySubmitCodeEnum.SUCCESS_CODES.contains(submitRespVO.getCode())) { 156 if (!MidjourneySubmitCodeEnum.SUCCESS_CODES.contains(submitRespVO.getCode())) {
164 throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, submitRespVO.getDescription()); 157 throw exception(AI_IMAGE_MIDJOURNEY_SUBMIT_FAIL, submitRespVO.getDescription());
165 } 158 }
@@ -170,6 +163,8 @@ public class AiImageServiceImpl implements AiImageService { @@ -170,6 +163,8 @@ public class AiImageServiceImpl implements AiImageService {
170 return aiImageDO.getId(); 163 return aiImageDO.getId();
171 } 164 }
172 165
  166 +
  167 +
173 @Override 168 @Override
174 public void deleteImageMy(Long id, Long userId) { 169 public void deleteImageMy(Long id, Long userId) {
175 // 1. 校验是否存在 170 // 1. 校验是否存在
@@ -182,7 +177,7 @@ public class AiImageServiceImpl implements AiImageService { @@ -182,7 +177,7 @@ public class AiImageServiceImpl implements AiImageService {
182 } 177 }
183 178
184 @Override 179 @Override
185 - public Boolean midjourneyNotify(Long loginUserId, MidjourneyNotifyReqVO notifyReqVO) { 180 + public Boolean midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO) {
186 // 1、根据 job id 查询关联的 image 181 // 1、根据 job id 查询关联的 image
187 AiImageDO image = imageMapper.selectByJobId(notifyReqVO.getId()); 182 AiImageDO image = imageMapper.selectByJobId(notifyReqVO.getId());
188 if (image == null) { 183 if (image == null) {
@@ -220,6 +215,34 @@ public class AiImageServiceImpl implements AiImageService { @@ -220,6 +215,34 @@ public class AiImageServiceImpl implements AiImageService {
220 return true; 215 return true;
221 } 216 }
222 217
  218 + @Override
  219 + @Transactional(rollbackFor = Exception.class)
  220 + public Boolean midjourneyAction(Long loginUserId, Long imageId, String customId) {
  221 + // 1、检查 image
  222 + AiImageDO aiImageDO = validateImageExists(imageId);
  223 + // 2、检查 customId
  224 + if (!validateCustomId(customId, aiImageDO.getButtons())) {
  225 + throw exception(AI_IMAGE_CUSTOM_ID_NOT_EXISTS);
  226 + }
  227 + // 3、调用 midjourney proxy
  228 + midjourneyProxyClient.action(
  229 + new MidjourneyActionReqVO()
  230 + .setCustomId(customId)
  231 + .setTaskId(aiImageDO.getJobId())
  232 + .setNotifyHook(midjourneyNotifyUrl)
  233 + );
  234 + return Boolean.TRUE;
  235 + }
  236 +
  237 + private static boolean validateCustomId(String customId, List<MidjourneyNotifyReqVO.Button> buttons) {
  238 + for (MidjourneyNotifyReqVO.Button button : buttons) {
  239 + if (button.getCustomId().equals(customId)) {
  240 + return true;
  241 + }
  242 + }
  243 + return false;
  244 + }
  245 +
223 private AiImageDO validateImageExists(Long id) { 246 private AiImageDO validateImageExists(Long id) {
224 AiImageDO image = imageMapper.selectById(id); 247 AiImageDO image = imageMapper.selectById(id);
225 if (image == null) { 248 if (image == null) {
@@ -237,4 +260,25 @@ public class AiImageServiceImpl implements AiImageService { @@ -237,4 +260,25 @@ public class AiImageServiceImpl implements AiImageService {
237 return SpringUtil.getBean(getClass()); 260 return SpringUtil.getBean(getClass());
238 } 261 }
239 262
  263 + /**
  264 + * 构建 Midjourney 自定义参数
  265 + *
  266 + * @param width
  267 + * @param height
  268 + * @param version
  269 + * @param model
  270 + * @return
  271 + */
  272 + private String buildParams(String width, String height, String version, MidjourneyModelEnum model) {
  273 + StringBuilder params = new StringBuilder();
  274 + // --ar 来设置尺寸
  275 + params.append(String.format(" --ar %s:%s ", width, height));
  276 + // --v 版本
  277 + params.append(String.format(" --v %s ", version));
  278 + // --niji 模型
  279 + if (MidjourneyModelEnum.NIJI == model) {
  280 + params.append(" --niji ");
  281 + }
  282 + return params.toString();
  283 + }
240 } 284 }
yudao-server/src/main/resources/application-local.yaml
@@ -80,7 +80,8 @@ server: @@ -80,7 +80,8 @@ server:
80 ai: 80 ai:
81 midjourney-proxy: 81 midjourney-proxy:
82 url: https://api.holdai.top/mj 82 url: https://api.holdai.top/mj
83 - notifyUrl: http://7b1aada4.r26.cpolar.top/admin-api/ai/image/midjourney-notify 83 + notifyUrl: http://61d61685.r21.cpolar.top/admin-api/ai/image/midjourney-notify
  84 + key: sk-c3qxUCVKsPfdQiYU8440E3Fc8dE5424d9cB124A4Ee2489E3
84 85
85 86
86 --- #################### 定时任务相关配置 #################### 87 --- #################### 定时任务相关配置 ####################