Commit 599a114a0f7c1f5d5f32b24e6ed99409274ac277

Authored by 芋道源码
Committed by Gitee
2 parents 342f6039 e582aaad

!1 【新增】AI:suno

Merge pull request !1 from 小新/master-jdk21-ai
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +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;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import jakarta.validation.Valid;
  10 +import lombok.RequiredArgsConstructor;
  11 +import org.springframework.web.bind.annotation.PostMapping;
  12 +import org.springframework.web.bind.annotation.RequestBody;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RestController;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  17 +
  18 +@Tag(name = "管理后台 - AI 音乐生成")
  19 +@RestController
  20 +@RequestMapping("/ai/music")
  21 +@RequiredArgsConstructor
  22 +public class MusicController {
  23 +
  24 + private final MusicService musicService;
  25 +
  26 + @PostMapping("/suno-gen")
  27 + @Operation(summary = "音乐生成")
  28 + public CommonResult<SunoRespVO> musicGen(@RequestBody @Valid SunoReqVO sunoReqVO) {
  29 + return success(musicService.musicGen(sunoReqVO));
  30 + }
  31 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * 表示单个音乐数据的类
  8 + */
  9 +@Data
  10 +public class MusicDataVO {
  11 + /**
  12 + * 音乐数据的 ID
  13 + */
  14 + private String id;
  15 +
  16 + /**
  17 + * 音乐音频的标题
  18 + */
  19 + private String title;
  20 +
  21 + /**
  22 + * 音乐音频的图片 URL
  23 + */
  24 + @JsonProperty("image_url")
  25 + private String imageUrl;
  26 +
  27 + /**
  28 + * 音乐音频的歌词
  29 + */
  30 + private String lyric;
  31 +
  32 + /**
  33 + * 音乐音频的 URL
  34 + */
  35 + @JsonProperty("audio_url")
  36 + private String audioUrl;
  37 +
  38 + /**
  39 + * 音乐视频的 URL
  40 + */
  41 + @JsonProperty("video_url")
  42 + private String videoUrl;
  43 +
  44 + /**
  45 + * 音乐音频的创建时间
  46 + */
  47 + @JsonProperty("created_at")
  48 + private String createdAt;
  49 +
  50 + /**
  51 + * 使用的模型名称
  52 + */
  53 + private String model;
  54 +
  55 + /**
  56 + * 生成音乐音频的提示
  57 + */
  58 + private String prompt;
  59 +
  60 + /**
  61 + * 音乐音频的风格
  62 + */
  63 + private String style;
  64 +}
0 65 \ 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/SunoReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonInclude;
  4 +import lombok.Data;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +@Data
  8 +@Accessors(chain = true)
  9 +@JsonInclude(value = JsonInclude.Include.NON_NULL)
  10 +public class SunoReqVO {
  11 + /**
  12 + * 用于生成音乐音频的提示
  13 + */
  14 + private String prompt;
  15 +
  16 + /**
  17 + * 用于生成音乐音频的歌词
  18 + */
  19 + private String lyric;
  20 +
  21 + /**
  22 + * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成
  23 + */
  24 + private boolean custom;
  25 +
  26 + /**
  27 + * 音乐音频的标题
  28 + */
  29 + private String title;
  30 +
  31 + /**
  32 + * 音乐音频的风格
  33 + */
  34 + private String style;
  35 +
  36 + /**
  37 + * 音乐音频生成后回调的 URL
  38 + */
  39 + private String callbackUrl;
  40 +}
0 41 \ 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 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * API 响应的数据
  10 + */
  11 +@Data
  12 +public class SunoRespVO {
  13 + /**
  14 + * 表示请求是否成功
  15 + */
  16 + private boolean success;
  17 +
  18 + /**
  19 + * 任务 ID
  20 + */
  21 + @JsonProperty("task_id")
  22 + private String taskId;
  23 +
  24 + /**
  25 + * 音乐数据列表
  26 + */
  27 + private List<MusicDataVO> data;
  28 +
  29 +}
0 30 \ No newline at end of file
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.music;
  2 +
  3 +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 +/**
  7 + * @Author xiaoxin
  8 + * @Date 2024/5/29
  9 + */
  10 +public interface MusicService {
  11 +
  12 + /**
  13 + * 音乐生成
  14 + *
  15 + * @param sunoReqVO 请求实体
  16 + * @return 响应实体
  17 + */
  18 + SunoRespVO musicGen(SunoReqVO sunoReqVO);
  19 +}
... ...
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.music;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO;
  6 +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO;
  7 +import lombok.RequiredArgsConstructor;
  8 +import org.springframework.stereotype.Service;
  9 +
  10 +/**
  11 + * @Author xiaoxin
  12 + * @Date 2024/5/29
  13 + */
  14 +@Service
  15 +@RequiredArgsConstructor
  16 +public class MusicServiceImpl implements MusicService {
  17 +
  18 + private final SunoApi sunoApi;
  19 +
  20 + @Override
  21 + public SunoRespVO musicGen(SunoReqVO sunoReqVO) {
  22 + SunoApi.SunoRequest req = BeanUtils.toBean(sunoReqVO, SunoApi.SunoRequest.class);
  23 + return BeanUtils.toBean(sunoApi.musicGen(req), SunoRespVO.class);
  24 + }
  25 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
... ... @@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.ai.config;
3 3 import cn.hutool.core.io.IoUtil;
4 4 import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory;
5 5 import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl;
  6 +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
  7 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
6 8 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient;
7 9 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal;
8 10 import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions;
... ... @@ -13,18 +15,14 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
13 15 import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
14 16 import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
15 17 import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.jetbrains.annotations.NotNull;
16 20 import org.springframework.ai.models.midjourney.MidjourneyConfig;
17 21 import org.springframework.ai.models.midjourney.MidjourneyMessage;
18 22 import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi;
19 23 import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler;
20 24 import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter;
21 25 import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener;
22   -import lombok.extern.slf4j.Slf4j;
23   -import org.jetbrains.annotations.NotNull;
24   -import org.springframework.ai.openai.OpenAiImageClient;
25   -import org.springframework.ai.openai.OpenAiImageOptions;
26   -import org.springframework.ai.openai.api.OpenAiImageApi;
27   -import org.springframework.ai.retry.RetryUtils;
28 26 import org.springframework.boot.autoconfigure.AutoConfiguration;
29 27 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
30 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
... ... @@ -150,6 +148,13 @@ public class YudaoAiAutoConfiguration {
150 148 return new MidjourneyInteractionsApi(midjourneyConfig);
151 149 }
152 150  
  151 + @Bean
  152 + @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true")
  153 + public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) {
  154 + // 创建 sunoApi
  155 + return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken()));
  156 + }
  157 +
153 158 private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext,
154 159 YudaoAiProperties.MidjourneyProperties midjourneyProperties) {
155 160 Map<String, String> requestTemplates = new HashMap<>();
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
... ... @@ -26,6 +26,7 @@ public class YudaoAiProperties {
26 26 private YiYanProperties yiyan;
27 27 private OpenAiImageProperties openAiImage;
28 28 private MidjourneyProperties midjourney;
  29 + private SunoProperties suno;
29 30  
30 31 @Data
31 32 @Accessors(chain = true)
... ... @@ -134,4 +135,14 @@ public class YudaoAiProperties {
134 135 */
135 136 private String channelId;
136 137 }
  138 +
  139 + @Data
  140 + @Accessors(chain = true)
  141 + public static class SunoProperties {
  142 + private boolean enable = false;
  143 + /**
  144 + * token
  145 + */
  146 + private String token;
  147 + }
137 148 }
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.model.suno;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +import lombok.experimental.Accessors;
  7 +
  8 +/**
  9 + * @Author xiaoxin
  10 + * @Date 2024/5/29
  11 + */
  12 +@Data
  13 +@Accessors(chain = true)
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class SunoConfig {
  17 + /**
  18 + * token信息
  19 + */
  20 + private String token;
  21 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.model.suno.api;
  2 +
  3 +
  4 +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  6 +import com.fasterxml.jackson.annotation.JsonInclude;
  7 +import com.fasterxml.jackson.annotation.JsonProperty;
  8 +import lombok.Data;
  9 +import lombok.experimental.Accessors;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import okhttp3.*;
  12 +
  13 +import java.io.IOException;
  14 +import java.util.List;
  15 +import java.util.concurrent.TimeUnit;
  16 +
  17 +/**
  18 + * @Author xiaoxin
  19 + * @Date 2024/5/27
  20 + */
  21 +@Slf4j
  22 +public class SunoApi {
  23 +
  24 + public static final String APPLICATION_JSON = "application/json";
  25 + public static final String TOKEN_PREFIX = "Bearer ";
  26 + public static final String API_URL = "https://api.acedata.cloud/suno/audios";
  27 + private static final int READ_TIMEOUT = 160; // 连接超时时间(秒),音乐生成时间较长,设置为 160s,后续可做callback
  28 + private final OkHttpClient client;
  29 +
  30 +
  31 + public SunoApi(SunoConfig sunoConfig) {
  32 + this.client = new OkHttpClient().newBuilder().readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
  33 + .addInterceptor(chain -> {
  34 + Request originalRequest = chain.request();
  35 + Request requestWithUserAgent = originalRequest.newBuilder()
  36 + .header("Authorization", TOKEN_PREFIX + sunoConfig.getToken())
  37 + .build();
  38 + return chain.proceed(requestWithUserAgent);
  39 + })
  40 + .build();
  41 + }
  42 +
  43 + public SunoResponse musicGen(SunoRequest sunoRequest) {
  44 + Request request = new Request.Builder()
  45 + .url(API_URL)
  46 + .post(RequestBody.create(MediaType.parse(APPLICATION_JSON), JsonUtils.toJsonString(sunoRequest)))
  47 + .build();
  48 +
  49 + try (Response response = client.newCall(request).execute()) {
  50 + if (!response.isSuccessful()) {
  51 + log.error("suno调用失败! response: {}", response);
  52 + throw new IllegalStateException("suno调用失败!" + response);
  53 + }
  54 + return JsonUtils.parseObject(response.body().string(), SunoResponse.class);
  55 + } catch (IOException ioException) {
  56 + throw new RuntimeException(ioException);
  57 + }
  58 + }
  59 +
  60 +
  61 + /**
  62 + * 请求数据对象,用于生成音乐音频
  63 + */
  64 + @Data
  65 + @Accessors(chain = true)
  66 + @JsonInclude(value = JsonInclude.Include.NON_NULL)
  67 + public static class SunoRequest {
  68 + /**
  69 + * 用于生成音乐音频的提示
  70 + */
  71 + private String prompt;
  72 +
  73 + /**
  74 + * 用于生成音乐音频的歌词
  75 + */
  76 + private String lyric;
  77 +
  78 + /**
  79 + * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成
  80 + */
  81 + private boolean custom;
  82 +
  83 + /**
  84 + * 音乐音频的标题
  85 + */
  86 + private String title;
  87 +
  88 + /**
  89 + * 音乐音频的风格
  90 + */
  91 + private String style;
  92 +
  93 + /**
  94 + * 音乐音频生成后回调的 URL
  95 + */
  96 + private String callbackUrl;
  97 + }
  98 +
  99 +
  100 + /**
  101 + * SunoAPI 响应的数据
  102 + */
  103 + @Data
  104 + public static class SunoResponse {
  105 + /**
  106 + * 表示请求是否成功
  107 + */
  108 + private boolean success;
  109 +
  110 + /**
  111 + * 任务 ID
  112 + */
  113 + @JsonProperty("task_id")
  114 + private String taskId;
  115 +
  116 + /**
  117 + * 音乐数据列表
  118 + */
  119 + private List<MusicData> data;
  120 +
  121 + /**
  122 + * 表示单个音乐数据的类
  123 + */
  124 + @Data
  125 + static class MusicData {
  126 + /**
  127 + * 音乐数据的 ID
  128 + */
  129 + private String id;
  130 +
  131 + /**
  132 + * 音乐音频的标题
  133 + */
  134 + private String title;
  135 +
  136 + /**
  137 + * 音乐音频的图片 URL
  138 + */
  139 + @JsonProperty("image_url")
  140 + private String imageUrl;
  141 +
  142 + /**
  143 + * 音乐音频的歌词
  144 + */
  145 + private String lyric;
  146 +
  147 + /**
  148 + * 音乐音频的 URL
  149 + */
  150 + @JsonProperty("audio_url")
  151 + private String audioUrl;
  152 +
  153 + /**
  154 + * 音乐视频的 URL
  155 + */
  156 + @JsonProperty("video_url")
  157 + private String videoUrl;
  158 +
  159 + /**
  160 + * 音乐音频的创建时间
  161 + */
  162 + @JsonProperty("created_at")
  163 + private String createdAt;
  164 +
  165 + /**
  166 + * 使用的模型名称
  167 + */
  168 + private String model;
  169 +
  170 + /**
  171 + * 生成音乐音频的提示
  172 + */
  173 + private String prompt;
  174 +
  175 + /**
  176 + * 音乐音频的风格
  177 + */
  178 + private String style;
  179 + }
  180 + }
  181 +
  182 +
  183 +}
... ...
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.suno;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
  4 +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
  5 +import org.junit.Before;
  6 +import org.junit.Test;
  7 +
  8 +/**
  9 + * @Author xiaoxin
  10 + * @Date 2024/5/27
  11 + */
  12 +public class SunoTests {
  13 +
  14 + private SunoConfig sunoConfig;
  15 +
  16 + @Before
  17 + public void setup() {
  18 + String token = "13f13540dd3f4ae9885f63ac9f5d0b9f";
  19 + this.sunoConfig = new SunoConfig(token);
  20 + }
  21 +
  22 + @Test
  23 + public void generateMusic() {
  24 + SunoApi sunoApi = new SunoApi(sunoConfig);
  25 + SunoApi.SunoRequest sunoRequest = new SunoApi
  26 + .SunoRequest()
  27 + .setPrompt("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。");
  28 + SunoApi.SunoResponse sunoResponse = sunoApi.musicGen(sunoRequest);
  29 + System.out.println(sunoResponse);
  30 + }
  31 +}
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -199,6 +199,9 @@ yudao.ai:
199 199 token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw
200 200 guild-id: 1237948819677904956
201 201 channel-id: 1237948819677904960
  202 + suno:
  203 + enable: true
  204 + token: 13f13540dd3f4ae9885f63ac9f5d0b9f
202 205  
203 206 --- #################### 芋道相关配置 ####################
204 207  
... ...