Commit 5fabb0b757190ac62309cd11eb5531fea30f0176
Committed by
Gitee
!3 接入suno-api
Merge pull request !3 from 小新/master-jdk21-ai
Showing
17 changed files
with
473 additions
and
248 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/AiMusicStatusEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.enums; | ||
| 2 | + | ||
| 3 | +import lombok.AllArgsConstructor; | ||
| 4 | +import lombok.Getter; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * @Author xiaoxin | ||
| 8 | + * @Date 2024/6/5 | ||
| 9 | + */ | ||
| 10 | +@AllArgsConstructor | ||
| 11 | +@Getter | ||
| 12 | +public enum AiMusicStatusEnum { | ||
| 13 | + | ||
| 14 | + SUBMITTED("submitted", "已提交"), | ||
| 15 | + QUEUED("queued", "排队中"), | ||
| 16 | + STREAMING("streaming", "进行中"), | ||
| 17 | + COMPLETE("complete", "完成"); | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * 状态 | ||
| 21 | + */ | ||
| 22 | + private final String status; | ||
| 23 | + /** | ||
| 24 | + * 状态名 | ||
| 25 | + */ | ||
| 26 | + private final String name; | ||
| 27 | + | ||
| 28 | + public static AiMusicStatusEnum valueOfStatus(String status) { | ||
| 29 | + for (AiMusicStatusEnum statusEnum : AiMusicStatusEnum.values()) { | ||
| 30 | + if (statusEnum.getStatus().equals(status)) { | ||
| 31 | + return statusEnum; | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + throw new IllegalArgumentException("未知会话状态: " + status); | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | +} |
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiModelEnum.java
| @@ -49,6 +49,11 @@ public enum AiModelEnum { | @@ -49,6 +49,11 @@ public enum AiModelEnum { | ||
| 49 | XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"), | 49 | XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"), |
| 50 | XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), | 50 | XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), |
| 51 | XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), | 51 | XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), |
| 52 | + | ||
| 53 | + //Suno | ||
| 54 | + SUNO_2( "SUNO-2", "chirp-v2-xxl-alpha",null), | ||
| 55 | + SUNO_3_0( "SUNO-3.0", "chirp-v3-0",null), | ||
| 56 | + SUNO_3_5( "SUNO-3.5", "chirp-v3.5",null), | ||
| 52 | ; | 57 | ; |
| 53 | 58 | ||
| 54 | /** | 59 | /** |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoLyricModeVO; | ||
| 4 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | 5 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; |
| 5 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; | ||
| 6 | import cn.iocoder.yudao.module.ai.service.music.MusicService; | 6 | import cn.iocoder.yudao.module.ai.service.music.MusicService; |
| 7 | import io.swagger.v3.oas.annotations.Operation; | 7 | import io.swagger.v3.oas.annotations.Operation; |
| 8 | import io.swagger.v3.oas.annotations.tags.Tag; | 8 | import io.swagger.v3.oas.annotations.tags.Tag; |
| @@ -13,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody; | @@ -13,6 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody; | ||
| 13 | import org.springframework.web.bind.annotation.RequestMapping; | 13 | import org.springframework.web.bind.annotation.RequestMapping; |
| 14 | import org.springframework.web.bind.annotation.RestController; | 14 | import org.springframework.web.bind.annotation.RestController; |
| 15 | 15 | ||
| 16 | +import java.util.List; | ||
| 17 | + | ||
| 16 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 18 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 17 | 19 | ||
| 18 | @Tag(name = "管理后台 - AI 音乐生成") | 20 | @Tag(name = "管理后台 - AI 音乐生成") |
| @@ -23,9 +25,16 @@ public class MusicController { | @@ -23,9 +25,16 @@ public class MusicController { | ||
| 23 | 25 | ||
| 24 | private final MusicService musicService; | 26 | private final MusicService musicService; |
| 25 | 27 | ||
| 26 | - @PostMapping("/suno-gen") | ||
| 27 | - @Operation(summary = "音乐生成") | ||
| 28 | - public CommonResult<SunoRespVO> musicGen(@RequestBody @Valid SunoReqVO sunoReqVO) { | ||
| 29 | - return success(musicService.musicGen(sunoReqVO)); | 28 | + @PostMapping("generate/description-mode") |
| 29 | + @Operation(summary = "音乐生成-描述模式") | ||
| 30 | + public CommonResult<List<Long>> descriptionMode(@RequestBody @Valid SunoReqVO sunoReqVO) { | ||
| 31 | + return success(musicService.descriptionMode(sunoReqVO)); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @PostMapping("generate/lyric-mode") | ||
| 35 | + @Operation(summary = "音乐生成-歌词模式") | ||
| 36 | + public CommonResult<List<Long>> lyricMode(@RequestBody @Valid SunoLyricModeVO sunoLyricModeVO) { | ||
| 37 | + return success(musicService.lyricMode(sunoLyricModeVO)); | ||
| 30 | } | 38 | } |
| 39 | + | ||
| 31 | } | 40 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||
| 4 | -import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 5 | -import lombok.Data; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | -import java.util.stream.Collectors; | ||
| 9 | - | ||
| 10 | -/** | ||
| 11 | - * 表示单个音乐数据的类 | ||
| 12 | - */ | ||
| 13 | -@Data | ||
| 14 | -public class MusicDataVO { | ||
| 15 | - /** | ||
| 16 | - * 音乐数据的 ID | ||
| 17 | - */ | ||
| 18 | - private String id; | ||
| 19 | - | ||
| 20 | - /** | ||
| 21 | - * 音乐音频的标题 | ||
| 22 | - */ | ||
| 23 | - private String title; | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 音乐音频的图片 URL | ||
| 27 | - */ | ||
| 28 | - @JsonProperty("image_url") | ||
| 29 | - private String imageUrl; | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 音乐音频的歌词 | ||
| 33 | - */ | ||
| 34 | - private String lyric; | ||
| 35 | - | ||
| 36 | - /** | ||
| 37 | - * 音乐音频的 URL | ||
| 38 | - */ | ||
| 39 | - @JsonProperty("audio_url") | ||
| 40 | - private String audioUrl; | ||
| 41 | - | ||
| 42 | - /** | ||
| 43 | - * 音乐视频的 URL | ||
| 44 | - */ | ||
| 45 | - @JsonProperty("video_url") | ||
| 46 | - private String videoUrl; | ||
| 47 | - | ||
| 48 | - /** | ||
| 49 | - * 音乐音频的创建时间 | ||
| 50 | - */ | ||
| 51 | - @JsonProperty("created_at") | ||
| 52 | - private String createdAt; | ||
| 53 | - | ||
| 54 | - /** | ||
| 55 | - * 使用的模型名称 | ||
| 56 | - */ | ||
| 57 | - private String model; | ||
| 58 | - | ||
| 59 | - /** | ||
| 60 | - * 生成音乐音频的提示 | ||
| 61 | - */ | ||
| 62 | - private String prompt; | ||
| 63 | - | ||
| 64 | - /** | ||
| 65 | - * 音乐音频的风格 | ||
| 66 | - */ | ||
| 67 | - private String style; | ||
| 68 | - | ||
| 69 | - public static List<MusicDataVO> convertFrom(List<SunoApi.SunoResp.MusicData> musicDataList) { | ||
| 70 | - return musicDataList.stream().map(musicData -> { | ||
| 71 | - MusicDataVO musicDataVO = new MusicDataVO(); | ||
| 72 | - musicDataVO.setId(musicData.id()); | ||
| 73 | - musicDataVO.setTitle(musicData.title()); | ||
| 74 | - musicDataVO.setImageUrl(musicData.imageUrl()); | ||
| 75 | - musicDataVO.setLyric(musicData.lyric()); | ||
| 76 | - musicDataVO.setAudioUrl(musicData.audioUrl()); | ||
| 77 | - musicDataVO.setVideoUrl(musicData.videoUrl()); | ||
| 78 | - musicDataVO.setCreatedAt(musicData.createdAt()); | ||
| 79 | - musicDataVO.setModel(musicData.model()); | ||
| 80 | - musicDataVO.setPrompt(musicData.prompt()); | ||
| 81 | - musicDataVO.setStyle(musicData.style()); | ||
| 82 | - return musicDataVO; | ||
| 83 | - }).collect(Collectors.toList()); | ||
| 84 | - } | ||
| 85 | -} | ||
| 86 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoLyricModeVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * @Author jxli@quant360.com | ||
| 7 | + * @Date 2024/6/7 | ||
| 8 | + */ | ||
| 9 | +@Data | ||
| 10 | +public class SunoLyricModeVO extends SunoReqVO { | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * 标签/音乐风格 | ||
| 14 | + */ | ||
| 15 | + private String tags; | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 音乐名称 | ||
| 19 | + */ | ||
| 20 | + private String title; | ||
| 21 | + | ||
| 22 | +} |
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; | @@ -2,39 +2,21 @@ package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | 2 | ||
| 3 | import com.fasterxml.jackson.annotation.JsonInclude; | 3 | import com.fasterxml.jackson.annotation.JsonInclude; |
| 4 | import lombok.Data; | 4 | import lombok.Data; |
| 5 | -import lombok.experimental.Accessors; | ||
| 6 | 5 | ||
| 7 | @Data | 6 | @Data |
| 8 | -@Accessors(chain = true) | ||
| 9 | @JsonInclude(value = JsonInclude.Include.NON_NULL) | 7 | @JsonInclude(value = JsonInclude.Include.NON_NULL) |
| 10 | public class SunoReqVO { | 8 | public class SunoReqVO { |
| 11 | /** | 9 | /** |
| 12 | * 用于生成音乐音频的提示 | 10 | * 用于生成音乐音频的提示 |
| 13 | */ | 11 | */ |
| 14 | private String prompt; | 12 | private String prompt; |
| 15 | - | ||
| 16 | - /** | ||
| 17 | - * 用于生成音乐音频的歌词 | ||
| 18 | - */ | ||
| 19 | - private String lyric; | ||
| 20 | - | ||
| 21 | /** | 13 | /** |
| 22 | - * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | 14 | + * 是否纯音乐 |
| 23 | */ | 15 | */ |
| 24 | - private boolean custom; | ||
| 25 | - | ||
| 26 | - /** | ||
| 27 | - * 音乐音频的标题 | ||
| 28 | - */ | ||
| 29 | - private String title; | ||
| 30 | - | ||
| 31 | - /** | ||
| 32 | - * 音乐音频的风格 | ||
| 33 | - */ | ||
| 34 | - private String style; | ||
| 35 | - | 16 | + private boolean makeInstrumental; |
| 36 | /** | 17 | /** |
| 37 | - * 音乐音频生成后回调的 URL | 18 | + * //todo 首次请求返回的模型是对的,后续更新音频返回的模型又变成v3.5了 |
| 19 | + * 模型版本 {@link cn.iocoder.yudao.module.ai.enums.AiModelEnum} Suno | ||
| 38 | */ | 20 | */ |
| 39 | - private String callbackUrl; | 21 | + private String mv; |
| 40 | } | 22 | } |
| 41 | \ No newline at end of file | 23 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | - | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||
| 4 | -import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 5 | -import lombok.Data; | ||
| 6 | - | ||
| 7 | -import java.util.List; | ||
| 8 | - | ||
| 9 | -/** | ||
| 10 | - * API 响应的数据 | ||
| 11 | - */ | ||
| 12 | -@Data | ||
| 13 | -public class SunoRespVO { | ||
| 14 | - /** | ||
| 15 | - * 表示请求是否成功 | ||
| 16 | - */ | ||
| 17 | - private boolean success; | ||
| 18 | - | ||
| 19 | - /** | ||
| 20 | - * 任务 ID | ||
| 21 | - */ | ||
| 22 | - @JsonProperty("task_id") | ||
| 23 | - private String taskId; | ||
| 24 | - | ||
| 25 | - /** | ||
| 26 | - * 音乐数据列表 | ||
| 27 | - */ | ||
| 28 | - private List<MusicDataVO> data; | ||
| 29 | - | ||
| 30 | - | ||
| 31 | - //把 SunoResp转为本vo类 | ||
| 32 | - public static SunoRespVO convertFrom(SunoApi.SunoResp sunoResp) { | ||
| 33 | - SunoRespVO sunoRespVO = new SunoRespVO(); | ||
| 34 | - sunoRespVO.setSuccess(sunoResp.success()); | ||
| 35 | - sunoRespVO.setTaskId(sunoResp.taskId()); | ||
| 36 | - sunoRespVO.setData(MusicDataVO.convertFrom(sunoResp.data())); | ||
| 37 | - return sunoRespVO; | ||
| 38 | - } | ||
| 39 | - | ||
| 40 | -} | ||
| 41 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.dal.dataobject.music; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.IdType; | ||
| 6 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 7 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 8 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 9 | +import lombok.Data; | ||
| 10 | + | ||
| 11 | +import java.util.List; | ||
| 12 | +import java.util.stream.Collectors; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * @Author xiaoxin | ||
| 16 | + * @Date 2024/6/5 | ||
| 17 | + */ | ||
| 18 | +@TableName("ai_music") | ||
| 19 | +@Data | ||
| 20 | +public class AiMusicDO extends BaseDO { | ||
| 21 | + @TableId(type = IdType.AUTO) | ||
| 22 | + @Schema(description = "编号") | ||
| 23 | + private Long id; | ||
| 24 | + | ||
| 25 | + @Schema(description = "用户编号") | ||
| 26 | + private Long userId; | ||
| 27 | + | ||
| 28 | + @Schema(description = "音乐名称") | ||
| 29 | + private String title; | ||
| 30 | + | ||
| 31 | + @Schema(description = "图片地址") | ||
| 32 | + private String imageUrl; | ||
| 33 | + | ||
| 34 | + @Schema(description = "歌词") | ||
| 35 | + private String lyric; | ||
| 36 | + | ||
| 37 | + @Schema(description = "音频地址") | ||
| 38 | + private String audioUrl; | ||
| 39 | + | ||
| 40 | + @Schema(description = "视频地址") | ||
| 41 | + private String videoUrl; | ||
| 42 | + | ||
| 43 | + @Schema(description = "音乐状态") | ||
| 44 | + private String status; | ||
| 45 | + | ||
| 46 | + @Schema(description = "描述词") | ||
| 47 | + private String gptDescriptionPrompt; | ||
| 48 | + | ||
| 49 | + @Schema(description = "提示词") | ||
| 50 | + private String prompt; | ||
| 51 | + | ||
| 52 | + @Schema(description = "模型") | ||
| 53 | + private String model; | ||
| 54 | + | ||
| 55 | + @Schema(description = "错误信息") | ||
| 56 | + private String errorMessage; | ||
| 57 | + | ||
| 58 | + @Schema(description = "音乐风格标签") | ||
| 59 | + private String tags; | ||
| 60 | + | ||
| 61 | + @Schema(description = "任务id") | ||
| 62 | + private String taskId; | ||
| 63 | + | ||
| 64 | + | ||
| 65 | + | ||
| 66 | + public static AiMusicDO convertFrom(SunoApi.MusicData musicData) { | ||
| 67 | + return new AiMusicDO() | ||
| 68 | + .setTaskId(musicData.id()) | ||
| 69 | + .setPrompt(musicData.prompt()) | ||
| 70 | + .setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | ||
| 71 | + .setAudioUrl(musicData.audioUrl()) | ||
| 72 | + .setVideoUrl(musicData.videoUrl()) | ||
| 73 | + .setImageUrl(musicData.imageUrl()) | ||
| 74 | + .setLyric(musicData.lyric()) | ||
| 75 | + .setTitle(musicData.title()) | ||
| 76 | + .setStatus(musicData.status()) | ||
| 77 | + .setModel(musicData.modelName()) | ||
| 78 | + .setTags(musicData.tags()); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + public static List<AiMusicDO> convertFrom(List<SunoApi.MusicData> musicDataList) { | ||
| 82 | + return musicDataList.stream() | ||
| 83 | + .map(AiMusicDO::convertFrom) | ||
| 84 | + .collect(Collectors.toList()); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + | ||
| 88 | +} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.dal.mysql.music; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 4 | +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | ||
| 5 | +import org.apache.ibatis.annotations.Mapper; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * @Author xiaoxin | ||
| 9 | + * @Date 2024/6/5 | ||
| 10 | + */ | ||
| 11 | +@Mapper | ||
| 12 | +public interface AiMusicMapper extends BaseMapperX<AiMusicDO> { | ||
| 13 | + | ||
| 14 | +} |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java
| 1 | package cn.iocoder.yudao.module.ai.service.music; | 1 | package cn.iocoder.yudao.module.ai.service.music; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoLyricModeVO; | ||
| 3 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | 4 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; |
| 4 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; | 5 | + |
| 6 | +import java.util.List; | ||
| 5 | 7 | ||
| 6 | /** | 8 | /** |
| 7 | * @Author xiaoxin | 9 | * @Author xiaoxin |
| @@ -10,10 +12,13 @@ import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; | @@ -10,10 +12,13 @@ import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; | ||
| 10 | public interface MusicService { | 12 | public interface MusicService { |
| 11 | 13 | ||
| 12 | /** | 14 | /** |
| 13 | - * 音乐生成 | ||
| 14 | - * | ||
| 15 | - * @param sunoReqVO 请求实体 | ||
| 16 | - * @return 响应实体 | 15 | + * 音乐生成-描述模式 |
| 17 | */ | 16 | */ |
| 18 | - SunoRespVO musicGen(SunoReqVO sunoReqVO); | 17 | + List<Long> descriptionMode(SunoReqVO reqVO); |
| 18 | + | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 音乐生成-歌词模式 | ||
| 22 | + **/ | ||
| 23 | + List<Long> lyricMode(SunoLyricModeVO reqVO); | ||
| 19 | } | 24 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java
| 1 | package cn.iocoder.yudao.module.ai.service.music; | 1 | package cn.iocoder.yudao.module.ai.service.music; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.text.StrPool; | ||
| 3 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 5 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 6 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoLyricModeVO; | ||
| 4 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | 7 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; |
| 5 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; | 8 | +import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 9 | +import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; | ||
| 10 | +import cn.iocoder.yudao.module.ai.enums.AiMusicStatusEnum; | ||
| 11 | +import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||
| 6 | import lombok.RequiredArgsConstructor; | 12 | import lombok.RequiredArgsConstructor; |
| 13 | +import lombok.extern.slf4j.Slf4j; | ||
| 14 | +import org.springframework.scheduling.annotation.Scheduled; | ||
| 7 | import org.springframework.stereotype.Service; | 15 | import org.springframework.stereotype.Service; |
| 16 | +import org.springframework.transaction.annotation.Transactional; | ||
| 17 | + | ||
| 18 | +import java.util.*; | ||
| 19 | +import java.util.concurrent.ConcurrentLinkedQueue; | ||
| 20 | +import java.util.concurrent.TimeUnit; | ||
| 21 | +import java.util.stream.Collectors; | ||
| 22 | + | ||
| 23 | +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||
| 8 | 24 | ||
| 9 | /** | 25 | /** |
| 10 | * @Author xiaoxin | 26 | * @Author xiaoxin |
| @@ -12,20 +28,70 @@ import org.springframework.stereotype.Service; | @@ -12,20 +28,70 @@ import org.springframework.stereotype.Service; | ||
| 12 | */ | 28 | */ |
| 13 | @Service | 29 | @Service |
| 14 | @RequiredArgsConstructor | 30 | @RequiredArgsConstructor |
| 31 | +@Slf4j | ||
| 15 | public class MusicServiceImpl implements MusicService { | 32 | public class MusicServiceImpl implements MusicService { |
| 16 | 33 | ||
| 17 | private final SunoApi sunoApi; | 34 | private final SunoApi sunoApi; |
| 35 | + private final AiMusicMapper musicMapper; | ||
| 36 | + | ||
| 37 | + private final Queue<String> taskQueue = new ConcurrentLinkedQueue<>(); | ||
| 38 | + | ||
| 18 | 39 | ||
| 19 | @Override | 40 | @Override |
| 20 | - public SunoRespVO musicGen(SunoReqVO sunoReqVO) { | ||
| 21 | - SunoApi.SunoResp sunoResp = sunoApi.musicGen(new SunoApi.SunoReq( | ||
| 22 | - sunoReqVO.getPrompt(), | ||
| 23 | - sunoReqVO.getLyric(), | ||
| 24 | - sunoReqVO.isCustom(), | ||
| 25 | - sunoReqVO.getTitle(), | ||
| 26 | - sunoReqVO.getStyle(), | ||
| 27 | - sunoReqVO.getCallbackUrl() | ||
| 28 | - )); | ||
| 29 | - return SunoRespVO.convertFrom(sunoResp); | 41 | + public List<Long> descriptionMode(SunoReqVO reqVO) { |
| 42 | + SunoApi.SunoReq sunoReq = new SunoApi.SunoReq(reqVO.getPrompt(), reqVO.getMv(), reqVO.isMakeInstrumental()); | ||
| 43 | + //默认异步 | ||
| 44 | + List<SunoApi.MusicData> musicDataList = sunoApi.generate(sunoReq); | ||
| 45 | + return insertMusicData(musicDataList); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + @Override | ||
| 50 | + public List<Long> lyricMode(SunoLyricModeVO reqVO) { | ||
| 51 | + SunoApi.SunoReq sunoReq = new SunoApi.SunoReq(reqVO.getPrompt(), reqVO.getMv(), reqVO.getTags(), reqVO.getTitle()); | ||
| 52 | + //默认异步 | ||
| 53 | + List<SunoApi.MusicData> musicDataList = sunoApi.customGenerate(sunoReq); | ||
| 54 | + return insertMusicData(musicDataList); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + /** | ||
| 58 | + * 新增音乐数据并提交 suno任务 | ||
| 59 | + * | ||
| 60 | + * @param musicDataList 音乐数据列表 | ||
| 61 | + * @return 音乐id集合 | ||
| 62 | + */ | ||
| 63 | + private List<Long> insertMusicData(List<SunoApi.MusicData> musicDataList) { | ||
| 64 | + if (CollUtil.isEmpty(musicDataList)) { | ||
| 65 | + return Collections.emptyList(); | ||
| 66 | + } | ||
| 67 | + return AiMusicDO.convertFrom(musicDataList).stream() | ||
| 68 | + .peek(musicDO -> musicMapper.insert(musicDO.setUserId(getLoginUserId()))) | ||
| 69 | + .peek(e -> Optional.of(e.getTaskId()).ifPresent(taskQueue::add)) | ||
| 70 | + .map(AiMusicDO::getId) | ||
| 71 | + .collect(Collectors.toList()); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + @Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) | ||
| 75 | + @Transactional | ||
| 76 | + public void flushSunoTask() { | ||
| 77 | + if (CollUtil.isEmpty(taskQueue)) { | ||
| 78 | + return; | ||
| 79 | + } | ||
| 80 | + CollUtil.split(taskQueue, 5). | ||
| 81 | + stream().map(chunk -> CollUtil.join(chunk, StrPool.COMMA)) | ||
| 82 | + .forEach(taskIds -> { | ||
| 83 | + List<SunoApi.MusicData> musicData = sunoApi.selectById(taskIds); | ||
| 84 | + musicData.stream() | ||
| 85 | + .map(AiMusicDO::convertFrom) | ||
| 86 | + .forEach(musicDO -> { | ||
| 87 | + //更新音乐生成结果 | ||
| 88 | + musicMapper.update(musicDO, Wrappers.<AiMusicDO>lambdaUpdate().eq(AiMusicDO::getTaskId, musicDO.getTaskId())); | ||
| 89 | + //完成后剔除任务 | ||
| 90 | + if (Objects.equals(AiMusicStatusEnum.COMPLETE.getStatus(), musicDO.getStatus())) { | ||
| 91 | + taskQueue.remove(musicDO.getTaskId()); | ||
| 92 | + } | ||
| 93 | + }); | ||
| 94 | + }); | ||
| 95 | + | ||
| 30 | } | 96 | } |
| 31 | } | 97 | } |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| @@ -151,7 +151,7 @@ public class YudaoAiAutoConfiguration { | @@ -151,7 +151,7 @@ public class YudaoAiAutoConfiguration { | ||
| 151 | @Bean | 151 | @Bean |
| 152 | @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") | 152 | @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") |
| 153 | public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { | 153 | public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { |
| 154 | - return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken())); | 154 | + return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getBaseUrl())); |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, | 157 | private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
| @@ -119,9 +119,9 @@ public class YudaoAiProperties { | @@ -119,9 +119,9 @@ public class YudaoAiProperties { | ||
| 119 | 119 | ||
| 120 | private boolean enable = false; | 120 | private boolean enable = false; |
| 121 | /** | 121 | /** |
| 122 | - * token | 122 | + * suno-api 服务的基本地址 |
| 123 | */ | 123 | */ |
| 124 | - private String token; | 124 | + private String baseUrl; |
| 125 | 125 | ||
| 126 | } | 126 | } |
| 127 | 127 |
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; | @@ -13,7 +13,7 @@ import lombok.NoArgsConstructor; | ||
| 13 | @AllArgsConstructor | 13 | @AllArgsConstructor |
| 14 | public class SunoConfig { | 14 | public class SunoConfig { |
| 15 | /** | 15 | /** |
| 16 | - * token信息 | 16 | + * suno-api服务的基本路径 |
| 17 | */ | 17 | */ |
| 18 | - private String token; | 18 | + private String baseUrl; |
| 19 | } | 19 | } |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
| @@ -4,112 +4,207 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; | @@ -4,112 +4,207 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; | ||
| 4 | import com.fasterxml.jackson.annotation.JsonInclude; | 4 | import com.fasterxml.jackson.annotation.JsonInclude; |
| 5 | import com.fasterxml.jackson.annotation.JsonProperty; | 5 | import com.fasterxml.jackson.annotation.JsonProperty; |
| 6 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | -import org.springframework.ai.openai.api.ApiUtils; | 7 | +import org.springframework.core.ParameterizedTypeReference; |
| 8 | +import org.springframework.http.HttpStatusCode; | ||
| 9 | +import org.springframework.http.MediaType; | ||
| 10 | +import org.springframework.web.reactive.function.client.ClientResponse; | ||
| 8 | import org.springframework.web.reactive.function.client.WebClient; | 11 | import org.springframework.web.reactive.function.client.WebClient; |
| 9 | import reactor.core.publisher.Mono; | 12 | import reactor.core.publisher.Mono; |
| 10 | 13 | ||
| 11 | import java.util.List; | 14 | import java.util.List; |
| 15 | +import java.util.function.Function; | ||
| 16 | +import java.util.function.Predicate; | ||
| 12 | 17 | ||
| 13 | /** | 18 | /** |
| 14 | * Suno API | 19 | * Suno API |
| 15 | * <br> | 20 | * <br> |
| 16 | - * 文档地址:https://platform.acedata.cloud/documents/d016ee3f-421b-4b6e-989a-8beba8701701 | 21 | + * 文档地址:https://github.com/status2xx/suno-api/blob/main/README_CN.md |
| 17 | * | 22 | * |
| 18 | * @Author xiaoxin | 23 | * @Author xiaoxin |
| 19 | - * @Date 2024/5/27 | 24 | + * @Date 2024/6/3 |
| 20 | */ | 25 | */ |
| 21 | @Slf4j | 26 | @Slf4j |
| 22 | public class SunoApi { | 27 | public class SunoApi { |
| 23 | 28 | ||
| 24 | - public static final String DEFAULT_BASE_URL = "https://api.acedata.cloud/suno"; | ||
| 25 | private final WebClient webClient; | 29 | private final WebClient webClient; |
| 30 | + private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful(); | ||
| 31 | + private final Function<ClientResponse, Mono<? extends Throwable>> EXCEPTION_FUNCTION = response -> response.bodyToMono(String.class) | ||
| 32 | + .handle((respBody, sink) -> { | ||
| 33 | + log.error("【suno-api】调用失败!resp: 【{}】", respBody); | ||
| 34 | + sink.error(new IllegalStateException("【suno-api】调用失败!")); | ||
| 35 | + }); | ||
| 36 | + | ||
| 26 | 37 | ||
| 27 | public SunoApi(SunoConfig config) { | 38 | public SunoApi(SunoConfig config) { |
| 28 | this.webClient = WebClient.builder() | 39 | this.webClient = WebClient.builder() |
| 29 | - .baseUrl(DEFAULT_BASE_URL) | ||
| 30 | - .defaultHeaders(ApiUtils.getJsonContentHeaders(config.getToken())) | 40 | + .baseUrl(config.getBaseUrl()) |
| 41 | + .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) | ||
| 31 | .build(); | 42 | .build(); |
| 32 | } | 43 | } |
| 33 | 44 | ||
| 34 | - // TODO @芋艿:方法名,要考虑下; | ||
| 35 | - public SunoResp musicGen(SunoReq sunReq) { | 45 | + public List<MusicData> generate(SunoApi.SunoReq sunReq) { |
| 46 | + return this.webClient.post() | ||
| 47 | + .uri("/api/generate") | ||
| 48 | + .body(Mono.just(sunReq), SunoApi.SunoReq.class) | ||
| 49 | + .retrieve() | ||
| 50 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 51 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 52 | + }) | ||
| 53 | + .block(); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + public List<MusicData> customGenerate(SunoApi.SunoReq sunReq) { | ||
| 57 | + return this.webClient.post() | ||
| 58 | + .uri("/api/custom_generate") | ||
| 59 | + .body(Mono.just(sunReq), SunoApi.SunoReq.class) | ||
| 60 | + .retrieve() | ||
| 61 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 62 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 63 | + }) | ||
| 64 | + .block(); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + public List<MusicData> doChatCompletion(String prompt) { | ||
| 36 | return this.webClient.post() | 68 | return this.webClient.post() |
| 37 | - .uri("/audios") | ||
| 38 | - .body(Mono.just(sunReq), SunoReq.class) | 69 | + .uri("/v1/chat/completions") |
| 70 | + .body(Mono.just(new SunoReq(prompt)), SunoApi.SunoReq.class) | ||
| 39 | .retrieve() | 71 | .retrieve() |
| 40 | - .onStatus(status -> !status.is2xxSuccessful(), | ||
| 41 | - response -> response.bodyToMono(String.class) | ||
| 42 | - .handle((respBody, sink) -> { | ||
| 43 | - log.error("【Suno】调用失败!resp: 【{}】", respBody); | ||
| 44 | - sink.error(new IllegalStateException("【Suno】调用失败!")); | ||
| 45 | - })) | ||
| 46 | - .bodyToMono(SunoResp.class) | 72 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) |
| 73 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 74 | + }) | ||
| 47 | .block(); | 75 | .block(); |
| 48 | } | 76 | } |
| 49 | 77 | ||
| 78 | + public LyricsData generateLyrics(String prompt) { | ||
| 79 | + return this.webClient.post() | ||
| 80 | + .uri("/api/generate_lyrics") | ||
| 81 | + .body(Mono.just(new SunoReq(prompt)), SunoApi.SunoReq.class) | ||
| 82 | + .retrieve() | ||
| 83 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 84 | + .bodyToMono(LyricsData.class) | ||
| 85 | + .block(); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + | ||
| 89 | + public List<MusicData> selectById(String ids) { | ||
| 90 | + return this.webClient.get() | ||
| 91 | + .uri(uriBuilder -> uriBuilder | ||
| 92 | + .path("/api/get") | ||
| 93 | + .queryParam("ids", ids) | ||
| 94 | + .build()) | ||
| 95 | + .retrieve() | ||
| 96 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 97 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 98 | + }) | ||
| 99 | + .block(); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + | ||
| 103 | + public LimitData selectLimit() { | ||
| 104 | + return this.webClient.get() | ||
| 105 | + .uri("/api/get_limit") | ||
| 106 | + .retrieve() | ||
| 107 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 108 | + .bodyToMono(LimitData.class) | ||
| 109 | + .block(); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + | ||
| 50 | /** | 113 | /** |
| 51 | - * 请求数据对象,用于生成音乐音频。 | 114 | + * 根据提示生成音频 |
| 52 | * | 115 | * |
| 53 | - * @param prompt 用于生成音乐音频的提示 | ||
| 54 | - * @param lyric 用于生成音乐音频的歌词 | ||
| 55 | - * @param custom 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | ||
| 56 | - * @param title 音乐音频的标题 | ||
| 57 | - * @param style 音乐音频的风格 | ||
| 58 | - * @param callbackUrl 音乐音频生成后回调的 URL | 116 | + * @param prompt 用于生成音乐音频的提示 |
| 117 | + * @param tags 音乐风格 | ||
| 118 | + * @param title 音乐名称 | ||
| 119 | + * @param mv 模型 | ||
| 120 | + * @param waitAudio false表示后台模式,仅返回音频任务信息,需要调用get API获取详细的音频信息。 | ||
| 121 | + * true表示同步模式,API最多等待100s,音频生成完毕后直接返回音频链接等信息,建议在GPT等agent中使用。 | ||
| 122 | + * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | ||
| 59 | */ | 123 | */ |
| 60 | @JsonInclude(value = JsonInclude.Include.NON_NULL) | 124 | @JsonInclude(value = JsonInclude.Include.NON_NULL) |
| 61 | public record SunoReq( | 125 | public record SunoReq( |
| 62 | String prompt, | 126 | String prompt, |
| 63 | - String lyric, | ||
| 64 | - boolean custom, | 127 | + String tags, |
| 65 | String title, | 128 | String title, |
| 66 | - String style, | ||
| 67 | - String callbackUrl | 129 | + String mv, |
| 130 | + @JsonProperty("wait_audio") boolean waitAudio, | ||
| 131 | + @JsonProperty("make_instrumental") boolean makeInstrumental | ||
| 68 | ) { | 132 | ) { |
| 69 | public SunoReq(String prompt) { | 133 | public SunoReq(String prompt) { |
| 70 | - this(prompt, null, false, null, null, null); | 134 | + this(prompt, null, null, null, false, false); |
| 135 | + } | ||
| 136 | + | ||
| 137 | + public SunoReq(String prompt, String mv, boolean makeInstrumental) { | ||
| 138 | + this(prompt, null, null, mv, false, makeInstrumental); | ||
| 71 | } | 139 | } |
| 72 | 140 | ||
| 141 | + | ||
| 142 | + public SunoReq(String prompt, String mv, String tags, String title) { | ||
| 143 | + this(prompt, tags, title, mv, false, false); | ||
| 144 | + } | ||
| 73 | } | 145 | } |
| 74 | 146 | ||
| 147 | + | ||
| 75 | /** | 148 | /** |
| 76 | - * SunoAPI 响应的数据。 | 149 | + * SunoAPI 响应的音频数据。 |
| 77 | * | 150 | * |
| 78 | - * @param success 表示请求是否成功 | ||
| 79 | - * @param taskId 任务 ID | ||
| 80 | - * @param data 音乐数据列表 | 151 | + * @param id 音乐数据的 ID |
| 152 | + * @param title 音乐音频的标题 | ||
| 153 | + * @param imageUrl 音乐音频的图片 URL | ||
| 154 | + * @param lyric 音乐音频的歌词 | ||
| 155 | + * @param audioUrl 音乐音频的 URL | ||
| 156 | + * @param videoUrl 音乐视频的 URL | ||
| 157 | + * @param createdAt 音乐音频的创建时间 | ||
| 158 | + * @param modelName | ||
| 159 | + * @param status submitted、queued、streaming、complete | ||
| 160 | + * @param gptDescriptionPrompt | ||
| 161 | + * @param prompt 生成音乐音频的提示 | ||
| 162 | + * @param type | ||
| 163 | + * @param tags | ||
| 81 | */ | 164 | */ |
| 82 | - public record SunoResp( | ||
| 83 | - boolean success, | ||
| 84 | - @JsonProperty("task_id") String taskId, | ||
| 85 | - List<MusicData> data | 165 | + public record MusicData( |
| 166 | + String id, | ||
| 167 | + String title, | ||
| 168 | + @JsonProperty("image_url") String imageUrl, | ||
| 169 | + String lyric, | ||
| 170 | + @JsonProperty("audio_url") String audioUrl, | ||
| 171 | + @JsonProperty("video_url") String videoUrl, | ||
| 172 | + @JsonProperty("created_at") String createdAt, | ||
| 173 | + @JsonProperty("model_name") String modelName, | ||
| 174 | + String status, | ||
| 175 | + @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, | ||
| 176 | + String prompt, | ||
| 177 | + String type, | ||
| 178 | + String tags | ||
| 179 | + ) { | ||
| 180 | + } | ||
| 181 | + | ||
| 182 | + | ||
| 183 | + /** | ||
| 184 | + * SunoAPI 响应的歌词数据。 | ||
| 185 | + * | ||
| 186 | + * @param text 歌词 | ||
| 187 | + * @param title 标题 | ||
| 188 | + * @param status 状态 | ||
| 189 | + */ | ||
| 190 | + public record LyricsData( | ||
| 191 | + String text, | ||
| 192 | + String title, | ||
| 193 | + String status | ||
| 86 | ) { | 194 | ) { |
| 87 | - /** | ||
| 88 | - * 单个音乐数据。 | ||
| 89 | - * | ||
| 90 | - * @param id 音乐数据的 ID | ||
| 91 | - * @param title 音乐音频的标题 | ||
| 92 | - * @param imageUrl 音乐音频的图片 URL | ||
| 93 | - * @param lyric 音乐音频的歌词 | ||
| 94 | - * @param audioUrl 音乐音频的 URL | ||
| 95 | - * @param videoUrl 音乐视频的 URL | ||
| 96 | - * @param createdAt 音乐音频的创建时间 | ||
| 97 | - * @param model 使用的模型名称 | ||
| 98 | - * @param prompt 生成音乐音频的提示 | ||
| 99 | - * @param style 音乐音频的风格 | ||
| 100 | - */ | ||
| 101 | - public record MusicData( | ||
| 102 | - String id, | ||
| 103 | - String title, | ||
| 104 | - @JsonProperty("image_url") String imageUrl, | ||
| 105 | - String lyric, | ||
| 106 | - @JsonProperty("audio_url") String audioUrl, | ||
| 107 | - @JsonProperty("video_url") String videoUrl, | ||
| 108 | - @JsonProperty("created_at") String createdAt, | ||
| 109 | - String model, | ||
| 110 | - String prompt, | ||
| 111 | - String style | ||
| 112 | - ) { | ||
| 113 | - } | ||
| 114 | } | 195 | } |
| 196 | + | ||
| 197 | + | ||
| 198 | + /** | ||
| 199 | + * SunoAPI 响应的限额数据,目前每日免费50 | ||
| 200 | + */ | ||
| 201 | + public record LimitData( | ||
| 202 | + @JsonProperty("credits_left") Long creditsLeft, | ||
| 203 | + String period, | ||
| 204 | + @JsonProperty("monthly_limit") Long monthlyLimit, | ||
| 205 | + @JsonProperty("monthly_usage") Long monthlyUsage | ||
| 206 | + ) { | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + | ||
| 115 | } | 210 | } |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java
| @@ -5,27 +5,54 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | @@ -5,27 +5,54 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||
| 5 | import org.junit.Before; | 5 | import org.junit.Before; |
| 6 | import org.junit.Test; | 6 | import org.junit.Test; |
| 7 | 7 | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 8 | /** | 10 | /** |
| 9 | * @Author xiaoxin | 11 | * @Author xiaoxin |
| 10 | * @Date 2024/5/27 | 12 | * @Date 2024/5/27 |
| 11 | */ | 13 | */ |
| 12 | public class SunoTests { | 14 | public class SunoTests { |
| 13 | 15 | ||
| 14 | - private SunoConfig sunoConfig; | 16 | + SunoApi sunoApi; |
| 15 | 17 | ||
| 16 | @Before | 18 | @Before |
| 17 | public void setup() { | 19 | public void setup() { |
| 18 | - String token = "16b4356581984d538652354b60d69ff0"; | ||
| 19 | - this.sunoConfig = new SunoConfig(token); | 20 | + String url = "https://suno-ix9nve79x-status2xxs-projects.vercel.app"; |
| 21 | + this.sunoApi = new SunoApi(new SunoConfig(url)); | ||
| 20 | } | 22 | } |
| 21 | 23 | ||
| 22 | @Test | 24 | @Test |
| 23 | - public void generateMusic() { | ||
| 24 | - SunoApi sunoApi = new SunoApi(sunoConfig); | ||
| 25 | - SunoApi.SunoReq sunoReq = new SunoApi.SunoReq("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); | 25 | + public void selectById() { |
| 26 | + System.out.println(sunoApi.selectById("d460ddda-7c87-4f34-b751-419b08a590ca,ff90ea66-49cd-4fd2-b44c-44267dfd5551")); | ||
| 26 | 27 | ||
| 27 | - SunoApi.SunoResp sunoResp = sunoApi.musicGen(sunoReq); | ||
| 28 | - System.out.println(sunoResp); | ||
| 29 | } | 28 | } |
| 30 | 29 | ||
| 30 | + @Test | ||
| 31 | + public void generate() { | ||
| 32 | + List<SunoApi.MusicData> generate = sunoApi.generate(new SunoApi.SunoReq("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")); | ||
| 33 | + System.out.println(generate); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + @Test | ||
| 38 | + public void doChatCompletion() { | ||
| 39 | + List<SunoApi.MusicData> generate = sunoApi.doChatCompletion("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); | ||
| 40 | + System.out.println(generate); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + @Test | ||
| 45 | + public void generateLyrics() { | ||
| 46 | + SunoApi.LyricsData lyricsData = sunoApi.generateLyrics("A soothing lullaby"); | ||
| 47 | + System.out.println(lyricsData); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + | ||
| 51 | + @Test | ||
| 52 | + public void selectLimit() { | ||
| 53 | + SunoApi.LimitData limitData = sunoApi.selectLimit(); | ||
| 54 | + System.out.println(limitData); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + | ||
| 31 | } | 58 | } |
yudao-server/src/main/resources/application.yaml
| @@ -199,7 +199,7 @@ yudao.ai: | @@ -199,7 +199,7 @@ yudao.ai: | ||
| 199 | channel-id: 1237948819677904960 | 199 | channel-id: 1237948819677904960 |
| 200 | suno: | 200 | suno: |
| 201 | enable: true | 201 | enable: true |
| 202 | - token: 16b4356581984d538652354b60d69ff0 | 202 | + base-url: https://suno-ix9nve79x-status2xxs-projects.vercel.app |
| 203 | 203 | ||
| 204 | --- #################### 芋道相关配置 #################### | 204 | --- #################### 芋道相关配置 #################### |
| 205 | 205 |