## 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>
382 lines
14 KiB
JavaScript
382 lines
14 KiB
JavaScript
/**
|
|
* Pipeline Runner - Client Side Logic
|
|
* Gestion de l'exécution des pipelines sauvegardés
|
|
*/
|
|
|
|
// État global
|
|
const state = {
|
|
pipelines: [],
|
|
selectedPipeline: null,
|
|
running: false
|
|
};
|
|
|
|
// ====================
|
|
// INITIALIZATION
|
|
// ====================
|
|
|
|
window.onload = async function() {
|
|
await loadPipelinesList();
|
|
};
|
|
|
|
// Charger la liste des pipelines
|
|
async function loadPipelinesList() {
|
|
try {
|
|
const response = await fetch('/api/pipeline/list');
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
state.pipelines = data.pipelines;
|
|
renderPipelinesDropdown();
|
|
}
|
|
} catch (error) {
|
|
showStatus(`Erreur chargement pipelines: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Rendre le dropdown de pipelines
|
|
function renderPipelinesDropdown() {
|
|
const select = document.getElementById('pipelineSelect');
|
|
select.innerHTML = '<option value="">-- Sélectionner un pipeline --</option>';
|
|
|
|
state.pipelines.forEach(pipeline => {
|
|
const option = document.createElement('option');
|
|
option.value = pipeline.name;
|
|
option.textContent = `${pipeline.name} (${pipeline.steps} étapes, ~${pipeline.estimatedDuration})`;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
// ====================
|
|
// PIPELINE LOADING
|
|
// ====================
|
|
|
|
async function loadPipeline() {
|
|
const select = document.getElementById('pipelineSelect');
|
|
const pipelineName = select.value;
|
|
|
|
if (!pipelineName) {
|
|
document.getElementById('pipelinePreview').style.display = 'none';
|
|
document.getElementById('btnRun').disabled = true;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/pipeline/${pipelineName}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
state.selectedPipeline = data.pipeline;
|
|
displayPipelinePreview(data.pipeline);
|
|
document.getElementById('btnRun').disabled = false;
|
|
}
|
|
} catch (error) {
|
|
showStatus(`Erreur chargement pipeline: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// Afficher la prévisualisation du pipeline
|
|
function displayPipelinePreview(pipeline) {
|
|
const preview = document.getElementById('pipelinePreview');
|
|
preview.style.display = 'block';
|
|
|
|
document.getElementById('pipelineName').textContent = pipeline.name;
|
|
document.getElementById('pipelineDesc').textContent = pipeline.description || 'Pas de description';
|
|
|
|
document.getElementById('summarySteps').textContent = pipeline.pipeline.length;
|
|
|
|
// Estimation durée
|
|
const estimatedSeconds = pipeline.pipeline.length * 20; // Rough estimate
|
|
const minutes = Math.floor(estimatedSeconds / 60);
|
|
const seconds = estimatedSeconds % 60;
|
|
document.getElementById('summaryDuration').textContent = minutes > 0
|
|
? `${minutes}m ${seconds}s`
|
|
: `${seconds}s`;
|
|
|
|
// Liste des étapes
|
|
const stepList = document.getElementById('stepList');
|
|
stepList.innerHTML = '';
|
|
|
|
pipeline.pipeline.forEach(step => {
|
|
const div = document.createElement('div');
|
|
div.className = 'step-item';
|
|
div.textContent = `${step.step}. ${step.module} (${step.mode}) - Intensité: ${step.intensity}`;
|
|
stepList.appendChild(div);
|
|
});
|
|
}
|
|
|
|
// ====================
|
|
// PIPELINE EXECUTION
|
|
// ====================
|
|
|
|
async function runPipeline() {
|
|
if (!state.selectedPipeline) {
|
|
showStatus('Aucun pipeline sélectionné', 'error');
|
|
return;
|
|
}
|
|
|
|
if (state.running) {
|
|
showStatus('Une exécution est déjà en cours', 'error');
|
|
return;
|
|
}
|
|
|
|
const rowNumber = parseInt(document.getElementById('rowNumber').value);
|
|
const saveIntermediateSteps = document.getElementById('saveIntermediateSteps').checked;
|
|
|
|
if (!rowNumber || rowNumber < 2) {
|
|
showStatus('Numéro de ligne invalide (minimum 2)', 'error');
|
|
return;
|
|
}
|
|
|
|
state.running = true;
|
|
document.getElementById('btnRun').disabled = true;
|
|
|
|
// Show progress section
|
|
document.getElementById('progressSection').style.display = 'block';
|
|
document.getElementById('progressBar').style.display = 'block';
|
|
document.getElementById('progressText').style.display = 'block';
|
|
document.getElementById('resultsSection').style.display = 'none';
|
|
|
|
showStatus('🚀 Exécution du pipeline en cours...', 'loading');
|
|
updateProgress(0, 'Initialisation...');
|
|
|
|
try {
|
|
const response = await fetch('/api/pipeline/execute', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
pipelineConfig: state.selectedPipeline,
|
|
rowNumber: rowNumber,
|
|
options: {
|
|
saveIntermediateSteps: saveIntermediateSteps
|
|
}
|
|
})
|
|
});
|
|
|
|
updateProgress(50, 'Traitement en cours...');
|
|
|
|
const data = await response.json();
|
|
|
|
updateProgress(100, 'Terminé!');
|
|
|
|
if (data.success) {
|
|
displayResults(data.result);
|
|
showStatus('✅ Pipeline exécuté avec succès!', 'success');
|
|
} else {
|
|
showStatus(`❌ Erreur: ${data.error}`, 'error');
|
|
}
|
|
|
|
} catch (error) {
|
|
showStatus(`❌ Erreur exécution: ${error.message}`, 'error');
|
|
console.error('Execution error:', error);
|
|
} finally {
|
|
state.running = false;
|
|
document.getElementById('btnRun').disabled = false;
|
|
|
|
setTimeout(() => {
|
|
document.getElementById('progressSection').style.display = 'none';
|
|
}, 2000);
|
|
}
|
|
}
|
|
|
|
// ====================
|
|
// RESULTS DISPLAY
|
|
// ====================
|
|
|
|
function displayResults(result) {
|
|
const resultsSection = document.getElementById('resultsSection');
|
|
resultsSection.style.display = 'block';
|
|
|
|
// Log complet des résultats dans la console
|
|
console.log('=== RÉSULTAT PIPELINE ===');
|
|
console.log('Contenu final:', result.finalContent || result.content);
|
|
console.log('Stats:', result.stats);
|
|
console.log('Version history:', result.versionHistory);
|
|
console.log('Résultat complet:', result);
|
|
console.log('========================');
|
|
|
|
// Stats
|
|
document.getElementById('statDuration').textContent =
|
|
`${result.stats.totalDuration}ms`;
|
|
|
|
document.getElementById('statSuccessSteps').textContent =
|
|
`${result.stats.successfulSteps}/${result.stats.totalSteps}`;
|
|
|
|
document.getElementById('statPersonality').textContent =
|
|
result.stats.personality || 'N/A';
|
|
|
|
// Final Content Display
|
|
const finalContentContainer = document.getElementById('finalContentContainer');
|
|
let rawContent = result.finalContent || result.content || result.organicContent;
|
|
|
|
// Extraire le texte si c'est un objet
|
|
let finalContent;
|
|
let isStructuredContent = false;
|
|
|
|
if (typeof rawContent === 'string') {
|
|
finalContent = rawContent;
|
|
} else if (rawContent && typeof rawContent === 'object') {
|
|
// Vérifier si c'est un contenu structuré (H2_1, H3_2, etc.)
|
|
const keys = Object.keys(rawContent);
|
|
if (keys.some(k => k.match(/^(H2|H3|P)_\d+$/))) {
|
|
isStructuredContent = true;
|
|
// Formater le contenu structuré
|
|
finalContent = keys
|
|
.sort((a, b) => {
|
|
// Trier par type (H2, H3, P) puis par numéro
|
|
const aMatch = a.match(/^([A-Z]+)_(\d+)$/);
|
|
const bMatch = b.match(/^([A-Z]+)_(\d+)$/);
|
|
if (!aMatch || !bMatch) return 0;
|
|
if (aMatch[1] !== bMatch[1]) return aMatch[1].localeCompare(bMatch[1]);
|
|
return parseInt(aMatch[2]) - parseInt(bMatch[2]);
|
|
})
|
|
.map(key => {
|
|
const match = key.match(/^([A-Z0-9]+)_(\d+)$/);
|
|
if (match) {
|
|
const tag = match[1];
|
|
return `[${tag}]\n${rawContent[key]}\n`;
|
|
}
|
|
return `${key}: ${rawContent[key]}\n`;
|
|
})
|
|
.join('\n');
|
|
} else {
|
|
// Si c'est un objet, essayer d'extraire le texte
|
|
finalContent = rawContent.text || rawContent.content || rawContent.organicContent || JSON.stringify(rawContent, null, 2);
|
|
}
|
|
}
|
|
|
|
if (finalContent) {
|
|
finalContentContainer.innerHTML = '';
|
|
|
|
// Warning si contenu incomplet
|
|
const elementCount = Object.keys(rawContent || {}).length;
|
|
if (isStructuredContent && elementCount < 30) {
|
|
const warningDiv = document.createElement('div');
|
|
warningDiv.style.cssText = 'padding: 10px; margin-bottom: 15px; background: #fed7d7; border: 1px solid #f56565; border-radius: 6px; color: #822727;';
|
|
warningDiv.innerHTML = `⚠️ <strong>Génération incomplète:</strong> ${elementCount} éléments générés (attendu ~33). Vérifiez les logs pour plus de détails.`;
|
|
finalContentContainer.appendChild(warningDiv);
|
|
}
|
|
|
|
// Créer un élément pre pour préserver le formatage
|
|
const contentDiv = document.createElement('div');
|
|
contentDiv.style.cssText = 'white-space: pre-wrap; line-height: 1.6; color: var(--text-dark); font-size: 14px;';
|
|
contentDiv.textContent = finalContent;
|
|
|
|
// Ajouter un bouton pour copier
|
|
const copyBtn = document.createElement('button');
|
|
copyBtn.textContent = '📋 Copier le contenu';
|
|
copyBtn.style.cssText = 'margin-bottom: 15px; padding: 8px 16px; background: var(--primary); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;';
|
|
copyBtn.onclick = () => {
|
|
navigator.clipboard.writeText(finalContent);
|
|
copyBtn.textContent = '✓ Copié!';
|
|
setTimeout(() => { copyBtn.textContent = '📋 Copier le contenu'; }, 2000);
|
|
};
|
|
|
|
finalContentContainer.appendChild(copyBtn);
|
|
finalContentContainer.appendChild(contentDiv);
|
|
|
|
// Ajouter les métadonnées si disponibles
|
|
if (result.stats) {
|
|
const metaDiv = document.createElement('div');
|
|
metaDiv.style.cssText = 'margin-top: 15px; padding: 10px; background: white; border-radius: 6px; font-size: 12px; color: var(--text-light);';
|
|
const contentLength = finalContent.length;
|
|
const wordCount = finalContent.split(/\s+/).length;
|
|
metaDiv.innerHTML = `<strong>Métadonnées:</strong> ${contentLength} caractères, ~${wordCount} mots`;
|
|
finalContentContainer.appendChild(metaDiv);
|
|
}
|
|
} else {
|
|
finalContentContainer.innerHTML = '<p style="color: var(--warning);">⚠️ Aucun contenu final disponible dans le résultat</p>';
|
|
}
|
|
|
|
// Version History
|
|
const versionHistoryContainer = document.getElementById('versionHistory');
|
|
versionHistoryContainer.innerHTML = '';
|
|
|
|
if (result.versionHistory && result.versionHistory.length > 0) {
|
|
result.versionHistory.forEach(version => {
|
|
const div = document.createElement('div');
|
|
div.style.cssText = 'padding: 10px; margin-bottom: 8px; background: white; border-radius: 6px; border-left: 4px solid var(--success);';
|
|
|
|
const versionLabel = document.createElement('strong');
|
|
versionLabel.textContent = `Version ${version.version}`;
|
|
versionLabel.style.color = 'var(--primary)';
|
|
|
|
const articleId = document.createElement('span');
|
|
articleId.textContent = ` - Article ID: ${version.articleId}`;
|
|
articleId.style.color = 'var(--text-dark)';
|
|
|
|
const modifications = document.createElement('span');
|
|
modifications.textContent = ` - ${version.modifications || 0} modifications`;
|
|
modifications.style.color = 'var(--text-light)';
|
|
modifications.style.fontSize = '13px';
|
|
modifications.style.marginLeft = '10px';
|
|
|
|
div.appendChild(versionLabel);
|
|
div.appendChild(articleId);
|
|
div.appendChild(modifications);
|
|
|
|
versionHistoryContainer.appendChild(div);
|
|
});
|
|
} else {
|
|
versionHistoryContainer.innerHTML = '<p style="color: var(--text-light);">Aucune version sauvegardée</p>';
|
|
}
|
|
|
|
// Google Sheets Link
|
|
if (result.gsheetsLink) {
|
|
document.getElementById('gsheetsLinkContainer').style.display = 'block';
|
|
document.getElementById('gsheetsLink').href = result.gsheetsLink;
|
|
} else {
|
|
document.getElementById('gsheetsLinkContainer').style.display = 'none';
|
|
}
|
|
|
|
// Execution log
|
|
const logContainer = document.getElementById('executionLog');
|
|
logContainer.innerHTML = '';
|
|
|
|
if (result.executionLog && result.executionLog.length > 0) {
|
|
result.executionLog.forEach(logEntry => {
|
|
const div = document.createElement('div');
|
|
div.className = `log-entry ${logEntry.success ? 'log-success' : 'log-error'}`;
|
|
|
|
const status = logEntry.success ? '✓' : '✗';
|
|
const text = `${status} Étape ${logEntry.step}: ${logEntry.module} (${logEntry.mode}) ` +
|
|
`- ${logEntry.duration}ms`;
|
|
|
|
if (logEntry.modifications !== undefined) {
|
|
div.textContent = text + ` - ${logEntry.modifications} modifs`;
|
|
} else {
|
|
div.textContent = text;
|
|
}
|
|
|
|
if (!logEntry.success && logEntry.error) {
|
|
div.textContent += ` - Erreur: ${logEntry.error}`;
|
|
}
|
|
|
|
logContainer.appendChild(div);
|
|
});
|
|
} else {
|
|
logContainer.textContent = 'Aucun log d\'exécution disponible';
|
|
}
|
|
}
|
|
|
|
// ====================
|
|
// HELPERS
|
|
// ====================
|
|
|
|
function updateProgress(percentage, text) {
|
|
document.getElementById('progressFill').style.width = percentage + '%';
|
|
document.getElementById('progressText').textContent = text;
|
|
}
|
|
|
|
function showStatus(message, type) {
|
|
const status = document.getElementById('status');
|
|
status.textContent = message;
|
|
status.className = `status ${type}`;
|
|
status.style.display = 'block';
|
|
|
|
if (type !== 'loading') {
|
|
setTimeout(() => {
|
|
status.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
}
|