Commit f2b9c14819c750ac43dcb190b0a56b6edcc5a341
1 parent
b09fc557
增加mj 图片生成消息转换
Showing
3 changed files
with
222 additions
and
30 deletions
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/MjMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.midjourney; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | +import lombok.experimental.Accessors; | ||
| 5 | + | ||
| 6 | +import java.util.List; | ||
| 7 | + | ||
| 8 | +@Data | ||
| 9 | +@Accessors(chain = true) | ||
| 10 | +public class MjMessage { | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * id是一个重要的字段,在同时生成多个的时候,可以区分生成信息 | ||
| 14 | + */ | ||
| 15 | + private String id; | ||
| 16 | + /** | ||
| 17 | + * 现在已知: | ||
| 18 | + * 0:我们发送的消息,和指令 | ||
| 19 | + * 20: mj生成图片发送过程中 | ||
| 20 | + */ | ||
| 21 | + private Integer type; | ||
| 22 | + /** | ||
| 23 | + * content | ||
| 24 | + */ | ||
| 25 | + private Content content; | ||
| 26 | + /** | ||
| 27 | + * 图片生成完成才有 | ||
| 28 | + */ | ||
| 29 | + private List<ComponentType> components; | ||
| 30 | + /** | ||
| 31 | + * 生成过程中如果有,预展示图片,这里会有 | ||
| 32 | + */ | ||
| 33 | + private List<Attachment> attachments; | ||
| 34 | + /** | ||
| 35 | + * 原始数据(discard 返回的原始数据) | ||
| 36 | + */ | ||
| 37 | + private String rawData; | ||
| 38 | + /** | ||
| 39 | + * 生成状态(用于区分生成状态) | ||
| 40 | + * 1、等待 | ||
| 41 | + * 2、进行中 | ||
| 42 | + * 3、完成 | ||
| 43 | + * {@link cn.iocoder.yudao.framework.ai.midjourney.constants.MjGennerateStatusEnum} | ||
| 44 | + */ | ||
| 45 | + private String generateStatus; | ||
| 46 | + | ||
| 47 | + @Data | ||
| 48 | + @Accessors(chain = true) | ||
| 49 | + public static class ComponentType { | ||
| 50 | + | ||
| 51 | + private int type; | ||
| 52 | + | ||
| 53 | + private List<Component> components; | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @Data | ||
| 57 | + @Accessors(chain = true) | ||
| 58 | + public static class Component { | ||
| 59 | + /** | ||
| 60 | + * 自定义ID,用于唯一标识特定交互动作及其上下文信息。 | ||
| 61 | + */ | ||
| 62 | + private String customId; | ||
| 63 | + | ||
| 64 | + /** | ||
| 65 | + * 样式编号,用于确定按钮的样式外观。 | ||
| 66 | + * 在某些应用中,例如Discord,2可能表示一种特定的颜色或形状的按钮。 | ||
| 67 | + */ | ||
| 68 | + private int style; | ||
| 69 | + | ||
| 70 | + /** | ||
| 71 | + * 按钮的标签文本,用户可见的内容。 | ||
| 72 | + */ | ||
| 73 | + private String label; | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * 组件类型,此处为2可能表示这是一种特定类型的交互组件, | ||
| 77 | + * 如在Discord API中,类型2对应的是一个可点击的按钮组件。 | ||
| 78 | + */ | ||
| 79 | + private int type; | ||
| 80 | + | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + @Data | ||
| 84 | + @Accessors(chain = true) | ||
| 85 | + public static class Attachment { | ||
| 86 | + // 文件名 | ||
| 87 | + private String filename; | ||
| 88 | + | ||
| 89 | + // 附件大小(字节) | ||
| 90 | + private int size; | ||
| 91 | + | ||
| 92 | + // 内容类型(例如:image/webp) | ||
| 93 | + private String contentType; | ||
| 94 | + | ||
| 95 | + // 图像宽度(像素) | ||
| 96 | + private int width; | ||
| 97 | + | ||
| 98 | + // 占位符版本号 | ||
| 99 | + private int placeholderVersion; | ||
| 100 | + | ||
| 101 | + // 代理URL,用于访问附件资源 | ||
| 102 | + private String proxyUrl; | ||
| 103 | + | ||
| 104 | + // 占位符标识符 | ||
| 105 | + private String placeholder; | ||
| 106 | + | ||
| 107 | + // 附件ID | ||
| 108 | + private String id; | ||
| 109 | + | ||
| 110 | + // 直接访问附件资源的URL | ||
| 111 | + private String url; | ||
| 112 | + | ||
| 113 | + // 图像高度(像素) | ||
| 114 | + private int height; | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + @Data | ||
| 118 | + @Accessors(chain = true) | ||
| 119 | + public static class Content { | ||
| 120 | + private String prompt; | ||
| 121 | + private String progress; | ||
| 122 | + private String status; | ||
| 123 | + } | ||
| 124 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/constants/MjGennerateStatusEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.midjourney.constants; | ||
| 2 | + | ||
| 3 | +import lombok.Getter; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * mj 生成状态 | ||
| 7 | + * | ||
| 8 | + * author: fansili | ||
| 9 | + * time: 2024/4/6 21:07 | ||
| 10 | + */ | ||
| 11 | +@Getter | ||
| 12 | +public enum MjGennerateStatusEnum { | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + WAITING("waiting", "等待..."), | ||
| 16 | + IN_PROGRESS("in_progress", "进行中"), | ||
| 17 | + COMPLETED("completed", "完成"), | ||
| 18 | + | ||
| 19 | + ; | ||
| 20 | + | ||
| 21 | + MjGennerateStatusEnum(String value, String message) { | ||
| 22 | + this.value = value; | ||
| 23 | + this.message = message; | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + private String value; | ||
| 27 | + | ||
| 28 | + private String message; | ||
| 29 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/midjourney/webSocket/listener/MjMessageListener.java
| 1 | package cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener; | 1 | package cn.iocoder.yudao.framework.ai.midjourney.webSocket.listener; |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | import cn.hutool.core.text.CharSequenceUtil; | 5 | import cn.hutool.core.text.CharSequenceUtil; |
| 6 | +import cn.hutool.core.util.StrUtil; | ||
| 7 | +import cn.hutool.json.JSONUtil; | ||
| 5 | import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; | 8 | import cn.iocoder.yudao.framework.ai.midjourney.MidjourneyConfig; |
| 9 | +import cn.iocoder.yudao.framework.ai.midjourney.MjMessage; | ||
| 6 | import cn.iocoder.yudao.framework.ai.midjourney.constants.MjConstants; | 10 | import cn.iocoder.yudao.framework.ai.midjourney.constants.MjConstants; |
| 11 | +import cn.iocoder.yudao.framework.ai.midjourney.constants.MjGennerateStatusEnum; | ||
| 7 | import cn.iocoder.yudao.framework.ai.midjourney.constants.MjMessageTypeEnum; | 12 | import cn.iocoder.yudao.framework.ai.midjourney.constants.MjMessageTypeEnum; |
| 13 | +import cn.iocoder.yudao.framework.ai.midjourney.util.MjUtil; | ||
| 14 | +import com.alibaba.fastjson.JSON; | ||
| 15 | +import com.google.common.collect.Lists; | ||
| 8 | import lombok.extern.slf4j.Slf4j; | 16 | import lombok.extern.slf4j.Slf4j; |
| 9 | import net.dv8tion.jda.api.utils.data.DataObject; | 17 | import net.dv8tion.jda.api.utils.data.DataObject; |
| 10 | 18 | ||
| 19 | +import java.io.UnsupportedEncodingException; | ||
| 20 | +import java.util.Arrays; | ||
| 21 | +import java.util.List; | ||
| 22 | + | ||
| 11 | @Slf4j | 23 | @Slf4j |
| 12 | public class MjMessageListener { | 24 | public class MjMessageListener { |
| 13 | 25 | ||
| 14 | - private MidjourneyConfig midjourneyConfig; | ||
| 15 | - | ||
| 16 | - public MjMessageListener(MidjourneyConfig midjourneyConfig) { | ||
| 17 | - this.midjourneyConfig = midjourneyConfig; | ||
| 18 | - } | ||
| 19 | - | ||
| 20 | - public void onMessage(DataObject raw) { | ||
| 21 | - MjMessageTypeEnum messageType = MjMessageTypeEnum.of(raw.getString("t")); | ||
| 22 | - if (messageType == null || MjMessageTypeEnum.DELETE == messageType) { | ||
| 23 | - return; | ||
| 24 | - } | ||
| 25 | - DataObject data = raw.getObject("d"); | ||
| 26 | - if (ignoreAndLogMessage(data, messageType)) { | ||
| 27 | - return; | ||
| 28 | - } | ||
| 29 | - System.err.println(data); | ||
| 30 | -// if (data.getBoolean(Constants.MJ_MESSAGE_HANDLED, false)) { | ||
| 31 | -// return; | ||
| 32 | -// } | ||
| 33 | - } | ||
| 34 | - | ||
| 35 | - private boolean ignoreAndLogMessage(DataObject data, MjMessageTypeEnum messageType) { | ||
| 36 | - String channelId = data.getString(MjConstants.CHANNEL_ID); | ||
| 37 | - if (!CharSequenceUtil.equals(channelId, midjourneyConfig.getChannelId())) { | ||
| 38 | - return true; | ||
| 39 | - } | ||
| 40 | - String authorName = data.optObject("author").map(a -> a.getString("username")).orElse("System"); | ||
| 41 | - log.debug("{} - {} - {}: {}", midjourneyConfig.getChannelId(), messageType.name(), authorName, data.opt("content").orElse("")); | ||
| 42 | - return false; | ||
| 43 | - } | 26 | + private MidjourneyConfig midjourneyConfig; |
| 27 | + | ||
| 28 | + public MjMessageListener(MidjourneyConfig midjourneyConfig) { | ||
| 29 | + this.midjourneyConfig = midjourneyConfig; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + public void onMessage(DataObject raw) { | ||
| 33 | + MjMessageTypeEnum messageType = MjMessageTypeEnum.of(raw.getString("t")); | ||
| 34 | + if (messageType == null || MjMessageTypeEnum.DELETE == messageType) { | ||
| 35 | + return; | ||
| 36 | + } | ||
| 37 | + DataObject data = raw.getObject("d"); | ||
| 38 | + if (ignoreAndLogMessage(data, messageType)) { | ||
| 39 | + return; | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + MjMessage mjMessage = new MjMessage(); | ||
| 43 | + mjMessage.setId(data.getString("id")); | ||
| 44 | + mjMessage.setType(data.getInt("type")); | ||
| 45 | + mjMessage.setRawData(StrUtil.str(raw.toJson(), "UTF-8")); | ||
| 46 | + mjMessage.setContent(MjUtil.parseContent(data.getString("content"))); | ||
| 47 | + | ||
| 48 | + if (!data.getArray("components").isEmpty()) { | ||
| 49 | + String componentsJson = StrUtil.str(data.getArray("components").toJson(), "UTF-8"); | ||
| 50 | + List<MjMessage.ComponentType> components = JSON.parseArray(componentsJson, MjMessage.ComponentType.class); | ||
| 51 | + mjMessage.setComponents(components); | ||
| 52 | + } | ||
| 53 | + if (!data.getArray("attachments").isEmpty()) { | ||
| 54 | + String attachmentsJson = StrUtil.str(data.getArray("attachments").toJson(), "UTF-8"); | ||
| 55 | + List<MjMessage.Attachment> attachments = JSON.parseArray(attachmentsJson, MjMessage.Attachment.class); | ||
| 56 | + mjMessage.setAttachments(attachments); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + // 转换状态 | ||
| 60 | + convertGenerateStatus(mjMessage); | ||
| 61 | + System.err.println(JSONUtil.toJsonPrettyStr(mjMessage)); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + private void convertGenerateStatus(MjMessage mjMessage) { | ||
| 65 | + if (mjMessage.getType() == 20 && mjMessage.getContent().getStatus().contains("Waiting")) { | ||
| 66 | + mjMessage.setGenerateStatus(MjGennerateStatusEnum.WAITING.getValue()); | ||
| 67 | + } else if (mjMessage.getType() == 20 && !StrUtil.isBlank(mjMessage.getContent().getProgress())) { | ||
| 68 | + mjMessage.setGenerateStatus(MjGennerateStatusEnum.IN_PROGRESS.getValue()); | ||
| 69 | + } else if (mjMessage.getType() == 0 && !CollUtil.isEmpty(mjMessage.getComponents())) { | ||
| 70 | + mjMessage.setGenerateStatus(MjGennerateStatusEnum.COMPLETED.getValue()); | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + private boolean ignoreAndLogMessage(DataObject data, MjMessageTypeEnum messageType) { | ||
| 75 | + String channelId = data.getString(MjConstants.CHANNEL_ID); | ||
| 76 | + if (!CharSequenceUtil.equals(channelId, midjourneyConfig.getChannelId())) { | ||
| 77 | + return true; | ||
| 78 | + } | ||
| 79 | + String authorName = data.optObject("author").map(a -> a.getString("username")).orElse("System"); | ||
| 80 | + log.debug("{} - {} - {}: {}", midjourneyConfig.getChannelId(), messageType.name(), authorName, data.opt("content").orElse("")); | ||
| 81 | + return false; | ||
| 82 | + } | ||
| 44 | } | 83 | } |