Commit 4a6a027beb09bf3f3f401ba46bcbaf199899ca21

Authored by xiaoxin
1 parent f300b8a1

【解决todo】AI 音乐:1.处理失败任务 2.两种生成类型提示词合并,库中区分

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