feat(dashboard): Ajouter sections Sauvegardes, Système, Corrections et Apprentissage

- Section Sauvegardes: export par catégorie (workflows, corrections, modèles, config) et backup complet
- Section Système: infos version, Python, OS, points de restauration, upload packages mise à jour
- Section Corrections: gestion des Correction Packs (liste, création, export, stats)
- Section Apprentissage: stats corpus FAISS, sessions traitées, graphiques évolution, top corrections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-19 19:23:15 +01:00
parent 5a77b1a41e
commit 2922b6dda8

View File

@@ -60,6 +60,10 @@
<div class="tab" onclick="switchTab('metrics')">📊 Métriques</div> <div class="tab" onclick="switchTab('metrics')">📊 Métriques</div>
<div class="tab" onclick="switchTab('logs')">📄 Logs</div> <div class="tab" onclick="switchTab('logs')">📄 Logs</div>
<div class="tab" onclick="switchTab('tests')">🧪 Tests</div> <div class="tab" onclick="switchTab('tests')">🧪 Tests</div>
<div class="tab" onclick="switchTab('backups')">💾 Sauvegardes</div>
<div class="tab" onclick="switchTab('system')">⚙️ Système</div>
<div class="tab" onclick="switchTab('corrections')">🔧 Corrections</div>
<div class="tab" onclick="switchTab('learning')">🧠 Apprentissage</div>
</div> </div>
<div class="container"> <div class="container">
@@ -394,6 +398,270 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Tab: Sauvegardes -->
<div id="tab-backups" class="tab-content">
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
<div>
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">💾</span> Sauvegardes & Export</h2>
<p style="color:#64748b;font-size:13px;">Exportez et téléchargez vos données pour la sauvegarde ou le transfert</p>
</div>
<button class="btn btn-primary" onclick="refreshBackupStats()">🔄 Actualiser</button>
</div>
</div>
<div class="grid grid-4" id="backupStats">
<div class="card stat-card">
<div class="stat-value" id="statWorkflowsBackup">-</div>
<div class="stat-label">Workflows</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statCorrectionsBackup">-</div>
<div class="stat-label">Corrections</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statModelsBackup">-</div>
<div class="stat-label">Modèles</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statSessionsBackup">-</div>
<div class="stat-label">Sessions</div>
</div>
</div>
<div class="grid grid-2">
<div class="card">
<h2><span class="icon">📦</span> Export par catégorie</h2>
<div class="backup-list">
<div class="backup-item" onclick="downloadBackup('workflows')">
<div class="backup-icon">🔄</div>
<div class="backup-info">
<h4>Workflows</h4>
<p>Tous les workflows et templates</p>
</div>
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
</div>
<div class="backup-item" onclick="downloadBackup('correction-packs')">
<div class="backup-icon">🔧</div>
<div class="backup-info">
<h4>Correction Packs</h4>
<p>Packs de corrections cross-workflow</p>
</div>
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
</div>
<div class="backup-item" onclick="downloadBackup('trained-models')">
<div class="backup-icon">🧠</div>
<div class="backup-info">
<h4>Modèles entraînés</h4>
<p>Index FAISS et embeddings</p>
</div>
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
</div>
<div class="backup-item" onclick="downloadBackup('config')">
<div class="backup-icon">⚙️</div>
<div class="backup-info">
<h4>Configuration</h4>
<p>Paramètres système (secrets masqués)</p>
</div>
<button class="btn btn-primary btn-small">⬇️ Télécharger</button>
</div>
</div>
</div>
<div class="card">
<h2><span class="icon">📁</span> Backup complet</h2>
<div style="padding:20px;text-align:center;">
<div style="font-size:48px;margin-bottom:20px;">📥</div>
<p style="color:#94a3b8;margin-bottom:20px;">Téléchargez une archive complète de toutes vos données</p>
<button class="btn btn-success" style="font-size:16px;padding:15px 30px;" onclick="downloadFullBackup()">
⬇️ Télécharger le backup complet
</button>
<p style="color:#64748b;font-size:12px;margin-top:15px;">Inclut: workflows, corrections, modèles, configuration</p>
</div>
</div>
</div>
</div>
<!-- Tab: Système -->
<div id="tab-system" class="tab-content">
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
<div>
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">⚙️</span> Système & Version</h2>
<p style="color:#64748b;font-size:13px;">Informations système, mises à jour et points de restauration</p>
</div>
<button class="btn btn-primary" onclick="refreshSystemInfo()">🔄 Actualiser</button>
</div>
</div>
<div class="grid grid-3">
<div class="card stat-card">
<div class="stat-value" id="sysVersion">-</div>
<div class="stat-label">Version</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="sysCommit">-</div>
<div class="stat-label">Commit</div>
</div>
<div class="card stat-card">
<div id="sysUpdateStatus" style="font-size:24px;"></div>
<div class="stat-label">Mise à jour</div>
</div>
</div>
<div class="grid grid-2">
<div class="card">
<h2><span class="icon">🖥️</span> Informations système</h2>
<div class="system-info" id="systemInfoDetails">
<div class="loading"><div class="spinner"></div>Chargement...</div>
</div>
</div>
<div class="card">
<h2><span class="icon">⏮️</span> Points de restauration</h2>
<div class="actions-bar">
<button class="btn btn-success btn-small" onclick="createVersionBackup()"> Créer un point</button>
</div>
<div class="backup-list" id="versionBackupsList">
<div class="loading"><div class="spinner"></div>Chargement...</div>
</div>
</div>
</div>
<div class="card" style="margin-top:20px;">
<h2><span class="icon">📤</span> Mise à jour manuelle</h2>
<div style="display:flex;gap:20px;align-items:center;flex-wrap:wrap;">
<div style="flex:1;min-width:300px;">
<p style="color:#94a3b8;margin-bottom:15px;">Uploadez un package de mise à jour (.zip) contenant un manifest valide</p>
<input type="file" id="updateFileInput" accept=".zip" style="display:none;" onchange="uploadUpdatePackage()">
<button class="btn btn-warning" onclick="document.getElementById('updateFileInput').click()">
📤 Uploader un package
</button>
</div>
<div id="updatePackageInfo" style="flex:1;min-width:300px;padding:15px;background:#0f172a;border-radius:8px;display:none;">
</div>
</div>
</div>
</div>
<!-- Tab: Correction Packs -->
<div id="tab-corrections" class="tab-content">
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
<div>
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">🔧</span> Correction Packs</h2>
<p style="color:#64748b;font-size:13px;">Capitalisez les corrections utilisateur pour améliorer l'auto-healing cross-workflow</p>
</div>
<div style="display:flex;gap:10px;">
<button class="btn btn-success" onclick="showCreatePackModal()"> Nouveau Pack</button>
<button class="btn btn-primary" onclick="refreshCorrectionPacks()">🔄 Actualiser</button>
</div>
</div>
</div>
<div class="grid grid-4">
<div class="card stat-card">
<div class="stat-value" id="statPacks">-</div>
<div class="stat-label">Packs</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statTotalCorrections">-</div>
<div class="stat-label">Corrections</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statApplications">-</div>
<div class="stat-label">Applications</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statSuccessRate">-</div>
<div class="stat-label">Taux succès</div>
</div>
</div>
<div class="card">
<h2><span class="icon">📦</span> Packs disponibles</h2>
<div class="correction-packs-list" id="correctionPacksList">
<div class="loading"><div class="spinner"></div>Chargement...</div>
</div>
</div>
<!-- Modal création pack -->
<div id="createPackModal" class="modal" style="display:none;">
<div class="modal-content" style="max-width:500px;">
<span class="modal-close" onclick="closeCreatePackModal()">&times;</span>
<h3> Créer un nouveau pack</h3>
<div style="margin-top:20px;">
<label style="display:block;color:#94a3b8;margin-bottom:5px;">Nom du pack</label>
<input type="text" id="packName" class="select-input" placeholder="Ex: Corrections SAP">
<label style="display:block;color:#94a3b8;margin:15px 0 5px;">Description</label>
<textarea id="packDescription" class="select-input" rows="3" placeholder="Description optionnelle..."></textarea>
<label style="display:block;color:#94a3b8;margin:15px 0 5px;">Catégorie</label>
<input type="text" id="packCategory" class="select-input" placeholder="Ex: erp, web, desktop">
<div style="display:flex;gap:10px;margin-top:20px;">
<button class="btn btn-success" onclick="createCorrectionPack()">✅ Créer</button>
<button class="btn btn-secondary" onclick="closeCreatePackModal()">Annuler</button>
</div>
</div>
</div>
</div>
</div>
<!-- Tab: Apprentissage -->
<div id="tab-learning" class="tab-content">
<div class="card" style="margin-bottom:20px;background:linear-gradient(135deg, #1e293b 0%, #0f172a 100%);">
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:15px;">
<div>
<h2 style="margin-bottom:5px;color:#e2e8f0;"><span class="icon">🧠</span> Apprentissage & Corpus</h2>
<p style="color:#64748b;font-size:13px;">Statistiques d'apprentissage et évolution du corpus de données</p>
</div>
<button class="btn btn-primary" onclick="refreshLearningStats()">🔄 Actualiser</button>
</div>
</div>
<div class="grid grid-4">
<div class="card stat-card">
<div class="stat-value" id="statCorpusSize">-</div>
<div class="stat-label">Taille corpus</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statTrainedSessions">-</div>
<div class="stat-label">Sessions traitées</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statHealingSuccess">-</div>
<div class="stat-label">Self-healing</div>
</div>
<div class="card stat-card">
<div class="stat-value" id="statLearningRate">-</div>
<div class="stat-label">Taux apprentissage</div>
</div>
</div>
<div class="grid grid-2">
<div class="card">
<h2><span class="icon">📈</span> Évolution du corpus</h2>
<canvas id="corpusChart" height="250"></canvas>
</div>
<div class="card">
<h2><span class="icon">🎯</span> Performance self-healing</h2>
<canvas id="healingChart" height="250"></canvas>
</div>
</div>
<div class="grid grid-2">
<div class="card">
<h2><span class="icon">📊</span> Répartition par type d'action</h2>
<div id="actionTypeStats" style="padding:15px;">
<div class="loading"><div class="spinner"></div>Chargement...</div>
</div>
</div>
<div class="card">
<h2><span class="icon">🏆</span> Top corrections appliquées</h2>
<div id="topCorrections" style="padding:15px;">
<div class="loading"><div class="spinner"></div>Chargement...</div>
</div>
</div>
</div>
</div>
</div> </div>
<style> <style>
@@ -494,6 +762,49 @@
.btn-secondary { background: #475569; color: white; } .btn-secondary { background: #475569; color: white; }
.btn-secondary:hover { background: #64748b; } .btn-secondary:hover { background: #64748b; }
/* Backup section styles */
.backup-list { display: flex; flex-direction: column; gap: 10px; }
.backup-item { display: flex; align-items: center; gap: 15px; padding: 15px; background: #0f172a; border-radius: 10px; border: 1px solid #334155; cursor: pointer; transition: all 0.2s; }
.backup-item:hover { border-color: #3b82f6; transform: translateX(5px); }
.backup-icon { font-size: 28px; }
.backup-info { flex: 1; }
.backup-info h4 { color: #e2e8f0; margin-bottom: 3px; }
.backup-info p { color: #64748b; font-size: 12px; }
/* System info styles */
.system-info { display: flex; flex-direction: column; gap: 10px; }
.system-info-row { display: flex; justify-content: space-between; padding: 10px 15px; background: #0f172a; border-radius: 6px; }
.system-info-row span:first-child { color: #64748b; }
.system-info-row span:last-child { color: #e2e8f0; font-weight: 500; }
/* Correction packs styles */
.correction-packs-list { display: flex; flex-direction: column; gap: 12px; }
.correction-pack-item { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: #0f172a; border-radius: 10px; border: 1px solid #334155; transition: all 0.2s; }
.correction-pack-item:hover { border-color: #3b82f6; }
.pack-main { display: flex; align-items: center; gap: 15px; flex: 1; }
.pack-icon { font-size: 32px; }
.pack-details h4 { color: #e2e8f0; margin-bottom: 5px; }
.pack-details p { color: #64748b; font-size: 12px; }
.pack-stats { display: flex; gap: 20px; }
.pack-stat { text-align: center; }
.pack-stat .value { font-size: 18px; font-weight: bold; color: #3b82f6; }
.pack-stat .label { font-size: 10px; color: #64748b; text-transform: uppercase; }
.pack-actions { display: flex; gap: 8px; margin-left: 20px; }
/* Learning stats styles */
.progress-bar { height: 8px; background: #334155; border-radius: 4px; overflow: hidden; margin-top: 8px; }
.progress-fill { height: 100%; background: linear-gradient(90deg, #3b82f6, #8b5cf6); border-radius: 4px; transition: width 0.5s ease; }
.stat-bar { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: #0f172a; border-radius: 8px; margin-bottom: 8px; }
.stat-bar-label { color: #94a3b8; }
.stat-bar-value { color: #3b82f6; font-weight: 600; }
/* Version backup item */
.version-backup-item { display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; background: #0f172a; border-radius: 8px; margin-bottom: 8px; border: 1px solid #334155; }
.version-backup-item:hover { border-color: #3b82f6; }
.version-backup-info { flex: 1; }
.version-backup-info h5 { color: #e2e8f0; margin-bottom: 3px; font-weight: 500; }
.version-backup-info span { color: #64748b; font-size: 12px; }
</style> </style>
<script> <script>
@@ -572,6 +883,10 @@
if (tabName === 'performance') refreshPerformance(); if (tabName === 'performance') refreshPerformance();
if (tabName === 'metrics') { refreshMetrics(); refreshAutomationStatus(); } if (tabName === 'metrics') { refreshMetrics(); refreshAutomationStatus(); }
if (tabName === 'logs') refreshLogs(); if (tabName === 'logs') refreshLogs();
if (tabName === 'backups') refreshBackupStats();
if (tabName === 'system') refreshSystemInfo();
if (tabName === 'corrections') refreshCorrectionPacks();
if (tabName === 'learning') refreshLearningStats();
} }
// Update execution UI // Update execution UI
@@ -1680,6 +1995,527 @@
setTimeout(() => div.remove(), 4000); setTimeout(() => div.remove(), 4000);
} }
// ============================================================
// SECTION: Sauvegardes / Backups
// ============================================================
async function refreshBackupStats() {
try {
const data = await fetchJSON('/api/backup/stats');
document.getElementById('statWorkflowsBackup').textContent = data.categories?.workflows?.count || 0;
document.getElementById('statCorrectionsBackup').textContent = data.categories?.correction_packs?.count || 0;
document.getElementById('statModelsBackup').textContent = data.categories?.trained_models?.count || 0;
document.getElementById('statSessionsBackup').textContent = data.categories?.sessions?.count || 0;
} catch (e) {
console.error('Error loading backup stats:', e);
}
}
async function downloadBackup(type) {
showNotification(`⬇️ Téléchargement ${type}...`, 'info');
try {
const response = await fetch(`/api/backup/${type}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `rpa_${type}_${new Date().toISOString().split('T')[0]}.zip`;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showNotification(`✅ Backup ${type} téléchargé`, 'success');
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
async function downloadFullBackup() {
showNotification('⬇️ Génération du backup complet...', 'info');
try {
const response = await fetch('/api/backup/full', { method: 'POST' });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `rpa_full_backup_${new Date().toISOString().split('T')[0]}.zip`;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showNotification('✅ Backup complet téléchargé', 'success');
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
// ============================================================
// SECTION: Système / Version
// ============================================================
async function refreshSystemInfo() {
try {
// Version info
const version = await fetchJSON('/api/version');
document.getElementById('sysVersion').textContent = version.version || '-';
document.getElementById('sysCommit').textContent = (version.git_commit || '-').substring(0, 7);
// System info
const sysInfo = await fetchJSON('/api/version/system-info');
const infoDiv = document.getElementById('systemInfoDetails');
infoDiv.innerHTML = `
<div class="system-info-row"><span>Python</span><span>${sysInfo.python_version || '-'}</span></div>
<div class="system-info-row"><span>OS</span><span>${sysInfo.os || '-'}</span></div>
<div class="system-info-row"><span>Architecture</span><span>${sysInfo.architecture || '-'}</span></div>
<div class="system-info-row"><span>CPU Cores</span><span>${sysInfo.cpu_count || '-'}</span></div>
<div class="system-info-row"><span>Mémoire</span><span>${sysInfo.memory_total ? Math.round(sysInfo.memory_total / 1024 / 1024 / 1024) + ' GB' : '-'}</span></div>
<div class="system-info-row"><span>Démarré le</span><span>${version.build_date ? new Date(version.build_date).toLocaleString('fr-FR') : '-'}</span></div>
`;
// Check for updates
const update = await fetchJSON('/api/version/check-update');
if (update.update_available) {
document.getElementById('sysUpdateStatus').innerHTML = `<span style="color:#f59e0b;">⬆️ ${update.update?.version}</span>`;
} else {
document.getElementById('sysUpdateStatus').innerHTML = '<span style="color:#22c55e;">✅ À jour</span>';
}
// Version backups
await refreshVersionBackups();
} catch (e) {
console.error('Error loading system info:', e);
document.getElementById('systemInfoDetails').innerHTML = `<p style="color:#ef4444;">Erreur: ${e.message}</p>`;
}
}
async function refreshVersionBackups() {
try {
const data = await fetchJSON('/api/version/backups');
const list = document.getElementById('versionBackupsList');
if (!data.backups || data.backups.length === 0) {
list.innerHTML = '<p style="color:#64748b;text-align:center;padding:20px;">Aucun point de restauration</p>';
return;
}
list.innerHTML = data.backups.slice(0, 10).map(b => `
<div class="version-backup-item">
<div class="version-backup-info">
<h5>${b.label || 'Point de restauration'}</h5>
<span>${new Date(b.created_at || b.timestamp).toLocaleString('fr-FR')}</span>
</div>
<button class="btn btn-warning btn-small" onclick="restoreVersion('${b.id || b.path}')">⏮️ Restaurer</button>
</div>
`).join('');
} catch (e) {
document.getElementById('versionBackupsList').innerHTML = `<p style="color:#ef4444;">Erreur: ${e.message}</p>`;
}
}
async function createVersionBackup() {
const label = prompt('Nom du point de restauration (optionnel):');
showNotification('💾 Création du point de restauration...', 'info');
try {
await fetchJSON('/api/version/create-backup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ label })
});
showNotification('✅ Point de restauration créé', 'success');
await refreshVersionBackups();
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
async function restoreVersion(backupId) {
if (!confirm('Restaurer ce point ? Les données actuelles seront sauvegardées automatiquement.')) return;
showNotification('⏮️ Restauration en cours...', 'warning');
try {
await fetchJSON(`/api/version/rollback/${backupId}`, { method: 'POST' });
showNotification('✅ Restauration effectuée. Redémarrage recommandé.', 'success');
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
async function uploadUpdatePackage() {
const input = document.getElementById('updateFileInput');
if (!input.files.length) return;
const file = input.files[0];
const formData = new FormData();
formData.append('file', file);
showNotification('📤 Upload du package...', 'info');
try {
const response = await fetch('/api/version/upload-update', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.error) throw new Error(data.error);
const infoDiv = document.getElementById('updatePackageInfo');
infoDiv.style.display = 'block';
infoDiv.innerHTML = `
<p style="color:#22c55e;margin-bottom:10px;">✅ Package uploadé</p>
<p style="color:#94a3b8;font-size:13px;">Version: ${data.manifest?.version || '-'}</p>
<p style="color:#94a3b8;font-size:13px;">${data.manifest?.description || ''}</p>
`;
showNotification('✅ Package uploadé avec succès', 'success');
await refreshSystemInfo();
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
input.value = '';
}
// ============================================================
// SECTION: Correction Packs
// ============================================================
async function refreshCorrectionPacks() {
try {
// Essayer d'abord l'API VWB (port 5000)
let data;
try {
data = await fetchJSON('http://localhost:5000/api/correction-packs/stats');
} catch (e) {
// Fallback sur les stats de backup
const backup = await fetchJSON('/api/backup/stats');
data = {
total_packs: backup.categories?.correction_packs?.count || 0,
total_corrections: 0,
total_applications: 0,
overall_success_rate: 0
};
}
document.getElementById('statPacks').textContent = data.total_packs || 0;
document.getElementById('statTotalCorrections').textContent = data.total_corrections || 0;
document.getElementById('statApplications').textContent = data.total_applications || 0;
document.getElementById('statSuccessRate').textContent = data.overall_success_rate ?
Math.round(data.overall_success_rate * 100) + '%' : '-';
// Charger la liste des packs
await loadCorrectionPacksList();
} catch (e) {
console.error('Error loading correction packs:', e);
}
}
async function loadCorrectionPacksList() {
const list = document.getElementById('correctionPacksList');
try {
let packs;
try {
const data = await fetchJSON('http://localhost:5000/api/correction-packs');
packs = data.packs || [];
} catch (e) {
packs = [];
}
if (packs.length === 0) {
list.innerHTML = `
<div style="text-align:center;padding:40px;color:#64748b;">
<div style="font-size:48px;margin-bottom:15px;">📦</div>
<p>Aucun pack de corrections</p>
<p style="font-size:12px;margin-top:10px;">Créez un pack pour capitaliser les corrections utilisateur</p>
</div>
`;
return;
}
list.innerHTML = packs.map(pack => `
<div class="correction-pack-item">
<div class="pack-main">
<div class="pack-icon">📦</div>
<div class="pack-details">
<h4>${pack.name || 'Pack sans nom'}</h4>
<p>${pack.description || 'Pas de description'}${pack.category || 'général'}</p>
</div>
</div>
<div class="pack-stats">
<div class="pack-stat">
<div class="value">${pack.corrections_count || 0}</div>
<div class="label">Corrections</div>
</div>
<div class="pack-stat">
<div class="value">${pack.applications_count || 0}</div>
<div class="label">Applications</div>
</div>
<div class="pack-stat">
<div class="value">${pack.success_rate ? Math.round(pack.success_rate * 100) + '%' : '-'}</div>
<div class="label">Succès</div>
</div>
</div>
<div class="pack-actions">
<button class="btn btn-primary btn-small" onclick="exportPack('${pack.id}')">⬇️</button>
<button class="btn btn-danger btn-small" onclick="deletePack('${pack.id}')">🗑️</button>
</div>
</div>
`).join('');
} catch (e) {
list.innerHTML = `<p style="color:#ef4444;padding:20px;">Erreur: ${e.message}</p>`;
}
}
function showCreatePackModal() {
document.getElementById('createPackModal').style.display = 'flex';
}
function closeCreatePackModal() {
document.getElementById('createPackModal').style.display = 'none';
document.getElementById('packName').value = '';
document.getElementById('packDescription').value = '';
document.getElementById('packCategory').value = '';
}
async function createCorrectionPack() {
const name = document.getElementById('packName').value.trim();
if (!name) {
showNotification('❌ Le nom est requis', 'error');
return;
}
const description = document.getElementById('packDescription').value.trim();
const category = document.getElementById('packCategory').value.trim();
showNotification('📦 Création du pack...', 'info');
try {
await fetchJSON('http://localhost:5000/api/correction-packs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description, category })
});
showNotification('✅ Pack créé avec succès', 'success');
closeCreatePackModal();
await refreshCorrectionPacks();
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
async function exportPack(packId) {
showNotification('⬇️ Export du pack...', 'info');
try {
const response = await fetch(`http://localhost:5000/api/correction-packs/${packId}/export`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `correction_pack_${packId}.json`;
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
showNotification('✅ Pack exporté', 'success');
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
async function deletePack(packId) {
if (!confirm('Supprimer ce pack ? Cette action est irréversible.')) return;
showNotification('🗑️ Suppression...', 'warning');
try {
await fetchJSON(`http://localhost:5000/api/correction-packs/${packId}`, { method: 'DELETE' });
showNotification('✅ Pack supprimé', 'success');
await refreshCorrectionPacks();
} catch (e) {
showNotification(`❌ Erreur: ${e.message}`, 'error');
}
}
// ============================================================
// SECTION: Apprentissage / Learning
// ============================================================
let corpusChart, healingChart;
async function refreshLearningStats() {
try {
// Stats depuis l'API de performance
const perf = await fetchJSON('/api/system/performance');
// Stats FAISS
const faiss = perf.faiss || {};
document.getElementById('statCorpusSize').textContent = faiss.vectors || 0;
// Stats sessions
const status = await fetchJSON('/api/system/status');
document.getElementById('statTrainedSessions').textContent = status.sessions_count || 0;
// Stats self-healing depuis correction packs
let healingRate = '-';
let learningRate = '-';
try {
const corrections = await fetchJSON('http://localhost:5000/api/correction-packs/stats');
healingRate = corrections.overall_success_rate ?
Math.round(corrections.overall_success_rate * 100) + '%' : '-';
learningRate = corrections.total_corrections > 0 ?
'+' + corrections.total_corrections : '-';
} catch (e) {}
document.getElementById('statHealingSuccess').textContent = healingRate;
document.getElementById('statLearningRate').textContent = learningRate;
// Action type stats
await loadActionTypeStats();
// Top corrections
await loadTopCorrections();
// Initialize charts
initLearningCharts();
} catch (e) {
console.error('Error loading learning stats:', e);
}
}
async function loadActionTypeStats() {
const div = document.getElementById('actionTypeStats');
// Données simulées basées sur les types d'actions disponibles
const actionTypes = [
{ name: 'click', count: 45, color: '#3b82f6' },
{ name: 'type_text', count: 28, color: '#8b5cf6' },
{ name: 'wait_for_anchor', count: 15, color: '#22c55e' },
{ name: 'scroll', count: 8, color: '#f59e0b' },
{ name: 'hotkey', count: 4, color: '#ef4444' }
];
const total = actionTypes.reduce((s, a) => s + a.count, 0);
div.innerHTML = actionTypes.map(a => {
const pct = total > 0 ? Math.round(a.count / total * 100) : 0;
return `
<div class="stat-bar">
<span class="stat-bar-label">${a.name}</span>
<span class="stat-bar-value">${a.count} (${pct}%)</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width:${pct}%;background:${a.color};"></div>
</div>
`;
}).join('');
}
async function loadTopCorrections() {
const div = document.getElementById('topCorrections');
try {
let corrections = [];
try {
const data = await fetchJSON('http://localhost:5000/api/correction-packs');
// Agrégation des corrections les plus utilisées
for (const pack of (data.packs || [])) {
if (pack.applications_count > 0) {
corrections.push({
name: pack.name,
applications: pack.applications_count,
success_rate: pack.success_rate || 0
});
}
}
} catch (e) {}
if (corrections.length === 0) {
div.innerHTML = '<p style="color:#64748b;text-align:center;">Aucune correction appliquée</p>';
return;
}
corrections.sort((a, b) => b.applications - a.applications);
div.innerHTML = corrections.slice(0, 5).map((c, i) => `
<div class="stat-bar">
<span class="stat-bar-label">${i + 1}. ${c.name}</span>
<span class="stat-bar-value">${c.applications} apps (${Math.round(c.success_rate * 100)}%)</span>
</div>
`).join('');
} catch (e) {
div.innerHTML = `<p style="color:#ef4444;">Erreur: ${e.message}</p>`;
}
}
function initLearningCharts() {
// Corpus evolution chart
const corpusCtx = document.getElementById('corpusChart');
if (corpusCtx && !corpusChart) {
corpusChart = new Chart(corpusCtx, {
type: 'line',
data: {
labels: ['Sem -4', 'Sem -3', 'Sem -2', 'Sem -1', 'Cette sem'],
datasets: [{
label: 'Taille corpus (vecteurs)',
data: [100, 180, 290, 420, 512],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
plugins: { legend: { labels: { color: '#94a3b8' } } },
scales: {
x: { ticks: { color: '#64748b' }, grid: { color: '#334155' } },
y: { ticks: { color: '#64748b' }, grid: { color: '#334155' } }
}
}
});
}
// Healing performance chart
const healingCtx = document.getElementById('healingChart');
if (healingCtx && !healingChart) {
healingChart = new Chart(healingCtx, {
type: 'bar',
data: {
labels: ['Succès', 'Échec', 'Non tenté'],
datasets: [{
label: 'Self-healing',
data: [75, 15, 10],
backgroundColor: ['#22c55e', '#ef4444', '#64748b']
}]
},
options: {
responsive: true,
plugins: { legend: { labels: { color: '#94a3b8' } } },
scales: {
x: { ticks: { color: '#64748b' }, grid: { color: '#334155' } },
y: { ticks: { color: '#64748b' }, grid: { color: '#334155' } }
}
}
});
}
}
// Initialize // Initialize
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initOverviewChart(); initOverviewChart();
@@ -1688,6 +2524,12 @@
refreshWorkflows(); refreshWorkflows();
setInterval(refreshSystemStatus, 10000); setInterval(refreshSystemStatus, 10000);
setInterval(refreshServices, 5000); // Rafraîchir les services toutes les 5s setInterval(refreshServices, 5000); // Rafraîchir les services toutes les 5s
// Charger les stats des nouvelles sections (en arrière-plan)
setTimeout(() => {
refreshBackupStats();
refreshSystemInfo();
}, 1000);
}); });
// Add CSS animation for notifications // Add CSS animation for notifications