seo-generator-server/public/modular-pipeline-demo.html
StillHammer f51c4095f6 Add modular pipeline demo system with real module integration
- Add complete modular demo interface (public/modular-pipeline-demo.html)
- Add standalone demo server (simple-server.js) on port 3333
- Integrate real SelectiveCore, AdversarialCore, HumanSimulation, PatternBreaking modules
- Add configurable pipeline with step-by-step content transformation display
- Add new trend management and workflow configuration modules
- Add comprehensive test suite for full pipeline validation
- Update core modules for better modular integration and demo compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-23 16:03:20 +08:00

779 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Démo Pipeline Modulaire SEO</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 10px; /* Réduire le padding pour gagner de l'espace vertical */
}
.container {
max-width: 1800px; /* Plus large pour PC */
margin: 0 auto;
width: 95%; /* Utiliser plus d'espace disponible */
background: white;
border-radius: 15px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(45deg, #2c3e50, #34495e);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
opacity: 0.9;
font-size: 1.2em;
}
.main-content {
display: flex;
height: calc(100vh - 100px); /* Maximiser la hauteur - padding body (10px*2) + header (80px) */
}
.config-panel {
width: 350px; /* Plus étroit pour PC */
background: #f8f9fa;
border-right: 1px solid #dee2e6;
padding: 25px;
overflow-y: auto;
}
.results-panel {
flex: 1; /* Prend tout l'espace restant */
padding: 25px;
overflow-y: auto;
background: #fafbfc; /* Arrière-plan légèrement différent */
}
.section {
margin-bottom: 30px;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.section h3 {
color: #2c3e50;
margin-bottom: 15px;
font-size: 1.3em;
border-bottom: 2px solid #3498db;
padding-bottom: 8px;
}
.module-item {
background: #ffffff;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.module-item:hover {
border-color: #3498db;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.2);
}
.module-item.selected {
border-color: #27ae60;
background: #d5f4e6;
}
.module-item h4 {
color: #2c3e50;
margin-bottom: 8px;
font-size: 1.1em;
}
.module-item p {
color: #6c757d;
font-size: 0.9em;
line-height: 1.4;
}
.pipeline-builder {
min-height: 200px;
border: 2px dashed #dee2e6;
border-radius: 10px;
padding: 20px;
background: #fafbfc;
}
.pipeline-step {
background: linear-gradient(45deg, #3498db, #2980b9);
color: white;
padding: 12px 20px;
margin: 8px 0;
border-radius: 25px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 3px 10px rgba(52, 152, 219, 0.3);
}
.pipeline-step .remove-btn {
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 25px;
height: 25px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
}
.controls {
text-align: center;
margin-top: 20px;
}
.btn {
background: linear-gradient(45deg, #27ae60, #2ecc71);
color: white;
border: none;
padding: 15px 30px;
border-radius: 25px;
font-size: 1.1em;
cursor: pointer;
margin: 0 10px;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.3);
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(39, 174, 96, 0.4);
}
.btn:disabled {
background: #95a5a6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-clear {
background: linear-gradient(45deg, #e74c3c, #c0392b);
box-shadow: 0 5px 15px rgba(231, 76, 60, 0.3);
}
.results-area {
background: #f8f9fa;
border-radius: 10px;
padding: 12px; /* Réduit de moitié : 25px → 12px */
min-height: 400px;
/* max-height supprimé pour que le conteneur puisse grandir avec le contenu */
border: 1px solid #dee2e6;
/* overflow-y: auto supprimé aussi - le scroll sera géré par le parent si besoin */
}
.step-result {
background: white;
border-left: 4px solid #3498db;
margin-bottom: 10px; /* Réduit de moitié : 20px → 10px */
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
/* min-height supprimé - s'adapte au contenu */
}
.step-header {
background: #3498db;
color: white;
padding: 8px 10px; /* Réduit de moitié : 15px 20px → 8px 10px */
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.copy-btn {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 5px 12px;
border-radius: 15px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
}
.copy-btn:hover {
background: rgba(255,255,255,0.3);
transform: scale(1.05);
}
.copy-btn:active {
transform: scale(0.95);
}
.step-content {
padding: 12px; /* Réduit de moitié : 25px → 12px */
line-height: 1.7;
color: #2c3e50;
font-size: 15px; /* Légèrement plus gros pour PC */
/* Pas de max-height - prend toute la place disponible */
}
.step-content pre {
background: #f1f2f6;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin: 10px 0;
border-left: 3px solid #3498db;
}
.loading {
text-align: center;
padding: 40px;
color: #6c757d;
}
/* Optimisations pour grands écrans PC */
@media (min-width: 1600px) {
.container {
max-width: 2000px; /* Encore plus large sur très grands écrans */
}
.config-panel {
width: 400px; /* Un peu plus large sur grands écrans */
}
.step-content {
font-size: 16px; /* Texte plus gros sur grands écrans */
/* Pas de limitation de hauteur - utilise tout l'espace */
}
/* .step-result n'a plus de min-height - s'adapte au contenu */
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.config-option {
margin-bottom: 15px;
}
.config-option label {
display: block;
margin-bottom: 5px;
font-weight: 600;
color: #2c3e50;
}
.config-option select, .config-option input {
width: 100%;
padding: 8px 12px;
border: 1px solid #dee2e6;
border-radius: 5px;
font-size: 14px;
}
.status-bar {
background: #2c3e50;
color: white;
padding: 10px 20px;
font-size: 14px;
display: flex;
justify-content: space-between;
align-items: center;
}
.step-count {
background: #3498db;
padding: 5px 12px;
border-radius: 15px;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 Pipeline Modulaire SEO</h1>
<p>Configuration libre et exécution étape par étape</p>
</div>
<div class="main-content">
<div class="config-panel">
<div class="section">
<h3>📝 Données d'Entrée</h3>
<div class="config-option">
<label>Mot-clé principal:</label>
<input type="text" id="keyword" value="plaque personnalisée" placeholder="Ex: plaque personnalisée">
</div>
<div class="config-option">
<label>Titre principal:</label>
<input type="text" id="title" value="Créer une plaque personnalisée unique" placeholder="Ex: Guide complet...">
</div>
<div class="config-option">
<label>Personnalité:</label>
<select id="personality">
<option value="Marc">Marc (technique)</option>
<option value="Sophie">Sophie (déco)</option>
<option value="Laurent">Laurent (commercial)</option>
<option value="Julie">Julie (architecture)</option>
<option value="Kévin">Kévin (terrain)</option>
</select>
</div>
</div>
<div class="section">
<h3>🔧 Modules Disponibles</h3>
<div class="module-item" data-module="selective-light">
<h4>🎯 Selective Light</h4>
<p>Enhancement technique léger avec OpenAI</p>
</div>
<div class="module-item" data-module="selective-standard">
<h4>⚡ Selective Standard</h4>
<p>Enhancement technique + transitions (OpenAI + Gemini)</p>
</div>
<div class="module-item" data-module="selective-full">
<h4>🔥 Selective Full</h4>
<p>Enhancement complet 3 couches (multi-LLM)</p>
</div>
<div class="module-item" data-module="adversarial-general">
<h4>🛡️ Adversarial General</h4>
<p>Anti-détection standard avec régénération</p>
</div>
<div class="module-item" data-module="adversarial-gptZero">
<h4>🎭 Adversarial GPTZero</h4>
<p>Anti-GPTZero spécialisé</p>
</div>
<div class="module-item" data-module="human-light">
<h4>👤 Human Light</h4>
<p>Simulation erreurs humaines légères</p>
</div>
<div class="module-item" data-module="human-personality">
<h4>🎨 Human Personality</h4>
<p>Erreurs spécifiques à la personnalité</p>
</div>
<div class="module-item" data-module="pattern-syntax">
<h4>🔀 Pattern Syntax</h4>
<p>Cassage patterns avec variations syntaxiques</p>
</div>
<div class="module-item" data-module="pattern-connectors">
<h4>🔗 Pattern Connectors</h4>
<p>Connecteurs naturels anti-LLM</p>
</div>
</div>
<div class="section">
<h3>🏗️ Pipeline Configuré</h3>
<div style="background: #e8f4fd; border: 2px solid #3498db; border-radius: 8px; padding: 15px; margin-bottom: 15px;">
<p style="margin: 0; color: #2c3e50; font-weight: 600;">
<strong>Étape 0 (Automatique):</strong> Génération Normale avec Claude
</p>
<p style="margin: 5px 0 0 0; color: #6c757d; font-size: 0.9em;">
Cette étape est toujours exécutée en premier, avant vos modules personnalisés
</p>
</div>
<div class="pipeline-builder" id="pipeline-builder">
<p style="color: #6c757d; text-align: center; margin-top: 80px;">
Cliquez sur les modules ci-dessus pour construire votre pipeline
</p>
</div>
<div class="controls">
<button class="btn" id="execute-btn" disabled>▶️ Exécuter Pipeline</button>
<button class="btn btn-clear" id="clear-btn">🗑️ Effacer</button>
</div>
</div>
</div>
<div class="results-panel">
<div class="section">
<h3>📊 Résultats d'Exécution</h3>
<div class="results-area" id="results-area">
<div style="text-align: center; color: #6c757d; margin-top: 150px;">
<h4>🎯 Prêt à démarrer</h4>
<p>Configurez votre pipeline et lancez l'exécution pour voir les résultats étape par étape</p>
</div>
</div>
</div>
</div>
</div>
<div class="status-bar">
<span>Status: <span id="status">Prêt</span></span>
<span class="step-count">Étapes: <span id="step-count">0</span></span>
</div>
</div>
<script>
let pipeline = [];
let currentStep = 0;
let executing = false;
const modules = {
'selective-light': {
name: 'Selective Light Enhancement',
description: 'Enhancement technique léger avec OpenAI',
action: 'Enhancement technique avec focus sur la précision et la clarté'
},
'selective-standard': {
name: 'Selective Standard Enhancement',
description: 'Enhancement technique + transitions (OpenAI + Gemini)',
action: 'Enhancement technique approfondi avec amélioration des transitions'
},
'selective-full': {
name: 'Selective Full Enhancement',
description: 'Enhancement complet 3 couches (multi-LLM)',
action: 'Enhancement complet avec couches techniques, transitions et style'
},
'adversarial-general': {
name: 'Adversarial General Defense',
description: 'Anti-détection standard avec régénération',
action: 'Application de défenses anti-détection générales'
},
'adversarial-gptZero': {
name: 'Adversarial GPTZero Defense',
description: 'Anti-GPTZero spécialisé',
action: 'Défense spécialisée contre la détection GPTZero'
},
'human-light': {
name: 'Human Light Simulation',
description: 'Simulation erreurs humaines légères',
action: 'Injection d\'erreurs humaines subtiles et naturelles'
},
'human-personality': {
name: 'Human Personality Simulation',
description: 'Erreurs spécifiques à la personnalité',
action: 'Simulation d\'erreurs typiques de la personnalité sélectionnée'
},
'pattern-syntax': {
name: 'Pattern Syntax Breaking',
description: 'Cassage patterns avec variations syntaxiques',
action: 'Cassage des patterns LLM via variations syntaxiques'
},
'pattern-connectors': {
name: 'Pattern Connectors Breaking',
description: 'Connecteurs naturels anti-LLM',
action: 'Remplacement des connecteurs par des alternatives naturelles'
}
};
// Event listeners
document.querySelectorAll('.module-item').forEach(item => {
item.addEventListener('click', () => {
const moduleId = item.dataset.module;
addToPipeline(moduleId);
updatePipelineDisplay();
});
});
document.getElementById('execute-btn').addEventListener('click', executePipeline);
document.getElementById('clear-btn').addEventListener('click', clearPipeline);
function addToPipeline(moduleId) {
pipeline.push(moduleId);
updateStepCount();
updateExecuteButton();
}
function updatePipelineDisplay() {
const builder = document.getElementById('pipeline-builder');
if (pipeline.length === 0) {
builder.innerHTML = '<p style="color: #6c757d; text-align: center; margin-top: 80px;">Cliquez sur les modules ci-dessus pour construire votre pipeline</p>';
return;
}
builder.innerHTML = pipeline.map((moduleId, index) => `
<div class="pipeline-step">
<span>${index + 1}. ${modules[moduleId].name}</span>
<button class="remove-btn" onclick="removeFromPipeline(${index})">×</button>
</div>
`).join('');
}
function removeFromPipeline(index) {
pipeline.splice(index, 1);
updatePipelineDisplay();
updateStepCount();
updateExecuteButton();
}
function clearPipeline() {
pipeline = [];
updatePipelineDisplay();
updateStepCount();
updateExecuteButton();
clearResults();
}
function updateStepCount() {
document.getElementById('step-count').textContent = pipeline.length;
}
function updateExecuteButton() {
// Le bouton est toujours actif car la génération normale peut être exécutée seule
document.getElementById('execute-btn').disabled = executing;
}
function updateStatus(status) {
document.getElementById('status').textContent = status;
}
function clearResults() {
document.getElementById('results-area').innerHTML = `
<div style="text-align: center; color: #6c757d; margin-top: 150px;">
<h4>🎯 Prêt à démarrer</h4>
<p>Configurez votre pipeline et lancez l'exécution pour voir les résultats étape par étape</p>
</div>
`;
}
async function executePipeline() {
// La génération normale peut être exécutée seule (pipeline vide) ou avec des modules
executing = true;
updateExecuteButton();
updateStatus('Exécution en cours...');
const resultsArea = document.getElementById('results-area');
resultsArea.innerHTML = '';
// Données d'entrée
const keyword = document.getElementById('keyword').value || 'plaque personnalisée';
const title = document.getElementById('title').value || 'Guide complet';
const personality = document.getElementById('personality').value || 'Marc';
updateStatus('Étape 0: Génération Normale Obligatoire');
// ÉTAPE OBLIGATOIRE: Génération normale avec Claude
let currentText;
try {
const normalResponse = await fetch('/api/generate-normal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ keyword, title, personality })
});
const normalResult = await normalResponse.json();
if (normalResult.success) {
currentText = normalResult.content;
// Sauvegarder le contenu structuré pour les modules
window.currentStructuredContent = normalResult.structuredContent;
} else {
throw new Error(normalResult.error);
}
} catch (error) {
console.error('Erreur génération normale:', error);
// Fallback sur un texte simple
currentText = `# ${title}
## Introduction
${keyword} représente un marché en pleine expansion avec de nombreuses possibilités créatives. Dans ce guide complet, nous explorons les différentes options disponibles pour créer une ${keyword} qui répond parfaitement à vos besoins.
## Les avantages d'une ${keyword}
Une ${keyword} de qualité offre plusieurs avantages significatifs pour votre projet. Elle permet de personnaliser votre espace tout en conservant un aspect professionnel et esthétique.
## Conclusion
En conclusion, choisir la bonne ${keyword} nécessite une réflexion approfondie sur vos besoins spécifiques et votre budget disponible.`;
}
// Afficher le texte de génération normale (OBLIGATOIRE)
addStepResult('🌱 Génération Normale (OBLIGATOIRE)', 'Création du contenu de base avec Claude - Étape requise avant tout module', currentText, 0);
// Exécuter chaque étape du pipeline
for (let i = 0; i < pipeline.length; i++) {
const moduleId = pipeline[i];
const module = modules[moduleId];
updateStatus(`Étape ${i + 1}/${pipeline.length}: ${module.name}`);
try {
// Appel API du vrai module
// Utiliser le contenu structuré si disponible, sinon le texte courant
const contentToSend = window.currentStructuredContent ?
{ structuredContent: window.currentStructuredContent } :
currentText;
const moduleResponse = await fetch('/api/apply-module', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
moduleId,
content: contentToSend,
config: { keyword, personality }
})
});
const moduleResult = await moduleResponse.json();
if (moduleResult.success) {
// Le module retourne un objet avec statistiques, extraire le contenu
const responseContent = moduleResult.content;
if (typeof responseContent === 'string') {
currentText = responseContent;
// Plus de contenu structuré disponible
window.currentStructuredContent = null;
} else if (responseContent && typeof responseContent === 'object') {
// Si c'est un objet structuré, le garder pour les prochains modules
window.currentStructuredContent = responseContent;
// Assembler pour l'affichage
currentText = [
responseContent.Titre_H1 && `# ${responseContent.Titre_H1}`,
responseContent.Introduction && `## Introduction\n\n${responseContent.Introduction}`,
responseContent.Contenu_Principal && `## Contenu Principal\n\n${responseContent.Contenu_Principal}`,
responseContent.Conclusion && `## Conclusion\n\n${responseContent.Conclusion}`
].filter(Boolean).join('\n\n') || JSON.stringify(responseContent, null, 2);
} else {
currentText = JSON.stringify(responseContent, null, 2);
window.currentStructuredContent = null;
}
console.log('Module response:', moduleResult);
} else {
throw new Error(moduleResult.error);
}
addStepResult(
`${i + 1}. ${module.name}`,
module.action,
currentText,
i + 1
);
} catch (error) {
console.error(`Erreur module ${moduleId}:`, error);
addStepResult(
`${i + 1}. ${module.name} (ERREUR)`,
`Erreur: ${error.message}`,
currentText,
i + 1
);
}
}
executing = false;
updateExecuteButton();
updateStatus('Terminé');
}
// Plus besoin de simulation - on utilise les vrais modules !
function addStepResult(title, description, content, stepNumber = 0) {
const resultsArea = document.getElementById('results-area');
const stepDiv = document.createElement('div');
stepDiv.className = 'step-result';
const contentId = `content-${Date.now()}-${stepNumber}`;
stepDiv.innerHTML = `
<div class="step-header">
<span>${title}</span>
<div>
<button class="copy-btn" onclick="copyContent('${contentId}', event)">📋 Copier</button>
<span style="margin-left: 15px;">${new Date().toLocaleTimeString()}</span>
</div>
</div>
<div class="step-content">
<p><strong>Action:</strong> ${description}</p>
<details ${stepNumber === 0 ? 'open' : ''}>
<summary style="cursor: pointer; margin: 15px 0 10px 0; padding: 10px; background: #f8f9fa; border-radius: 5px;">
📄 Contenu généré (${content ? content.length : 0} caractères)
</summary>
<pre id="${contentId}">${content || 'Aucun contenu disponible'}</pre>
</details>
</div>
`;
resultsArea.appendChild(stepDiv);
// Auto-scroll vers le dernier résultat
stepDiv.scrollIntoView({ behavior: 'smooth', block: 'end' });
}
// Fonction pour copier le contenu
window.copyContent = function(contentId, event) {
const contentElement = document.getElementById(contentId);
if (!contentElement) {
console.error('Element not found:', contentId);
alert('Élément non trouvé');
return;
}
const textToCopy = contentElement.textContent || contentElement.innerText;
if (!textToCopy || textToCopy.trim() === '') {
alert('Aucun contenu à copier');
return;
}
navigator.clipboard.writeText(textToCopy).then(() => {
// Feedback visuel
const btn = event.target;
const originalText = btn.textContent;
btn.textContent = '✅ Copié !';
btn.style.background = 'rgba(39, 174, 96, 0.3)';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = 'rgba(255,255,255,0.2)';
}, 2000);
}).catch(err => {
console.error('Erreur copie:', err);
alert('Erreur lors de la copie: ' + err.message);
});
}
// Initialisation
updateStepCount();
updateExecuteButton();
</script>
</body>
</html>