last fixes, home and videos

This commit is contained in:
Thomas Demirdjian
2025-11-10 14:24:16 +01:00
parent d7d9211fbe
commit dd4cf9b4d4
40 changed files with 984 additions and 647 deletions
+81 -20
View File
@@ -7,7 +7,6 @@ const {
} = require("../config/suno");
const { SUNO_API_KEY } = require("../config/keys");
const axios = require("axios");
const admin = require("firebase-admin");
const { FieldValue } = require("firebase-admin/firestore");
const { refList } = require("../index");
@@ -41,7 +40,7 @@ const STRUCTURE_ALIASES = {
"intro instrumentale longue": "long_intro",
"pré-refrain": "pre_refrain_instrumental",
"pre-refrain": "pre_refrain_instrumental",
"pre_refrain": "pre_refrain_instrumental",
pre_refrain: "pre_refrain_instrumental",
"pre chorus": "pre_refrain_instrumental",
"pre-chorus": "pre_refrain_instrumental",
prechorus: "pre_refrain_instrumental",
@@ -62,7 +61,9 @@ const STRUCTURE_ALIASES = {
};
const normalizeStructureValue = (value) => {
const raw = String(value || "").trim().toLowerCase();
const raw = String(value || "")
.trim()
.toLowerCase();
if (!raw) return "";
if (STRUCTURE_PROMPT_LABELS[raw]) return raw;
if (STRUCTURE_ALIASES[raw]) return STRUCTURE_ALIASES[raw];
@@ -160,9 +161,7 @@ const moderationIndicatesBlock = (moderation = {}, fallbackText = "") => {
const excerpts = Array.isArray(moderation.excerpts)
? moderation.excerpts
: [];
const reasons = Array.isArray(moderation.reasons)
? moderation.reasons
: [];
const reasons = Array.isArray(moderation.reasons) ? moderation.reasons : [];
const keywordDetected = (value) =>
typeof value === "string" && TOXIC_KEYWORDS.some((rx) => rx.test(value));
@@ -182,12 +181,7 @@ exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => {
style = "",
audience = "",
emotion = "",
structure: rawStructure = [
"couplet",
"refrain",
"couplet",
"refrain",
],
structure: rawStructure = ["couplet", "refrain", "couplet", "refrain"],
rhymes = "",
} = data;
@@ -248,14 +242,14 @@ exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => {
const system = `
Tu es un parolier expert en chanson française moderne et populaire.
Tu écris des chansons marquantes, accessibles, sur des thématiques d'innovation, d'esprit d'équipe et de créativité digitale.
Ton écriture doit toucher à la fois l'humain, le collectif et la passion d'entreprendre.
Analyse attentivement les informations placées entre balises XML-like.
Génère un titre accrocheur et mémorable en plus des paroles, tout en respectant strictement la structure demandée.
Retourne uniquement un JSON conforme au schéma fourni, sans ajouter d'autres textes.
`.trim();
const structureTags = (Array.isArray(promptStructure) ? promptStructure : [])
const structureTags = (
Array.isArray(promptStructure) ? promptStructure : []
)
.map((part, index) => {
const content = part || "";
return ` <SECTION ordre="${index + 1}">${content}</SECTION>`;
@@ -314,14 +308,12 @@ ${structureTags || fallbackStructureTags}
lyrics: z
.string()
.describe(
"Paroles de la section, chaque ligne séparée " +
"par un retour à la ligne",
"Paroles de la section, chaque ligne séparée par un retour à la ligne",
),
}),
)
.describe(
"Paroles de la chanson sous forme de tableau de sections " +
"structurées.",
"Paroles de la chanson sous forme de tableau de sections structurées.",
),
lyricsDescription: z
.string()
@@ -339,6 +331,74 @@ ${structureTags || fallbackStructureTags}
}
});
const severityScore = (severity = "") => {
const normalized = String(severity || "").toLowerCase();
switch (normalized) {
case "critical":
return 4;
case "high":
return 3;
case "medium":
return 2;
case "low":
return 1;
default:
return 0;
}
};
const softenModerationDecision = (rawResult = {}) => {
const result = { ...rawResult };
const excerpts = Array.isArray(result.excerpts) ? result.excerpts : [];
const reasons = Array.isArray(result.reasons) ? result.reasons : [];
const highestSeverity = excerpts.reduce(
(max, excerpt) => Math.max(max, severityScore(excerpt?.severity)),
0,
);
const score =
typeof result.score === "number" && !Number.isNaN(result.score)
? Math.min(Math.max(result.score, 0), 1)
: 0;
const narrativeHint = reasons.some((reason) =>
/narrati|story|persona|fiction|metaphor|metaphore|récit|roleplay|contexte/i.test(
reason || "",
),
);
const adjustments = [];
if (result.blocked) {
const shouldUnblockLowSeverity = highestSeverity <= 1 && score < 0.65;
if (shouldUnblockLowSeverity) {
result.blocked = false;
adjustments.push("auto-unblock-low-severity");
}
}
if (!result.blocked && result.flagged) {
const lowSignal = highestSeverity <= 1 && score < 0.4;
const contextual = narrativeHint && highestSeverity <= 2 && score < 0.55;
if (lowSignal) {
result.flagged = false;
adjustments.push("drop-flag-low-signal");
} else if (contextual) {
result.flagged = false;
adjustments.push("drop-flag-contextual");
}
}
return {
...result,
moderationAdjustments: adjustments,
moderationCalibration: {
highestSeverity,
score,
},
};
};
// Vérifie la toxicité des paroles via Gemini et retourne un résultat structuré
// Nom Cloud Function: lyrics-analyseLyricsToxicity
exports.analyseLyricsToxicity = onCall({}, async ({ data = {} }) => {
@@ -364,10 +424,11 @@ exports.analyseLyricsToxicity = onCall({}, async ({ data = {} }) => {
});
const parsed = requestSchema.parse(data || {});
const result = await analyseLyrics({
const aiResult = await analyseLyrics({
title: parsed.title || "",
lyrics: parsed.lyrics,
});
const result = softenModerationDecision(aiResult);
// Prépare un message lisible pour le front
const highCats = Object.entries(result.categories || {})