fix function and music generation
This commit is contained in:
@@ -1,25 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
es6: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2018,
|
|
||||||
},
|
|
||||||
extends: ["eslint:recommended", "google"],
|
|
||||||
rules: {
|
|
||||||
"no-restricted-globals": ["error", "name", "length"],
|
|
||||||
"prefer-arrow-callback": "error",
|
|
||||||
quotes: ["error", "double", { allowTemplateLiterals: true }],
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ["**/*.spec.*"],
|
|
||||||
env: {
|
|
||||||
mocha: true,
|
|
||||||
},
|
|
||||||
rules: {},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
globals: {},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Firebase
|
||||||
|
.firebase/
|
||||||
|
|
||||||
|
# Service account key
|
||||||
|
serviceAccountKey.json
|
||||||
|
|
||||||
|
# API keys
|
||||||
|
config/keys.js
|
||||||
|
|
||||||
|
# Build files
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
const admin = require("firebase-admin");
|
||||||
|
const serviceAccount = require("./serviceAccountKey.json");
|
||||||
|
const { googleAI, gemini25FlashLite } = require("@genkit-ai/googleai");
|
||||||
|
const { GEMINI_API_KEY } = require("./config/keys");
|
||||||
|
const { genkit } = require("genkit");
|
||||||
|
|
||||||
|
admin.initializeApp({
|
||||||
|
credential: admin.credential.cert(serviceAccount),
|
||||||
|
storageBucket: "musicland-d33f9.firebasestorage.app",
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.generateAI = async ({ system = "", prompt = "", schema }) => {
|
||||||
|
const genkitInstance = genkit({
|
||||||
|
plugins: [
|
||||||
|
googleAI({
|
||||||
|
apiKey: GEMINI_API_KEY,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
model: gemini25FlashLite,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prompt?.length < 1) {
|
||||||
|
throw new Error(
|
||||||
|
"Vous devez spécifier un prompt pour effectuer cette action.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { output } = await genkitInstance.generate({
|
||||||
|
system,
|
||||||
|
prompt,
|
||||||
|
output: { schema },
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Exporter toutes les fonctions
|
||||||
|
exports.music = require("./src/music");
|
||||||
|
exports.lyrics = require("./src/lyrics");
|
||||||
Generated
+4770
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "functions",
|
||||||
|
"description": "Cloud Functions for Firebase",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"serve": "firebase emulators:start --only functions",
|
||||||
|
"shell": "firebase functions:shell",
|
||||||
|
"start": "npm run shell",
|
||||||
|
"deploy": "firebase deploy --only functions",
|
||||||
|
"logs": "firebase functions:log"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "18"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"@genkit-ai/googleai": "^1.16.0",
|
||||||
|
"axios": "^1.6.0",
|
||||||
|
"firebase-admin": "^12.1.0",
|
||||||
|
"firebase-functions": "^5.0.0",
|
||||||
|
"genkit": "^1.16.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.15.0",
|
||||||
|
"eslint-config-google": "^0.14.0"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
const {onCall} = require("firebase-functions/https");
|
const { onCall } = require("firebase-functions/v2/https");
|
||||||
const {z} = require("genkit");
|
const { z } = require("genkit");
|
||||||
const {generateAI} = require("../index");
|
const { generateAI } = require("../index");
|
||||||
|
|
||||||
exports.generateLyrics = onCall({}, async ({auth = {}, data = {}}) => {
|
exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
objective = "Célébrer l'agence Minuit et mettre en avant son " +
|
objective = "Célébrer l'agence Minuit et mettre en avant son " +
|
||||||
@@ -24,7 +24,8 @@ exports.generateLyrics = onCall({}, async ({auth = {}, data = {}}) => {
|
|||||||
|
|
||||||
console.log("Function generate lyrics start with data", data);
|
console.log("Function generate lyrics start with data", data);
|
||||||
|
|
||||||
const system = `
|
const system =
|
||||||
|
`
|
||||||
Tu es un parolier expert en chanson française moderne et populaire.
|
Tu es un parolier expert en chanson française moderne et populaire.
|
||||||
Tu écris des chansons marquantes, accessibles, sur des thématiques ` +
|
Tu écris des chansons marquantes, accessibles, sur des thématiques ` +
|
||||||
`d'innovation, d'esprit d'équipe et de créativité digitale.
|
`d'innovation, d'esprit d'équipe et de créativité digitale.
|
||||||
@@ -39,7 +40,8 @@ Respecte ` +
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
// Construction du prompt utilisateur
|
// Construction du prompt utilisateur
|
||||||
const prompt = `
|
const prompt =
|
||||||
|
`
|
||||||
Objectif : ${objective}
|
Objectif : ${objective}
|
||||||
Contexte : ${context}
|
Contexte : ${context}
|
||||||
Émotion : ${emotion}
|
Émotion : ${emotion}
|
||||||
@@ -66,7 +68,7 @@ Pour chaque ` +
|
|||||||
type: z
|
type: z
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
"Type de section : \"couplet\" ou \"refrain\" " +
|
'Type de section : "couplet" ou "refrain" ' +
|
||||||
"selon la structure",
|
"selon la structure",
|
||||||
),
|
),
|
||||||
lyrics: z
|
lyrics: z
|
||||||
|
|||||||
+151
-145
@@ -1,5 +1,5 @@
|
|||||||
const { onCall, onRequest } = require("firebase-functions/v2/https");
|
const { onCall, onRequest } = require("firebase-functions/v2/https");
|
||||||
const fetch = require("node-fetch");
|
const axios = require("axios");
|
||||||
const admin = require("firebase-admin");
|
const admin = require("firebase-admin");
|
||||||
const { logger } = require("firebase-functions/logger");
|
const { logger } = require("firebase-functions/logger");
|
||||||
const { SUNO_API_KEY } = require("../config/keys");
|
const { SUNO_API_KEY } = require("../config/keys");
|
||||||
@@ -14,7 +14,7 @@ const SUNO_API_BASE = "https://api.sunoapi.org";
|
|||||||
const SUNO_API_PATH = "/api/v1/generate";
|
const SUNO_API_PATH = "/api/v1/generate";
|
||||||
const SUNO_STATUS_PATH = "/api/v1/generate/record-info";
|
const SUNO_STATUS_PATH = "/api/v1/generate/record-info";
|
||||||
const SUNO_MODEL = "V4_5";
|
const SUNO_MODEL = "V4_5";
|
||||||
const SUNO_INSTRUMENTAL = false;
|
// const SUNO_MODEL = "V3_5";
|
||||||
const SUNO_CALLBACK_URL =
|
const SUNO_CALLBACK_URL =
|
||||||
"https://us-central1-musicland-d33f9.cloudfunctions.net/music-sunoCallback";
|
"https://us-central1-musicland-d33f9.cloudfunctions.net/music-sunoCallback";
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ function lyricsToTaggedPrompt(lyrics = []) {
|
|||||||
const sections = lyrics
|
const sections = lyrics
|
||||||
.map((section) => {
|
.map((section) => {
|
||||||
if (!section || typeof section !== "object") return "";
|
if (!section || typeof section !== "object") return "";
|
||||||
const { type = "verse", content = "" } = section;
|
const { type = "verse", lyrics: content = "" } = section;
|
||||||
if (!content.trim()) return "";
|
if (!content.trim()) return "";
|
||||||
|
|
||||||
const tag = type.toLowerCase();
|
const tag = type.toLowerCase();
|
||||||
@@ -88,7 +88,6 @@ function buildSunoPrompt({
|
|||||||
projectContext = "",
|
projectContext = "",
|
||||||
emotionGuide = "",
|
emotionGuide = "",
|
||||||
styleGuide = "",
|
styleGuide = "",
|
||||||
taggedLyrics = "",
|
|
||||||
}) {
|
}) {
|
||||||
const parts = [
|
const parts = [
|
||||||
title ? `Titre: ${title}` : "",
|
title ? `Titre: ${title}` : "",
|
||||||
@@ -96,7 +95,6 @@ function buildSunoPrompt({
|
|||||||
projectContext ? `Contexte: ${projectContext}` : "",
|
projectContext ? `Contexte: ${projectContext}` : "",
|
||||||
emotionGuide ? `Émotion: ${emotionGuide}` : "",
|
emotionGuide ? `Émotion: ${emotionGuide}` : "",
|
||||||
styleGuide ? `Style: ${styleGuide}` : "",
|
styleGuide ? `Style: ${styleGuide}` : "",
|
||||||
taggedLyrics ? `Paroles:\n${taggedLyrics}` : "",
|
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
return parts.join("\n\n");
|
return parts.join("\n\n");
|
||||||
@@ -145,9 +143,7 @@ function buildGuides({
|
|||||||
/**
|
/**
|
||||||
* Fonction principale de génération de musique
|
* Fonction principale de génération de musique
|
||||||
*/
|
*/
|
||||||
exports.generateMusic = onCall(
|
exports.generateMusic = onCall(async ({ data = {} }) => {
|
||||||
{ region: "europe-west1" },
|
|
||||||
async ({ data = {} }) => {
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
title = "",
|
title = "",
|
||||||
@@ -170,82 +166,54 @@ exports.generateMusic = onCall(
|
|||||||
|
|
||||||
const safeTitle = clampLen(title, 80);
|
const safeTitle = clampLen(title, 80);
|
||||||
|
|
||||||
// Construire les informations de contexte pour le style (sans les paroles)
|
// Construire les informations de contexte pour le style
|
||||||
const contextInfo = buildSunoPrompt({
|
const contextInfo = buildSunoPrompt({
|
||||||
title: safeTitle,
|
title: safeTitle,
|
||||||
audience,
|
audience,
|
||||||
projectContext,
|
projectContext,
|
||||||
emotionGuide,
|
emotionGuide,
|
||||||
styleGuide,
|
styleGuide,
|
||||||
taggedLyrics: "", // Ne pas inclure les paroles dans le contexte
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Le style combine les informations de genre ET le contexte
|
// Le style combine les informations de genre ET le contexte
|
||||||
const baseStyle = buildStyle({ genres, voice, instruments, tempo });
|
const baseStyle = buildStyle({ genres, voice, instruments, tempo });
|
||||||
const enhancedStyle = contextInfo
|
const enhancedStyle = `${baseStyle}. ${contextInfo}`;
|
||||||
? `${baseStyle}. ${contextInfo}`
|
|
||||||
: baseStyle;
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
customMode: true,
|
customMode: true,
|
||||||
|
instrumental: false,
|
||||||
model: SUNO_MODEL,
|
model: SUNO_MODEL,
|
||||||
|
prompt: clampLen(taggedLyrics, 3000),
|
||||||
title: safeTitle,
|
title: safeTitle,
|
||||||
style: clampLen(enhancedStyle, 600),
|
style: clampLen(enhancedStyle, 600),
|
||||||
instrumental: SUNO_INSTRUMENTAL,
|
|
||||||
callBackUrl: SUNO_CALLBACK_URL || "",
|
callBackUrl: SUNO_CALLBACK_URL || "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Les paroles vont dans le prompt, pas les instructions de style
|
console.log("PAYLOAD", payload);
|
||||||
if (!SUNO_INSTRUMENTAL && taggedLyrics) {
|
|
||||||
payload.prompt = clampLen(taggedLyrics, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${SUNO_API_BASE}${SUNO_API_PATH}`, {
|
const response = await axios.post(
|
||||||
method: "POST",
|
`${SUNO_API_BASE}${SUNO_API_PATH}`,
|
||||||
|
payload,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
},
|
||||||
});
|
|
||||||
|
|
||||||
const raw = await response.text();
|
|
||||||
let parsed;
|
|
||||||
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(raw);
|
|
||||||
} catch (parseError) {
|
|
||||||
console.error("❌ Erreur de parsing JSON:", parseError);
|
|
||||||
const { HttpsError } = require("firebase-functions/v2/https");
|
|
||||||
throw new HttpsError(
|
|
||||||
"internal",
|
|
||||||
`Réponse API invalide: ${raw.substring(0, 200)}...`,
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
const parsed = response.data;
|
||||||
console.error("❌ Erreur API Suno:", {
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
body: parsed || raw,
|
|
||||||
});
|
|
||||||
throw new Error(
|
|
||||||
`Erreur API Suno (${response.status}): ${
|
|
||||||
(parsed && parsed.error) || response.statusText
|
|
||||||
}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: !!parsed?.data?.taskId,
|
||||||
request: {
|
request: {
|
||||||
model: SUNO_MODEL,
|
model: SUNO_MODEL,
|
||||||
instrumental: SUNO_INSTRUMENTAL,
|
instrumental: false,
|
||||||
title: safeTitle,
|
title: safeTitle,
|
||||||
style: enhancedStyle,
|
style: enhancedStyle,
|
||||||
prompt: SUNO_INSTRUMENTAL ? undefined : taggedLyrics,
|
prompt: taggedLyrics,
|
||||||
},
|
},
|
||||||
response: parsed || { raw },
|
response: parsed || {},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Erreur lors de la génération de musique:", error);
|
console.error("❌ Erreur lors de la génération de musique:", error);
|
||||||
@@ -253,15 +221,12 @@ exports.generateMusic = onCall(
|
|||||||
`Erreur lors de la génération de musique: ${error.message}`,
|
`Erreur lors de la génération de musique: ${error.message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction pour suivre le statut d'une génération de musique Suno
|
* Fonction pour suivre le statut d'une génération de musique Suno
|
||||||
*/
|
*/
|
||||||
exports.getSunoStatus = onCall(
|
exports.getSunoStatus = onCall(async ({ data = {} }) => {
|
||||||
{ region: "europe-west1" },
|
|
||||||
async ({ data = {} }) => {
|
|
||||||
try {
|
try {
|
||||||
const { taskId } = data;
|
const { taskId } = data;
|
||||||
|
|
||||||
@@ -269,37 +234,42 @@ exports.getSunoStatus = onCall(
|
|||||||
throw new Error("TaskId manquant");
|
throw new Error("TaskId manquant");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("🔍 Vérification du statut Suno", { taskId });
|
console.log("🔍 Vérification du statut Suno", { taskId });
|
||||||
|
|
||||||
const response = await fetch(
|
let response;
|
||||||
`${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${SUNO_API_KEY}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const raw = await response.text();
|
|
||||||
let parsed;
|
let parsed;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(raw);
|
response = await axios.get(
|
||||||
} catch (parseError) {
|
`${SUNO_API_BASE}${SUNO_STATUS_PATH}?taskId=${taskId}`,
|
||||||
console.error("❌ Erreur de parsing JSON:", parseError);
|
{
|
||||||
throw new Error(`Réponse API invalide: ${raw.substring(0, 200)}...`);
|
headers: {
|
||||||
}
|
Authorization: `Bearer ${SUNO_API_KEY}`,
|
||||||
|
},
|
||||||
|
timeout: 30000, // 30 secondes de timeout
|
||||||
|
},
|
||||||
|
);
|
||||||
|
parsed = response.data;
|
||||||
|
|
||||||
if (!response.ok) {
|
console.log("📊 Réponse Suno API:", parsed);
|
||||||
console.error("❌ Erreur API Suno Status:", {
|
} catch (error) {
|
||||||
status: response.status,
|
console.error("❌ Erreur détaillée API Suno Status:", {
|
||||||
statusText: response.statusText,
|
message: error.message,
|
||||||
body: parsed || raw,
|
status: error.response?.status,
|
||||||
|
statusText: error.response?.statusText,
|
||||||
|
data: error.response?.data,
|
||||||
|
config: {
|
||||||
|
url: error.config?.url,
|
||||||
|
headers: error.config?.headers,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestion spéciale pour les taskId de test
|
// Gestion spéciale pour les taskId de test
|
||||||
if (response.status === 404 && taskId.includes("test")) {
|
if (
|
||||||
|
error.response &&
|
||||||
|
error.response.status === 404 &&
|
||||||
|
taskId.includes("test")
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
taskId,
|
taskId,
|
||||||
@@ -311,31 +281,48 @@ exports.getSunoStatus = onCall(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
// Retourner une erreur plus descriptive
|
||||||
`Erreur API Suno Status (${response.status}): ${
|
const errorStatus = error.response?.status || "UNKNOWN";
|
||||||
(parsed && parsed.error) || response.statusText
|
const errorMessage =
|
||||||
}`,
|
error.response?.data?.msg ||
|
||||||
);
|
error.response?.data?.message ||
|
||||||
|
error.response?.statusText ||
|
||||||
|
error.message;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
taskId,
|
||||||
|
error: {
|
||||||
|
status: errorStatus,
|
||||||
|
message: errorMessage,
|
||||||
|
type: "API_ERROR",
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("✅ Statut Suno récupéré", {
|
console.log("✅ Statut Suno récupéré", {
|
||||||
taskId,
|
taskId,
|
||||||
status: parsed && parsed.status,
|
status: parsed?.data?.status || parsed?.status,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
taskId,
|
taskId,
|
||||||
data: parsed,
|
data: parsed?.data || parsed,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Erreur lors de la vérification du statut:", error);
|
console.error("❌ Erreur lors de la vérification du statut:", error);
|
||||||
throw new Error(
|
|
||||||
`Erreur lors de la vérification du statut: ${error.message}`,
|
return {
|
||||||
);
|
success: false,
|
||||||
}
|
taskId: data?.taskId,
|
||||||
|
error: {
|
||||||
|
message: error.message,
|
||||||
|
type: "INTERNAL_ERROR",
|
||||||
},
|
},
|
||||||
);
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cloud Function pour recevoir les callbacks de l'API Suno
|
* Cloud Function pour recevoir les callbacks de l'API Suno
|
||||||
@@ -344,23 +331,24 @@ exports.getSunoStatus = onCall(
|
|||||||
*/
|
*/
|
||||||
exports.sunoCallback = onRequest(
|
exports.sunoCallback = onRequest(
|
||||||
{
|
{
|
||||||
region: "europe-west1",
|
|
||||||
cors: true,
|
|
||||||
methods: ["POST"],
|
methods: ["POST"],
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
try {
|
try {
|
||||||
logger.info("🎵 Callback Suno reçu", { data: req.body });
|
console.log("🎵 Received music generation callback body:", req.body);
|
||||||
|
|
||||||
// Vérifier que c'est une requête POST
|
|
||||||
if (req.method !== "POST") {
|
if (req.method !== "POST") {
|
||||||
logger.warn("⚠️ Méthode non autorisée:", req.method);
|
console.warn("⚠️ Méthode non autorisée:", req.method);
|
||||||
return res.status(405).json({ error: "Méthode non autorisée" });
|
return res.status(405).json({ error: "Méthode non autorisée" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extraire les données du callback
|
const { code, msg, data } = req.body || {};
|
||||||
|
console.log("🎵 Callback details:", { code, msg, data });
|
||||||
|
|
||||||
|
// Extraire les données du callback - peut être dans data ou directement dans req.body
|
||||||
|
const callbackData = data || req.body || {};
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
|
taskId,
|
||||||
status,
|
status,
|
||||||
audio_url: audioUrl,
|
audio_url: audioUrl,
|
||||||
video_url: videoUrl,
|
video_url: videoUrl,
|
||||||
@@ -370,69 +358,87 @@ exports.sunoCallback = onRequest(
|
|||||||
tags,
|
tags,
|
||||||
duration,
|
duration,
|
||||||
error_message: errorMessage,
|
error_message: errorMessage,
|
||||||
} = req.body;
|
} = callbackData;
|
||||||
|
|
||||||
// Vérifier que l'ID est présent
|
// Normaliser le statut global et le taskId
|
||||||
if (!id) {
|
const overallStatus = status || code || callbackData?.status || null;
|
||||||
logger.error("❌ ID manquant dans le callback");
|
const overallTaskId = callbackData?.taskId || callbackData?.task_id || null;
|
||||||
return res.status(400).json({ error: "ID manquant" });
|
|
||||||
|
// Déterminer les éléments piste(s) à traiter
|
||||||
|
const items = Array.isArray(callbackData?.response?.data)
|
||||||
|
? callbackData.response.data
|
||||||
|
: Array.isArray(callbackData?.sunoData)
|
||||||
|
? callbackData.sunoData
|
||||||
|
: id || audioUrl || imageUrl || title || tags || duration
|
||||||
|
? [callbackData]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
// Aucun contenu piste à mettre à jour: acquitter le callback sans erreur
|
||||||
|
return res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: "Callback reçu (aucune piste à mettre à jour)",
|
||||||
|
taskId: overallTaskId,
|
||||||
|
status: overallStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates = [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const trackId = item.id || item.musicId || item.audioId || item.audio_id;
|
||||||
|
if (!trackId) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chercher le document correspondant dans Firestore
|
// Chercher le document correspondant dans Firestore
|
||||||
const musicRef = db.collection("music").doc(id);
|
const musicRef = db.collection("music").doc(trackId);
|
||||||
const musicDoc = await musicRef.get();
|
const musicDoc = await musicRef.get();
|
||||||
|
|
||||||
if (!musicDoc.exists) {
|
if (!musicDoc.exists) {
|
||||||
logger.error("❌ Document non trouvé pour l'ID:", id);
|
// Si le doc n'existe pas, ignorer silencieusement cet item
|
||||||
return res.status(404).json({ error: "Document non trouvé" });
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Préparer les données de mise à jour
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
status: status,
|
status: item.status || overallStatus,
|
||||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ajouter les URLs si disponibles
|
const audioU = item.audio_url || item.audioUrl || item.streamAudioUrl;
|
||||||
if (audioUrl) {
|
const videoU = item.video_url || item.videoUrl;
|
||||||
updateData.audioUrl = audioUrl;
|
const imageU = item.image_url || item.imageUrl;
|
||||||
}
|
const lyricText = item.lyric || item.prompt;
|
||||||
if (videoUrl) {
|
const titleText = item.title;
|
||||||
updateData.videoUrl = videoUrl;
|
const tagsText = item.tags;
|
||||||
}
|
const durationVal = item.duration;
|
||||||
if (imageUrl) {
|
const errMsg = item.error_message || item.errorMessage;
|
||||||
updateData.imageUrl = imageUrl;
|
|
||||||
}
|
if (audioU) updateData.audioUrl = audioU;
|
||||||
if (lyric) {
|
if (videoU) updateData.videoUrl = videoU;
|
||||||
updateData.lyrics = lyric;
|
if (imageU) updateData.imageUrl = imageU;
|
||||||
}
|
if (lyricText) updateData.lyrics = lyricText;
|
||||||
if (title) {
|
if (titleText) updateData.title = titleText;
|
||||||
updateData.title = title;
|
if (tagsText) updateData.style = tagsText;
|
||||||
}
|
if (durationVal) updateData.duration = durationVal;
|
||||||
if (tags) {
|
if (errMsg) updateData.errorMessage = errMsg;
|
||||||
updateData.style = tags;
|
|
||||||
}
|
updates.push(
|
||||||
if (duration) {
|
musicRef
|
||||||
updateData.duration = duration;
|
.update(updateData)
|
||||||
}
|
.then(() => ({ id: trackId, ok: true }))
|
||||||
if (errorMessage) {
|
.catch(() => ({ id: trackId, ok: false })),
|
||||||
updateData.errorMessage = errorMessage;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettre à jour le document dans Firestore
|
const results = await Promise.all(updates);
|
||||||
await musicRef.update(updateData);
|
const updated = results.filter((r) => r.ok).map((r) => r.id);
|
||||||
|
|
||||||
logger.info("✅ Document mis à jour avec succès", {
|
return res.status(200).json({
|
||||||
id: id,
|
|
||||||
status: status,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Répondre avec succès
|
|
||||||
res.status(200).json({
|
|
||||||
success: true,
|
success: true,
|
||||||
message: "Callback traité avec succès",
|
message: "Callback traité avec succès",
|
||||||
id: id,
|
updated,
|
||||||
status: status,
|
taskId: overallTaskId,
|
||||||
|
status: overallStatus,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("❌ Erreur lors du traitement du callback:", error);
|
logger.error("❌ Erreur lors du traitement du callback:", error);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ if (!firebase?.apps?.filter(({ name_ }) => name_ === "[DEFAULT]").length) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
firebase.functions().useEmulator("192.168.68.98", 5001);
|
firebase.functions().useEmulator("192.168.1.81", 5001);
|
||||||
}
|
}
|
||||||
console.log("Firebase init");
|
console.log("Firebase init");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { navigate } from "../../navigation/NavigationService";
|
|||||||
import { Routes } from "../../navigation";
|
import { Routes } from "../../navigation";
|
||||||
import { gutters } from "../../styles";
|
import { gutters } from "../../styles";
|
||||||
import firebase from "../../config/firebase";
|
import firebase from "../../config/firebase";
|
||||||
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
|
|
||||||
import { responsiveHeight } from "react-native-responsive-dimensions";
|
import { responsiveHeight } from "react-native-responsive-dimensions";
|
||||||
|
import useMinuit from "react-native-minuit/src/hooks/useMinuit";
|
||||||
|
|
||||||
const Studio = () => {
|
const Studio = () => {
|
||||||
const { setIsLoading } = useMinuit();
|
const { setIsLoading } = useMinuit();
|
||||||
@@ -70,7 +70,7 @@ const Studio = () => {
|
|||||||
const { data } = await firebase
|
const { data } = await firebase
|
||||||
.functions()
|
.functions()
|
||||||
.httpsCallable("music-getSunoStatus")({
|
.httpsCallable("music-getSunoStatus")({
|
||||||
taskId: "1b04b24a9b95a46125a73f25144b74c2",
|
taskId: "2c9f00d27a30ace15b1b7cbc36886bff",
|
||||||
});
|
});
|
||||||
console.log("🎵 Status data:", data);
|
console.log("🎵 Status data:", data);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user