Commit 4a6a027beb09bf3f3f401ba46bcbaf199899ca21
1 parent
f300b8a1
【解决todo】AI 音乐:1.处理失败任务 2.两种生成类型提示词合并,库中区分
Showing
10 changed files
with
61 additions
and
72 deletions
yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java
| @@ -16,7 +16,8 @@ import java.util.Arrays; | @@ -16,7 +16,8 @@ import java.util.Arrays; | ||
| 16 | public enum AiMusicStatusEnum implements IntArrayValuable { | 16 | public enum AiMusicStatusEnum implements IntArrayValuable { |
| 17 | 17 | ||
| 18 | IN_PROGRESS(10, "进行中"), | 18 | IN_PROGRESS(10, "进行中"), |
| 19 | - SUCCESS(20, "已完成"); | 19 | + SUCCESS(20, "已完成"), |
| 20 | + FAIL(30, "已失败"); | ||
| 20 | 21 | ||
| 21 | /** | 22 | /** |
| 22 | * 状态 | 23 | * 状态 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java
| @@ -38,10 +38,6 @@ public class AiMusicController { | @@ -38,10 +38,6 @@ public class AiMusicController { | ||
| 38 | @PostMapping("/generate") | 38 | @PostMapping("/generate") |
| 39 | @Operation(summary = "音乐生成") | 39 | @Operation(summary = "音乐生成") |
| 40 | public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { | 40 | public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { |
| 41 | - if (true) { | ||
| 42 | - musicService.syncMusic(); | ||
| 43 | - return null; | ||
| 44 | - } | ||
| 45 | return success(musicService.generateMusic(getLoginUserId(), reqVO)); | 41 | return success(musicService.generateMusic(getLoginUserId(), reqVO)); |
| 46 | } | 42 | } |
| 47 | 43 | ||
| @@ -64,12 +60,11 @@ public class AiMusicController { | @@ -64,12 +60,11 @@ public class AiMusicController { | ||
| 64 | return success(BeanUtils.toBean(music, AiMusicRespVO.class)); | 60 | return success(BeanUtils.toBean(music, AiMusicRespVO.class)); |
| 65 | } | 61 | } |
| 66 | 62 | ||
| 67 | - // TODO @xin:这个搞成 updateMy ,修改【我的】音乐。方便后续支持其它字段;另外,需要校验下,更新的音乐,是不是我的! | ||
| 68 | - @PostMapping("/updateTitle-my") | 63 | + @PostMapping("/update-my") |
| 69 | @Operation(summary = "修改【我的】音乐 目前只支持修改标题") | 64 | @Operation(summary = "修改【我的】音乐 目前只支持修改标题") |
| 70 | @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") | 65 | @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") |
| 71 | - public CommonResult<Boolean> updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { | ||
| 72 | - musicService.updateMusicTitle(updateReqVO); | 66 | + public CommonResult<Boolean> updateMy(AiMusicUpdateReqVO updateReqVO) { |
| 67 | + musicService.updateMyMusic(updateReqVO, getLoginUserId()); | ||
| 73 | return success(true); | 68 | return success(true); |
| 74 | } | 69 | } |
| 75 | 70 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java
| @@ -15,4 +15,7 @@ public class AiMusicUpdateReqVO { | @@ -15,4 +15,7 @@ public class AiMusicUpdateReqVO { | ||
| 15 | @Schema(description = "是否发布", example = "true") | 15 | @Schema(description = "是否发布", example = "true") |
| 16 | private Boolean publicStatus; | 16 | private Boolean publicStatus; |
| 17 | 17 | ||
| 18 | + @Schema(description = "音乐名称", example = "夜空中最亮的星") | ||
| 19 | + private String title; | ||
| 20 | + | ||
| 18 | } | 21 | } |
| 19 | \ No newline at end of file | 22 | \ 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/AiMusicUpdateTitleReqVO.java deleted
| 1 | -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | - | ||
| 3 | -import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | -import jakarta.validation.constraints.NotNull; | ||
| 5 | -import lombok.Data; | ||
| 6 | - | ||
| 7 | -@Schema(description = "管理后台 - AI 音乐修改名称 Request VO") | ||
| 8 | -@Data | ||
| 9 | -public class AiMusicUpdateTitleReqVO { | ||
| 10 | - | ||
| 11 | - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") | ||
| 12 | - private Long id; | ||
| 13 | - | ||
| 14 | - @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星") | ||
| 15 | - @NotNull(message = "音乐名称不能为空") | ||
| 16 | - private String title; | ||
| 17 | - | ||
| 18 | -} | ||
| 19 | \ 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/AiSunoGenerateReqVO.java
| @@ -24,10 +24,10 @@ public class AiSunoGenerateReqVO { | @@ -24,10 +24,10 @@ public class AiSunoGenerateReqVO { | ||
| 24 | @NotNull(message = "生成模式不能为空") | 24 | @NotNull(message = "生成模式不能为空") |
| 25 | private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 | 25 | private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 |
| 26 | 26 | ||
| 27 | - // TODO @xin:方案一:prompt => lyric 歌词;gptDescriptionPrompt => description 描述(db 那字段也改下,避免和 gpt 直接耦合);这样搞完后,会更统一好理解一点 | ||
| 28 | - // TODO @xin:方案二:还是之前的做法,都用 prompt;不过最终 gptDescriptionPrompt 还是存储 description 算描述。可以微信一起讨论下。 | ||
| 29 | @Schema(description = "用于生成音乐音频的歌词提示", | 27 | @Schema(description = "用于生成音乐音频的歌词提示", |
| 30 | example = """ | 28 | example = """ |
| 29 | + 1.描述模式:创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。 | ||
| 30 | + 2.歌词模式: | ||
| 31 | [Verse] | 31 | [Verse] |
| 32 | 阳光下奔跑 多么欢快 | 32 | 阳光下奔跑 多么欢快 |
| 33 | 假期就要来 心都飞起来 | 33 | 假期就要来 心都飞起来 |
| @@ -39,11 +39,7 @@ public class AiSunoGenerateReqVO { | @@ -39,11 +39,7 @@ public class AiSunoGenerateReqVO { | ||
| 39 | 日子太短暂 别再等待 | 39 | 日子太短暂 别再等待 |
| 40 | 马上放假了 梦想起飞 | 40 | 马上放假了 梦想起飞 |
| 41 | """) | 41 | """) |
| 42 | - private String prompt; // 歌词模式用 | ||
| 43 | - | ||
| 44 | - @Schema(description = "用于生成音乐音频的描述", | ||
| 45 | - example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | ||
| 46 | - private String gptDescriptionPrompt; // 描述模式用 | 42 | + private String prompt; |
| 47 | 43 | ||
| 48 | @Schema(description = "是否纯音乐", example = "true") | 44 | @Schema(description = "是否纯音乐", example = "true") |
| 49 | private Boolean makeInstrumental; | 45 | private Boolean makeInstrumental; |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
| @@ -75,11 +75,7 @@ public class AiMusicDO extends BaseDO { | @@ -75,11 +75,7 @@ public class AiMusicDO extends BaseDO { | ||
| 75 | /** | 75 | /** |
| 76 | * 描述词 | 76 | * 描述词 |
| 77 | */ | 77 | */ |
| 78 | - private String gptDescriptionPrompt; | ||
| 79 | - /** | ||
| 80 | - * 提示词 | ||
| 81 | - */ | ||
| 82 | - private String prompt; | 78 | + private String description; |
| 83 | 79 | ||
| 84 | /** | 80 | /** |
| 85 | * 平台 | 81 | * 平台 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
| @@ -38,11 +38,11 @@ public interface AiMusicService { | @@ -38,11 +38,11 @@ public interface AiMusicService { | ||
| 38 | void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO); | 38 | void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO); |
| 39 | 39 | ||
| 40 | /** | 40 | /** |
| 41 | - * 更新音乐名称 | 41 | + * 更新我的音乐 |
| 42 | * | 42 | * |
| 43 | * @param updateReqVO 更新信息 | 43 | * @param updateReqVO 更新信息 |
| 44 | */ | 44 | */ |
| 45 | - void updateMusicTitle(@Valid AiMusicUpdateTitleReqVO updateReqVO); | 45 | + void updateMyMusic(@Valid AiMusicUpdateReqVO updateReqVO, Long userId); |
| 46 | 46 | ||
| 47 | /** | 47 | /** |
| 48 | * 删除AI 音乐 | 48 | * 删除AI 音乐 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
| @@ -3,11 +3,14 @@ package cn.iocoder.yudao.module.ai.service.music; | @@ -3,11 +3,14 @@ package cn.iocoder.yudao.module.ai.service.music; | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.text.StrPool; | 4 | import cn.hutool.core.text.StrPool; |
| 5 | import cn.hutool.core.util.ObjUtil; | 5 | import cn.hutool.core.util.ObjUtil; |
| 6 | +import cn.hutool.core.util.ObjectUtil; | ||
| 6 | import cn.hutool.core.util.StrUtil; | 7 | import cn.hutool.core.util.StrUtil; |
| 7 | import cn.hutool.http.HttpUtil; | 8 | import cn.hutool.http.HttpUtil; |
| 8 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 9 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 10 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; | 11 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; |
| 12 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdateReqVO; | ||
| 13 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | ||
| 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | 14 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 12 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; | 15 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; |
| 13 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; | 16 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; |
| @@ -48,18 +51,18 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -48,18 +51,18 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 48 | public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { | 51 | public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { |
| 49 | // 1. 调用 Suno 生成音乐 | 52 | // 1. 调用 Suno 生成音乐 |
| 50 | SunoApi sunoApi = apiKeyService.getSunoApi(); | 53 | SunoApi sunoApi = apiKeyService.getSunoApi(); |
| 51 | - // TODO @xin:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 | 54 | + // TODO 芋艿:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 --xin:大部分ok的,补充了error_message |
| 52 | List<SunoApi.MusicData> musicDataList; | 55 | List<SunoApi.MusicData> musicDataList; |
| 53 | - if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { | ||
| 54 | - // 1.1 歌词模式 | 56 | + if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { |
| 57 | + // 1.1 描述模式 | ||
| 58 | + SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( | ||
| 59 | + reqVO.getPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental()); | ||
| 60 | + musicDataList = sunoApi.generate(generateRequest); | ||
| 61 | + } else if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { | ||
| 62 | + // 1.2 歌词模式 | ||
| 55 | SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( | 63 | SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( |
| 56 | reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); | 64 | reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); |
| 57 | musicDataList = sunoApi.customGenerate(generateRequest); | 65 | musicDataList = sunoApi.customGenerate(generateRequest); |
| 58 | - } else if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { | ||
| 59 | - // 1.2 描述模式 | ||
| 60 | - SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( | ||
| 61 | - reqVO.getGptDescriptionPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental()); | ||
| 62 | - musicDataList = sunoApi.generate(generateRequest); | ||
| 63 | } else { | 66 | } else { |
| 64 | throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); | 67 | throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); |
| 65 | } | 68 | } |
| @@ -108,9 +111,12 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -108,9 +111,12 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 108 | } | 111 | } |
| 109 | 112 | ||
| 110 | @Override | 113 | @Override |
| 111 | - public void updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { | ||
| 112 | - // 校验存在 | ||
| 113 | - validateMusicExists(updateReqVO.getId()); | 114 | + public void updateMyMusic(AiMusicUpdateReqVO updateReqVO, Long userId) { |
| 115 | + // 校验音乐是否存在 | ||
| 116 | + AiMusicDO musicDO = validateMusicExists(updateReqVO.getId()); | ||
| 117 | + if (ObjUtil.notEqual(musicDO.getUserId(), userId)) { | ||
| 118 | + throw exception(MUSIC_NOT_EXISTS); | ||
| 119 | + } | ||
| 114 | // 更新 | 120 | // 更新 |
| 115 | musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle())); | 121 | musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle())); |
| 116 | } | 122 | } |
| @@ -156,29 +162,38 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -156,29 +162,38 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 156 | * @return AiMusicDO 集合 | 162 | * @return AiMusicDO 集合 |
| 157 | */ | 163 | */ |
| 158 | private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { | 164 | private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { |
| 159 | - // TODO @xin:它有 status = error 状态,表示失败噢。 | ||
| 160 | - return convertList(musicList, musicData -> new AiMusicDO() | ||
| 161 | - .setTaskId(musicData.id()).setModel(musicData.modelName()) | ||
| 162 | - .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | ||
| 163 | - // TODO @xin:只有在完成的状态,在下载文件 | ||
| 164 | - .setAudioUrl(downloadFile(musicData.audioUrl())) | ||
| 165 | - .setVideoUrl(downloadFile(musicData.videoUrl())) | ||
| 166 | - .setImageUrl(downloadFile(musicData.imageUrl())) | ||
| 167 | - .setTitle(musicData.title()).setDuration(musicData.duration()) | ||
| 168 | - .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) | ||
| 169 | - .setStatus(Objects.equals("complete", musicData.status()) ? | ||
| 170 | - AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); | 165 | + return convertList(musicList, musicData -> { |
| 166 | + Integer status; | ||
| 167 | + if (Objects.equals("complete", musicData.status())) { | ||
| 168 | + status = AiMusicStatusEnum.SUCCESS.getStatus(); | ||
| 169 | + } else if (Objects.equals("error", musicData.status())) { | ||
| 170 | + status = AiMusicStatusEnum.FAIL.getStatus(); | ||
| 171 | + } else { | ||
| 172 | + status = AiMusicStatusEnum.IN_PROGRESS.getStatus(); | ||
| 173 | + } | ||
| 174 | + return new AiMusicDO() | ||
| 175 | + .setTaskId(musicData.id()).setModel(musicData.modelName()) | ||
| 176 | + .setDescription(musicData.gptDescriptionPrompt()) | ||
| 177 | + .setAudioUrl(downloadFile(status, musicData.audioUrl())) | ||
| 178 | + .setVideoUrl(downloadFile(status, musicData.videoUrl())) | ||
| 179 | + .setImageUrl(downloadFile(status, musicData.imageUrl())) | ||
| 180 | + .setTitle(musicData.title()).setDuration(musicData.duration()) | ||
| 181 | + .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) | ||
| 182 | + .setErrorMessage(musicData.errorMessage()) | ||
| 183 | + .setStatus(status); | ||
| 184 | + }); | ||
| 171 | } | 185 | } |
| 172 | 186 | ||
| 173 | /** | 187 | /** |
| 174 | - * 将生成的音频文件上传到文件服务器 | 188 | + * 音乐生成好后,将音频文件上传到文件服务器 |
| 175 | * | 189 | * |
| 176 | - * @param url 音频文件地址 | 190 | + * @param status 音乐状态 |
| 191 | + * @param url 音频文件地址 | ||
| 177 | * @return 内部文件地址 | 192 | * @return 内部文件地址 |
| 178 | */ | 193 | */ |
| 179 | - private String downloadFile(String url) { | ||
| 180 | - if (StrUtil.isBlank(url)) { | ||
| 181 | - return null; | 194 | + private String downloadFile(Integer status, String url) { |
| 195 | + if (StrUtil.isBlank(url) || ObjectUtil.notEqual(status, AiMusicStatusEnum.SUCCESS.getStatus())) { | ||
| 196 | + return url; | ||
| 182 | } | 197 | } |
| 183 | try { | 198 | try { |
| 184 | byte[] bytes = HttpUtil.downloadBytes(url); | 199 | byte[] bytes = HttpUtil.downloadBytes(url); |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
| @@ -175,6 +175,7 @@ public class SunoApi { | @@ -175,6 +175,7 @@ public class SunoApi { | ||
| 175 | @JsonProperty("model_name") String modelName, | 175 | @JsonProperty("model_name") String modelName, |
| 176 | String status, | 176 | String status, |
| 177 | @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, | 177 | @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, |
| 178 | + @JsonProperty("error_message") String errorMessage, | ||
| 178 | String prompt, | 179 | String prompt, |
| 179 | String type, | 180 | String type, |
| 180 | String tags, | 181 | String tags, |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java
| @@ -17,8 +17,8 @@ public class SunoTests { | @@ -17,8 +17,8 @@ public class SunoTests { | ||
| 17 | 17 | ||
| 18 | @Before | 18 | @Before |
| 19 | public void setup() { | 19 | public void setup() { |
| 20 | -// String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; | ||
| 21 | - String url = "http://127.0.0.1:3001"; | 20 | + String url = "https://suno-55ishh05u-status2xxs-projects.vercel.app"; |
| 21 | +// String url = "http://127.0.0.1:3001"; | ||
| 22 | this.sunoApi = new SunoApi(url); | 22 | this.sunoApi = new SunoApi(url); |
| 23 | } | 23 | } |
| 24 | 24 |