241 lines
7.6 KiB
JavaScript
241 lines
7.6 KiB
JavaScript
// ========================================
|
|
// FICHIER: MissingKeywords.js - Version Node.js
|
|
// Description: Génération automatique des mots-clés manquants
|
|
// ========================================
|
|
|
|
const { logSh } = require('./ErrorReporting');
|
|
const { callLLM } = require('./LLMManager');
|
|
|
|
/**
|
|
* Génère automatiquement les mots-clés manquants pour les éléments non définis
|
|
* @param {Array} elements - Liste des éléments extraits
|
|
* @param {Object} csvData - Données CSV avec personnalité
|
|
* @returns {Object} Éléments mis à jour avec nouveaux mots-clés
|
|
*/
|
|
async function generateMissingKeywords(elements, csvData) {
|
|
logSh('>>> GÉNÉRATION MOTS-CLÉS MANQUANTS <<<', 'INFO');
|
|
|
|
// 1. IDENTIFIER tous les éléments manquants
|
|
const missingElements = [];
|
|
elements.forEach(element => {
|
|
if (element.resolvedContent.includes('non défini') ||
|
|
element.resolvedContent.includes('non résolu') ||
|
|
element.resolvedContent.trim() === '') {
|
|
|
|
missingElements.push({
|
|
tag: element.originalTag,
|
|
name: element.name,
|
|
type: element.type,
|
|
currentContent: element.resolvedContent,
|
|
context: getElementContext(element, elements, csvData)
|
|
});
|
|
}
|
|
});
|
|
|
|
if (missingElements.length === 0) {
|
|
logSh('Aucun mot-clé manquant détecté', 'INFO');
|
|
return {};
|
|
}
|
|
|
|
logSh(`${missingElements.length} mots-clés manquants détectés`, 'INFO');
|
|
|
|
// 2. ANALYSER le contexte global disponible
|
|
const contextAnalysis = analyzeAvailableContext(elements, csvData);
|
|
|
|
// 3. GÉNÉRER tous les manquants en UN SEUL appel IA
|
|
const generatedKeywords = await callOpenAIForMissingKeywords(missingElements, contextAnalysis, csvData);
|
|
|
|
// 4. METTRE À JOUR les éléments avec les nouveaux mots-clés
|
|
const updatedElements = updateElementsWithKeywords(elements, generatedKeywords);
|
|
|
|
logSh(`Mots-clés manquants générés: ${Object.keys(generatedKeywords).length}`, 'INFO');
|
|
return updatedElements;
|
|
}
|
|
|
|
/**
|
|
* Analyser le contexte disponible pour guider la génération
|
|
* @param {Array} elements - Tous les éléments
|
|
* @param {Object} csvData - Données CSV
|
|
* @returns {Object} Analyse contextuelle
|
|
*/
|
|
function analyzeAvailableContext(elements, csvData) {
|
|
const availableKeywords = [];
|
|
const availableContent = [];
|
|
|
|
// Récupérer tous les mots-clés/contenu déjà disponibles
|
|
elements.forEach(element => {
|
|
if (element.resolvedContent &&
|
|
!element.resolvedContent.includes('non défini') &&
|
|
!element.resolvedContent.includes('non résolu') &&
|
|
element.resolvedContent.trim() !== '') {
|
|
|
|
if (element.type.includes('titre')) {
|
|
availableKeywords.push(element.resolvedContent);
|
|
} else {
|
|
availableContent.push(element.resolvedContent.substring(0, 100));
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
mainKeyword: csvData.mc0,
|
|
mainTitle: csvData.t0,
|
|
availableKeywords: availableKeywords,
|
|
availableContent: availableContent,
|
|
theme: csvData.mc0, // Thème principal
|
|
businessContext: "Autocollant.fr - signalétique personnalisée, plaques"
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Obtenir le contexte spécifique d'un élément
|
|
* @param {Object} element - Élément à analyser
|
|
* @param {Array} allElements - Tous les éléments
|
|
* @param {Object} csvData - Données CSV
|
|
* @returns {Object} Contexte de l'élément
|
|
*/
|
|
function getElementContext(element, allElements, csvData) {
|
|
const context = {
|
|
elementType: element.type,
|
|
hierarchyLevel: element.name,
|
|
nearbyElements: []
|
|
};
|
|
|
|
// Trouver les éléments proches dans la hiérarchie
|
|
const elementParts = element.name.split('_');
|
|
if (elementParts.length >= 2) {
|
|
const baseLevel = elementParts.slice(0, 2).join('_'); // Ex: "Titre_H3"
|
|
|
|
allElements.forEach(otherElement => {
|
|
if (otherElement.name.startsWith(baseLevel) &&
|
|
otherElement.resolvedContent &&
|
|
!otherElement.resolvedContent.includes('non défini')) {
|
|
|
|
context.nearbyElements.push(otherElement.resolvedContent);
|
|
}
|
|
});
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* Appel IA pour générer tous les mots-clés manquants en un seul batch
|
|
* @param {Array} missingElements - Éléments manquants
|
|
* @param {Object} contextAnalysis - Analyse contextuelle
|
|
* @param {Object} csvData - Données CSV avec personnalité
|
|
* @returns {Object} Mots-clés générés
|
|
*/
|
|
async function callOpenAIForMissingKeywords(missingElements, contextAnalysis, csvData) {
|
|
const personality = csvData.personality;
|
|
|
|
let prompt = `Tu es ${personality.nom} (${personality.description}). Style: ${personality.style}
|
|
|
|
MISSION: GÉNÈRE ${missingElements.length} MOTS-CLÉS/EXPRESSIONS MANQUANTS pour ${contextAnalysis.mainKeyword}
|
|
|
|
CONTEXTE:
|
|
- Sujet: ${contextAnalysis.mainKeyword}
|
|
- Entreprise: Autocollant.fr (signalétique)
|
|
- Mots-clés existants: ${contextAnalysis.availableKeywords.slice(0, 3).join(', ')}
|
|
|
|
ÉLÉMENTS MANQUANTS:
|
|
`;
|
|
|
|
missingElements.forEach((missing, index) => {
|
|
prompt += `${index + 1}. [${missing.name}] → Mot-clé SEO\n`;
|
|
});
|
|
|
|
prompt += `\nCONSIGNES:
|
|
- Thème: ${contextAnalysis.mainKeyword}
|
|
- Mots-clés SEO naturels
|
|
- Varie les termes
|
|
- Évite répétitions
|
|
|
|
FORMAT:
|
|
[${missingElements[0].name}]
|
|
mot-clé
|
|
|
|
[${missingElements[1] ? missingElements[1].name : 'exemple'}]
|
|
mot-clé
|
|
|
|
etc...`;
|
|
|
|
try {
|
|
logSh('Génération mots-clés manquants...', 'DEBUG');
|
|
|
|
// Utilisation du LLM Manager avec fallback
|
|
const response = await callLLM('openai', prompt, {
|
|
temperature: 0.7,
|
|
maxTokens: 2000
|
|
}, personality);
|
|
|
|
// Parser la réponse
|
|
const generatedKeywords = parseMissingKeywordsResponse(response, missingElements);
|
|
|
|
return generatedKeywords;
|
|
|
|
} catch (error) {
|
|
logSh(`❌ FATAL: Génération mots-clés manquants échouée: ${error}`, 'ERROR');
|
|
throw new Error(`FATAL: Génération mots-clés LLM impossible - arrêt du workflow: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parser la réponse IA pour extraire les mots-clés générés
|
|
* @param {string} response - Réponse de l'IA
|
|
* @param {Array} missingElements - Éléments manquants
|
|
* @returns {Object} Mots-clés parsés
|
|
*/
|
|
function parseMissingKeywordsResponse(response, missingElements) {
|
|
const results = {};
|
|
|
|
const regex = /\[([^\]]+)\]\s*\n([^[]*?)(?=\n\[|$)/gs;
|
|
let match;
|
|
|
|
while ((match = regex.exec(response)) !== null) {
|
|
const elementName = match[1].trim();
|
|
const generatedKeyword = match[2].trim();
|
|
|
|
results[elementName] = generatedKeyword;
|
|
|
|
logSh(`✓ Mot-clé généré [${elementName}]: "${generatedKeyword}"`, 'DEBUG');
|
|
}
|
|
|
|
// FATAL si parsing partiel
|
|
if (Object.keys(results).length < missingElements.length) {
|
|
const manquants = missingElements.length - Object.keys(results).length;
|
|
logSh(`❌ FATAL: Parsing mots-clés partiel - ${manquants}/${missingElements.length} manquants`, 'ERROR');
|
|
throw new Error(`FATAL: Parsing mots-clés incomplet (${manquants}/${missingElements.length} manquants) - arrêt du workflow`);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Mettre à jour les éléments avec les nouveaux mots-clés générés
|
|
* @param {Array} elements - Éléments originaux
|
|
* @param {Object} generatedKeywords - Nouveaux mots-clés
|
|
* @returns {Array} Éléments mis à jour
|
|
*/
|
|
function updateElementsWithKeywords(elements, generatedKeywords) {
|
|
const updatedElements = elements.map(element => {
|
|
const newKeyword = generatedKeywords[element.name];
|
|
|
|
if (newKeyword) {
|
|
return {
|
|
...element,
|
|
resolvedContent: newKeyword
|
|
};
|
|
}
|
|
|
|
return element;
|
|
});
|
|
|
|
logSh('Éléments mis à jour avec nouveaux mots-clés', 'INFO');
|
|
return updatedElements;
|
|
}
|
|
|
|
// Exports CommonJS
|
|
module.exports = {
|
|
generateMissingKeywords
|
|
}; |