Quellcode durchsuchen

!1 【新增】AI:suno
Merge pull request !1 from 小新/master-jdk21-ai

芋道源码 vor 1 Jahr
Ursprung
Commit
599a114a0f
12 geänderte Dateien mit 468 neuen und 6 gelöschten Zeilen
  1. 31 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java
  2. 64 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java
  3. 40 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java
  4. 29 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java
  5. 19 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java
  6. 25 0
      yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java
  7. 11 6
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
  8. 11 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
  9. 21 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java
  10. 183 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
  11. 31 0
      yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java
  12. 3 0
      yudao-server/src/main/resources/application.yaml

+ 31 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java

@@ -0,0 +1,31 @@
+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.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;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - AI 音乐生成")
+@RestController
+@RequestMapping("/ai/music")
+@RequiredArgsConstructor
+public class MusicController {
+
+    private final MusicService musicService;
+
+    @PostMapping("/suno-gen")
+    @Operation(summary = "音乐生成")
+    public CommonResult<SunoRespVO> musicGen(@RequestBody @Valid SunoReqVO sunoReqVO) {
+        return success(musicService.musicGen(sunoReqVO));
+    }
+}

+ 64 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java

@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * 表示单个音乐数据的类
+ */
+@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;
+}

+ 40 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java

@@ -0,0 +1,40 @@
+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;
+
+    /**
+     * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成
+     */
+    private boolean custom;
+
+    /**
+     * 音乐音频的标题
+     */
+    private String title;
+
+    /**
+     * 音乐音频的风格
+     */
+    private String style;
+
+    /**
+     * 音乐音频生成后回调的 URL
+     */
+    private String callbackUrl;
+}

+ 29 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.ai.controller.admin.music.vo;
+
+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<MusicDataVO> data;
+
+}

+ 19 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.ai.service.music;
+
+import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO;
+
+/**
+ * @Author xiaoxin
+ * @Date 2024/5/29
+ */
+public interface MusicService {
+
+    /**
+     * 音乐生成
+     *
+     * @param sunoReqVO 请求实体
+     * @return 响应实体
+     */
+    SunoRespVO musicGen(SunoReqVO sunoReqVO);
+}

+ 25 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.ai.service.music;
+
+import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO;
+import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Author xiaoxin
+ * @Date 2024/5/29
+ */
+@Service
+@RequiredArgsConstructor
+public class MusicServiceImpl implements MusicService {
+
+    private final SunoApi sunoApi;
+
+    @Override
+    public SunoRespVO musicGen(SunoReqVO sunoReqVO) {
+        SunoApi.SunoRequest req = BeanUtils.toBean(sunoReqVO, SunoApi.SunoRequest.class);
+        return BeanUtils.toBean(sunoApi.musicGen(req), SunoRespVO.class);
+    }
+}

+ 11 - 6
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;
 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.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;
@@ -13,18 +15,14 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
 import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient;
 import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions;
 import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.ai.models.midjourney.MidjourneyConfig;
 import org.springframework.ai.models.midjourney.MidjourneyMessage;
 import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi;
 import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler;
 import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter;
 import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener;
-import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.NotNull;
-import org.springframework.ai.openai.OpenAiImageClient;
-import org.springframework.ai.openai.OpenAiImageOptions;
-import org.springframework.ai.openai.api.OpenAiImageApi;
-import org.springframework.ai.retry.RetryUtils;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -150,6 +148,13 @@ public class YudaoAiAutoConfiguration {
         return new MidjourneyInteractionsApi(midjourneyConfig);
     }
 
+    @Bean
+    @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true")
+    public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) {
+        // 创建 sunoApi
+        return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken()));
+    }
+
     private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext,
                                                                  YudaoAiProperties.MidjourneyProperties midjourneyProperties) {
         Map<String, String> requestTemplates = new HashMap<>();

+ 11 - 0
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 {
     private YiYanProperties yiyan;
     private OpenAiImageProperties openAiImage;
     private MidjourneyProperties midjourney;
+    private SunoProperties suno;
 
     @Data
     @Accessors(chain = true)
@@ -134,4 +135,14 @@ public class YudaoAiProperties {
          */
         private String channelId;
     }
+
+    @Data
+    @Accessors(chain = true)
+    public static class SunoProperties {
+        private boolean enable = false;
+        /**
+         * token
+         */
+        private String token;
+    }
 }

+ 21 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.ai.core.model.suno;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author xiaoxin
+ * @Date 2024/5/29
+ */
+@Data
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class SunoConfig {
+    /**
+     * token信息
+     */
+    private String token;
+}

+ 183 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java

@@ -0,0 +1,183 @@
+package cn.iocoder.yudao.framework.ai.core.model.suno.api;
+
+
+import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Author xiaoxin
+ * @Date 2024/5/27
+ */
+@Slf4j
+public class SunoApi {
+
+    public static final String APPLICATION_JSON = "application/json";
+    public static final String TOKEN_PREFIX = "Bearer ";
+    public static final String API_URL = "https://api.acedata.cloud/suno/audios";
+    private static final int READ_TIMEOUT = 160; // 连接超时时间(秒),音乐生成时间较长,设置为 160s,后续可做callback
+    private final OkHttpClient client;
+
+
+    public SunoApi(SunoConfig sunoConfig) {
+        this.client = new OkHttpClient().newBuilder().readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
+                .addInterceptor(chain -> {
+                    Request originalRequest = chain.request();
+                    Request requestWithUserAgent = originalRequest.newBuilder()
+                            .header("Authorization", TOKEN_PREFIX + sunoConfig.getToken())
+                            .build();
+                    return chain.proceed(requestWithUserAgent);
+                })
+                .build();
+    }
+
+    public SunoResponse musicGen(SunoRequest sunoRequest) {
+        Request request = new Request.Builder()
+                .url(API_URL)
+                .post(RequestBody.create(MediaType.parse(APPLICATION_JSON), JsonUtils.toJsonString(sunoRequest)))
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                log.error("suno调用失败! response: {}", response);
+                throw new IllegalStateException("suno调用失败!" + response);
+            }
+            return JsonUtils.parseObject(response.body().string(), SunoResponse.class);
+        } catch (IOException ioException) {
+            throw new RuntimeException(ioException);
+        }
+    }
+
+
+    /**
+     * 请求数据对象,用于生成音乐音频
+     */
+    @Data
+    @Accessors(chain = true)
+    @JsonInclude(value = JsonInclude.Include.NON_NULL)
+    public static class SunoRequest {
+        /**
+         * 用于生成音乐音频的提示
+         */
+        private String prompt;
+
+        /**
+         * 用于生成音乐音频的歌词
+         */
+        private String lyric;
+
+        /**
+         * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成
+         */
+        private boolean custom;
+
+        /**
+         * 音乐音频的标题
+         */
+        private String title;
+
+        /**
+         * 音乐音频的风格
+         */
+        private String style;
+
+        /**
+         * 音乐音频生成后回调的 URL
+         */
+        private String callbackUrl;
+    }
+
+
+    /**
+     * SunoAPI 响应的数据
+     */
+    @Data
+    public static class SunoResponse {
+        /**
+         * 表示请求是否成功
+         */
+        private boolean success;
+
+        /**
+         * 任务 ID
+         */
+        @JsonProperty("task_id")
+        private String taskId;
+
+        /**
+         * 音乐数据列表
+         */
+        private List<MusicData> data;
+
+        /**
+         * 表示单个音乐数据的类
+         */
+        @Data
+        static class MusicData {
+            /**
+             * 音乐数据的 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;
+        }
+    }
+
+
+}

+ 31 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.framework.ai.suno;
+
+import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig;
+import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @Author xiaoxin
+ * @Date 2024/5/27
+ */
+public class SunoTests {
+
+    private SunoConfig sunoConfig;
+
+    @Before
+    public void setup() {
+        String token = "13f13540dd3f4ae9885f63ac9f5d0b9f";
+        this.sunoConfig = new SunoConfig(token);
+    }
+
+    @Test
+    public void generateMusic() {
+        SunoApi sunoApi = new SunoApi(sunoConfig);
+        SunoApi.SunoRequest sunoRequest = new SunoApi
+                .SunoRequest()
+                .setPrompt("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。");
+        SunoApi.SunoResponse sunoResponse = sunoApi.musicGen(sunoRequest);
+        System.out.println(sunoResponse);
+    }
+}

+ 3 - 0
yudao-server/src/main/resources/application.yaml

@@ -199,6 +199,9 @@ yudao.ai:
     token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw
     guild-id: 1237948819677904956
     channel-id: 1237948819677904960
+  suno:
+    enable: true
+    token: 13f13540dd3f4ae9885f63ac9f5d0b9f
 
 --- #################### 芋道相关配置 ####################