diff --git a/functions/package-lock.json b/functions/package-lock.json index 72b2cad..fff4c5e 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -541,7 +541,6 @@ "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.6.tgz", "integrity": "sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", @@ -1655,7 +1654,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -1751,7 +1749,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "1.25.1" }, @@ -2939,7 +2936,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "1.25.1", "@opentelemetry/semantic-conventions": "1.25.1" @@ -2982,7 +2978,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz", "integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "1.25.1", "@opentelemetry/resources": "1.25.1", @@ -3036,7 +3031,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz", "integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/core": "1.25.1", "@opentelemetry/resources": "1.25.1", @@ -3557,7 +3551,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4384,7 +4377,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4879,7 +4871,6 @@ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz", "integrity": "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "1.0.8", @@ -5184,7 +5175,6 @@ "resolved": "https://registry.npmjs.org/genkit/-/genkit-1.27.0.tgz", "integrity": "sha512-54OAzw9+dlOs2H4bWnktMwKVA1wwY9XmudKBAz2uBdWhep5r0xHy1qNE6tUVnSgn+LGGaR/0xfYRSs8uqNPFVw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@genkit-ai/ai": "1.27.0", "@genkit-ai/core": "1.27.0", @@ -7838,7 +7828,6 @@ "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.8", @@ -8025,7 +8014,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/functions/src/lyrics.js b/functions/src/lyrics.js index a816202..ae720d3 100644 --- a/functions/src/lyrics.js +++ b/functions/src/lyrics.js @@ -82,6 +82,35 @@ const sanitizeStructureEntries = (structure = []) => { const mapStructureToPrompt = (structure = []) => sanitizeStructureEntries(structure).map((entry) => STRUCTURE_PROMPT_LABELS[entry] || entry) +const RHYMES_PRESETS = [ + { + matchers: ['avec rimes', 'avec des rimes', 'rimes'], + prompt: 'Rimes riches et variées (rimes finales + internes). Évite les rimes faciles.', + }, + { + matchers: ['sans rimes', 'sans rime', 'pas de rimes', 'pas de rime'], + prompt: + "Vers libres sans rimes finales ni schéma de rimes. Évite les rimes intentionnelles, privilégie le rythme et l'imagerie.", + }, + { + matchers: ['mélange des deux', 'melange des deux', 'mixte'], + prompt: + 'Mélange: alterne sections rimées et sections non rimées. Au moins la moitié des lignes sans rime finale. Quand tu rimes, rimes riches et variées.', + }, +] + +const resolveRhymesInstruction = (value) => { + if (typeof value === 'string') { + const trimmed = value.trim() + if (!trimmed) return '' + const key = trimmed.toLowerCase() + const preset = RHYMES_PRESETS.find((option) => option.matchers.includes(key)) + return preset ? preset.prompt : trimmed + } + if (Array.isArray(value)) return value.join(', ') + return '' +} + // --- MODERATION --- const buildModerationBrief = ({ @@ -119,6 +148,7 @@ exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => { structure: rawStructure = ['couplet', 'refrain', 'couplet', 'refrain'], rhymes = '', } = data + const rhymesInstruction = resolveRhymesInstruction(rhymes) // 1. Préparation Structure const sanitizedStructure = sanitizeStructureEntries(rawStructure) @@ -135,7 +165,7 @@ exports.generateLyrics = onCall({}, async ({ auth = {}, data = {} }) => { audience, emotion, structure: promptStructure, - rhymes, + rhymes: rhymesInstruction, } const moderationInput = buildModerationBrief(moderationPayload) @@ -172,7 +202,7 @@ Ta spécialité est la **Prosodie** (le rythme naturel des mots) et l'impact ém TES RÈGLES D'OR : 1. **MÉTRIQUE & RYTHME** : Tes vers doivent être "chantables". Compte les syllabes pour qu'elles collent au style musical demandé. Évite les phrases trop longues ou imprononçables rapidement. -2. **RIMES RICHES** : Évite les rimes faciles (amour/toujours). Cherche des assonances et des rimes internes. +2. **RIMES** : Suis strictement du brief. Si "Sans rimes" ou "Mélange des deux" est demandé, respecte l'absence/alternance de rimes. Si rien n'est indiqué, fais des rimes riches et évite les rimes faciles (amour/toujours). 3. **SHOW, DON'T TELL** : N'écris pas "Je suis triste", écris "La pluie brouille mes carreaux". Utilise des détails sensoriels. 4. **STRUCTURE STRICTE** : Respecte exactement l'ordre des sections demandées. - **Couplet** : Raconte l'histoire, pose le décor. @@ -199,7 +229,7 @@ Retourne UNIQUEMENT un JSON valide. ${emotion || 'Intense'} ${style || 'Pop Moderne'} ${audience || 'Tout public'} - ${rhymes || 'Rimes croisées et riches'} + ${rhymesInstruction || 'Rimes croisées et riches'}