new song structure

This commit is contained in:
2025-10-21 11:38:48 +02:00
parent 59328aaaed
commit 3a5d449424
11 changed files with 807 additions and 175 deletions
+80 -7
View File
@@ -10,6 +10,67 @@ const axios = require("axios");
const admin = require("firebase-admin");
const { refList } = require("../index");
const STRUCTURE_PROMPT_LABELS = {
couplet: "couplet",
refrain: "refrain",
short_intro: "introduction instrumentale courte",
long_intro: "introduction instrumentale longue",
pre_chorus: "pré-refrain",
};
const STRUCTURE_ALIASES = {
"short intro": "short_intro",
"introduction instrumentale courte": "short_intro",
"intro instrumentale courte": "short_intro",
"long intro": "long_intro",
"introduction instrumentale longue": "long_intro",
"intro instrumentale longue": "long_intro",
"pré-refrain": "pre_chorus",
"pre-refrain": "pre_chorus",
"pre chorus": "pre_chorus",
"pre-chorus": "pre_chorus",
prechorus: "pre_chorus",
};
const normalizeStructureValue = (value) => {
const raw = String(value || "").trim().toLowerCase();
if (!raw) return "";
if (STRUCTURE_PROMPT_LABELS[raw]) return raw;
if (STRUCTURE_ALIASES[raw]) return STRUCTURE_ALIASES[raw];
const sanitized = raw.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
if (STRUCTURE_PROMPT_LABELS[sanitized]) return sanitized;
if (STRUCTURE_ALIASES[sanitized]) return STRUCTURE_ALIASES[sanitized];
return sanitized;
};
const sanitizeStructureEntries = (structure = []) => {
if (!Array.isArray(structure)) return [];
const output = [];
structure.forEach((entry) => {
const normalized = normalizeStructureValue(entry);
if (!normalized) return;
if (normalized === "short_intro" || normalized === "long_intro") {
const existingIndex = output.findIndex(
(item) => item === "short_intro" || item === "long_intro",
);
if (existingIndex !== -1) output.splice(existingIndex, 1);
output.push(normalized);
return;
}
if (normalized === "pre_chorus") {
if (!output.includes("pre_chorus")) output.push("pre_chorus");
return;
}
output.push(normalized);
});
return output;
};
const mapStructureToPrompt = (structure = []) =>
sanitizeStructureEntries(structure).map(
(entry) => STRUCTURE_PROMPT_LABELS[entry] || entry,
);
const TOXIC_KEYWORDS = [
/\bnazis?\b/i,
/\bnazisme\b/i,
@@ -39,8 +100,9 @@ const buildModerationBrief = ({
`<RIMES>${Array.isArray(rhymes) ? rhymes.join(", ") : rhymes || ""}</RIMES>`,
];
if (Array.isArray(structure) && structure.length) {
sections.push(`<STRUCTURE>${structure.join(" | ")}</STRUCTURE>`);
const promptStructure = mapStructureToPrompt(structure);
if (promptStructure.length) {
sections.push(`<STRUCTURE>${promptStructure.join(" | ")}</STRUCTURE>`);
}
return `<BRIEF_UTILISATEUR>\n${sections.join("\n")}\n</BRIEF_UTILISATEUR>`;
@@ -76,17 +138,29 @@ exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => {
style = "",
audience = "",
emotion = "",
structure = ["couplet", "refrain", "couplet", "refrain"],
structure: rawStructure = [
"couplet",
"refrain",
"couplet",
"refrain",
],
rhymes = "",
} = data;
const sanitizedStructure = sanitizeStructureEntries(rawStructure);
const promptStructure = sanitizedStructure.length
? sanitizedStructure.map(
(entry) => STRUCTURE_PROMPT_LABELS[entry] || entry,
)
: [];
const moderationPayload = {
objective,
context,
style,
audience,
emotion,
structure,
structure: promptStructure,
rhymes,
};
@@ -137,7 +211,7 @@ Génère un titre accrocheur et mémorable en plus des paroles, tout en respecta
Retourne uniquement un JSON conforme au schéma fourni, sans ajouter d'autres textes.
`.trim();
const structureTags = (Array.isArray(structure) ? structure : [])
const structureTags = (Array.isArray(promptStructure) ? promptStructure : [])
.map((part, index) => {
const content = part || "";
return ` <SECTION ordre="${index + 1}">${content}</SECTION>`;
@@ -191,8 +265,7 @@ ${structureTags || fallbackStructureTags}
type: z
.string()
.describe(
'Type de section : "couplet" ou "refrain" ' +
"selon la structure",
"Type de section : respecter exactement le nom de la section demandé dans la structure",
),
lyrics: z
.string()