Added plan.md with complete architecture for format-agnostic content generation: - Support for Markdown, HTML, Plain Text, JSON formats - New FormatExporter module with neutral data structure - Integration strategy with existing ContentAssembly and ArticleStorage - Bonus features: SEO metadata generation, readability scoring, WordPress Gutenberg format - Implementation roadmap with 4 phases (6h total estimated) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
574 lines
21 KiB
JavaScript
574 lines
21 KiB
JavaScript
// ========================================
|
|
// DETECTOR STRATEGIES - NIVEAU 3
|
|
// Responsabilité: Stratégies spécialisées par détecteur IA
|
|
// Anti-détection: Techniques ciblées contre chaque analyseur
|
|
// ========================================
|
|
|
|
const { logSh } = require('../ErrorReporting');
|
|
const { tracer } = require('../trace');
|
|
|
|
/**
|
|
* STRATÉGIES DÉTECTEUR PAR DÉTECTEUR
|
|
* Chaque classe implémente une approche spécialisée
|
|
*/
|
|
|
|
class BaseDetectorStrategy {
|
|
constructor(name) {
|
|
this.name = name;
|
|
this.effectiveness = 0.8;
|
|
this.targetMetrics = [];
|
|
}
|
|
|
|
/**
|
|
* Générer instructions spécifiques pour ce détecteur
|
|
*/
|
|
generateInstructions(elementType, personality, csvData) {
|
|
throw new Error('generateInstructions must be implemented by subclass');
|
|
}
|
|
|
|
/**
|
|
* Obtenir instructions anti-détection (NOUVEAU pour modularité)
|
|
*/
|
|
getInstructions(intensity = 1.0) {
|
|
throw new Error('getInstructions must be implemented by subclass');
|
|
}
|
|
|
|
/**
|
|
* Obtenir conseils d'amélioration (NOUVEAU pour modularité)
|
|
*/
|
|
getEnhancementTips(intensity = 1.0) {
|
|
throw new Error('getEnhancementTips must be implemented by subclass');
|
|
}
|
|
|
|
/**
|
|
* Analyser efficacité contre ce détecteur
|
|
*/
|
|
analyzeEffectiveness(content) {
|
|
return {
|
|
detector: this.name,
|
|
effectiveness: this.effectiveness,
|
|
metrics: this.analyzeContent(content)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Analyser contenu selon métriques de ce détecteur
|
|
*/
|
|
analyzeContent(content) {
|
|
return {
|
|
wordCount: content.split(/\s+/).length,
|
|
sentenceCount: content.split(/[.!?]+/).length
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* STRATÉGIE ANTI-GPTZERO
|
|
* Focus: Imprévisibilité et variation syntaxique
|
|
*/
|
|
class GPTZeroStrategy extends BaseDetectorStrategy {
|
|
constructor() {
|
|
super('GPTZero');
|
|
this.effectiveness = 0.9;
|
|
this.targetMetrics = ['perplexity', 'burstiness', 'unpredictability'];
|
|
|
|
this.techniques = {
|
|
syntaxVariation: {
|
|
name: 'Variation syntaxique drastique',
|
|
rules: [
|
|
'Alterne phrases simples (5-8 mots) et complexes (25-35 mots)',
|
|
'Utilise structures atypiques : inversion, anacoluthe, ellipse',
|
|
'Intercale incises, parenthèses, tirets pour briser linéarité',
|
|
'Varie types : déclarative → interrogative → exclamative'
|
|
]
|
|
},
|
|
lexicalUnpredictability: {
|
|
name: 'Imprévisibilité lexicale',
|
|
rules: [
|
|
'Synonymes rares ou détournés : "performant" → "qui tient la route"',
|
|
'Néologismes justifiés : "sur-mesurable" pour "très mesurable"',
|
|
'Registres mélangés : soutenu puis familier dans même paragraphe',
|
|
'Évite collocations attendues : "haute qualité" → "qualité qui dépote"'
|
|
]
|
|
},
|
|
narrativeDisruption: {
|
|
name: 'Disruption narrative',
|
|
rules: [
|
|
'Apartés personnels : "(j\'ai testé, c\'est bluffant)"',
|
|
'Questions rhétoriques inattendues au milieu des faits',
|
|
'Changements de perspective soudains : technique → humain → commercial',
|
|
'Digressions courtes puis retour au sujet'
|
|
]
|
|
}
|
|
};
|
|
}
|
|
|
|
generateInstructions(elementType, personality, csvData) {
|
|
const instructions = [];
|
|
|
|
instructions.push(`ANTI-GPTZERO - MAXIMUM IMPRÉVISIBILITÉ:`);
|
|
|
|
// Techniques syntaxiques
|
|
instructions.push(`\nSYNTAXE VARIABLE:`);
|
|
this.techniques.syntaxVariation.rules.forEach(rule => {
|
|
instructions.push(`• ${rule}`);
|
|
});
|
|
|
|
// Imprévisibilité lexicale
|
|
instructions.push(`\nLEXIQUE IMPRÉVISIBLE:`);
|
|
this.techniques.lexicalUnpredictability.rules.forEach(rule => {
|
|
instructions.push(`• ${rule}`);
|
|
});
|
|
|
|
// Adaptation selon type élément
|
|
if (elementType === 'titre_h1' || elementType === 'titre_h2') {
|
|
instructions.push(`\nTITRES ANTI-GPTZERO:`);
|
|
instructions.push(`• Évite formules attendues : "Guide complet" → "Le vrai topo sur"`);
|
|
instructions.push(`• Structure atypique : question, exclamation, fragment`);
|
|
} else if (elementType === 'texte') {
|
|
instructions.push(`\nTEXTES ANTI-GPTZERO:`);
|
|
this.techniques.narrativeDisruption.rules.forEach(rule => {
|
|
instructions.push(`• ${rule}`);
|
|
});
|
|
}
|
|
|
|
// Adaptation personnalité
|
|
if (personality) {
|
|
instructions.push(`\nAVEC STYLE ${personality.nom.toUpperCase()}:`);
|
|
instructions.push(`• Pousse ${personality.style} vers extrêmes : plus marqué, moins lisse`);
|
|
instructions.push(`• Accentue tics de langage : répétitions, expressions favorites`);
|
|
}
|
|
|
|
return instructions.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Instructions modulaires pour AdversarialCore
|
|
*/
|
|
getInstructions(intensity = 1.0) {
|
|
const baseRules = [
|
|
'Surprends avec tournures inattendues et constructions atypiques',
|
|
'Évite phrases trop prévisibles ou patterns répétitifs',
|
|
'Utilise synonymes moins évidents et vocabulaire moins commun',
|
|
'Varie drastiquement la complexité syntaxique entre phrases'
|
|
];
|
|
|
|
const intensiveRules = [
|
|
'Intercale observations personnelles ou détours narratifs',
|
|
'Alterne registres de langue (soutenu → familier → technique)',
|
|
'Brise la logique linéaire avec des parenthèses, incises, apartés',
|
|
'Créé ruptures narratives et changements de perspective soudains'
|
|
];
|
|
|
|
return intensity >= 1.0 ? [...baseRules, ...intensiveRules] : baseRules;
|
|
}
|
|
|
|
/**
|
|
* Conseils d'amélioration pour enhancement method
|
|
*/
|
|
getEnhancementTips(intensity = 1.0) {
|
|
return [
|
|
'Remplace connecteurs prévisibles par variations inattendues',
|
|
'Ajoute incises courtes pour briser la linéarité',
|
|
'Varie longueurs phrases dans même paragraphe',
|
|
'Utilise synonymes moins courants mais naturels',
|
|
...(intensity > 0.8 ? [
|
|
'Insère questions rhétoriques ponctuelles',
|
|
'Ajoute nuances et hésitations authentiques'
|
|
] : [])
|
|
];
|
|
}
|
|
|
|
analyzeContent(content) {
|
|
const baseMetrics = super.analyzeContent(content);
|
|
|
|
// Analyse perplexité approximative
|
|
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 5);
|
|
const sentenceLengths = sentences.map(s => s.split(/\s+/).length);
|
|
|
|
// Variance longueur (proxy pour burstiness)
|
|
const avgLength = sentenceLengths.reduce((a, b) => a + b, 0) / sentenceLengths.length;
|
|
const variance = sentenceLengths.reduce((acc, len) => acc + Math.pow(len - avgLength, 2), 0) / sentenceLengths.length;
|
|
const burstiness = Math.sqrt(variance) / avgLength;
|
|
|
|
// Diversité lexicale (proxy pour imprévisibilité)
|
|
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
const uniqueWords = [...new Set(words)];
|
|
const lexicalDiversity = uniqueWords.length / words.length;
|
|
|
|
return {
|
|
...baseMetrics,
|
|
burstiness: Math.round(burstiness * 100) / 100,
|
|
lexicalDiversity: Math.round(lexicalDiversity * 100) / 100,
|
|
avgSentenceLength: Math.round(avgLength),
|
|
gptZeroRiskLevel: this.calculateGPTZeroRisk(burstiness, lexicalDiversity)
|
|
};
|
|
}
|
|
|
|
calculateGPTZeroRisk(burstiness, lexicalDiversity) {
|
|
// Heuristique : GPTZero détecte uniformité faible + diversité faible
|
|
const uniformityScore = Math.min(burstiness, 1) * 100;
|
|
const diversityScore = lexicalDiversity * 100;
|
|
const combinedScore = (uniformityScore + diversityScore) / 2;
|
|
|
|
if (combinedScore > 70) return 'low';
|
|
if (combinedScore > 40) return 'medium';
|
|
return 'high';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* STRATÉGIE ANTI-ORIGINALITY
|
|
* Focus: Diversité sémantique et originalité
|
|
*/
|
|
class OriginalityStrategy extends BaseDetectorStrategy {
|
|
constructor() {
|
|
super('Originality');
|
|
this.effectiveness = 0.85;
|
|
this.targetMetrics = ['semantic_diversity', 'originality_score', 'vocabulary_range'];
|
|
|
|
this.techniques = {
|
|
semanticCreativity: {
|
|
name: 'Créativité sémantique',
|
|
rules: [
|
|
'Métaphores inattendues : "cette plaque, c\'est le passeport de votre façade"',
|
|
'Comparaisons originales : évite clichés, invente analogies',
|
|
'Reformulations créatives : "résistant aux intempéries" → "qui brave les saisons"',
|
|
'Néologismes justifiés et expressifs'
|
|
]
|
|
},
|
|
perspectiveShifting: {
|
|
name: 'Changements de perspective',
|
|
rules: [
|
|
'Angles multiples sur même info : technique → esthétique → pratique',
|
|
'Points de vue variés : fabricant, utilisateur, installateur, voisin',
|
|
'Temporalités mélangées : présent, futur proche, retour d\'expérience',
|
|
'Niveaux d\'abstraction : détail précis puis vue d\'ensemble'
|
|
]
|
|
},
|
|
linguisticInventiveness: {
|
|
name: 'Inventivité linguistique',
|
|
rules: [
|
|
'Jeux de mots subtils et expressions détournées',
|
|
'Régionalismes et références culturelles précises',
|
|
'Vocabulaire technique humanisé avec créativité',
|
|
'Rythmes et sonorités travaillés : allitérations, assonances'
|
|
]
|
|
}
|
|
};
|
|
}
|
|
|
|
generateInstructions(elementType, personality, csvData) {
|
|
const instructions = [];
|
|
|
|
instructions.push(`ANTI-ORIGINALITY - MAXIMUM CRÉATIVITÉ SÉMANTIQUE:`);
|
|
|
|
// Créativité sémantique
|
|
instructions.push(`\nCRÉATIVITÉ SÉMANTIQUE:`);
|
|
this.techniques.semanticCreativity.rules.forEach(rule => {
|
|
instructions.push(`• ${rule}`);
|
|
});
|
|
|
|
// Changements de perspective
|
|
instructions.push(`\nPERSPECTIVES MULTIPLES:`);
|
|
this.techniques.perspectiveShifting.rules.forEach(rule => {
|
|
instructions.push(`• ${rule}`);
|
|
});
|
|
|
|
// Spécialisation par élément
|
|
if (elementType === 'intro') {
|
|
instructions.push(`\nINTROS ANTI-ORIGINALITY:`);
|
|
instructions.push(`• Commence par angle totalement inattendu pour le sujet`);
|
|
instructions.push(`• Évite intro-types, réinvente présentation du sujet`);
|
|
instructions.push(`• Crée surprise puis retour naturel au cœur du sujet`);
|
|
} else if (elementType.includes('faq')) {
|
|
instructions.push(`\nFAQ ANTI-ORIGINALITY:`);
|
|
instructions.push(`• Questions vraiment originales, pas standard secteur`);
|
|
instructions.push(`• Réponses avec angles créatifs et exemples inédits`);
|
|
}
|
|
|
|
// Contexte métier créatif
|
|
if (csvData && csvData.mc0) {
|
|
instructions.push(`\nCRÉATIVITÉ CONTEXTUELLE ${csvData.mc0.toUpperCase()}:`);
|
|
instructions.push(`• Réinvente façon de parler de ${csvData.mc0}`);
|
|
instructions.push(`• Évite vocabulaire convenu du secteur, invente expressions`);
|
|
instructions.push(`• Trouve analogies originales spécifiques à ${csvData.mc0}`);
|
|
}
|
|
|
|
// Inventivité linguistique
|
|
instructions.push(`\nINVENTIVITÉ LINGUISTIQUE:`);
|
|
this.techniques.linguisticInventiveness.rules.forEach(rule => {
|
|
instructions.push(`• ${rule}`);
|
|
});
|
|
|
|
return instructions.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Instructions modulaires pour AdversarialCore
|
|
*/
|
|
getInstructions(intensity = 1.0) {
|
|
const baseRules = [
|
|
'Vocabulaire TRÈS varié : évite répétitions même de synonymes',
|
|
'Structures phrases délibérément irrégulières et asymétriques',
|
|
'Changements angles fréquents : technique → personnel → général',
|
|
'Créativité sémantique : métaphores, comparaisons inattendues'
|
|
];
|
|
|
|
const intensiveRules = [
|
|
'Évite formulations académiques ou trop structurées',
|
|
'Intègre références culturelles, expressions régionales',
|
|
'Subvertis les attentes : commence par la fin, questionne l\'évidence',
|
|
'Réinvente façon de présenter informations basiques'
|
|
];
|
|
|
|
return intensity >= 1.0 ? [...baseRules, ...intensiveRules] : baseRules;
|
|
}
|
|
|
|
/**
|
|
* Conseils d'amélioration pour enhancement method
|
|
*/
|
|
getEnhancementTips(intensity = 1.0) {
|
|
return [
|
|
'Trouve synonymes créatifs et expressions détournées',
|
|
'Ajoute métaphores subtiles et comparaisons originales',
|
|
'Varie angles d\'approche dans même contenu',
|
|
'Utilise vocabulaire technique humanisé',
|
|
...(intensity > 0.8 ? [
|
|
'Insère références culturelles ou régionalismes',
|
|
'Crée néologismes justifiés et expressifs'
|
|
] : [])
|
|
];
|
|
}
|
|
|
|
analyzeContent(content) {
|
|
const baseMetrics = super.analyzeContent(content);
|
|
|
|
// Analyse diversité sémantique
|
|
const words = content.toLowerCase().split(/\s+/).filter(w => w.length > 3);
|
|
const uniqueWords = [...new Set(words)];
|
|
const semanticDiversity = uniqueWords.length / words.length;
|
|
|
|
// Détection créativité (heuristique)
|
|
const creativityIndicators = [
|
|
'comme', 'tel', 'sorte de', 'façon de', 'manière de', // métaphores
|
|
'(', ')', '"', // originalité structure
|
|
'?', '!', // variation tonale
|
|
];
|
|
|
|
const creativityCount = creativityIndicators.reduce((count, indicator) => {
|
|
return count + (content.match(new RegExp(indicator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
}, 0);
|
|
|
|
const creativityScore = Math.min(100, (creativityCount / words.length) * 1000);
|
|
|
|
return {
|
|
...baseMetrics,
|
|
semanticDiversity: Math.round(semanticDiversity * 100) / 100,
|
|
creativityScore: Math.round(creativityScore),
|
|
uniqueWordRatio: Math.round((uniqueWords.length / words.length) * 100),
|
|
originalityRiskLevel: this.calculateOriginalityRisk(semanticDiversity, creativityScore)
|
|
};
|
|
}
|
|
|
|
calculateOriginalityRisk(semanticDiversity, creativityScore) {
|
|
const diversityScore = semanticDiversity * 100;
|
|
const combinedScore = (diversityScore + creativityScore) / 2;
|
|
|
|
if (combinedScore > 60) return 'low';
|
|
if (combinedScore > 35) return 'medium';
|
|
return 'high';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* STRATÉGIE GÉNÉRALE
|
|
* Équilibre entre toutes les techniques
|
|
*/
|
|
class GeneralStrategy extends BaseDetectorStrategy {
|
|
constructor() {
|
|
super('General');
|
|
this.effectiveness = 0.75;
|
|
this.targetMetrics = ['overall_naturalness', 'human_likeness'];
|
|
}
|
|
|
|
generateInstructions(elementType, personality, csvData) {
|
|
const instructions = [];
|
|
|
|
instructions.push(`ANTI-DÉTECTION GÉNÉRALE - ÉQUILIBRE OPTIMAL:`);
|
|
|
|
// Instructions générales équilibrées
|
|
instructions.push(`\nNATUREL ET VARIÉ:`);
|
|
instructions.push(`• Évite mots typiques IA : "optimal", "comprehensive", "seamless", "robust"`);
|
|
instructions.push(`• Mélange phrases courtes (5-10 mots) et longues (15-25 mots) naturellement`);
|
|
instructions.push(`• Utilise expressions idiomatiques françaises authentiques`);
|
|
instructions.push(`• Intègre imperfections humaines : répétitions légères, hésitations`);
|
|
instructions.push(`• Change angle ou ton 1-2 fois selon longueur du contenu`);
|
|
|
|
// Adaptation personnalité équilibrée
|
|
if (personality) {
|
|
instructions.push(`\nSTYLE ${personality.nom} NATUREL:`);
|
|
instructions.push(`• Applique style ${personality.style} de façon authentique, pas forcée`);
|
|
instructions.push(`• Respecte vocabulaire naturel : ${personality.vocabulairePref || 'varié'}`);
|
|
instructions.push(`• Utilise connecteurs variés : ${personality.connecteursPref || 'naturels'}`);
|
|
}
|
|
|
|
return instructions.join('\n');
|
|
}
|
|
|
|
/**
|
|
* Instructions modulaires pour AdversarialCore
|
|
*/
|
|
getInstructions(intensity = 1.0) {
|
|
const baseRules = [
|
|
'Évite ces mots typiques IA : "optimal", "comprehensive", "seamless", "robust", "leverage"',
|
|
'Varie longueurs phrases : mélange phrases courtes (5-10 mots) ET longues (20-30 mots)',
|
|
'Utilise expressions françaises familières et tournures idiomatiques',
|
|
'Ajoute imperfections naturelles : répétitions légères, hésitations, reformulations'
|
|
];
|
|
|
|
const intensiveRules = [
|
|
'Change angle ou perspective 1-2 fois dans le texte',
|
|
'Préfère synonymes moins évidents : "bon" plutôt qu\'excellent", "solide" plutôt qu\'optimisé"',
|
|
'Intègre connecteurs variés et naturels selon le contexte',
|
|
'Simule variation naturelle d\'humeur et d\'énergie rédactionnelle'
|
|
];
|
|
|
|
return intensity >= 0.8 ? [...baseRules, ...intensiveRules] : baseRules;
|
|
}
|
|
|
|
/**
|
|
* Conseils d'amélioration pour enhancement method
|
|
*/
|
|
getEnhancementTips(intensity = 1.0) {
|
|
return [
|
|
'Remplace mots typiques IA par synonymes plus naturels',
|
|
'Ajoute nuances et hésitations : "peut-être", "généralement", "souvent"',
|
|
'Varie connecteurs pour éviter répétitions mécaniques',
|
|
'Personnalise avec observations subjectives légères',
|
|
...(intensity > 0.7 ? [
|
|
'Intègre "erreurs" humaines : corrections, précisions',
|
|
'Simule changement léger de ton ou d\'énergie'
|
|
] : [])
|
|
];
|
|
}
|
|
|
|
analyzeContent(content) {
|
|
const baseMetrics = super.analyzeContent(content);
|
|
|
|
// Métrique naturalité générale
|
|
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 5);
|
|
const avgWordsPerSentence = baseMetrics.wordCount / baseMetrics.sentenceCount;
|
|
|
|
// Détection mots typiques IA
|
|
const aiWords = ['optimal', 'comprehensive', 'seamless', 'robust', 'leverage'];
|
|
const aiWordCount = aiWords.reduce((count, word) => {
|
|
return count + (content.toLowerCase().match(new RegExp(`\\b${word}\\b`, 'g')) || []).length;
|
|
}, 0);
|
|
|
|
const aiWordDensity = aiWordCount / baseMetrics.wordCount * 100;
|
|
const naturalness = Math.max(0, 100 - (aiWordDensity * 10) - Math.abs(avgWordsPerSentence - 15));
|
|
|
|
return {
|
|
...baseMetrics,
|
|
avgWordsPerSentence: Math.round(avgWordsPerSentence),
|
|
aiWordCount,
|
|
aiWordDensity: Math.round(aiWordDensity * 100) / 100,
|
|
naturalnessScore: Math.round(naturalness),
|
|
generalRiskLevel: naturalness > 70 ? 'low' : naturalness > 40 ? 'medium' : 'high'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FACTORY POUR CRÉER STRATÉGIES
|
|
*/
|
|
class DetectorStrategyFactory {
|
|
static strategies = {
|
|
'general': GeneralStrategy,
|
|
'gptZero': GPTZeroStrategy,
|
|
'originality': OriginalityStrategy
|
|
};
|
|
|
|
static createStrategy(detectorName) {
|
|
const StrategyClass = this.strategies[detectorName];
|
|
if (!StrategyClass) {
|
|
logSh(`⚠️ Stratégie inconnue: ${detectorName}, fallback vers général`, 'WARNING');
|
|
return new GeneralStrategy();
|
|
}
|
|
return new StrategyClass();
|
|
}
|
|
|
|
static getSupportedDetectors() {
|
|
return Object.keys(this.strategies).map(name => {
|
|
const strategy = this.createStrategy(name);
|
|
return {
|
|
name,
|
|
displayName: strategy.name,
|
|
effectiveness: strategy.effectiveness,
|
|
targetMetrics: strategy.targetMetrics
|
|
};
|
|
});
|
|
}
|
|
|
|
static analyzeContentAgainstAllDetectors(content) {
|
|
const results = {};
|
|
|
|
Object.keys(this.strategies).forEach(detectorName => {
|
|
const strategy = this.createStrategy(detectorName);
|
|
results[detectorName] = strategy.analyzeEffectiveness(content);
|
|
});
|
|
|
|
return results;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FONCTION UTILITAIRE - SÉLECTION STRATÉGIE OPTIMALE
|
|
*/
|
|
function selectOptimalStrategy(elementType, personality, previousResults = {}) {
|
|
// Logique de sélection intelligente
|
|
|
|
// Si résultats précédents disponibles, adapter
|
|
if (previousResults.gptZero && previousResults.gptZero.effectiveness < 0.6) {
|
|
return 'gptZero'; // Renforcer anti-GPTZero
|
|
}
|
|
|
|
if (previousResults.originality && previousResults.originality.effectiveness < 0.6) {
|
|
return 'originality'; // Renforcer anti-Originality
|
|
}
|
|
|
|
// Sélection par type d'élément
|
|
if (elementType === 'titre_h1' || elementType === 'titre_h2') {
|
|
return 'gptZero'; // Titres bénéficient imprévisibilité
|
|
}
|
|
|
|
if (elementType === 'intro' || elementType === 'texte') {
|
|
return 'originality'; // Corps bénéficie créativité sémantique
|
|
}
|
|
|
|
if (elementType.includes('faq')) {
|
|
return 'general'; // FAQ équilibre naturalité
|
|
}
|
|
|
|
// Par personnalité
|
|
if (personality) {
|
|
if (personality.style === 'créatif' || personality.style === 'original') {
|
|
return 'originality';
|
|
}
|
|
if (personality.style === 'technique' || personality.style === 'expert') {
|
|
return 'gptZero';
|
|
}
|
|
}
|
|
|
|
return 'general'; // Fallback
|
|
}
|
|
|
|
module.exports = {
|
|
DetectorStrategyFactory,
|
|
GPTZeroStrategy,
|
|
OriginalityStrategy,
|
|
GeneralStrategy,
|
|
selectOptimalStrategy,
|
|
BaseDetectorStrategy
|
|
}; |