feat: Model selection.

1. Use the model number as an optional parameter, defaulting to 3.5;
2. Improve documentation.
This commit is contained in:
blueeon
2024-06-08 12:05:59 +08:00
parent 9be31d8519
commit cb36f1b1a4
10 changed files with 498 additions and 9078 deletions
+3 -2
View File
@@ -1,5 +1,5 @@
import { NextResponse, NextRequest } from "next/server";
import { sunoApi } from "@/lib/SunoApi";
import { DEFAULT_MODEL, sunoApi } from "@/lib/SunoApi";
import { corsHeaders } from "@/lib/utils";
export const maxDuration = 60; // allow longer timeout for wait_audio == true
@@ -9,7 +9,7 @@ export async function POST(req: NextRequest) {
if (req.method === 'POST') {
try {
const body = await req.json();
const { prompt, tags, title, make_instrumental, wait_audio } = body;
const { prompt, tags, title, make_instrumental, model, wait_audio } = body;
if (!prompt || !tags || !title) {
return new NextResponse(JSON.stringify({ error: 'Prompt, tags, and title are required' }), {
status: 400,
@@ -22,6 +22,7 @@ export async function POST(req: NextRequest) {
const audioInfo = await (await sunoApi).custom_generate(
prompt, tags, title,
make_instrumental == true,
model || DEFAULT_MODEL,
wait_audio == true
);
return new NextResponse(JSON.stringify(audioInfo), {
+3 -4
View File
@@ -1,5 +1,5 @@
import { NextResponse, NextRequest } from "next/server";
import { sunoApi } from "@/lib/SunoApi";
import { DEFAULT_MODEL, sunoApi } from "@/lib/SunoApi";
import { corsHeaders } from "@/lib/utils";
export const dynamic = "force-dynamic";
@@ -8,8 +8,7 @@ export async function POST(req: NextRequest) {
if (req.method === 'POST') {
try {
const body = await req.json();
const { audio_id, prompt, continue_at, tags, title } = body;
console.log(body)
const { audio_id, prompt, continue_at, tags, title, model } = body;
if (!audio_id) {
return new NextResponse(JSON.stringify({ error: 'Audio ID is required' }), {
@@ -22,7 +21,7 @@ export async function POST(req: NextRequest) {
}
const audioInfo = await (await sunoApi)
.extendAudio(audio_id, prompt, continue_at, tags, title);
.extendAudio(audio_id, prompt, continue_at, tags, title, model || DEFAULT_MODEL);
return new NextResponse(JSON.stringify(audioInfo), {
status: 200,
+8 -3
View File
@@ -1,5 +1,5 @@
import { NextResponse, NextRequest } from "next/server";
import { sunoApi } from "@/lib/SunoApi";
import { DEFAULT_MODEL, sunoApi } from "@/lib/SunoApi";
import { corsHeaders } from "@/lib/utils";
export const dynamic = "force-dynamic";
@@ -8,7 +8,7 @@ export async function POST(req: NextRequest) {
if (req.method === 'POST') {
try {
const body = await req.json();
const { prompt, make_instrumental, wait_audio } = body;
const { prompt, make_instrumental, model, wait_audio } = body;
if (!prompt) {
return new NextResponse(JSON.stringify({ error: 'Prompt is required' }), {
@@ -20,7 +20,12 @@ export async function POST(req: NextRequest) {
});
}
const audioInfo = await (await sunoApi).generate(prompt, make_instrumental == true, wait_audio == true);
const audioInfo = await (await sunoApi).generate(
prompt,
make_instrumental == true,
model || DEFAULT_MODEL,
wait_audio == true
);
return new NextResponse(JSON.stringify(audioInfo), {
status: 200,
+162 -2
View File
@@ -33,6 +33,11 @@
"description": "Whether to generate instrumental music",
"example": "false"
},
"model": {
"type": "string",
"description": "Model name ,default is chirp-v3-5",
"example": "chirp-v3-5|chirp-v3-0"
},
"wait_audio": {
"type": "boolean",
"description": "Whether to wait for music generation, default is false, directly return audio task information; set to true, will wait for up to 100s until the audio is generated.",
@@ -79,7 +84,7 @@
"application/json": {
"schema": {
"type": "object",
"required": ["prompt", "make_instrumental", "wait_audio"],
"required": ["prompt"],
"properties": {
"prompt": {
"type": "string",
@@ -143,6 +148,11 @@
"description": "Whether to generate instrumental music",
"example": "false"
},
"model": {
"type": "string",
"description": "Model name ,default is chirp-v3-5",
"example": "chirp-v3-5|chirp-v3-0"
},
"wait_audio": {
"type": "boolean",
"description": "Whether to wait for music generation, default is false, directly return audio task information; set to true, will wait for up to 100s until the audio is generated.",
@@ -199,7 +209,7 @@
"prompt": {
"type": "string",
"description": "Detailed prompt, including information such as music lyrics.",
"example": ""
"example": "[lrc]Silent battlegrounds, no birds' song\nShadows of war, where we don't belong\nMay flowers of peace bloom in this place\nLet's guard this precious dream with grace\n[endlrc]"
},
"continue_at": {
"type": "string",
@@ -215,6 +225,11 @@
"type": "string",
"description": "Music genre",
"example": ""
},
"model": {
"type": "string",
"description": "Model name ,default is chirp-v3-5",
"example": "chirp-v3-5|chirp-v3-0"
}
}
}
@@ -357,6 +372,151 @@
}
}
}
},
"/api/clip": {
"get": {
"summary": "Get clip information based on ID.",
"description": "Retrieve specific clip information using the provided clip ID as a query parameter.",
"tags": ["default"],
"parameters": [
{
"name": "id",
"in": "query",
"required": true,
"description": "Clip ID",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/audio_info"
}
}
}
},
"400": {
"description": "Missing parameter id",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Missing parameter id"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Internal server error"
}
}
}
}
}
}
}
}
},
"/api/concat": {
"post": {
"summary": "Generate the whole song from extensions.",
"description": "Concatenate audio clips to generate a complete song using the provided clip ID.",
"tags": ["default"],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["clip_id"],
"properties": {
"clip_id": {
"type": "string",
"description": "Clip ID"
}
}
}
}
}
},
"responses": {
"200": {
"description": "success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/audio_info"
}
}
}
},
"400": {
"description": "Clip id is required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Clip id is required"
}
}
}
}
}
},
"402": {
"description": "Payment required",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Payment required"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {
"type": "string",
"example": "Internal server error"
}
}
}
}
}
}
}
}
}
},
"components": {
+14 -9
View File
@@ -6,6 +6,7 @@ import { CookieJar } from "tough-cookie";
import { sleep } from "@/lib/utils";
const logger = pino();
export const DEFAULT_MODEL = "chirp-v3-5";
export interface AudioInfo {
@@ -64,7 +65,7 @@ class SunoApi {
*/
private async getAuthToken() {
// URL to get session ID
const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=4.73.2`;                                                                                      
const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_clerk_js_version=4.73.2`;
// Get session ID
const sessionResponse = await this.client.get(getSessionUrl);
if (!sessionResponse?.data?.response?.['last_active_session_id']) {
@@ -83,7 +84,7 @@ class SunoApi {
throw new Error("Session ID is not set. Cannot renew token.");
}
// URL to renew session token
const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_clerk_js_version==4.73.2`;  
const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_clerk_js_version==4.73.2`;
// Renew session token
const renewResponse = await this.client.post(renewUrl);
logger.info("KeepAlive...\n");
@@ -91,7 +92,6 @@ class SunoApi {
await sleep(1, 2);
}
const newToken = renewResponse.data['jwt'];
console.log("newToken:===\n\n", newToken);
// Update Authorization field in request header with the new JWT token
this.currentToken = newToken;
}
@@ -106,11 +106,13 @@ class SunoApi {
public async generate(
prompt: string,
make_instrumental: boolean = false,
model?: string,
wait_audio: boolean = false,
): Promise<AudioInfo[]> {
await this.keepAlive(false);
const startTime = Date.now();
const audios = this.generateSongs(prompt, false, undefined, undefined, make_instrumental, wait_audio);
const audios = this.generateSongs(prompt, false, undefined, undefined, make_instrumental, model, wait_audio);
const costTime = Date.now() - startTime;
logger.info("Generate Response:\n" + JSON.stringify(audios, null, 2));
logger.info("Cost time: " + costTime);
@@ -155,10 +157,11 @@ class SunoApi {
tags: string,
title: string,
make_instrumental: boolean = false,
model?: string,
wait_audio: boolean = false,
): Promise<AudioInfo[]> {
const startTime = Date.now();
const audios = await this.generateSongs(prompt, true, tags, title, make_instrumental, wait_audio);
const audios = await this.generateSongs(prompt, true, tags, title, make_instrumental, model, wait_audio);
const costTime = Date.now() - startTime;
logger.info("Custom Generate Response:\n" + JSON.stringify(audios, null, 2));
logger.info("Cost time: " + costTime);
@@ -182,12 +185,13 @@ class SunoApi {
tags?: string,
title?: string,
make_instrumental?: boolean,
model?: string,
wait_audio: boolean = false
): Promise<AudioInfo[]> {
await this.keepAlive(false);
const payload: any = {
make_instrumental: make_instrumental == true,
mv: "chirp-v3-5",
mv: model || DEFAULT_MODEL,
prompt: "",
};
if (isCustom) {
@@ -297,15 +301,16 @@ class SunoApi {
prompt: string = "",
continueAt: string = "0",
tags: string = "",
title: string = ""
title: string = "",
model?: string,
): Promise<AudioInfo> {
const response = await this.client.post(`${SunoApi.BASE_URL}/api/generate/v2/`, {
continue_clip_id: audioId,
continue_at: continueAt,
mv: "chirp-v3-0",
mv: model || DEFAULT_MODEL,
prompt: prompt,
tags: tags,
title: ""
title: title
});
return response.data;
}