Commit 6856f5f192d4c726a67ef3b45c9e744a49c735ae
1 parent
05635031
【新增】AI:进一步统一 DALL、SD 的绘制实现
Showing
34 changed files
with
220 additions
and
587 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/AiCommonConstants.java deleted
| 1 | -package cn.iocoder.yudao.module.ai; | |
| 2 | - | |
| 3 | -/** | |
| 4 | - * ai 常用的常量 | |
| 5 | - * | |
| 6 | - * @author fansili | |
| 7 | - * @time 2024/5/7 09:29 | |
| 8 | - * @since 1.0 | |
| 9 | - */ | |
| 10 | -public class AiCommonConstants { | |
| 11 | - | |
| 12 | - /** | |
| 13 | - * 绘画 request - style | |
| 14 | - */ | |
| 15 | - public static final String DRAW_REQ_KEY_STYLE = "style"; | |
| 16 | - /** | |
| 17 | - * dall size - 模板(1024x1024) | |
| 18 | - */ | |
| 19 | - public static final String DALL_SIZE_TEMPLATE = "%sx%s"; | |
| 20 | - | |
| 21 | -} |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/api/package-info.java
0 → 100644
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatConversationTypeEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.enums; | |
| 2 | - | |
| 3 | -import lombok.AllArgsConstructor; | |
| 4 | -import lombok.Getter; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * 对话类型 | |
| 8 | - * 创建对话、继续对话 | |
| 9 | - * | |
| 10 | - * @author fansili | |
| 11 | - * @time 2024/4/14 18:15 | |
| 12 | - * @since 1.0 | |
| 13 | - */ | |
| 14 | -@AllArgsConstructor | |
| 15 | -@Getter | |
| 16 | -public enum AiChatConversationTypeEnum { | |
| 17 | - | |
| 18 | - // roleChat、userChat | |
| 19 | - ROLE_CHAT("roleChat", "角色对话"), | |
| 20 | - USER_CHAT("userChat", "用户对话"), | |
| 21 | - | |
| 22 | - ; | |
| 23 | - | |
| 24 | - private String type; | |
| 25 | - | |
| 26 | - private String name; | |
| 27 | - | |
| 28 | - public static AiChatConversationTypeEnum valueOfType(String type) { | |
| 29 | - for (AiChatConversationTypeEnum itemEnum : AiChatConversationTypeEnum.values()) { | |
| 30 | - if (itemEnum.getType().equals(type)) { | |
| 31 | - return itemEnum; | |
| 32 | - } | |
| 33 | - } | |
| 34 | - throw new IllegalArgumentException("Invalid MessageType value: " + type); | |
| 35 | - } | |
| 36 | - | |
| 37 | -} |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiChatRoleCategoryEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.enums; | |
| 2 | - | |
| 3 | -import lombok.AllArgsConstructor; | |
| 4 | -import lombok.Getter; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * 聊天role 分类 | |
| 8 | - * | |
| 9 | - * @author fansili | |
| 10 | - * @time 2024/4/24 16:41 | |
| 11 | - * @since 1.0 | |
| 12 | - */ | |
| 13 | -@AllArgsConstructor | |
| 14 | -@Getter | |
| 15 | -public enum AiChatRoleCategoryEnum { | |
| 16 | - | |
| 17 | - WRITING("writing", "写作"), | |
| 18 | - | |
| 19 | - ENTERTAINMENT("entertainment", "娱乐"), | |
| 20 | - | |
| 21 | - ; | |
| 22 | - | |
| 23 | - | |
| 24 | - private String category; | |
| 25 | - | |
| 26 | - private String name; | |
| 27 | - | |
| 28 | - | |
| 29 | - public static AiChatRoleCategoryEnum valueOfCategory(String category) { | |
| 30 | - for (AiChatRoleCategoryEnum itemEnum : AiChatRoleCategoryEnum.values()) { | |
| 31 | - if (itemEnum.getCategory().equals(category)) { | |
| 32 | - return itemEnum; | |
| 33 | - } | |
| 34 | - } | |
| 35 | - throw new IllegalArgumentException("Invalid MessageType value: " + category); | |
| 36 | - } | |
| 37 | -} |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiImagePublicStatusEnum.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.enums; | |
| 2 | - | |
| 3 | -import lombok.AllArgsConstructor; | |
| 4 | -import lombok.Getter; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * ai绘画 public 状态 | |
| 8 | - * | |
| 9 | - * @author fansili | |
| 10 | - * @time 2024/4/28 17:05 | |
| 11 | - * @since 1.0 | |
| 12 | - */ | |
| 13 | -@AllArgsConstructor | |
| 14 | -@Getter | |
| 15 | -public enum AiImagePublicStatusEnum { | |
| 16 | - | |
| 17 | - PRIVATE("private", "私有"), | |
| 18 | - PUBLIC("public", "公开"), | |
| 19 | - | |
| 20 | - ; | |
| 21 | - | |
| 22 | - // TODO @fan:final 一下 | |
| 23 | - private final String status; | |
| 24 | - | |
| 25 | - private final String name; | |
| 26 | - | |
| 27 | - | |
| 28 | - public static AiImagePublicStatusEnum valueOfStatus(String status) { | |
| 29 | - for (AiImagePublicStatusEnum itemEnum : AiImagePublicStatusEnum.values()) { | |
| 30 | - if (itemEnum.getStatus().equals(status)) { | |
| 31 | - return itemEnum; | |
| 32 | - } | |
| 33 | - } | |
| 34 | - throw new IllegalArgumentException("Invalid MessageType value: " + status); | |
| 35 | - } | |
| 36 | -} |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/ErrorCodeConstants.java renamed to yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiImageStatusEnum.java renamed to yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/image/AiImageStatusEnum.java
| 1 | -package cn.iocoder.yudao.module.ai.enums; | |
| 1 | +package cn.iocoder.yudao.module.ai.enums.image; | |
| 2 | 2 | |
| 3 | 3 | import lombok.AllArgsConstructor; |
| 4 | 4 | import lombok.Getter; |
| ... | ... | @@ -13,7 +13,7 @@ import lombok.Getter; |
| 13 | 13 | public enum AiImageStatusEnum { |
| 14 | 14 | |
| 15 | 15 | IN_PROGRESS("10", "进行中"), |
| 16 | - COMPLETE("20", "完成"), | |
| 16 | + SUCCESS("20", "完成"), | |
| 17 | 17 | FAIL("30", "失败"); |
| 18 | 18 | |
| 19 | 19 | /** | ... | ... |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java renamed to yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java
yudao-module-ai/yudao-module-ai-biz/src/main/resources/http/image.http renamed to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.http
| 1 | 1 | |
| 2 | -### chat dallDrawing | |
| 3 | - | |
| 4 | -POST {{baseUrl}}/admin-api/ai/image/dallDrawing | |
| 2 | +### 生成图片:OpenAI(DALL) | |
| 3 | +POST {{baseUrl}}/ai/image/draw | |
| 5 | 4 | Content-Type: application/json |
| 6 | 5 | Authorization: {{token}} |
| 7 | 6 | |
| 8 | 7 | { |
| 9 | - "modal": "dall-e-3", | |
| 10 | - "size": "1024x1024", | |
| 11 | - "style": "vivid", | |
| 12 | - "prompt": "中国长城" | |
| 8 | + "platform": "OpenAI", | |
| 9 | + "prompt": "可爱的小喵星人", | |
| 10 | + "model": "dall-e-3", | |
| 11 | + "height": "1024", | |
| 12 | + "width": "1024", | |
| 13 | + "options": { | |
| 14 | + "style": "vivid" | |
| 15 | + } | |
| 13 | 16 | } |
| 14 | 17 | |
| 18 | +### 生成图片:StableDiffusion | |
| 19 | +POST {{baseUrl}}/ai/image/draw | |
| 20 | +Content-Type: application/json | |
| 21 | +Authorization: {{token}} | |
| 22 | + | |
| 23 | +{ | |
| 24 | + "platform": "StableDiffusion", | |
| 25 | + "prompt": "中国长城", | |
| 26 | + "model": "stable-diffusion-v1-6", | |
| 27 | + "height": "1024", | |
| 28 | + "width": "1024", | |
| 29 | + "style": "vivid" | |
| 30 | +} | |
| 15 | 31 | |
| 16 | 32 | ### chat midjourney |
| 17 | 33 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java
| ... | ... | @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 6 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 8 | 8 | import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; |
| 9 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; | |
| 9 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; | |
| 10 | 10 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; |
| 11 | 11 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageRespVO; |
| 12 | 12 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| ... | ... | @@ -48,13 +48,10 @@ public class AiImageController { |
| 48 | 48 | return success(BeanUtils.toBean(image, AiImageRespVO.class)); |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | - // TODO @fan:建议把 dallDrawing、midjourney 融合成一个 draw 接口,异步绘制;然后返回一个 id 给前端;前端通过 get 接口轮询,直到获取到生成成功 | |
| 52 | - // TODO @芋艿: 参数差异较大 | |
| 53 | - // TODO @fan:直接参数平铺?写好注释,要么? | |
| 54 | - @Operation(summary = "dall2/dall3绘画", description = "openAi dall3是付费的!") | |
| 55 | - @PostMapping("/dall") | |
| 56 | - public CommonResult<Long> dall(@Validated @RequestBody AiImageDallReqVO req) { | |
| 57 | - return success(imageService.dall(getLoginUserId(), req)); | |
| 51 | + @Operation(summary = "生成图片") | |
| 52 | + @PostMapping("/draw") | |
| 53 | + public CommonResult<Long> drawImage(@Validated @RequestBody AiImageDrawReqVO drawReqVO) { | |
| 54 | + return success(imageService.drawImage(getLoginUserId(), drawReqVO)); | |
| 58 | 55 | } |
| 59 | 56 | |
| 60 | 57 | @Operation(summary = "删除【我的】绘画记录") |
| ... | ... | @@ -73,11 +70,11 @@ public class AiImageController { |
| 73 | 70 | return success(imageService.midjourneyImagine(getLoginUserId(), req)); |
| 74 | 71 | } |
| 75 | 72 | |
| 76 | - // TODO @fan:可以考虑,复用 AiImageDallRespVO,统一成 AIImageRespVO | |
| 73 | + // TODO @芋艿:不拦截 | |
| 77 | 74 | @Operation(summary = "midjourney proxy - 回调通知") |
| 78 | 75 | @RequestMapping("/midjourney-notify") |
| 79 | 76 | public CommonResult<Boolean> midjourneyNotify(MidjourneyNotifyReqVO notifyReqVO) { |
| 80 | 77 | return success(imageService.midjourneyNotify(getLoginUserId(), notifyReqVO)); |
| 81 | 78 | } |
| 82 | 79 | |
| 83 | 80 | -} |
| 81 | +} | |
| 84 | 82 | \ No newline at end of file | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDallReqVO.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 jakarta.validation.constraints.Size; | |
| 6 | -import lombok.Data; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * dall2/dall2 绘画 | |
| 10 | - * | |
| 11 | - * @author fansili | |
| 12 | - * @time 2024/4/25 16:24 | |
| 13 | - * @since 1.0 | |
| 14 | - */ | |
| 15 | -@Data | |
| 16 | -public class AiImageDallReqVO { | |
| 17 | - | |
| 18 | - @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 19 | - private String platform; // 参见 AiPlatformEnum 枚举 | |
| 20 | - | |
| 21 | - @Schema(description = "提示词") | |
| 22 | - @NotNull(message = "提示词不能为空!") | |
| 23 | - @Size(max = 1200, message = "提示词最大1200") | |
| 24 | - private String prompt; | |
| 25 | - | |
| 26 | - @Schema(description = "模型(dall2、dall3)") | |
| 27 | - @NotNull(message = "模型不能为空") | |
| 28 | - private String model; | |
| 29 | - | |
| 30 | - @Schema(description = "图像生成的风格。可为vivid(生动)或natural(自然)") | |
| 31 | - private String style; | |
| 32 | - | |
| 33 | - @Schema(description = "图片高度。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。") | |
| 34 | - @NotNull(message = "图片高度不能为空!") | |
| 35 | - private Integer height; | |
| 36 | - | |
| 37 | - @Schema(description = "图片宽度。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。") | |
| 38 | - @NotNull(message = "图片宽度不能为空!") | |
| 39 | - private Integer width; | |
| 40 | - | |
| 41 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageDrawReqVO.java
0 → 100644
| 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.NotEmpty; | |
| 5 | +import jakarta.validation.constraints.NotNull; | |
| 6 | +import jakarta.validation.constraints.Size; | |
| 7 | +import lombok.Data; | |
| 8 | +import org.springframework.ai.openai.OpenAiImageOptions; | |
| 9 | +import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; | |
| 10 | + | |
| 11 | +import java.util.Map; | |
| 12 | + | |
| 13 | +@Schema(description = "管理后台 - 绘画 Request VO") | |
| 14 | +@Data | |
| 15 | +public class AiImageDrawReqVO { | |
| 16 | + | |
| 17 | + @Schema(description = "模型平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") | |
| 18 | + private String platform; // 参见 AiPlatformEnum 枚举 | |
| 19 | + | |
| 20 | + @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "画一个长城") | |
| 21 | + @NotEmpty(message = "提示词不能为空") | |
| 22 | + @Size(max = 1200, message = "提示词最大 1200") | |
| 23 | + private String prompt; | |
| 24 | + | |
| 25 | + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6") | |
| 26 | + @NotEmpty(message = "模型不能为空") | |
| 27 | + private String model; | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 1. dall-e-2 模型:256x256、512x512、1024x1024 | |
| 31 | + * 2. dall-e-3 模型:1024x1024, 1792x1024, 或 1024x1792 | |
| 32 | + */ | |
| 33 | + @Schema(description = "图片高度") | |
| 34 | + @NotNull(message = "图片高度不能为空") | |
| 35 | + private Integer height; | |
| 36 | + | |
| 37 | + @Schema(description = "图片宽度") | |
| 38 | + @NotNull(message = "图片宽度不能为空") | |
| 39 | + private Integer width; | |
| 40 | + | |
| 41 | + // ========== 各平台绘画的拓展参数 ========== | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 绘制参数,不同 platform 的不同参数 | |
| 45 | + * | |
| 46 | + * 1. {@link OpenAiImageOptions} | |
| 47 | + * 2. {@link StabilityAiImageOptions} | |
| 48 | + */ | |
| 49 | + @Schema(description = "绘制参数") | |
| 50 | + private Map<String, String> options; | |
| 51 | + | |
| 52 | +} | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageMidjourneyOperateReqVO.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 | -/** | |
| 9 | - * midjourney req | |
| 10 | - * | |
| 11 | - * @author fansili | |
| 12 | - * @time 2024/4/28 17:42 | |
| 13 | - * @since 1.0 | |
| 14 | - */ | |
| 15 | -@Data | |
| 16 | -@Accessors(chain = true) | |
| 17 | -public class AiImageMidjourneyOperateReqVO { | |
| 18 | - | |
| 19 | - | |
| 20 | - @NotNull(message = "图片编号不能为空") | |
| 21 | - @Schema(description = "编号") | |
| 22 | - private Long id; | |
| 23 | - | |
| 24 | - @NotNull(message = "消息编号不能为空") | |
| 25 | - @Schema(description = "消息编号") | |
| 26 | - private String messageId; | |
| 27 | - | |
| 28 | - @NotNull(message = "操作编号不能为空") | |
| 29 | - @Schema(description = "操作编号") | |
| 30 | - private String operateId; | |
| 31 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImageRespVO.java
| ... | ... | @@ -5,63 +5,44 @@ import lombok.Data; |
| 5 | 5 | |
| 6 | 6 | import java.util.Map; |
| 7 | 7 | |
| 8 | -// TODO @芋艿:完善 swagger 注解 | |
| 8 | +@Schema(description = "管理后台 - 绘画 Response VO") | |
| 9 | 9 | @Data |
| 10 | 10 | public class AiImageRespVO { |
| 11 | 11 | |
| 12 | - @Schema(description = "id编号", example = "1") | |
| 12 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 13 | 13 | private Long id; |
| 14 | 14 | |
| 15 | - @Schema(description = "用户id", example = "1") | |
| 15 | + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 16 | 16 | private Long userId; |
| 17 | 17 | |
| 18 | - @Schema(description = "提示词", example = "南极的小企鹅") | |
| 19 | - private String prompt; | |
| 20 | - | |
| 21 | - @Schema(description = "平台", example = "openai") | |
| 22 | - private String platform; | |
| 18 | + @Schema(description = "平台", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") | |
| 19 | + private String platform; // 参见 AiPlatformEnum 枚举 | |
| 23 | 20 | |
| 24 | - @Schema(description = "模型", example = "dall2") | |
| 21 | + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "stable-diffusion-v1-6") | |
| 25 | 22 | private String model; |
| 26 | 23 | |
| 27 | - @Schema(description = "图片宽度", example = "1024") | |
| 28 | - private String width; | |
| 24 | + @Schema(description = "提示词", requiredMode = Schema.RequiredMode.REQUIRED, example = "南极的小企鹅") | |
| 25 | + private String prompt; | |
| 29 | 26 | |
| 30 | - @Schema(description = "图片高度", example = "1024") | |
| 31 | - private String height; | |
| 27 | + @Schema(description = "图片宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 28 | + private Integer width; | |
| 32 | 29 | |
| 33 | - @Schema(description = "绘画状态:10 进行中、20 绘画完成、30 绘画失败", example = "10") | |
| 34 | - private String status; | |
| 30 | + @Schema(description = "图片高度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 31 | + private Integer height; | |
| 35 | 32 | |
| 36 | - @Schema(description = "是否发布", example = "public") | |
| 37 | - private String publicStatus; | |
| 33 | + @Schema(description = "绘画状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") | |
| 34 | + private Integer status; | |
| 38 | 35 | |
| 39 | - @Schema(description = "图片地址(自己服务器)", example = "http://") | |
| 40 | - private String picUrl; | |
| 36 | + @Schema(description = "是否发布", requiredMode = Schema.RequiredMode.REQUIRED, example = "public") | |
| 37 | + private Boolean publicStatus; | |
| 41 | 38 | |
| 42 | - @Schema(description = "绘画图片地址(绘画好的服务器)", example = "http://") | |
| 43 | - private String originalPicUrl; | |
| 39 | + @Schema(description = "图片地址", example = "https://www.iocoder.cn/1.png") | |
| 40 | + private String picUrl; | |
| 44 | 41 | |
| 45 | 42 | @Schema(description = "绘画错误信息", example = "图片错误信息") |
| 46 | 43 | private String errorMessage; |
| 47 | 44 | |
| 48 | - // ============ 绘画请求参数 | |
| 49 | - | |
| 50 | - // todo @fan:下面的 style、mjNonceId 直接就不用注释啦,直接去看 DO 完事哈 | |
| 51 | - | |
| 52 | - /** | |
| 53 | - * - style | |
| 54 | - */ | |
| 55 | - @Schema(description = "绘画请求参数") | |
| 56 | - private Map<String, Object> drawRequest; | |
| 57 | - | |
| 58 | - /** | |
| 59 | - * - mjNonceId | |
| 60 | - * - mjOperationId | |
| 61 | - * - mjOperationName | |
| 62 | - * - mjOperations | |
| 63 | - */ | |
| 64 | - @Schema(description = "绘画请求响应参数") | |
| 65 | - private Map<String, Object> drawResponse; | |
| 45 | + @Schema(description = "绘制参数") | |
| 46 | + private Map<String, String> options; | |
| 66 | 47 | |
| 67 | 48 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatConversationDO.java
| ... | ... | @@ -3,14 +3,13 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.chat; |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 4 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 5 | 5 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 6 | -import cn.iocoder.yudao.module.ai.enums.AiModelEnum; | |
| 6 | +import cn.iocoder.yudao.module.ai.enums.model.AiModelEnum; | |
| 7 | 7 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 8 | 8 | import com.baomidou.mybatisplus.annotation.TableId; |
| 9 | 9 | import com.baomidou.mybatisplus.annotation.TableName; |
| 10 | 10 | import lombok.*; |
| 11 | 11 | |
| 12 | 12 | import java.time.LocalDateTime; |
| 13 | -import java.util.Date; | |
| 14 | 13 | |
| 15 | 14 | /** |
| 16 | 15 | * AI Chat 对话 DO | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
| ... | ... | @@ -5,7 +5,7 @@ import org.springframework.ai.chat.messages.MessageType; |
| 5 | 5 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 6 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; |
| 7 | 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 8 | -import cn.iocoder.yudao.module.ai.enums.AiModelEnum; | |
| 8 | +import cn.iocoder.yudao.module.ai.enums.model.AiModelEnum; | |
| 9 | 9 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 10 | 10 | import com.baomidou.mybatisplus.annotation.TableName; |
| 11 | 11 | import lombok.*; | ... | ... |
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 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | +import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatModelDO; | |
| 5 | +import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; | |
| 6 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 4 | 7 | import com.baomidou.mybatisplus.annotation.IdType; |
| 5 | 8 | import com.baomidou.mybatisplus.annotation.TableField; |
| 6 | 9 | import com.baomidou.mybatisplus.annotation.TableId; |
| 7 | 10 | import com.baomidou.mybatisplus.annotation.TableName; |
| 8 | 11 | import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
| 9 | -import io.swagger.v3.oas.annotations.media.Schema; | |
| 10 | 12 | import lombok.Data; |
| 13 | +import org.springframework.ai.openai.OpenAiImageOptions; | |
| 14 | +import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; | |
| 11 | 15 | |
| 12 | 16 | import java.util.Map; |
| 13 | 17 | |
| ... | ... | @@ -16,70 +20,86 @@ import java.util.Map; |
| 16 | 20 | * |
| 17 | 21 | * @author fansili |
| 18 | 22 | */ |
| 19 | -@TableName("ai_image") | |
| 23 | +@TableName(value = "ai_image", autoResultMap = true) | |
| 20 | 24 | @Data |
| 21 | 25 | public class AiImageDO extends BaseDO { |
| 22 | 26 | |
| 23 | 27 | // TODO @fan:1)使用 java 注释哈,不要注解。2)关联、枚举字段,要关联到对应类,参考 AiChatMessageDO 的注释 |
| 24 | 28 | |
| 29 | + /** | |
| 30 | + * 编号 | |
| 31 | + */ | |
| 25 | 32 | @TableId(type = IdType.AUTO) |
| 26 | 33 | private Long id; |
| 27 | 34 | |
| 28 | - @Schema(description = "用户编号") | |
| 35 | + /** | |
| 36 | + * 用户编号 | |
| 37 | + * | |
| 38 | + * 关联 {@link AdminUserRespDTO#getId()} | |
| 39 | + */ | |
| 29 | 40 | private Long userId; |
| 30 | 41 | |
| 31 | - @Schema(description = "midjourney proxy 关联的 job id") | |
| 32 | - private String jobId; | |
| 33 | - | |
| 34 | - @Schema(description = "提示词") | |
| 42 | + /** | |
| 43 | + * 提示词 | |
| 44 | + */ | |
| 35 | 45 | private String prompt; |
| 36 | 46 | |
| 37 | - @Schema(description = "平台") | |
| 47 | + /** | |
| 48 | + * 平台 | |
| 49 | + * | |
| 50 | + * 枚举 {@link cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum} | |
| 51 | + */ | |
| 38 | 52 | private String platform; |
| 39 | - | |
| 40 | - @Schema(description = "模型") | |
| 53 | + /** | |
| 54 | + * 模型 | |
| 55 | + * | |
| 56 | + * 冗余 {@link AiChatModelDO#getModel()} | |
| 57 | + */ | |
| 41 | 58 | private String model; |
| 42 | 59 | |
| 43 | - @Schema(description = "图片宽度") | |
| 60 | + /** | |
| 61 | + * 图片宽度 | |
| 62 | + */ | |
| 44 | 63 | private Integer width; |
| 45 | - | |
| 46 | - @Schema(description = "图片高度") | |
| 64 | + /** | |
| 65 | + * 图片高度 | |
| 66 | + */ | |
| 47 | 67 | private Integer height; |
| 48 | 68 | |
| 49 | 69 | // TODO @fan:这种就注释绘画状态,然后枚举类关联下就好啦 |
| 50 | - @Schema(description = "绘画状态:提交、排队、绘画中、绘画完成、绘画失败") | |
| 70 | + /** | |
| 71 | + * 生成状态 | |
| 72 | + * | |
| 73 | + * 枚举 {@link AiImageStatusEnum} | |
| 74 | + */ | |
| 51 | 75 | private String status; |
| 52 | 76 | |
| 53 | - @Schema(description = "是否发布") | |
| 54 | - private String publicStatus; | |
| 55 | - | |
| 56 | - @Schema(description = "图片地址(自己服务器)") | |
| 77 | + /** | |
| 78 | + * 图片地址 | |
| 79 | + */ | |
| 57 | 80 | private String picUrl; |
| 58 | - | |
| 59 | - // TODO @芋艿:可能要删除掉 | |
| 60 | - @Schema(description = "绘画图片地址(绘画好的服务器)") | |
| 61 | - private String originalPicUrl; | |
| 62 | - | |
| 63 | - // ============ 绘画请求参数 ============ | |
| 64 | - | |
| 65 | 81 | /** |
| 66 | - * - style | |
| 82 | + * 是否公开 | |
| 67 | 83 | */ |
| 68 | - @Schema(description = "绘画请求参数") | |
| 69 | - @TableField(typeHandler = JacksonTypeHandler.class) | |
| 70 | - private Map<String, Object> drawRequest; | |
| 84 | + private Boolean publicStatus; | |
| 71 | 85 | |
| 72 | 86 | /** |
| 73 | - * - mjNonceId | |
| 74 | - * - mjOperationId | |
| 75 | - * - mjOperationName | |
| 76 | - * - mjOperations | |
| 87 | + * 绘制参数,不同 platform 的不同参数 | |
| 88 | + * | |
| 89 | + * 1. {@link OpenAiImageOptions} | |
| 90 | + * 2. {@link StabilityAiImageOptions} | |
| 77 | 91 | */ |
| 78 | - @Schema(description = "绘画请求响应参数") | |
| 79 | 92 | @TableField(typeHandler = JacksonTypeHandler.class) |
| 80 | - private Map<String, Object> drawResponse; | |
| 93 | + private Map<String, String> options; | |
| 81 | 94 | |
| 82 | - @Schema(description = "绘画错误信息") | |
| 95 | + // TODO @芋艿:再瞅瞅 | |
| 96 | + /** | |
| 97 | + * midjourney proxy 关联的 job id | |
| 98 | + */ | |
| 99 | + private String jobId; | |
| 100 | + /** | |
| 101 | + * 绘画错误信息 | |
| 102 | + */ | |
| 83 | 103 | private String errorMessage; |
| 84 | 104 | |
| 85 | 105 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatConversationServiceImpl.java
| ... | ... | @@ -25,8 +25,8 @@ import java.util.List; |
| 25 | 25 | |
| 26 | 26 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 27 | 27 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 28 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_MODEL_ERROR; | |
| 29 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; | |
| 28 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_MODEL_ERROR; | |
| 29 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; | |
| 30 | 30 | |
| 31 | 31 | /** |
| 32 | 32 | * AI 聊天对话 Service 实现类 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
| ... | ... | @@ -8,7 +8,7 @@ import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; |
| 8 | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | 10 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | -import cn.iocoder.yudao.module.ai.ErrorCodeConstants; | |
| 11 | +import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; | |
| 12 | 12 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; |
| 13 | 13 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; |
| 14 | 14 | import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageSendReqVO; |
| ... | ... | @@ -39,8 +39,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU |
| 39 | 39 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error; |
| 40 | 40 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 41 | 41 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 42 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST; | |
| 43 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; | |
| 42 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_CHAT_MESSAGE_NOT_EXIST; | |
| 43 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_CONVERSATION_NOT_EXISTS; | |
| 44 | 44 | |
| 45 | 45 | /** |
| 46 | 46 | * AI 聊天消息 Service 实现类 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.service.image; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; |
| 6 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; | |
| 6 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; | |
| 7 | 7 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; |
| 8 | 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 9 | 9 | |
| ... | ... | @@ -32,12 +32,12 @@ public interface AiImageService { |
| 32 | 32 | AiImageDO getImage(Long id); |
| 33 | 33 | |
| 34 | 34 | /** |
| 35 | - * ai绘画 - dall2/dall3 绘画 | |
| 35 | + * 绘制图片 | |
| 36 | 36 | * |
| 37 | - * @param loginUserId | |
| 38 | - * @param req | |
| 37 | + * @param userId 用户编号 | |
| 38 | + * @param drawReqVO 绘制请求 | |
| 39 | 39 | */ |
| 40 | - Long dall(Long loginUserId, AiImageDallReqVO req); | |
| 40 | + Long drawImage(Long userId, AiImageDrawReqVO drawReqVO); | |
| 41 | 41 | |
| 42 | 42 | /** |
| 43 | 43 | * midjourney 图片生成 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.service.image; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.bean.BeanUtil; | |
| 4 | 3 | import cn.hutool.core.codec.Base64; |
| 4 | +import cn.hutool.core.map.MapUtil; | |
| 5 | 5 | import cn.hutool.core.util.ObjUtil; |
| 6 | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | 7 | import cn.hutool.extra.spring.SpringUtil; |
| 8 | 8 | import cn.hutool.http.HttpUtil; |
| 9 | 9 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 10 | -import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; | |
| 11 | -import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum; | |
| 12 | 10 | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; |
| 13 | 11 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 14 | 12 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 15 | 13 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 16 | -import cn.iocoder.yudao.module.ai.AiCommonConstants; | |
| 17 | 14 | import cn.iocoder.yudao.module.ai.client.MidjourneyProxyClient; |
| 18 | 15 | import cn.iocoder.yudao.module.ai.client.enums.MidjourneyModelEnum; |
| 19 | 16 | import cn.iocoder.yudao.module.ai.client.enums.MidjourneySubmitCodeEnum; |
| ... | ... | @@ -21,17 +18,18 @@ import cn.iocoder.yudao.module.ai.client.enums.MidjourneyTaskStatusEnum; |
| 21 | 18 | import cn.iocoder.yudao.module.ai.client.vo.MidjourneyImagineReqVO; |
| 22 | 19 | import cn.iocoder.yudao.module.ai.client.vo.MidjourneyNotifyReqVO; |
| 23 | 20 | import cn.iocoder.yudao.module.ai.client.vo.MidjourneySubmitRespVO; |
| 24 | -import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDallReqVO; | |
| 21 | +import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; | |
| 25 | 22 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyImagineReqVO; |
| 26 | 23 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 27 | 24 | import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; |
| 28 | -import cn.iocoder.yudao.module.ai.enums.AiImagePublicStatusEnum; | |
| 29 | -import cn.iocoder.yudao.module.ai.enums.AiImageStatusEnum; | |
| 25 | +import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; | |
| 30 | 26 | import cn.iocoder.yudao.module.infra.api.file.FileApi; |
| 31 | -import com.google.common.collect.ImmutableMap; | |
| 32 | 27 | import jakarta.annotation.Resource; |
| 33 | 28 | import lombok.extern.slf4j.Slf4j; |
| 34 | -import org.springframework.ai.image.*; | |
| 29 | +import org.springframework.ai.image.ImageClient; | |
| 30 | +import org.springframework.ai.image.ImageOptions; | |
| 31 | +import org.springframework.ai.image.ImagePrompt; | |
| 32 | +import org.springframework.ai.image.ImageResponse; | |
| 35 | 33 | import org.springframework.ai.openai.OpenAiImageOptions; |
| 36 | 34 | import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; |
| 37 | 35 | import org.springframework.beans.factory.annotation.Autowired; |
| ... | ... | @@ -41,7 +39,7 @@ import org.springframework.stereotype.Service; |
| 41 | 39 | import org.springframework.transaction.annotation.Transactional; |
| 42 | 40 | |
| 43 | 41 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 44 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.AI_IMAGE_NOT_EXISTS; | |
| 42 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.AI_IMAGE_NOT_EXISTS; | |
| 45 | 43 | |
| 46 | 44 | /** |
| 47 | 45 | * AI 绘画 Service 实现类 |
| ... | ... | @@ -78,22 +76,18 @@ public class AiImageServiceImpl implements AiImageService { |
| 78 | 76 | } |
| 79 | 77 | |
| 80 | 78 | @Override |
| 81 | - public Long dall(Long userId, AiImageDallReqVO req) { | |
| 82 | - req.setPlatform("dall"); // TODO 芋艿:临时写死 | |
| 79 | + public Long drawImage(Long userId, AiImageDrawReqVO drawReqVO) { | |
| 83 | 80 | // 1. 保存数据库 |
| 84 | - AiImageDO image = BeanUtils.toBean(req, AiImageDO.class) | |
| 85 | - .setUserId(userId).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()) | |
| 86 | - .setWidth(req.getWidth()).setHeight(req.getHeight()) | |
| 87 | - .setDrawRequest(ImmutableMap.of(AiCommonConstants.DRAW_REQ_KEY_STYLE, req.getStyle())) | |
| 88 | - .setPublicStatus(AiImagePublicStatusEnum.PRIVATE.getStatus()); | |
| 81 | + AiImageDO image = BeanUtils.toBean(drawReqVO, AiImageDO.class).setUserId(userId).setPublicStatus(false) | |
| 82 | + .setWidth(drawReqVO.getWidth()).setHeight(drawReqVO.getHeight()).setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); | |
| 89 | 83 | imageMapper.insert(image); |
| 90 | - // 2. 异步绘制,后续前端通过返回的 id 进行伦旭 | |
| 91 | - getSelf().doDall(image, req); | |
| 84 | + // 2. 异步绘制,后续前端通过返回的 id 进行轮询结果 | |
| 85 | + getSelf().doDall(image, drawReqVO); | |
| 92 | 86 | return image.getId(); |
| 93 | 87 | } |
| 94 | 88 | |
| 95 | 89 | @Async |
| 96 | - public void doDall(AiImageDO image, AiImageDallReqVO req) { | |
| 90 | + public void doDall(AiImageDO image, AiImageDrawReqVO req) { | |
| 97 | 91 | try { |
| 98 | 92 | // 1.1 构建请求 |
| 99 | 93 | ImageOptions request = buildImageOptions(req); |
| ... | ... | @@ -106,7 +100,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 106 | 100 | String filePath = fileApi.createFile(fileContent); |
| 107 | 101 | |
| 108 | 102 | // 3. 更新数据库 |
| 109 | - imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(AiImageStatusEnum.COMPLETE.getStatus()) | |
| 103 | + imageMapper.updateById(new AiImageDO().setId(image.getId()).setStatus(AiImageStatusEnum.SUCCESS.getStatus()) | |
| 110 | 104 | .setPicUrl(filePath)); |
| 111 | 105 | } catch (Exception ex) { |
| 112 | 106 | log.error("[doDall][image({}) 生成异常]", image, ex); |
| ... | ... | @@ -115,30 +109,28 @@ public class AiImageServiceImpl implements AiImageService { |
| 115 | 109 | } |
| 116 | 110 | } |
| 117 | 111 | |
| 118 | - private static ImageOptions buildImageOptions(AiImageDallReqVO draw) { | |
| 119 | - if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.OPEN_AI_DALL.getPlatform())) { | |
| 120 | - OpenAiImageOptions request = new OpenAiImageOptions(); | |
| 121 | - request.setModel(OpenAiImageModelEnum.valueOfModel(draw.getModel()).getModel()); | |
| 122 | - request.setStyle(OpenAiImageStyleEnum.valueOfStyle(draw.getStyle()).getStyle()); | |
| 123 | - request.setSize(String.format(AiCommonConstants.DALL_SIZE_TEMPLATE, draw.getWidth(), draw.getHeight())); | |
| 124 | - request.setResponseFormat("b64_json"); | |
| 125 | - return request; | |
| 126 | - } else { | |
| 127 | - // https://platform.stability.ai/docs/api-reference#tag/Generate/paths/~1v2beta~1stable-image~1generate~1sd3/post | |
| 128 | - return StabilityAiImageOptions.builder().withModel(draw.getModel()) | |
| 112 | + private static ImageOptions buildImageOptions(AiImageDrawReqVO draw) { | |
| 113 | + if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) { | |
| 114 | + // https://platform.openai.com/docs/api-reference/images/create | |
| 115 | + return OpenAiImageOptions.builder().withModel(draw.getModel()) | |
| 129 | 116 | .withHeight(draw.getHeight()).withWidth(draw.getWidth()) |
| 117 | + .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格 | |
| 118 | + .withResponseFormat("b64_json") | |
| 119 | + .build(); | |
| 120 | + } else if (ObjUtil.equal(draw.getPlatform(), AiPlatformEnum.STABLE_DIFFUSION.getPlatform())) { | |
| 121 | + // https://platform.stability.ai/docs/api-reference#tag/Text-to-Image/operation/textToImage | |
| 122 | + return StabilityAiImageOptions.builder().withModel(draw.getModel()) | |
| 123 | + .withHeight(draw.getHeight()).withWidth(draw.getWidth()) // TODO @芋艿:各种参数 | |
| 130 | 124 | .build(); |
| 131 | 125 | } |
| 132 | -// return null; | |
| 126 | + throw new IllegalArgumentException("不支持的 AI 平台:" + draw.getPlatform()); | |
| 133 | 127 | } |
| 134 | 128 | |
| 135 | 129 | @Override |
| 136 | 130 | @Transactional(rollbackFor = Exception.class) |
| 137 | 131 | public Long midjourneyImagine(Long loginUserId, AiImageMidjourneyImagineReqVO req) { |
| 138 | - | |
| 139 | 132 | // 1、构建 AiImageDO |
| 140 | 133 | AiImageDO aiImageDO = new AiImageDO(); |
| 141 | - aiImageDO.setId(null); | |
| 142 | 134 | aiImageDO.setUserId(loginUserId); |
| 143 | 135 | aiImageDO.setPrompt(req.getPrompt()); |
| 144 | 136 | aiImageDO.setPlatform(AiPlatformEnum.MIDJOURNEY.getPlatform()); |
| ... | ... | @@ -147,12 +139,6 @@ public class AiImageServiceImpl implements AiImageService { |
| 147 | 139 | aiImageDO.setWidth(null); |
| 148 | 140 | aiImageDO.setHeight(null); |
| 149 | 141 | aiImageDO.setStatus(AiImageStatusEnum.IN_PROGRESS.getStatus()); |
| 150 | - aiImageDO.setPublicStatus(AiImagePublicStatusEnum.PRIVATE.getStatus()); | |
| 151 | - aiImageDO.setPicUrl(null); | |
| 152 | - aiImageDO.setOriginalPicUrl(null); | |
| 153 | - aiImageDO.setDrawRequest(null); | |
| 154 | - aiImageDO.setDrawResponse(null); | |
| 155 | - aiImageDO.setErrorMessage(null); | |
| 156 | 142 | |
| 157 | 143 | // 2、保存 image |
| 158 | 144 | imageMapper.insert(aiImageDO); |
| ... | ... | @@ -211,7 +197,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 211 | 197 | // |
| 212 | 198 | String imageStatus = null; |
| 213 | 199 | if (MidjourneyTaskStatusEnum.SUCCESS == notifyReqVO.getStatus()) { |
| 214 | - imageStatus = AiImageStatusEnum.COMPLETE.getStatus(); | |
| 200 | + imageStatus = AiImageStatusEnum.SUCCESS.getStatus(); | |
| 215 | 201 | } else if (MidjourneyTaskStatusEnum.FAILURE == notifyReqVO.getStatus()) { |
| 216 | 202 | imageStatus = AiImageStatusEnum.FAIL.getStatus(); |
| 217 | 203 | } |
| ... | ... | @@ -226,8 +212,7 @@ public class AiImageServiceImpl implements AiImageService { |
| 226 | 212 | .setId(image.getId()) |
| 227 | 213 | .setStatus(imageStatus) |
| 228 | 214 | .setPicUrl(filePath) |
| 229 | - .setOriginalPicUrl(notifyReqVO.getImageUrl()) | |
| 230 | - .setDrawResponse(BeanUtil.beanToMap(notifyReqVO)) | |
| 215 | +// .setOriginalPicUrl(notifyReqVO.getImageUrl()) TODO @fan:就不存原始的图片地址啦 | |
| 231 | 216 | ); |
| 232 | 217 | return true; |
| 233 | 218 | } | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/midjourneyHandler/YuDaoMidjourneyMessageHandler.java
| ... | ... | @@ -5,7 +5,7 @@ import cn.hutool.core.util.StrUtil; |
| 5 | 5 | import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageMidjourneyOperationsVO; |
| 6 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO; |
| 7 | 7 | import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; |
| 8 | -import cn.iocoder.yudao.module.ai.enums.AiImageStatusEnum; | |
| 8 | +import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; | |
| 9 | 9 | import com.alibaba.fastjson.JSON; |
| 10 | 10 | import lombok.AllArgsConstructor; |
| 11 | 11 | import lombok.extern.slf4j.Slf4j; |
| ... | ... | @@ -89,7 +89,7 @@ public class YuDaoMidjourneyMessageHandler implements MidjourneyMessageHandler { |
| 89 | 89 | AiImageStatusEnum drawingStatusEnum = null; |
| 90 | 90 | String generateStatus = midjourneyMessage.getGenerateStatus(); |
| 91 | 91 | if (MidjourneyGennerateStatusEnum.COMPLETED.getStatus().equals(generateStatus)) { |
| 92 | - drawingStatusEnum = AiImageStatusEnum.COMPLETE; | |
| 92 | + drawingStatusEnum = AiImageStatusEnum.SUCCESS; | |
| 93 | 93 | } else if (MidjourneyGennerateStatusEnum.IN_PROGRESS.getStatus().equals(generateStatus)) { |
| 94 | 94 | drawingStatusEnum = AiImageStatusEnum.IN_PROGRESS; |
| 95 | 95 | } |
| ... | ... | @@ -101,7 +101,6 @@ public class YuDaoMidjourneyMessageHandler implements MidjourneyMessageHandler { |
| 101 | 101 | // 更新数据库 |
| 102 | 102 | aiImageMapper.updateByMjNonce(nonceId, |
| 103 | 103 | new AiImageDO() |
| 104 | - .setOriginalPicUrl(imageUrl) | |
| 105 | 104 | .setStatus(drawingStatusEnum == null ? null : drawingStatusEnum.getStatus()) |
| 106 | 105 | // .setMjNonceId(midjourneyMessage.getId()) |
| 107 | 106 | // .setMjOperations(JsonUtils.toJsonString(midjourneyOperations)) | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/midjourneyHandler/vo/AiChatModalChatConfigVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.image.midjourneyHandler.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 | -/** | |
| 9 | - * chat config | |
| 10 | - * | |
| 11 | - * @author fansili | |
| 12 | - * @time 2024/5/6 15:06 | |
| 13 | - * @since 1.0 | |
| 14 | - */ | |
| 15 | -@Data | |
| 16 | -@Accessors(chain = true) | |
| 17 | -public class AiChatModalChatConfigVO extends AiChatModalConfigVO { | |
| 18 | - | |
| 19 | - @NotNull | |
| 20 | - @Schema(description = "在生成消息时采用的Top-K采样大小") | |
| 21 | - private Double topK; | |
| 22 | - | |
| 23 | - @NotNull | |
| 24 | - @Schema(description = "Top-P核采样方法的概率阈值") | |
| 25 | - private Double topP; | |
| 26 | - | |
| 27 | - @NotNull | |
| 28 | - @Schema(description = "温度参数,用于调整生成回复的随机性和多样性程度") | |
| 29 | - private Double temperature; | |
| 30 | - | |
| 31 | - @NotNull | |
| 32 | - @Schema(description = "最大 tokens") | |
| 33 | - private Integer maxTokens; | |
| 34 | - | |
| 35 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/midjourneyHandler/vo/AiChatModalConfigVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.image.midjourneyHandler.vo; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.experimental.Accessors; | |
| 6 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | |
| 7 | -import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel; | |
| 8 | - | |
| 9 | -/** | |
| 10 | - * modal config | |
| 11 | - * | |
| 12 | - * @author fansili | |
| 13 | - * @time 2024/5/6 15:06 | |
| 14 | - * @since 1.0 | |
| 15 | - */ | |
| 16 | -@Data | |
| 17 | -@Accessors(chain = true) | |
| 18 | -public class AiChatModalConfigVO { | |
| 19 | - | |
| 20 | - /** | |
| 21 | - * 模型平台 (冗余,方便类型转换) | |
| 22 | - * 参考:{@link AiPlatformEnum} | |
| 23 | - */ | |
| 24 | - private String platform; | |
| 25 | - /** | |
| 26 | - * 模型类型(冗余,方便类型转换) | |
| 27 | - * {@link YiYanChatModel} | |
| 28 | - * {@link XingHuoChatModel} | |
| 29 | - */ | |
| 30 | - private String type; | |
| 31 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/midjourneyHandler/vo/AiChatModalDallConfigVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.image.midjourneyHandler.vo; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum; | |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.experimental.Accessors; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * dall | |
| 9 | - * | |
| 10 | - * @author fansili | |
| 11 | - * @time 2024/5/6 15:06 | |
| 12 | - * @since 1.0 | |
| 13 | - */ | |
| 14 | -@Data | |
| 15 | -@Accessors(chain = true) | |
| 16 | -public class AiChatModalDallConfigVO extends AiChatModalConfigVO { | |
| 17 | - // 可选字段,默认为1 | |
| 18 | - // 生成图像的数量,必须在1到10之间。对于dall-e-3模型,目前仅支持n=1。 | |
| 19 | - private Integer n = 1; | |
| 20 | - | |
| 21 | - // 可选字段,默认为standard | |
| 22 | - // 设置生成图像的质量。hd质量将创建细节更丰富、图像整体一致性更高的图片。该参数仅对dall-e-3模型有效。 | |
| 23 | - private String quality = "standard"; | |
| 24 | - | |
| 25 | - // 可选字段,默认为url | |
| 26 | - // 返回生成图像的格式。必须是url或b64_json中的一种。URL链接的有效期是从生成图像后开始计算的60分钟内有效。 | |
| 27 | - private String responseFormat = "url"; | |
| 28 | - | |
| 29 | - // 可选字段,默认为1024x1024 | |
| 30 | - // 生成图像的尺寸大小。对于dall-e-2模型,尺寸可为256x256, 512x512, 或 1024x1024。对于dall-e-3模型,尺寸可为1024x1024, 1792x1024, 或 1024x1792。 | |
| 31 | - private String size = "1024x1024"; | |
| 32 | - | |
| 33 | - // 可选字段,默认为vivid | |
| 34 | - // 图像生成的风格。可为vivid(生动)或natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 | |
| 35 | - private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID; | |
| 36 | - | |
| 37 | - // 可选字段 | |
| 38 | - // 代表您的终端用户的唯一标识符,有助于OpenAI监控并检测滥用行为。了解更多信息请参考官方文档。 | |
| 39 | - private String endUserId = "UID123456"; | |
| 40 | - | |
| 41 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/midjourneyHandler/vo/AiChatModalMidjourneyConfigVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.service.image.midjourneyHandler.vo; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | -import lombok.experimental.Accessors; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * Midjourney Config | |
| 8 | - * | |
| 9 | - * @author fansili | |
| 10 | - * @time 2024/5/6 15:07 | |
| 11 | - * @since 1.0 | |
| 12 | - */ | |
| 13 | -@Data | |
| 14 | -@Accessors(chain = true) | |
| 15 | -public class AiChatModalMidjourneyConfigVO extends AiChatModalConfigVO { | |
| 16 | -} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiApiKeyServiceImpl.java
| ... | ... | @@ -17,7 +17,7 @@ import org.springframework.validation.annotation.Validated; |
| 17 | 17 | import java.util.List; |
| 18 | 18 | |
| 19 | 19 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 20 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.*; | |
| 20 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; | |
| 21 | 21 | |
| 22 | 22 | /** |
| 23 | 23 | * AI API 密钥 Service 实现类 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatModelServiceImpl.java
| ... | ... | @@ -16,7 +16,7 @@ import java.util.Collection; |
| 16 | 16 | import java.util.List; |
| 17 | 17 | |
| 18 | 18 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 19 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.*; | |
| 19 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; | |
| 20 | 20 | |
| 21 | 21 | /** |
| 22 | 22 | * AI 聊天模型 Service 实现类 | ... | ... |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiChatRoleServiceImpl.java
| ... | ... | @@ -21,7 +21,7 @@ import java.util.List; |
| 21 | 21 | |
| 22 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 23 | 23 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 24 | -import static cn.iocoder.yudao.module.ai.ErrorCodeConstants.*; | |
| 24 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; | |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | 27 | * AI 聊天角色 Service 实现类 | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
| ... | ... | @@ -3,10 +3,9 @@ package cn.iocoder.yudao.framework.ai.config; |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| 5 | 5 | import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel; |
| 6 | -import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; | |
| 7 | -import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum; | |
| 8 | 6 | import lombok.Data; |
| 9 | 7 | import lombok.experimental.Accessors; |
| 8 | +import org.springframework.ai.autoconfigure.openai.OpenAiImageProperties; | |
| 10 | 9 | import org.springframework.boot.context.properties.ConfigurationProperties; |
| 11 | 10 | |
| 12 | 11 | /** |
| ... | ... | @@ -94,27 +93,6 @@ public class YudaoAiProperties { |
| 94 | 93 | |
| 95 | 94 | @Data |
| 96 | 95 | @Accessors(chain = true) |
| 97 | - public static class OpenAiImageProperties { | |
| 98 | - | |
| 99 | - private boolean enable = false; | |
| 100 | - | |
| 101 | - /** | |
| 102 | - * api key | |
| 103 | - */ | |
| 104 | - private String apiKey; | |
| 105 | - /** | |
| 106 | - * 模型 | |
| 107 | - */ | |
| 108 | - private OpenAiImageModelEnum model = OpenAiImageModelEnum.DALL_E_2; | |
| 109 | - /** | |
| 110 | - * 风格 | |
| 111 | - */ | |
| 112 | - private OpenAiImageStyleEnum style = OpenAiImageStyleEnum.VIVID; | |
| 113 | - | |
| 114 | - } | |
| 115 | - | |
| 116 | - @Data | |
| 117 | - @Accessors(chain = true) | |
| 118 | 96 | public static class MidjourneyProperties { |
| 119 | 97 | private boolean enable = false; |
| 120 | 98 | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java
| ... | ... | @@ -20,7 +20,6 @@ public enum AiPlatformEnum { |
| 20 | 20 | QIAN_WEN("QianWen", "千问"), // 阿里 |
| 21 | 21 | GEMIR ("gemir ", "gemir "), // 谷歌 |
| 22 | 22 | |
| 23 | - OPEN_AI_DALL("dall", "dall"), // TODO OpenAI 提供的绘图,接入中;TODO 要不要统一下?! | |
| 24 | 23 | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI |
| 25 | 24 | MIDJOURNEY("midjourney", "midjourney"), // TODO MJ 提供的绘图,接入中 |
| 26 | 25 | ; | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/OpenAiImageModelEnum.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.enums; | |
| 2 | - | |
| 3 | -import lombok.AllArgsConstructor; | |
| 4 | -import lombok.Getter; | |
| 5 | - | |
| 6 | -// TODO 芋艿:待梳理 | |
| 7 | -/** | |
| 8 | - * open ai | |
| 9 | - * | |
| 10 | - * @author fansili | |
| 11 | - * @time 2024/4/28 14:21 | |
| 12 | - * @since 1.0 | |
| 13 | - */ | |
| 14 | -@AllArgsConstructor | |
| 15 | -@Getter | |
| 16 | -@Deprecated | |
| 17 | -public enum OpenAiImageModelEnum { | |
| 18 | - | |
| 19 | - DALL_E_2("dall-e-2", "dall-e-2"), | |
| 20 | - | |
| 21 | - DALL_E_3("dall-e-3", "dall-e-3") | |
| 22 | - | |
| 23 | - ; | |
| 24 | - | |
| 25 | - private String model; | |
| 26 | - | |
| 27 | - private String name; | |
| 28 | - | |
| 29 | - public static OpenAiImageModelEnum valueOfModel(String model) { | |
| 30 | - for (OpenAiImageModelEnum itemEnum : OpenAiImageModelEnum.values()) { | |
| 31 | - if (itemEnum.getModel().equals(model)) { | |
| 32 | - return itemEnum; | |
| 33 | - } | |
| 34 | - } | |
| 35 | - throw new IllegalArgumentException("Invalid MessageType value: " + model); | |
| 36 | - } | |
| 37 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/OpenAiImageStyleEnum.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.enums; | |
| 2 | - | |
| 3 | -import lombok.AllArgsConstructor; | |
| 4 | -import lombok.Getter; | |
| 5 | - | |
| 6 | -// TODO 芋艿:待梳理 | |
| 7 | -/** | |
| 8 | - * open ai image style | |
| 9 | - * | |
| 10 | - * @author fansili | |
| 11 | - * @time 2024/4/28 16:15 | |
| 12 | - * @since 1.0 | |
| 13 | - */ | |
| 14 | -@AllArgsConstructor | |
| 15 | -@Getter | |
| 16 | -@Deprecated | |
| 17 | -public enum OpenAiImageStyleEnum { | |
| 18 | - | |
| 19 | - // 图像生成的风格。可为vivid(生动)或 natural(自然)。vivid会使模型偏向生成超现实和戏剧性的图像,而natural则会让模型产出更自然、不那么超现实的图像。该参数仅对dall-e-3模型有效。 | |
| 20 | - | |
| 21 | - VIVID("vivid", "生动"), | |
| 22 | - NATURAL("natural", "自然"), | |
| 23 | - | |
| 24 | - ; | |
| 25 | - | |
| 26 | - private String style; | |
| 27 | - | |
| 28 | - private String name; | |
| 29 | - | |
| 30 | - public static OpenAiImageStyleEnum valueOfStyle(String style) { | |
| 31 | - for (OpenAiImageStyleEnum itemEnum : OpenAiImageStyleEnum.values()) { | |
| 32 | - if (itemEnum.getStyle().equals(style)) { | |
| 33 | - return itemEnum; | |
| 34 | - } | |
| 35 | - } | |
| 36 | - throw new IllegalArgumentException("Invalid MessageType value: " + style); | |
| 37 | - } | |
| 38 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
| ... | ... | @@ -33,6 +33,7 @@ import org.springframework.ai.openai.OpenAiChatOptions; |
| 33 | 33 | import org.springframework.ai.openai.OpenAiImageClient; |
| 34 | 34 | import org.springframework.ai.openai.api.ApiUtils; |
| 35 | 35 | import org.springframework.ai.openai.api.OpenAiApi; |
| 36 | +import org.springframework.ai.stabilityai.StabilityAiImageClient; | |
| 36 | 37 | |
| 37 | 38 | import java.util.List; |
| 38 | 39 | |
| ... | ... | @@ -88,12 +89,15 @@ public class AiClientFactoryImpl implements AiClientFactory { |
| 88 | 89 | |
| 89 | 90 | @Override |
| 90 | 91 | public ImageClient getDefaultImageClient(AiPlatformEnum platform) { |
| 92 | + //noinspection EnhancedSwitchMigration | |
| 91 | 93 | switch (platform) { |
| 92 | - case OPEN_AI_DALL: | |
| 94 | + case OPENAI: | |
| 93 | 95 | return SpringUtil.getBean(OpenAiImageClient.class); |
| 94 | - | |
| 96 | + case STABLE_DIFFUSION: | |
| 97 | + return SpringUtil.getBean(StabilityAiImageClient.class); | |
| 98 | + default: | |
| 99 | + throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); | |
| 95 | 100 | } |
| 96 | - return null; | |
| 97 | 101 | } |
| 98 | 102 | |
| 99 | 103 | private static String buildClientCacheKey(Class<?> clazz, Object... params) { | ... | ... |