From f1292fb7fe3cfc41430ddd5ddd8f2af0a993ae0d Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Fri, 7 Jun 2024 14:51:45 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=A2=9E=E5=8A=A0=E3=80=91AI=20Music?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=8F=8F=E8=BF=B0=E6=A8=A1=E5=BC=8F=E3=80=81?= =?UTF-8?q?=E6=AD=8C=E8=AF=8D=E6=A8=A1=E5=BC=8F=E7=94=9F=E6=88=90=E9=9F=B3?= =?UTF-8?q?=E4=B9=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao/module/ai/enums/AiModelEnum.java | 5 + .../module/ai/enums/AiMusicStatusEnum.java | 37 ++++++ .../admin/music/MusicController.java | 19 ++- .../admin/music/vo/MusicDataVO.java | 85 ------------- .../admin/music/vo/SunoLyricModeVO.java | 22 ++++ .../controller/admin/music/vo/SunoReqVO.java | 28 +---- .../controller/admin/music/vo/SunoRespVO.java | 40 ------ .../ai/dal/dataobject/music/AiMusicDO.java | 88 ++++++++++++++ .../ai/dal/mysql/music/AiMusicMapper.java | 14 +++ .../module/ai/service/music/MusicService.java | 17 ++- .../ai/service/music/MusicServiceImpl.java | 92 ++++++++++++-- .../ai/config/YudaoAiAutoConfiguration.java | 6 +- .../ai/config/YudaoAiProperties.java | 4 +- .../ai/core/model/suno/SunoConfig.java | 4 +- .../core/model/suno/api/AceDataSunoApi.java | 115 ------------------ .../ai/core/model/suno/api/SunoApi.java | 12 +- .../yudao/framework/ai/suno/SunoTests.java | 12 +- .../src/main/resources/application.yaml | 2 +- 18 files changed, 291 insertions(+), 311 deletions(-) create mode 100644 yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java delete mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/AceDataSunoApi.java diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java index 3d30d27cb..46e77a56c 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiModelEnum.java @@ -49,6 +49,11 @@ public enum AiModelEnum { XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"), XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), + + //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/AiMusicStatusEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java new file mode 100644 index 000000000..c29cee9d4 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.ai.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @Author xiaoxin + * @Date 2024/6/5 + */ +@AllArgsConstructor +@Getter +public enum AiMusicStatusEnum { + + SUBMITTED("submitted", "已提交"), + QUEUED("queued", "排队中"), + STREAMING("streaming", "进行中"), + COMPLETE("complete", "完成"); + + /** + * 状态 + */ + private final String status; + /** + * 状态名 + */ + private final String name; + + public static AiMusicStatusEnum valueOfStatus(String status) { + for (AiMusicStatusEnum statusEnum : AiMusicStatusEnum.values()) { + if (statusEnum.getStatus().equals(status)) { + return statusEnum; + } + } + throw new IllegalArgumentException("未知会话状态: " + status); + } + +} 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/MusicController.java index ae66d71aa..125c4a9a6 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/MusicController.java @@ -1,8 +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.controller.admin.music.vo.SunoRespVO; import cn.iocoder.yudao.module.ai.service.music.MusicService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -13,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - AI 音乐生成") @@ -23,9 +25,16 @@ public class MusicController { private final MusicService musicService; - @PostMapping("/suno-gen") - @Operation(summary = "音乐生成") - public CommonResult musicGen(@RequestBody @Valid SunoReqVO sunoReqVO) { - return success(musicService.musicGen(sunoReqVO)); + @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)); + } + } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java deleted file mode 100644 index f138d605e..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java +++ /dev/null @@ -1,85 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * 表示单个音乐数据的类 - */ -@Data -public class MusicDataVO { - /** - * 音乐数据的 ID - */ - private String id; - - /** - * 音乐音频的标题 - */ - private String title; - - /** - * 音乐音频的图片 URL - */ - @JsonProperty("image_url") - private String imageUrl; - - /** - * 音乐音频的歌词 - */ - private String lyric; - - /** - * 音乐音频的 URL - */ - @JsonProperty("audio_url") - private String audioUrl; - - /** - * 音乐视频的 URL - */ - @JsonProperty("video_url") - private String videoUrl; - - /** - * 音乐音频的创建时间 - */ - @JsonProperty("created_at") - private String createdAt; - - /** - * 使用的模型名称 - */ - private String model; - - /** - * 生成音乐音频的提示 - */ - private String prompt; - - /** - * 音乐音频的风格 - */ - private String style; - - public static List convertFrom(List musicDataList) { - return musicDataList.stream().map(musicData -> { - MusicDataVO musicDataVO = new MusicDataVO(); - musicDataVO.setId(musicData.id()); - musicDataVO.setTitle(musicData.title()); - musicDataVO.setImageUrl(musicData.imageUrl()); - musicDataVO.setLyric(musicData.lyric()); - musicDataVO.setAudioUrl(musicData.audioUrl()); - musicDataVO.setVideoUrl(musicData.videoUrl()); - musicDataVO.setCreatedAt(musicData.createdAt()); - musicDataVO.setModel(musicData.model()); - musicDataVO.setPrompt(musicData.prompt()); - musicDataVO.setStyle(musicData.style()); - return musicDataVO; - }).collect(Collectors.toList()); - } -} \ No newline at end of file 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 new file mode 100644 index 000000000..4f869b3f8 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java @@ -0,0 +1,22 @@ +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 ae40cf540..ad1e9fe29 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 @@ -2,39 +2,21 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; -import lombok.experimental.Accessors; @Data -@Accessors(chain = true) @JsonInclude(value = JsonInclude.Include.NON_NULL) public class SunoReqVO { /** * 用于生成音乐音频的提示 */ private String prompt; - /** - * 用于生成音乐音频的歌词 + * 是否纯音乐 */ - private String lyric; - + private boolean makeInstrumental; /** - * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 + * //todo 首次请求返回的模型是对的,后续更新音频返回的模型又变成v3.5了 + * 模型版本 {@link cn.iocoder.yudao.module.ai.enums.AiModelEnum} Suno */ - private boolean custom; - - /** - * 音乐音频的标题 - */ - private String title; - - /** - * 音乐音频的风格 - */ - private String style; - - /** - * 音乐音频生成后回调的 URL - */ - private String callbackUrl; + private String mv; } \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java deleted file mode 100644 index 5eed0b81d..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -/** - * API 响应的数据 - */ -@Data -public class SunoRespVO { - /** - * 表示请求是否成功 - */ - private boolean success; - - /** - * 任务 ID - */ - @JsonProperty("task_id") - private String taskId; - - /** - * 音乐数据列表 - */ - private List data; - - - //把 SunoResp转为本vo类 - public static SunoRespVO convertFrom(AceDataSunoApi.SunoResp sunoResp) { - SunoRespVO sunoRespVO = new SunoRespVO(); - sunoRespVO.setSuccess(sunoResp.success()); - sunoRespVO.setTaskId(sunoResp.taskId()); - sunoRespVO.setData(MusicDataVO.convertFrom(sunoResp.data())); - return sunoRespVO; - } - -} \ 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 new file mode 100644 index 000000000..3ab958c2f --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java @@ -0,0 +1,88 @@ +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.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @Author xiaoxin + * @Date 2024/6/5 + */ +@TableName("ai_music") +@Data +public class AiMusicDO extends BaseDO { + @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; + + @Schema(description = "音乐状态") + private String status; + + @Schema(description = "描述词") + private String gptDescriptionPrompt; + + @Schema(description = "提示词") + private String prompt; + + @Schema(description = "模型") + private String model; + + @Schema(description = "错误信息") + private String errorMessage; + + @Schema(description = "音乐风格标签") + private String tags; + + @Schema(description = "任务id") + private String taskId; + + + + 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 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/dal/mysql/music/AiMusicMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java new file mode 100644 index 000000000..88d9b1846 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.ai.dal.mysql.music; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * @Author xiaoxin + * @Date 2024/6/5 + */ +@Mapper +public interface AiMusicMapper extends BaseMapperX { + +} 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 index c0e7b34f1..7de2ee644 100644 --- 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 @@ -1,7 +1,9 @@ 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 cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; + +import java.util.List; /** * @Author xiaoxin @@ -10,10 +12,13 @@ import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; public interface MusicService { /** - * 音乐生成 - * - * @param sunoReqVO 请求实体 - * @return 响应实体 + * 音乐生成-描述模式 */ - SunoRespVO musicGen(SunoReqVO sunoReqVO); + 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 index f8b2a9d50..0f0d4197f 100644 --- 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 @@ -1,10 +1,26 @@ package cn.iocoder.yudao.module.ai.service.music; -import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; +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.controller.admin.music.vo.SunoRespVO; +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 @@ -12,20 +28,70 @@ import org.springframework.stereotype.Service; */ @Service @RequiredArgsConstructor +@Slf4j public class MusicServiceImpl implements MusicService { - private final AceDataSunoApi aceDataSunoApi; + private final SunoApi sunoApi; + private final AiMusicMapper musicMapper; + + private final Queue taskQueue = new ConcurrentLinkedQueue<>(); + @Override - public SunoRespVO musicGen(SunoReqVO sunoReqVO) { - AceDataSunoApi.SunoResp sunoResp = aceDataSunoApi.musicGen(new AceDataSunoApi.SunoReq( - sunoReqVO.getPrompt(), - sunoReqVO.getLyric(), - sunoReqVO.isCustom(), - sunoReqVO.getTitle(), - sunoReqVO.getStyle(), - sunoReqVO.getCallbackUrl() - )); - return SunoRespVO.convertFrom(sunoResp); + public List descriptionMode(SunoReqVO reqVO) { + SunoApi.SunoReq sunoReq = new SunoApi.SunoReq(reqVO.getPrompt(), reqVO.getMv(), reqVO.isMakeInstrumental()); + //默认异步 + List musicDataList = sunoApi.generate(sunoReq); + return insertMusicData(musicDataList); + } + + + @Override + public List lyricMode(SunoLyricModeVO reqVO) { + SunoApi.SunoReq sunoReq = new SunoApi.SunoReq(reqVO.getPrompt(), reqVO.getMv(), reqVO.getTags(), reqVO.getTitle()); + //默认异步 + List musicDataList = sunoApi.customGenerate(sunoReq); + return insertMusicData(musicDataList); + } + + /** + * 新增音乐数据并提交 suno任务 + * + * @param musicDataList 音乐数据列表 + * @return 音乐id集合 + */ + private List insertMusicData(List musicDataList) { + if (CollUtil.isEmpty(musicDataList)) { + return Collections.emptyList(); + } + 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()); + } + + @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 cd7512bf3..b22eff721 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 @@ -4,7 +4,7 @@ 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.AceDataSunoApi; +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; import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions; @@ -150,8 +150,8 @@ public class YudaoAiAutoConfiguration { @Bean @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") - public AceDataSunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { - return new AceDataSunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken())); + public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { + return new SunoApi(new SunoConfig(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/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java index e0cfa05f9..7b3df827a 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java @@ -141,9 +141,9 @@ public class YudaoAiProperties { private boolean enable = false; /** - * token + * suno-api 服务的基本地址 */ - private String token; + 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/SunoConfig.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java index b0526e665..1d1b62870 100644 --- 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 @@ -13,7 +13,7 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class SunoConfig { /** - * token信息 + * suno-api服务的基本路径 */ - private String token; + 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/AceDataSunoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/AceDataSunoApi.java deleted file mode 100644 index d50379d59..000000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/AceDataSunoApi.java +++ /dev/null @@ -1,115 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.suno.api; - -import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.openai.api.ApiUtils; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.util.List; - -/** - * Suno API - *
- * 文档地址:https://platform.acedata.cloud/documents/d016ee3f-421b-4b6e-989a-8beba8701701 - * - * @Author xiaoxin - * @Date 2024/5/27 - */ -@Slf4j -public class AceDataSunoApi { - - public static final String DEFAULT_BASE_URL = "https://api.acedata.cloud/suno"; - private final WebClient webClient; - - public AceDataSunoApi(SunoConfig config) { - this.webClient = WebClient.builder() - .baseUrl(DEFAULT_BASE_URL) - .defaultHeaders(ApiUtils.getJsonContentHeaders(config.getToken())) - .build(); - } - - // TODO @芋艿:方法名,要考虑下; - public SunoResp musicGen(SunoReq sunReq) { - return this.webClient.post() - .uri("/audios") - .body(Mono.just(sunReq), SunoReq.class) - .retrieve() - .onStatus(status -> !status.is2xxSuccessful(), - response -> response.bodyToMono(String.class) - .handle((respBody, sink) -> { - log.error("【Suno】调用失败!resp: 【{}】", respBody); - sink.error(new IllegalStateException("【Suno】调用失败!")); - })) - .bodyToMono(SunoResp.class) - .block(); - } - - /** - * 请求数据对象,用于生成音乐音频。 - * - * @param prompt 用于生成音乐音频的提示 - * @param lyric 用于生成音乐音频的歌词 - * @param custom 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 - * @param title 音乐音频的标题 - * @param style 音乐音频的风格 - * @param callbackUrl 音乐音频生成后回调的 URL - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record SunoReq( - String prompt, - String lyric, - boolean custom, - String title, - String style, - String callbackUrl - ) { - public SunoReq(String prompt) { - this(prompt, null, false, null, null, null); - } - - } - - /** - * SunoAPI 响应的数据。 - * - * @param success 表示请求是否成功 - * @param taskId 任务 ID - * @param data 音乐数据列表 - */ - public record SunoResp( - boolean success, - @JsonProperty("task_id") String taskId, - List data - ) { - /** - * 单个音乐数据。 - * - * @param id 音乐数据的 ID - * @param title 音乐音频的标题 - * @param imageUrl 音乐音频的图片 URL - * @param lyric 音乐音频的歌词 - * @param audioUrl 音乐音频的 URL - * @param videoUrl 音乐视频的 URL - * @param createdAt 音乐音频的创建时间 - * @param model 使用的模型名称 - * @param prompt 生成音乐音频的提示 - * @param style 音乐音频的风格 - */ - public record MusicData( - String id, - String title, - @JsonProperty("image_url") String imageUrl, - String lyric, - @JsonProperty("audio_url") String audioUrl, - @JsonProperty("video_url") String videoUrl, - @JsonProperty("created_at") String createdAt, - String model, - String prompt, - String style - ) { - } - } -} 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 2c591c65a..406f7000e 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,5 +1,6 @@ package cn.iocoder.yudao.framework.ai.core.model.suno.api; +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; @@ -25,10 +26,7 @@ import java.util.function.Predicate; @Slf4j public class SunoApi { - public static final String DEFAULT_BASE_URL = "https://suno-9323szg26-status2xxs-projects.vercel.app"; private final WebClient webClient; - - private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); private final Function> EXCEPTION_FUNCTION = response -> response.bodyToMono(String.class) .handle((respBody, sink) -> { @@ -37,9 +35,9 @@ public class SunoApi { }); - public SunoApi() { + public SunoApi(SunoConfig config) { this.webClient = WebClient.builder() - .baseUrl(DEFAULT_BASE_URL) + .baseUrl(config.getBaseUrl()) .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) .build(); } @@ -141,8 +139,8 @@ public class SunoApi { } - public SunoReq(String prompt, String tags, String title) { - this(prompt, tags, title, null, false, false); + public SunoReq(String prompt, String mv, String tags, String title) { + this(prompt, tags, title, mv, false, false); } } 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 39ac1ea9b..e89ab1f00 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 @@ -13,24 +13,22 @@ import java.util.List; */ public class SunoTests { - private SunoConfig sunoConfig; + SunoApi sunoApi; @Before public void setup() { - String token = "16b4356581984d538652354b60d69ff0"; - this.sunoConfig = new SunoConfig(token); + String url = "https://suno-ix9nve79x-status2xxs-projects.vercel.app"; + this.sunoApi = new SunoApi(new SunoConfig(url)); } @Test public void selectById() { - SunoApi sunoApi = new SunoApi(); System.out.println(sunoApi.selectById("d460ddda-7c87-4f34-b751-419b08a590ca,ff90ea66-49cd-4fd2-b44c-44267dfd5551")); } @Test public void generate() { - SunoApi sunoApi = new SunoApi(); List generate = sunoApi.generate(new SunoApi.SunoReq("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")); System.out.println(generate); } @@ -38,7 +36,6 @@ public class SunoTests { @Test public void doChatCompletion() { - SunoApi sunoApi = new SunoApi(); List generate = sunoApi.doChatCompletion("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); System.out.println(generate); } @@ -46,16 +43,13 @@ public class SunoTests { @Test public void generateLyrics() { - SunoApi sunoApi = new SunoApi(); SunoApi.LyricsData lyricsData = sunoApi.generateLyrics("A soothing lullaby"); System.out.println(lyricsData); } - @Test public void selectLimit() { - SunoApi sunoApi = new SunoApi(); SunoApi.LimitData limitData = sunoApi.selectLimit(); System.out.println(limitData); } diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index f1b8f56f7..1c3b35cd4 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -201,7 +201,7 @@ yudao.ai: channel-id: 1237948819677904960 suno: enable: true - token: 16b4356581984d538652354b60d69ff0 + base-url: https://suno-ix9nve79x-status2xxs-projects.vercel.app --- #################### 芋道相关配置 ####################