## SelectiveSmartTouch (NEW) - Architecture révolutionnaire: Analyse intelligente → Améliorations ciblées précises - 5 modules: SmartAnalysisLayer, SmartTechnicalLayer, SmartStyleLayer, SmartReadabilityLayer, SmartTouchCore - Système 10% segments: amélioration uniquement des segments les plus faibles (intensity-based) - Détection contexte globale pour prompts adaptatifs multi-secteurs - Intégration complète dans PipelineExecutor et PipelineDefinition ## Pipeline Validator Spec (NEW) - Spécification complète système validation qualité par LLM - 5 critères universels: Qualité, Verbosité, SEO, Répétitions, Naturalité - Échantillonnage intelligent par filtrage balises (pas XML) - Évaluation multi-versions avec justifications détaillées - Coût estimé: ~$1/validation (260 appels LLM) ## Optimizations - Réduction intensités fullEnhancement (technical 1.0→0.7, style 0.8→0.5) - Ajout gardes-fous anti-familiarité excessive dans StyleLayer - Sauvegarde étapes intermédiaires activée par défaut (pipeline-runner) ## Fixes - Fix typo critique SmartTouchCore.js:110 (determineLayers ToApply → determineLayersToApply) - Prompts généralisés multi-secteurs (e-commerce, SaaS, services, informatif) 🚀 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
591 lines
20 KiB
JavaScript
591 lines
20 KiB
JavaScript
// ========================================
|
|
// SELECTIVE LAYERS - COUCHES COMPOSABLES
|
|
// Responsabilité: Stacks prédéfinis et couches adaptatives pour selective enhancement
|
|
// Architecture: Composable layers avec orchestration intelligente
|
|
// ========================================
|
|
|
|
const { logSh } = require('../ErrorReporting');
|
|
const { tracer } = require('../trace');
|
|
const { applySelectiveLayer } = require('./SelectiveCore');
|
|
|
|
/**
|
|
* STACKS PRÉDÉFINIS SELECTIVE ENHANCEMENT
|
|
*/
|
|
const PREDEFINED_STACKS = {
|
|
// Stack léger - Amélioration technique uniquement
|
|
lightEnhancement: {
|
|
name: 'lightEnhancement',
|
|
description: 'Amélioration technique légère avec OpenAI',
|
|
layers: [
|
|
{ type: 'technical', llm: 'gpt-4o-mini', intensity: 0.7 }
|
|
],
|
|
layersCount: 1
|
|
},
|
|
|
|
// Stack standard - Technique + Transitions
|
|
standardEnhancement: {
|
|
name: 'standardEnhancement',
|
|
description: 'Amélioration technique et style (OpenAI + Mistral)',
|
|
layers: [
|
|
{ type: 'technical', llm: 'gpt-4o-mini', intensity: 0.9 },
|
|
{ type: 'style', llm: 'mistral-small', intensity: 0.8 }
|
|
],
|
|
layersCount: 2
|
|
},
|
|
|
|
// Stack complet - Toutes couches séquentielles
|
|
fullEnhancement: {
|
|
name: 'fullEnhancement',
|
|
description: 'Enhancement complet multi-LLM (OpenAI + Mistral) - modéré pour éviter sur-stylisation',
|
|
layers: [
|
|
{ type: 'technical', llm: 'gpt-4o-mini', intensity: 0.7 }, // ✅ MODÉRÉ: Réduit de 1.0 à 0.7
|
|
{ type: 'style', llm: 'mistral-small', intensity: 0.5 } // ✅ MODÉRÉ: Réduit de 0.8 à 0.5 pour éviter familiarité excessive
|
|
],
|
|
layersCount: 2
|
|
},
|
|
|
|
// Stack personnalité - Style prioritaire
|
|
personalityFocus: {
|
|
name: 'personalityFocus',
|
|
description: 'Focus personnalité et style avec Mistral + technique légère',
|
|
layers: [
|
|
{ type: 'style', llm: 'mistral-small', intensity: 1.2 },
|
|
{ type: 'technical', llm: 'gpt-4o-mini', intensity: 0.6 }
|
|
],
|
|
layersCount: 2
|
|
},
|
|
|
|
// Stack fluidité - Style prioritaire
|
|
fluidityFocus: {
|
|
name: 'fluidityFocus',
|
|
description: 'Focus style et technique avec Mistral + OpenAI',
|
|
layers: [
|
|
{ type: 'style', llm: 'mistral-small', intensity: 1.1 },
|
|
{ type: 'technical', llm: 'gpt-4o-mini', intensity: 0.7 }
|
|
],
|
|
layersCount: 2
|
|
}
|
|
};
|
|
|
|
/**
|
|
* APPLIQUER STACK PRÉDÉFINI
|
|
*/
|
|
async function applyPredefinedStack(content, stackName, config = {}) {
|
|
return await tracer.run('SelectiveLayers.applyPredefinedStack()', async () => {
|
|
const stack = PREDEFINED_STACKS[stackName];
|
|
|
|
if (!stack) {
|
|
throw new Error(`Stack selective prédéfini inconnu: ${stackName}. Disponibles: ${Object.keys(PREDEFINED_STACKS).join(', ')}`);
|
|
}
|
|
|
|
await tracer.annotate({
|
|
selectivePredefinedStack: true,
|
|
stackName,
|
|
layersCount: stack.layersCount,
|
|
elementsCount: Object.keys(content).length
|
|
});
|
|
|
|
const startTime = Date.now();
|
|
logSh(`📦 APPLICATION STACK SELECTIVE: ${stack.name} (${stack.layersCount} couches)`, 'INFO');
|
|
logSh(` 📊 ${Object.keys(content).length} éléments | Description: ${stack.description}`, 'INFO');
|
|
|
|
// 🆕 LOG: Détail des couches configurées dans ce stack
|
|
logSh(` 🔍 Configuration du stack:`, 'INFO');
|
|
stack.layers.forEach((layer, idx) => {
|
|
logSh(` Couche ${idx + 1}: ${layer.type} | LLM: ${layer.llm} | Intensité: ${layer.intensity}`, 'INFO');
|
|
});
|
|
|
|
try {
|
|
let currentContent = content;
|
|
const stackStats = {
|
|
stackName,
|
|
layers: [],
|
|
totalModifications: 0,
|
|
totalDuration: 0,
|
|
success: true
|
|
};
|
|
|
|
// Appliquer chaque couche séquentiellement
|
|
for (let i = 0; i < stack.layers.length; i++) {
|
|
const layer = stack.layers[i];
|
|
|
|
try {
|
|
logSh(`\n 🔧 === COUCHE ${i + 1}/${stack.layersCount} ===`, 'INFO');
|
|
logSh(` Type: ${layer.type} | LLM: ${layer.llm} | Intensité: ${layer.intensity}`, 'INFO');
|
|
logSh(` Éléments en entrée: ${Object.keys(currentContent).length}`, 'DEBUG');
|
|
|
|
// 🆕 LOG: Échantillon du contenu avant transformation
|
|
const sampleKey = Object.keys(currentContent)[0];
|
|
const sampleBefore = currentContent[sampleKey]?.substring(0, 100) || 'N/A';
|
|
logSh(` 📝 Échantillon AVANT (${sampleKey}): ${sampleBefore}...`, 'DEBUG');
|
|
|
|
// Préparer configuration avec support tendances
|
|
const layerConfig = {
|
|
...config,
|
|
layerType: layer.type,
|
|
llmProvider: layer.llm,
|
|
intensity: config.intensity ? config.intensity * layer.intensity : layer.intensity,
|
|
analysisMode: true
|
|
};
|
|
|
|
// Ajouter tendance si présente
|
|
if (config.trendManager) {
|
|
layerConfig.trendManager = config.trendManager;
|
|
logSh(` 🎯 Tendance active: ${config.trendManager.currentTrendId || 'none'}`, 'DEBUG');
|
|
}
|
|
|
|
const layerStartTime = Date.now();
|
|
const layerResult = await applySelectiveLayer(currentContent, layerConfig);
|
|
const layerDuration = Date.now() - layerStartTime;
|
|
|
|
currentContent = layerResult.content;
|
|
|
|
// 🆕 LOG: Échantillon après transformation
|
|
const sampleAfter = currentContent[sampleKey]?.substring(0, 100) || 'N/A';
|
|
logSh(` 📝 Échantillon APRÈS (${sampleKey}): ${sampleAfter}...`, 'DEBUG');
|
|
|
|
// 🆕 LOG: Résultats détaillés de la couche
|
|
const modifications = layerResult.stats.elementsEnhanced;
|
|
const modificationRate = ((modifications / Object.keys(currentContent).length) * 100).toFixed(1);
|
|
|
|
stackStats.layers.push({
|
|
order: i + 1,
|
|
type: layer.type,
|
|
llm: layer.llm,
|
|
intensity: layer.intensity,
|
|
elementsEnhanced: modifications,
|
|
duration: layerDuration,
|
|
success: !layerResult.stats.fallback
|
|
});
|
|
|
|
stackStats.totalModifications += modifications;
|
|
stackStats.totalDuration += layerDuration;
|
|
|
|
logSh(` ✅ RÉSULTAT: ${modifications} éléments modifiés (${modificationRate}% du total)`, 'INFO');
|
|
logSh(` ⏱️ Durée: ${layerDuration}ms`, 'DEBUG');
|
|
|
|
} catch (layerError) {
|
|
logSh(` ❌ Couche ${layer.type} échouée: ${layerError.message}`, 'ERROR');
|
|
|
|
stackStats.layers.push({
|
|
order: i + 1,
|
|
type: layer.type,
|
|
llm: layer.llm,
|
|
error: layerError.message,
|
|
duration: 0,
|
|
success: false
|
|
});
|
|
|
|
// Continuer avec les autres couches
|
|
}
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
const successfulLayers = stackStats.layers.filter(l => l.success).length;
|
|
|
|
logSh(`\n✅ === STACK SELECTIVE ${stackName} TERMINÉ ===`, 'INFO');
|
|
logSh(` 📊 Couches réussies: ${successfulLayers}/${stack.layersCount}`, 'INFO');
|
|
logSh(` 🔄 Modifications totales: ${stackStats.totalModifications}`, 'INFO');
|
|
logSh(` ⏱️ Durée totale: ${duration}ms`, 'INFO');
|
|
|
|
// 🆕 LOG: Tableau récapitulatif par couche
|
|
logSh(`\n 📋 RÉCAPITULATIF PAR COUCHE:`, 'INFO');
|
|
stackStats.layers.forEach(layer => {
|
|
const status = layer.success ? '✅' : '❌';
|
|
logSh(` ${status} Couche ${layer.order}: ${layer.type} (${layer.llm}) - ${layer.elementsEnhanced || 0} modifs en ${layer.duration}ms`, 'INFO');
|
|
});
|
|
|
|
await tracer.event('Stack selective appliqué', { ...stackStats, totalDuration: duration });
|
|
|
|
return {
|
|
content: currentContent,
|
|
stats: { ...stackStats, totalDuration: duration },
|
|
modifications: stackStats.totalModifications, // ✅ AJOUTÉ: Mapping pour PipelineExecutor
|
|
original: content,
|
|
stackApplied: stackName
|
|
};
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
logSh(`❌ STACK SELECTIVE ${stackName} ÉCHOUÉ après ${duration}ms: ${error.message}`, 'ERROR');
|
|
|
|
return {
|
|
content,
|
|
stats: { stackName, error: error.message, duration, success: false },
|
|
original: content,
|
|
fallback: true
|
|
};
|
|
}
|
|
}, { content: Object.keys(content), stackName, config });
|
|
}
|
|
|
|
/**
|
|
* APPLIQUER COUCHES ADAPTATIVES
|
|
*/
|
|
async function applyAdaptiveLayers(content, config = {}) {
|
|
return await tracer.run('SelectiveLayers.applyAdaptiveLayers()', async () => {
|
|
const {
|
|
maxIntensity = 1.0,
|
|
analysisThreshold = 0.4,
|
|
csvData = null
|
|
} = config;
|
|
|
|
await tracer.annotate({
|
|
selectiveAdaptiveLayers: true,
|
|
maxIntensity,
|
|
analysisThreshold,
|
|
elementsCount: Object.keys(content).length
|
|
});
|
|
|
|
const startTime = Date.now();
|
|
logSh(`🧠 APPLICATION COUCHES ADAPTATIVES SELECTIVE`, 'INFO');
|
|
logSh(` 📊 ${Object.keys(content).length} éléments | Seuil: ${analysisThreshold}`, 'INFO');
|
|
|
|
try {
|
|
// 1. Analyser besoins de chaque type de couche
|
|
const needsAnalysis = await analyzeSelectiveNeeds(content, csvData);
|
|
|
|
logSh(` 📋 Analyse besoins: Tech=${needsAnalysis.technical.score.toFixed(2)} | Trans=${needsAnalysis.transitions.score.toFixed(2)} | Style=${needsAnalysis.style.score.toFixed(2)}`, 'DEBUG');
|
|
|
|
// 2. Déterminer couches à appliquer selon scores
|
|
const layersToApply = [];
|
|
|
|
if (needsAnalysis.technical.needed && needsAnalysis.technical.score > analysisThreshold) {
|
|
layersToApply.push({
|
|
type: 'technical',
|
|
llm: 'gpt-4o-mini',
|
|
intensity: Math.min(maxIntensity, needsAnalysis.technical.score * 1.2),
|
|
priority: 1
|
|
});
|
|
}
|
|
|
|
// Transitions layer removed - Gemini disabled
|
|
|
|
if (needsAnalysis.style.needed && needsAnalysis.style.score > analysisThreshold) {
|
|
layersToApply.push({
|
|
type: 'style',
|
|
llm: 'mistral-small',
|
|
intensity: Math.min(maxIntensity, needsAnalysis.style.score),
|
|
priority: 3
|
|
});
|
|
}
|
|
|
|
if (layersToApply.length === 0) {
|
|
logSh(`✅ COUCHES ADAPTATIVES: Aucune amélioration nécessaire`, 'INFO');
|
|
return {
|
|
content,
|
|
stats: {
|
|
adaptive: true,
|
|
layersApplied: 0,
|
|
analysisOnly: true,
|
|
duration: Date.now() - startTime
|
|
},
|
|
modifications: 0 // ✅ AJOUTÉ: Mapping pour PipelineExecutor (pas de modifications)
|
|
};
|
|
}
|
|
|
|
// 3. Appliquer couches par ordre de priorité
|
|
layersToApply.sort((a, b) => a.priority - b.priority);
|
|
logSh(` 🎯 Couches sélectionnées: ${layersToApply.map(l => `${l.type}(${l.intensity.toFixed(1)})`).join(' → ')}`, 'INFO');
|
|
|
|
let currentContent = content;
|
|
const adaptiveStats = {
|
|
layersAnalyzed: 3,
|
|
layersApplied: layersToApply.length,
|
|
layers: [],
|
|
totalModifications: 0,
|
|
adaptive: true
|
|
};
|
|
|
|
for (const layer of layersToApply) {
|
|
try {
|
|
logSh(` 🔧 Couche adaptative: ${layer.type} (intensité: ${layer.intensity.toFixed(1)})`, 'DEBUG');
|
|
|
|
const layerResult = await applySelectiveLayer(currentContent, {
|
|
...config,
|
|
layerType: layer.type,
|
|
llmProvider: layer.llm,
|
|
intensity: layer.intensity,
|
|
analysisMode: true
|
|
});
|
|
|
|
currentContent = layerResult.content;
|
|
|
|
adaptiveStats.layers.push({
|
|
type: layer.type,
|
|
llm: layer.llm,
|
|
intensity: layer.intensity,
|
|
elementsEnhanced: layerResult.stats.elementsEnhanced,
|
|
duration: layerResult.stats.duration,
|
|
success: !layerResult.stats.fallback
|
|
});
|
|
|
|
adaptiveStats.totalModifications += layerResult.stats.elementsEnhanced;
|
|
|
|
} catch (layerError) {
|
|
logSh(` ❌ Couche adaptative ${layer.type} échouée: ${layerError.message}`, 'ERROR');
|
|
|
|
adaptiveStats.layers.push({
|
|
type: layer.type,
|
|
error: layerError.message,
|
|
success: false
|
|
});
|
|
}
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
const successfulLayers = adaptiveStats.layers.filter(l => l.success).length;
|
|
|
|
logSh(`✅ COUCHES ADAPTATIVES: ${successfulLayers}/${layersToApply.length} appliquées | ${adaptiveStats.totalModifications} modifications (${duration}ms)`, 'INFO');
|
|
|
|
await tracer.event('Couches adaptatives appliquées', { ...adaptiveStats, totalDuration: duration });
|
|
|
|
return {
|
|
content: currentContent,
|
|
stats: { ...adaptiveStats, totalDuration: duration },
|
|
modifications: adaptiveStats.totalModifications, // ✅ AJOUTÉ: Mapping pour PipelineExecutor
|
|
original: content
|
|
};
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
logSh(`❌ COUCHES ADAPTATIVES ÉCHOUÉES après ${duration}ms: ${error.message}`, 'ERROR');
|
|
|
|
return {
|
|
content,
|
|
stats: { adaptive: true, error: error.message, duration },
|
|
original: content,
|
|
fallback: true
|
|
};
|
|
}
|
|
}, { content: Object.keys(content), config });
|
|
}
|
|
|
|
/**
|
|
* PIPELINE COUCHES PERSONNALISÉ
|
|
*/
|
|
async function applyLayerPipeline(content, layerSequence, config = {}) {
|
|
return await tracer.run('SelectiveLayers.applyLayerPipeline()', async () => {
|
|
if (!Array.isArray(layerSequence) || layerSequence.length === 0) {
|
|
throw new Error('Séquence de couches invalide ou vide');
|
|
}
|
|
|
|
await tracer.annotate({
|
|
selectiveLayerPipeline: true,
|
|
pipelineLength: layerSequence.length,
|
|
elementsCount: Object.keys(content).length
|
|
});
|
|
|
|
const startTime = Date.now();
|
|
logSh(`🔄 PIPELINE COUCHES SELECTIVE PERSONNALISÉ: ${layerSequence.length} étapes`, 'INFO');
|
|
|
|
try {
|
|
let currentContent = content;
|
|
const pipelineStats = {
|
|
pipelineLength: layerSequence.length,
|
|
steps: [],
|
|
totalModifications: 0,
|
|
success: true
|
|
};
|
|
|
|
for (let i = 0; i < layerSequence.length; i++) {
|
|
const step = layerSequence[i];
|
|
|
|
try {
|
|
logSh(` 📍 Étape ${i + 1}/${layerSequence.length}: ${step.type} (${step.llm || 'auto'})`, 'DEBUG');
|
|
|
|
const stepResult = await applySelectiveLayer(currentContent, {
|
|
...config,
|
|
...step
|
|
});
|
|
|
|
currentContent = stepResult.content;
|
|
|
|
pipelineStats.steps.push({
|
|
order: i + 1,
|
|
...step,
|
|
elementsEnhanced: stepResult.stats.elementsEnhanced,
|
|
duration: stepResult.stats.duration,
|
|
success: !stepResult.stats.fallback
|
|
});
|
|
|
|
pipelineStats.totalModifications += stepResult.stats.elementsEnhanced;
|
|
|
|
} catch (stepError) {
|
|
logSh(` ❌ Étape ${i + 1} échouée: ${stepError.message}`, 'ERROR');
|
|
|
|
pipelineStats.steps.push({
|
|
order: i + 1,
|
|
...step,
|
|
error: stepError.message,
|
|
success: false
|
|
});
|
|
}
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
const successfulSteps = pipelineStats.steps.filter(s => s.success).length;
|
|
|
|
logSh(`✅ PIPELINE SELECTIVE: ${successfulSteps}/${layerSequence.length} étapes | ${pipelineStats.totalModifications} modifications (${duration}ms)`, 'INFO');
|
|
|
|
await tracer.event('Pipeline selective appliqué', { ...pipelineStats, totalDuration: duration });
|
|
|
|
return {
|
|
content: currentContent,
|
|
stats: { ...pipelineStats, totalDuration: duration },
|
|
modifications: pipelineStats.totalModifications, // ✅ AJOUTÉ: Mapping pour PipelineExecutor
|
|
original: content
|
|
};
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
logSh(`❌ PIPELINE SELECTIVE ÉCHOUÉ après ${duration}ms: ${error.message}`, 'ERROR');
|
|
|
|
return {
|
|
content,
|
|
stats: { error: error.message, duration, success: false },
|
|
original: content,
|
|
fallback: true
|
|
};
|
|
}
|
|
}, { content: Object.keys(content), layerSequence, config });
|
|
}
|
|
|
|
// ============= HELPER FUNCTIONS =============
|
|
|
|
/**
|
|
* Analyser besoins selective enhancement
|
|
*/
|
|
async function analyzeSelectiveNeeds(content, csvData) {
|
|
const analysis = {
|
|
technical: { needed: false, score: 0, elements: [] },
|
|
transitions: { needed: false, score: 0, elements: [] },
|
|
style: { needed: false, score: 0, elements: [] }
|
|
};
|
|
|
|
// Analyser chaque élément pour tous types de besoins
|
|
Object.entries(content).forEach(([tag, text]) => {
|
|
// Analyse technique (import depuis SelectiveCore logic)
|
|
const technicalNeed = assessTechnicalNeed(text, csvData);
|
|
if (technicalNeed.score > 0.3) {
|
|
analysis.technical.needed = true;
|
|
analysis.technical.score += technicalNeed.score;
|
|
analysis.technical.elements.push({ tag, score: technicalNeed.score });
|
|
}
|
|
|
|
// Analyse transitions
|
|
const transitionNeed = assessTransitionNeed(text);
|
|
if (transitionNeed.score > 0.3) {
|
|
analysis.transitions.needed = true;
|
|
analysis.transitions.score += transitionNeed.score;
|
|
analysis.transitions.elements.push({ tag, score: transitionNeed.score });
|
|
}
|
|
|
|
// Analyse style
|
|
const styleNeed = assessStyleNeed(text, csvData?.personality);
|
|
if (styleNeed.score > 0.3) {
|
|
analysis.style.needed = true;
|
|
analysis.style.score += styleNeed.score;
|
|
analysis.style.elements.push({ tag, score: styleNeed.score });
|
|
}
|
|
});
|
|
|
|
// Normaliser scores
|
|
const elementCount = Object.keys(content).length;
|
|
analysis.technical.score = analysis.technical.score / elementCount;
|
|
analysis.transitions.score = analysis.transitions.score / elementCount;
|
|
analysis.style.score = analysis.style.score / elementCount;
|
|
|
|
return analysis;
|
|
}
|
|
|
|
/**
|
|
* Évaluer besoin technique (simplifié de SelectiveCore)
|
|
*/
|
|
function assessTechnicalNeed(content, csvData) {
|
|
let score = 0;
|
|
|
|
// Manque de termes techniques spécifiques
|
|
if (csvData?.mc0) {
|
|
const technicalTerms = ['dibond', 'pmma', 'aluminium', 'fraisage', 'impression', 'gravure'];
|
|
const foundTerms = technicalTerms.filter(term => content.toLowerCase().includes(term));
|
|
|
|
if (foundTerms.length === 0 && content.length > 100) {
|
|
score += 0.4;
|
|
}
|
|
}
|
|
|
|
// Vocabulaire générique
|
|
const genericWords = ['produit', 'solution', 'service', 'qualité'];
|
|
const genericCount = genericWords.filter(word => content.toLowerCase().includes(word)).length;
|
|
|
|
if (genericCount > 2) score += 0.3;
|
|
|
|
return { score: Math.min(1, score) };
|
|
}
|
|
|
|
/**
|
|
* Évaluer besoin transitions (simplifié)
|
|
*/
|
|
function assessTransitionNeed(content) {
|
|
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 10);
|
|
if (sentences.length < 2) return { score: 0 };
|
|
|
|
let score = 0;
|
|
|
|
// Connecteurs répétitifs
|
|
const connectors = ['par ailleurs', 'en effet', 'de plus'];
|
|
let repetitions = 0;
|
|
|
|
connectors.forEach(connector => {
|
|
const matches = (content.match(new RegExp(connector, 'gi')) || []);
|
|
if (matches.length > 1) repetitions++;
|
|
});
|
|
|
|
if (repetitions > 1) score += 0.4;
|
|
|
|
return { score: Math.min(1, score) };
|
|
}
|
|
|
|
/**
|
|
* Évaluer besoin style (simplifié)
|
|
*/
|
|
function assessStyleNeed(content, personality) {
|
|
let score = 0;
|
|
|
|
if (!personality) {
|
|
score += 0.2;
|
|
return { score };
|
|
}
|
|
|
|
// Style générique
|
|
const personalityWords = (personality.vocabulairePref || '').toLowerCase().split(',');
|
|
const personalityFound = personalityWords.some(word =>
|
|
word.trim() && content.toLowerCase().includes(word.trim())
|
|
);
|
|
|
|
if (!personalityFound && content.length > 50) score += 0.4;
|
|
|
|
return { score: Math.min(1, score) };
|
|
}
|
|
|
|
/**
|
|
* Obtenir stacks disponibles
|
|
*/
|
|
function getAvailableStacks() {
|
|
return Object.values(PREDEFINED_STACKS);
|
|
}
|
|
|
|
module.exports = {
|
|
// Main functions
|
|
applyPredefinedStack,
|
|
applyAdaptiveLayers,
|
|
applyLayerPipeline,
|
|
|
|
// Utils
|
|
getAvailableStacks,
|
|
analyzeSelectiveNeeds,
|
|
|
|
// Constants
|
|
PREDEFINED_STACKS
|
|
}; |