From abd80fe390483620bca604cadb0f4f20e6170818 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Wed, 19 Jun 2024 12:21:01 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E8=A7=A3=E5=86=B3todo=E3=80=91AI=20Mu?= =?UTF-8?q?sic:=20=E7=BB=93=E6=9E=84=E4=BC=98=E5=8C=96=EF=BC=8Ctask?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5=E4=BD=BF=E7=94=A8=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=A0=87=E5=87=86Job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/ai/enums/model/AiModelEnum.java | 3 +- .../ai/enums/music/AiMusicGenerateEnum.java | 35 ++++++ .../enums/{ => music}/AiMusicStatusEnum.java | 14 +-- ...Controller.java => AiMusicController.java} | 22 ++-- .../admin/music/vo/SunoLyricModeVO.java | 22 ---- .../controller/admin/music/vo/SunoReqVO.java | 44 ++++--- .../ai/dal/dataobject/music/AiMusicDO.java | 117 +++++++++++------- .../iocoder/yudao/module/ai/job/SunoJob.java | 59 +++++++++ .../ai/service/music/AiMusicConvert.java | 40 ++++++ .../ai/service/music/AiMusicService.java | 36 ++++++ .../ai/service/music/AiMusicServiceImpl.java | 103 +++++++++++++++ .../module/ai/service/music/MusicService.java | 24 ---- .../ai/service/music/MusicServiceImpl.java | 102 --------------- .../ai/config/YudaoAiAutoConfiguration.java | 3 +- .../ai/core/enums/AiPlatformEnum.java | 1 + .../ai/core/model/suno/SunoConfig.java | 23 ---- .../ai/core/model/suno/api/SunoApi.java | 93 +++++++------- .../yudao/framework/ai/suno/SunoTests.java | 15 ++- .../src/main/resources/application.yaml | 2 +- 19 files changed, 446 insertions(+), 312 deletions(-) create mode 100644 yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateEnum.java rename yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/{ => music}/AiMusicStatusEnum.java (60%) rename yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/{MusicController.java => AiMusicController.java} (51%) delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/SunoJob.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicConvert.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java index 01f0460ba..9dfe6c411 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java @@ -50,8 +50,7 @@ public enum AiModelEnum { XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), - // TODO @xin:// Suno;中间加个空格,会更清晰一点。一般来说,不同类型的单词之间,最好有空格。例如说,// 新增一个;再例如说;// 这是 1 个 create 逻辑 - //Suno + // Suno 模型 SUNO_2( "SUNO-2", "chirp-v2-xxl-alpha",null), SUNO_3_0( "SUNO-3.0", "chirp-v3-0",null), SUNO_3_5( "SUNO-3.5", "chirp-v3.5",null), diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateEnum.java new file mode 100644 index 000000000..df9776cbf --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateEnum.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.ai.enums.music; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * AI 音乐状态的枚举 + * + * @author xiaoxin + */ +@AllArgsConstructor +@Getter +public enum AiMusicGenerateEnum { + + LYRIC("lyric", "歌词模式"), + DESCRIPTION("description", "描述模式"); + + /** + * 模式 + */ + private final String mode; + /** + * 模式名 + */ + private final String name; + + public static AiMusicGenerateEnum valueOfMode(String mode) { + for (AiMusicGenerateEnum modeEnum : AiMusicGenerateEnum.values()) { + if (modeEnum.getMode().equals(mode)) { + return modeEnum; + } + } + throw new IllegalArgumentException("未知模式: " + mode); + } +} diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java similarity index 60% rename from yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java rename to yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java index cb7c8d350..12b3e4ca1 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java @@ -1,22 +1,18 @@ -package cn.iocoder.yudao.module.ai.enums; +package cn.iocoder.yudao.module.ai.enums.music; import lombok.AllArgsConstructor; import lombok.Getter; -// TODO @xin:这个类,挪到 enums/music 包下; -// TODO @xin:1)@author 这个是标准的 javadoc;2)@date 可以不要哈;3)可以加下枚举类的注释 /** - * @Author xiaoxin - * @Date 2024/6/5 + * AI 音乐状态的枚举 + * + * @author xiaoxin */ @AllArgsConstructor @Getter public enum AiMusicStatusEnum { - // TODO @xin:是不是收敛成,只有 3 个:进行中,成功,失败;类似 AiImageStatusEnum - - SUBMITTED("submitted", "已提交"), - QUEUED("queued", "排队中"), + // @xin 文档中无失败这个返回值 STREAMING("streaming", "进行中"), COMPLETE("complete", "完成"); diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java similarity index 51% rename from yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java rename to yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java index 502dbad64..bd4130d9b 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.ai.controller.admin.music; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoLyricModeVO; import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; -import cn.iocoder.yudao.module.ai.service.music.MusicService; +import cn.iocoder.yudao.module.ai.service.music.AiMusicService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -17,25 +16,18 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -// TODO @xin:AI 前缀;都要加下哈 @Tag(name = "管理后台 - AI 音乐生成") @RestController @RequestMapping("/ai/music") @RequiredArgsConstructor -public class MusicController { +public class AiMusicController { - private final MusicService musicService; + private final AiMusicService aiMusicService; - @PostMapping("generate/description-mode") - @Operation(summary = "音乐生成-描述模式") - public CommonResult> descriptionMode(@RequestBody @Valid SunoReqVO sunoReqVO) { - return success(musicService.descriptionMode(sunoReqVO)); - } - - @PostMapping("generate/lyric-mode") - @Operation(summary = "音乐生成-歌词模式") - public CommonResult> lyricMode(@RequestBody @Valid SunoLyricModeVO sunoLyricModeVO) { - return success(musicService.lyricMode(sunoLyricModeVO)); + @PostMapping("/generate") + @Operation(summary = "音乐生成") + public CommonResult> generateMusic(@RequestBody @Valid SunoReqVO sunoReqVO) { + return success(aiMusicService.generateMusic(sunoReqVO)); } } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java deleted file mode 100644 index 4f869b3f8..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import lombok.Data; - -/** - * @Author jxli@quant360.com - * @Date 2024/6/7 - */ -@Data -public class SunoLyricModeVO extends SunoReqVO { - - /** - * 标签/音乐风格 - */ - private String tags; - - /** - * 音乐名称 - */ - private String title; - -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java index 4685b2142..09ae4c302 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java @@ -1,23 +1,39 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo; -import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Data; +import java.util.List; + + +/** + * @author xiaoxin + */ @Data -@JsonInclude(value = JsonInclude.Include.NON_NULL) // TODO @xin:不用加这个哈 public class SunoReqVO { - /** - * 用于生成音乐音频的提示 - */ + + @Schema(description = "用于生成音乐音频的提示") private String prompt; - // TODO @xin:Boolean,不使用基本类型。 - /** - * 是否纯音乐 - */ - private boolean makeInstrumental; - /** - * //todo 首次请求返回的模型是对的,后续更新音频返回的模型又变成v3.5了 - * 模型版本 {@link cn.iocoder.yudao.module.ai.enums.AiModelEnum} Suno - */ + + @Schema(description = "是否纯音乐") + private Boolean makeInstrumental; + + @Schema(description = "模型版本 ") private String mv; + + @Schema(description = "音乐风格") + private List tags; + + @Schema(description = "音乐/歌曲名称") + private String title; + + @Schema(description = "平台") + @NotBlank(message = "平台不能为空") + private String platform; + + @Schema(description = "生成模式 lyric(歌词模式), description(描述模式)") + @NotBlank(message = "生成模式不能为空") + private String generateMode; + } \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java index 4938646c2..3441437ae 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java @@ -1,15 +1,16 @@ package cn.iocoder.yudao.module.ai.dal.dataobject.music; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import io.swagger.v3.oas.annotations.media.Schema; +import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; import lombok.Data; import java.util.List; -import java.util.stream.Collectors; /** * @Author xiaoxin @@ -19,77 +20,103 @@ import java.util.stream.Collectors; @Data public class AiMusicDO extends BaseDO { - // TODO @xin:@Schema 只在 VO 里使用,这里还是使用标准的注释哈 + /** + * 编号 + */ @TableId(type = IdType.AUTO) - @Schema(description = "编号") private Long id; - @Schema(description = "用户编号") + /** + * 用户编号 + */ private Long userId; - @Schema(description = "音乐名称") + /** + * 音乐名称 + */ private String title; - @Schema(description = "图片地址") + /** + * 图片地址 + */ private String imageUrl; - @Schema(description = "歌词") + /** + * 歌词 + */ private String lyric; - @Schema(description = "音频地址") + /** + * 音频地址 + */ private String audioUrl; - @Schema(description = "视频地址") + /** + * 视频地址 + */ private String videoUrl; - // TODO @xin:需要关联下对应的枚举 - @Schema(description = "音乐状态") + /** + * 音乐状态 + *

+ * 枚举 {@link AiMusicStatusEnum} + */ private String status; - @Schema(description = "描述词") + /** + * 描述词 + */ private String gptDescriptionPrompt; - - @Schema(description = "提示词") + /** + * 提示词 + */ private String prompt; - // TODO @xin:生成模式,需要记录下;歌词、描述 + /** + * 生成模式 + */ + private String generateMode; - // TODO @xin:多存储一个平台,platform;考虑未来可能有别的音乐接口 - @Schema(description = "模型") + /** + * 平台 + *

+ * 枚举 {@link cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum} + */ + private String platform; + + /** + * 模型 + */ private String model; - @Schema(description = "错误信息") + /** + * 错误信息 + */ private String errorMessage; - // TODO @xin:tags 要不要使用 List - @Schema(description = "音乐风格标签") - private String tags; + /** + * 音乐风格标签 + */ + @TableField(typeHandler = AiMusicTagsHandler.class) + private List tags; - @Schema(description = "任务编号") + /** + * 任务编号 + */ private String taskId; - // TODO @xin:转换不放在 DO 里面哈。 - public static AiMusicDO convertFrom(SunoApi.MusicData musicData) { - return new AiMusicDO() - .setTaskId(musicData.id()) - .setPrompt(musicData.prompt()) - .setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) - .setAudioUrl(musicData.audioUrl()) - .setVideoUrl(musicData.videoUrl()) - .setImageUrl(musicData.imageUrl()) - .setLyric(musicData.lyric()) - .setTitle(musicData.title()) - .setStatus(musicData.status()) - .setModel(musicData.modelName()) - .setTags(musicData.tags()); + public static class AiMusicTagsHandler extends AbstractJsonTypeHandler { + + @Override + protected Object parse(String json) { + return JsonUtils.parseArray(json, String.class); + } + + @Override + protected String toJson(Object obj) { + return JsonUtils.toJsonString(obj); + } } - - public static List convertFrom(List musicDataList) { - return musicDataList.stream() - .map(AiMusicDO::convertFrom) - .collect(Collectors.toList()); - } - } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/SunoJob.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/SunoJob.java new file mode 100644 index 000000000..02bffd288 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/job/SunoJob.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.ai.job; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; +import cn.iocoder.yudao.module.ai.service.music.AiMusicConvert; +import cn.iocoder.yudao.module.ai.service.music.AiMusicService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 处理 Suno Job + * @author xiaoxin + */ +@Component +@Slf4j +public class SunoJob implements JobHandler { + + @Resource + private SunoApi sunoApi; + @Resource + private AiMusicService musicService; + + @Override + public String execute(String param) { + List unCompletedTask = musicService.getUnCompletedTask(); + + if (CollUtil.isEmpty(unCompletedTask)) { + log.info("Suno 无进行中任务需要更新!"); + return "Suno 无进行中任务需要更新!"; + } + + + log.info("Suno 开始同步, 共 [{}] 个任务!", unCompletedTask.size()); + //GET 请求,为避免参数过长,分批次处理 + CollUtil.split(unCompletedTask, 4) + .forEach(chunk -> { + Map taskIdMap = CollUtil.toMap(chunk, new HashMap<>(), AiMusicDO::getTaskId, AiMusicDO::getId); + List musicTaskList = sunoApi.getMusicList(new ArrayList<>(taskIdMap.keySet())); + if (CollUtil.isNotEmpty(musicTaskList)) { + List aiMusicDOS = AiMusicConvert.convertFrom(musicTaskList); + //回填id + aiMusicDOS.forEach(aiMusicDO -> aiMusicDO.setId(taskIdMap.get(aiMusicDO.getTaskId()))); + musicService.updateBatch(aiMusicDOS); + } else { + log.warn("Suno 任务同步失败, 任务ID: [{}]", taskIdMap.keySet()); + } + }); + + return "Suno 同步 - ".concat(String.valueOf(unCompletedTask.size())).concat(" 个任务!"); + } +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicConvert.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicConvert.java new file mode 100644 index 000000000..26e86a8cd --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicConvert.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.ai.service.music; + +import cn.hutool.core.text.StrPool; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * AI 音乐 Convert + * + * @author xiaoxin + */ +public class AiMusicConvert { + + public static AiMusicDO convertFrom(SunoApi.MusicData musicData) { + return new AiMusicDO() + .setTaskId(musicData.id()) + .setPrompt(musicData.prompt()) + .setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) + .setAudioUrl(musicData.audioUrl()) + .setVideoUrl(musicData.videoUrl()) + .setImageUrl(musicData.imageUrl()) + .setLyric(musicData.lyric()) + .setTitle(musicData.title()) + .setStatus(musicData.status()) + .setModel(musicData.modelName()) + .setTags(StrUtil.isNotBlank(musicData.tags()) ? List.of(musicData.tags().split(StrPool.COMMA)) : null); + } + + public static List convertFrom(List musicDataList) { + return musicDataList.stream() + .map(AiMusicConvert::convertFrom) + .collect(Collectors.toList()); + } + + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java new file mode 100644 index 000000000..6f0dce923 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.ai.service.music; + +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; + +import java.util.List; + +/** + * AI 音乐 Service 接口 + * + * @author xiaoxin + */ +public interface AiMusicService { + + + /** + * 音乐生成 + * + * @param reqVO 请求参数 + * @return 生成的音乐ID + */ + List generateMusic(SunoReqVO reqVO); + + + /** + * 获取未完成状态的任务 + * + * @return 未完成任务列表 + */ + List getUnCompletedTask(); + + + Boolean updateBatch(List aiMusicDOList); + + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java new file mode 100644 index 000000000..26d93a785 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java @@ -0,0 +1,103 @@ +package cn.iocoder.yudao.module.ai.service.music; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.StrPool; +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; +import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; +import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateEnum; +import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * AI 音乐 Service 实现类 + * @author xiaoxin + */ +@Service +@Slf4j +public class AiMusicServiceImpl implements AiMusicService { + + @Resource + private SunoApi sunoApi; + @Resource + private AiMusicMapper musicMapper; + + @Override + public List generateMusic(SunoReqVO reqVO) { + AiMusicGenerateEnum generateEnum = AiMusicGenerateEnum.valueOfMode(reqVO.getGenerateMode()); + return switch (generateEnum) { + case DESCRIPTION -> descriptionMode(reqVO); + case LYRIC -> lyricMode(reqVO); + }; + } + + @Override + public List getUnCompletedTask() { + return musicMapper.selectList(new LambdaQueryWrapper().ne(AiMusicDO::getStatus, AiMusicStatusEnum.COMPLETE.getStatus())); + } + + @Override + public Boolean updateBatch(List aiMusicDOList) { + return musicMapper.updateBatch(aiMusicDOList); + } + + /** + * 描述模式生成音乐 + * + * @param reqVO 请求参数 + * @return 生成的音乐ID集合 + */ + public List descriptionMode(SunoReqVO reqVO) { + // 1. 异步生成 + SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getMv(), reqVO.getMakeInstrumental()); + List musicDataList = sunoApi.generate(sunoReq); + // 2. 插入数据库 + return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform()); + } + + /** + * 歌词模式生成音乐 + * + * @param reqVO 请求参数 + * @return 生成的音乐ID集合 + */ + public List lyricMode(SunoReqVO reqVO) { + // 1. 异步生成 + SunoApi.MusicGenerateRequest sunoReq = new SunoApi.MusicGenerateRequest(reqVO.getPrompt(), reqVO.getMv(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); + List musicDataList = sunoApi.customGenerate(sunoReq); + // 2. 插入数据库 + return insertMusicData(musicDataList, reqVO.getGenerateMode(), reqVO.getPlatform()); + } + + /** + * 新增音乐数据并提交 suno任务 + * + * @param musicDataList 音乐数据列表 + * @return 音乐id集合 + */ + private List insertMusicData(List musicDataList, String generateMode, String platform) { + if (CollUtil.isEmpty(musicDataList)) { + return Collections.emptyList(); + } + List aiMusicDOList = AiMusicConvert.convertFrom(musicDataList).stream() + .map(musicDO -> musicDO.setUserId(getLoginUserId()) + .setGenerateMode(generateMode) + .setPlatform(platform)) + .toList(); + musicMapper.insertBatch(aiMusicDOList); + return aiMusicDOList.stream() + .map(AiMusicDO::getId) + .collect(Collectors.toList()); + } + +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java deleted file mode 100644 index 7de2ee644..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.music; - -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoLyricModeVO; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; - -import java.util.List; - -/** - * @Author xiaoxin - * @Date 2024/5/29 - */ -public interface MusicService { - - /** - * 音乐生成-描述模式 - */ - List descriptionMode(SunoReqVO reqVO); - - - /** - * 音乐生成-歌词模式 - **/ - List lyricMode(SunoLyricModeVO reqVO); -} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java deleted file mode 100644 index 96ceddf05..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -package cn.iocoder.yudao.module.ai.service.music; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.text.StrPool; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoLyricModeVO; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; -import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; -import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; -import cn.iocoder.yudao.module.ai.enums.AiMusicStatusEnum; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -/** - * @Author xiaoxin - * @Date 2024/5/29 - */ -@Service -@RequiredArgsConstructor -@Slf4j -public class MusicServiceImpl implements MusicService { - - // TODO @xin:使用 @Resource 注入,整个项目保持统一哈; - private final SunoApi sunoApi; - private final AiMusicMapper musicMapper; - - private final Queue taskQueue = new ConcurrentLinkedQueue<>(); - - // TODO @xin:要不把 descriptionMode、lyricMode 合并,同一个 generateMusic 方法,然后根据传入的 mode 模式:歌词、描述来区分? - - @Override - public List descriptionMode(SunoReqVO reqVO) { - // 1. 异步生成 - SunoApi.SunoRequest sunoReq = new SunoApi.SunoRequest(reqVO.getPrompt(), reqVO.getMv(), reqVO.isMakeInstrumental()); - List musicDataList = sunoApi.generate(sunoReq); - // 2. 插入数据库 - return insertMusicData(musicDataList); - } - - @Override - public List lyricMode(SunoLyricModeVO reqVO) { - // 1. 异步生成 - SunoApi.SunoRequest sunoReq = new SunoApi.SunoRequest(reqVO.getPrompt(), reqVO.getMv(), reqVO.getTags(), reqVO.getTitle()); - List musicDataList = sunoApi.customGenerate(sunoReq); - // 2. 插入数据库 - return insertMusicData(musicDataList); - } - - /** - * 新增音乐数据并提交 suno任务 - * - * @param musicDataList 音乐数据列表 - * @return 音乐id集合 - */ - private List insertMusicData(List musicDataList) { - if (CollUtil.isEmpty(musicDataList)) { - return Collections.emptyList(); - } - // TODO @xin:建议使用 insertBatch 方法,批量插入 - return AiMusicDO.convertFrom(musicDataList).stream() - .peek(musicDO -> musicMapper.insert(musicDO.setUserId(getLoginUserId()))) - .peek(e -> Optional.of(e.getTaskId()).ifPresent(taskQueue::add)) - .map(AiMusicDO::getId) - .collect(Collectors.toList()); - } - - // TODO @xin:这个,改成标准的 job 来实现哈。从数据库加载任务,然后执行。 - @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) - @Transactional - public void flushSunoTask() { - if (CollUtil.isEmpty(taskQueue)) { - return; - } - CollUtil.split(taskQueue, 5). - stream().map(chunk -> CollUtil.join(chunk, StrPool.COMMA)) - .forEach(taskIds -> { - List musicData = sunoApi.selectById(taskIds); - musicData.stream() - .map(AiMusicDO::convertFrom) - .forEach(musicDO -> { - //更新音乐生成结果 - musicMapper.update(musicDO, Wrappers.lambdaUpdate().eq(AiMusicDO::getTaskId, musicDO.getTaskId())); - //完成后剔除任务 - if (Objects.equals(AiMusicStatusEnum.COMPLETE.getStatus(), musicDO.getStatus())) { - taskQueue.remove(musicDO.getTaskId()); - } - }); - }); - - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index b22eff721..c4ecd2422 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.ai.config; import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl; -import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; @@ -151,7 +150,7 @@ public class YudaoAiAutoConfiguration { @Bean @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { - return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getBaseUrl())); + return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); } private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java index 7ac341e46..b2c72f7cd 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java @@ -22,6 +22,7 @@ public enum AiPlatformEnum { STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI MIDJOURNEY("midjourney", "midjourney"), // TODO MJ 提供的绘图,接入中 + SUNO("Suno", "Suno"), // Suno AI ; /** diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java deleted file mode 100644 index 058842eab..000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.suno; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -// TODO @xin:不需要这个类哈,直接 SunoApi 传入 baseUrl 参数即可 -/** - * Suno 配置类 - * - * @author xiaoxin - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class SunoConfig { - - /** - * suno-api服务的基本路径 - */ - private String baseUrl; - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java index 80bdd5174..69abf4590 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java @@ -1,10 +1,12 @@ package cn.iocoder.yudao.framework.ai.core.model.suno.api; -import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.StrPool; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.ClientResponse; @@ -17,11 +19,10 @@ import java.util.function.Predicate; /** * Suno API - *
+ * * 文档地址:https://github.com/status2xx/suno-api/blob/main/README_CN.md * - * @Author xiaoxin - * @Date 2024/6/3 + * @author xiaoxin */ @Slf4j public class SunoApi { @@ -29,86 +30,88 @@ public class SunoApi { private final WebClient webClient; private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); - private final Function> EXCEPTION_FUNCTION = response -> response.bodyToMono(String.class) + + private final Function>> EXCEPTION_FUNCTION = reqParam -> response -> response.bodyToMono(String.class) .handle((respBody, sink) -> { - // TODO @xin:最好是 request、response 都有哈 - log.error("【suno-api】调用失败!resp: 【{}】", respBody); - sink.error(new IllegalStateException("【suno-api】调用失败!")); + HttpRequest request = response.request(); + log.error("[suno-api] 调用失败!请求方式:[{}], 请求地址:[{}], 请求参数:[{}], 响应数据: [{}]", request.getMethod(), request.getURI(), reqParam, respBody); + sink.error(new IllegalStateException("[suno-api] 调用失败!")); }); - public SunoApi(SunoConfig config) { + + public SunoApi(String baseUrl) { this.webClient = WebClient.builder() - .baseUrl(config.getBaseUrl()) + .baseUrl(baseUrl) .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) .build(); } - public List generate(SunoRequest request) { + public List generate(MusicGenerateRequest request) { return this.webClient.post() .uri("/api/generate") - .body(Mono.just(request), SunoRequest.class) + .body(Mono.just(request), MusicGenerateRequest.class) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) - .bodyToMono(new ParameterizedTypeReference>() { }) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(new ParameterizedTypeReference>() { + }) .block(); } - public List customGenerate(SunoRequest request) { + public List customGenerate(MusicGenerateRequest request) { return this.webClient.post() .uri("/api/custom_generate") - .body(Mono.just(request), SunoRequest.class) + .body(Mono.just(request), MusicGenerateRequest.class) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) - .bodyToMono(new ParameterizedTypeReference>() { }) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(new ParameterizedTypeReference>() { + }) .block(); } - // TODO @xin: 是不是叫 chatCompletion - public List doChatCompletion(String prompt) { + public List chatCompletion(String prompt) { return this.webClient.post() .uri("/v1/chat/completions") - .body(Mono.just(new SunoRequest(prompt)), SunoRequest.class) + .body(Mono.just(new MusicGenerateRequest(prompt)), MusicGenerateRequest.class) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) - .bodyToMono(new ParameterizedTypeReference>() { }) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(prompt)) + .bodyToMono(new ParameterizedTypeReference>() { + }) .block(); } public LyricsData generateLyrics(String prompt) { return this.webClient.post() .uri("/api/generate_lyrics") - .body(Mono.just(new SunoRequest(prompt)), SunoRequest.class) + .body(Mono.just(new MusicGenerateRequest(prompt)), MusicGenerateRequest.class) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(prompt)) .bodyToMono(LyricsData.class) .block(); } - // TODO @xin:应该传入 List ids - // TODO @xin:方法名,建议使用 getMusicList - public List selectById(String ids) { + public List getMusicList(List ids) { return this.webClient.get() .uri(uriBuilder -> uriBuilder .path("/api/get") - .queryParam("ids", ids) + .queryParam("ids", CollUtil.join(ids, StrPool.COMMA)) .build()) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) - .bodyToMono(new ParameterizedTypeReference>() { }) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(ids)) + .bodyToMono(new ParameterizedTypeReference>() { + }) .block(); } - // TODO @xin:方法名,建议使用 getLimitUsage - public LimitData selectLimit() { + public LimitUsageData getLimitUsage() { return this.webClient.get() .uri("/api/get_limit") .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) - .bodyToMono(LimitData.class) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(null)) + .bodyToMono(LimitUsageData.class) .block(); } - // TODO @xin:可以改成 MusicGenerateRequest + /** * 根据提示生成音频 * @@ -121,7 +124,7 @@ public class SunoApi { * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record SunoRequest( + public record MusicGenerateRequest( String prompt, String tags, String title, @@ -130,15 +133,15 @@ public class SunoApi { @JsonProperty("make_instrumental") boolean makeInstrumental ) { - public SunoRequest(String prompt) { + public MusicGenerateRequest(String prompt) { this(prompt, null, null, null, false, false); } - public SunoRequest(String prompt, String mv, boolean makeInstrumental) { + public MusicGenerateRequest(String prompt, String mv, boolean makeInstrumental) { this(prompt, null, null, mv, false, makeInstrumental); } - public SunoRequest(String prompt, String mv, String tags, String title) { + public MusicGenerateRequest(String prompt, String mv, String tags, String title) { this(prompt, tags, title, mv, false, false); } @@ -154,12 +157,12 @@ public class SunoApi { * @param audioUrl 音乐音频的 URL * @param videoUrl 音乐视频的 URL * @param createdAt 音乐音频的创建时间 - * @param modelName + * @param modelName 模型名称 * @param status submitted、queued、streaming、complete - * @param gptDescriptionPrompt + * @param gptDescriptionPrompt 描述词 * @param prompt 生成音乐音频的提示 - * @param type - * @param tags + * @param type 操作类型 + * @param tags 音乐类型标签 */ public record MusicData( String id, @@ -195,7 +198,7 @@ public class SunoApi { /** * Suno API 响应的限额数据,目前每日免费50 */ - public record LimitData( + public record LimitUsageData( @JsonProperty("credits_left") Long creditsLeft, String period, @JsonProperty("monthly_limit") Long monthlyLimit, diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java index bd7a9bd86..5b4466805 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.framework.ai.suno; -import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import org.junit.Before; import org.junit.Test; @@ -17,26 +16,26 @@ public class SunoTests { @Before public void setup() { - String url = "https://suno-ix9nve79x-status2xxs-projects.vercel.app"; - this.sunoApi = new SunoApi(new SunoConfig(url)); + String url = "https://suno-imrqwwui8-status2xxs-projects.vercel.app"; + this.sunoApi = new SunoApi(url); } @Test public void selectById() { - System.out.println(sunoApi.selectById("d460ddda-7c87-4f34-b751-419b08a590ca,ff90ea66-49cd-4fd2-b44c-44267dfd5551")); + System.out.println(sunoApi.getMusicList(List.of("d460ddda-7c87-4f34-b751-419b08a590ca,ff90ea66-49cd-4fd2-b44c-44267dfd5551"))); } @Test public void generate() { - List generate = sunoApi.generate(new SunoApi.SunoRequest("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")); + List generate = sunoApi.generate(new SunoApi.MusicGenerateRequest("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")); System.out.println(generate); } @Test public void doChatCompletion() { - List generate = sunoApi.doChatCompletion("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); + List generate = sunoApi.chatCompletion("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); System.out.println(generate); } @@ -50,8 +49,8 @@ public class SunoTests { @Test public void selectLimit() { - SunoApi.LimitData limitData = sunoApi.selectLimit(); - System.out.println(limitData); + SunoApi.LimitUsageData limitUsageData = sunoApi.getLimitUsage(); + System.out.println(limitUsageData); } diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 13fa7d177..27891e741 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -199,7 +199,7 @@ yudao.ai: channel-id: 1237948819677904960 suno: enable: true - base-url: https://suno-ix9nve79x-status2xxs-projects.vercel.app + base-url: https://suno-imrqwwui8-status2xxs-projects.vercel.app --- #################### 芋道相关配置 ####################