feat(dashboard): page Base de connaissances — métriques FAISS, sessions, patterns
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 12s
security-audit / pip-audit (CVE dépendances) (push) Successful in 11s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 12s
security-audit / pip-audit (CVE dépendances) (push) Successful in 11s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
Nouvelle page /knowledge-base avec : - Mémoire visuelle : 331 vecteurs FAISS / 13666 embeddings (alerte consolidation) - Sessions observées : 56 sessions, 6.66 Go, 3 machines - Réflexes natifs : 16 patterns UI en 6 catégories - Workflows appris : 29 Onglet 📚 Connaissances ajouté dans toute la navigation. Tout en français, dark theme, zéro jargon. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2445,6 +2445,170 @@ def proxy_audit(endpoint):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Base de connaissances — État mémoire et apprentissages
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@app.route('/knowledge-base')
|
||||||
|
def knowledge_base_page():
|
||||||
|
"""Page Base de connaissances."""
|
||||||
|
return render_template('knowledge_base.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/knowledge-base/stats')
|
||||||
|
def knowledge_base_stats():
|
||||||
|
"""Retourne toutes les métriques de la base de connaissances en JSON."""
|
||||||
|
import glob as glob_module
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'faiss': _kb_faiss_stats(),
|
||||||
|
'sessions': _kb_sessions_stats(),
|
||||||
|
'patterns': _kb_patterns_stats(),
|
||||||
|
'workflows': _kb_workflows_stats(),
|
||||||
|
}
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
def _kb_faiss_stats() -> dict:
|
||||||
|
"""Statistiques de l'index FAISS."""
|
||||||
|
faiss_index_path = DATA_PATH / "faiss_index" / "main.index"
|
||||||
|
embeddings_dir = LIVE_SESSIONS_PATH / "embeddings"
|
||||||
|
|
||||||
|
vectors_indexed = 0
|
||||||
|
index_size_mb = "0 Mo"
|
||||||
|
available = False
|
||||||
|
|
||||||
|
if faiss_index_path.exists():
|
||||||
|
# Taille fichier
|
||||||
|
size_bytes = faiss_index_path.stat().st_size
|
||||||
|
index_size_mb = f"{size_bytes / (1024 * 1024):.1f} Mo"
|
||||||
|
|
||||||
|
# Nombre de vecteurs via faiss
|
||||||
|
try:
|
||||||
|
import faiss
|
||||||
|
index = faiss.read_index(str(faiss_index_path))
|
||||||
|
vectors_indexed = index.ntotal
|
||||||
|
available = True
|
||||||
|
except ImportError:
|
||||||
|
# FAISS non installé — lire le metadata si dispo
|
||||||
|
metadata_path = DATA_PATH / "faiss_index" / "main.metadata"
|
||||||
|
if metadata_path.exists():
|
||||||
|
try:
|
||||||
|
meta = json.loads(metadata_path.read_text())
|
||||||
|
vectors_indexed = meta.get('ntotal', 0)
|
||||||
|
available = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
available = False
|
||||||
|
|
||||||
|
# Compter les embeddings (.npy)
|
||||||
|
embeddings_computed = 0
|
||||||
|
if embeddings_dir.exists():
|
||||||
|
embeddings_computed = len(list(embeddings_dir.glob("*.npy")))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'vectors_indexed': vectors_indexed,
|
||||||
|
'embeddings_computed': embeddings_computed,
|
||||||
|
'index_size_mb': index_size_mb,
|
||||||
|
'available': available,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _kb_sessions_stats() -> dict:
|
||||||
|
"""Statistiques des sessions shadow."""
|
||||||
|
machines = []
|
||||||
|
total_sessions = 0
|
||||||
|
total_bytes = 0
|
||||||
|
|
||||||
|
if LIVE_SESSIONS_PATH.exists():
|
||||||
|
for d in sorted(LIVE_SESSIONS_PATH.iterdir()):
|
||||||
|
if not d.is_dir():
|
||||||
|
continue
|
||||||
|
# Ignorer le dossier embeddings
|
||||||
|
if d.name == 'embeddings':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Dossiers machines (contiennent des sess_*)
|
||||||
|
if d.name.startswith('sess_'):
|
||||||
|
# Session orpheline à la racine
|
||||||
|
total_sessions += 1
|
||||||
|
total_bytes += _dir_size(d)
|
||||||
|
else:
|
||||||
|
# Dossier machine
|
||||||
|
sess_dirs = [s for s in d.iterdir() if s.is_dir() and s.name.startswith('sess_')]
|
||||||
|
count = len(sess_dirs)
|
||||||
|
total_sessions += count
|
||||||
|
|
||||||
|
# Dernière activité
|
||||||
|
last_activity = None
|
||||||
|
if sess_dirs:
|
||||||
|
latest = max(sess_dirs, key=lambda s: s.stat().st_mtime)
|
||||||
|
last_activity = datetime.fromtimestamp(latest.stat().st_mtime).strftime('%Y-%m-%d %H:%M')
|
||||||
|
|
||||||
|
machine_bytes = _dir_size(d)
|
||||||
|
total_bytes += machine_bytes
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
|
machines.append({
|
||||||
|
'machine_id': d.name,
|
||||||
|
'session_count': count,
|
||||||
|
'last_activity': last_activity,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Volume total
|
||||||
|
if total_bytes >= 1024 * 1024 * 1024:
|
||||||
|
total_volume = f"{total_bytes / (1024**3):.2f} Go"
|
||||||
|
elif total_bytes >= 1024 * 1024:
|
||||||
|
total_volume = f"{total_bytes / (1024**2):.1f} Mo"
|
||||||
|
else:
|
||||||
|
total_volume = f"{total_bytes / 1024:.0f} Ko"
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_sessions': total_sessions,
|
||||||
|
'total_volume': total_volume,
|
||||||
|
'machines': machines,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _kb_patterns_stats() -> dict:
|
||||||
|
"""Statistiques des patterns UI natifs."""
|
||||||
|
try:
|
||||||
|
from core.knowledge.ui_patterns import UIPatternLibrary
|
||||||
|
lib = UIPatternLibrary()
|
||||||
|
stats = lib.stats
|
||||||
|
return {
|
||||||
|
'total': stats.get('total', 0),
|
||||||
|
'by_category': stats.get('by_category', {}),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
return {'total': 0, 'by_category': {}}
|
||||||
|
|
||||||
|
|
||||||
|
def _kb_workflows_stats() -> dict:
|
||||||
|
"""Statistiques des workflows appris."""
|
||||||
|
total = 0
|
||||||
|
workflows_path = DATA_PATH / "workflows"
|
||||||
|
if workflows_path.exists():
|
||||||
|
# Compter récursivement les .json
|
||||||
|
total = len(list(workflows_path.rglob("*.json")))
|
||||||
|
return {'total': total}
|
||||||
|
|
||||||
|
|
||||||
|
def _dir_size(path: Path) -> int:
|
||||||
|
"""Calcule la taille totale d'un dossier (non récursif profond pour la perf)."""
|
||||||
|
total = 0
|
||||||
|
try:
|
||||||
|
for f in path.rglob('*'):
|
||||||
|
if f.is_file():
|
||||||
|
total += f.stat().st_size
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
pass
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Main
|
# Main
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -175,6 +175,7 @@
|
|||||||
<a href="/">🎛️ Dashboard</a>
|
<a href="/">🎛️ Dashboard</a>
|
||||||
<a href="/audit" class="active">⚖️ Audit</a>
|
<a href="/audit" class="active">⚖️ Audit</a>
|
||||||
<a href="/process-mining">🗺️ Cartographie</a>
|
<a href="/process-mining">🗺️ Cartographie</a>
|
||||||
|
<a href="/knowledge-base">📚 Connaissances</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
<div class="tab" onclick="switchTab('config')">🔧 Configuration</div>
|
<div class="tab" onclick="switchTab('config')">🔧 Configuration</div>
|
||||||
<div class="tab" onclick="switchTab('cleaner')">🧹 Nettoyage</div>
|
<div class="tab" onclick="switchTab('cleaner')">🧹 Nettoyage</div>
|
||||||
<a class="tab" href="/process-mining" style="text-decoration:none;color:#94a3b8;">🗺️ Cartographie</a>
|
<a class="tab" href="/process-mining" style="text-decoration:none;color:#94a3b8;">🗺️ Cartographie</a>
|
||||||
|
<a class="tab" href="/knowledge-base" style="text-decoration:none;color:#94a3b8;">📚 Connaissances</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
294
web_dashboard/templates/knowledge_base.html
Normal file
294
web_dashboard/templates/knowledge_base.html
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>RPA Vision V3 - Base de connaissances</title>
|
||||||
|
<style>
|
||||||
|
/* === Reset & base — identique au dashboard === */
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; }
|
||||||
|
|
||||||
|
/* === Header === */
|
||||||
|
.header { background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); color: white; padding: 20px 30px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px; }
|
||||||
|
.header h1 { font-size: 24px; display: flex; align-items: center; gap: 10px; }
|
||||||
|
.header-subtitle { color: rgba(255,255,255,0.75); font-size: 13px; margin-top: 4px; }
|
||||||
|
.header-nav { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.header-nav a {
|
||||||
|
color: rgba(255,255,255,0.8); text-decoration: none; font-size: 13px;
|
||||||
|
padding: 6px 14px; border-radius: 6px; transition: all 0.2s;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
.header-nav a:hover { background: rgba(255,255,255,0.2); }
|
||||||
|
.header-nav a.active { background: rgba(255,255,255,0.25); color: #fff; font-weight: 600; }
|
||||||
|
|
||||||
|
/* === Layout === */
|
||||||
|
.container { max-width: 1600px; margin: 0 auto; padding: 20px; }
|
||||||
|
|
||||||
|
/* === Cards === */
|
||||||
|
.card { background: #1e293b; border-radius: 12px; padding: 20px; border: 1px solid #334155; margin-bottom: 20px; }
|
||||||
|
.card h2 { font-size: 16px; margin-bottom: 15px; color: #94a3b8; display: flex; align-items: center; gap: 8px; }
|
||||||
|
|
||||||
|
/* === Grille indicateurs === */
|
||||||
|
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 20px; }
|
||||||
|
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 20px; }
|
||||||
|
@media (max-width: 900px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
@media (max-width: 500px) { .grid-3, .grid-4 { grid-template-columns: 1fr; } }
|
||||||
|
.stat-card { text-align: center; }
|
||||||
|
.stat-value { font-size: 36px; font-weight: bold; color: #3b82f6; }
|
||||||
|
.stat-value.warning { color: #f59e0b; }
|
||||||
|
.stat-value.success { color: #22c55e; }
|
||||||
|
.stat-label { font-size: 12px; color: #64748b; margin-top: 5px; text-transform: uppercase; }
|
||||||
|
|
||||||
|
/* === Section title === */
|
||||||
|
.section-title {
|
||||||
|
font-size: 18px; font-weight: 600; color: #e2e8f0;
|
||||||
|
margin-bottom: 15px; display: flex; align-items: center; gap: 10px;
|
||||||
|
padding-bottom: 10px; border-bottom: 1px solid #334155;
|
||||||
|
}
|
||||||
|
.section-title .icon { font-size: 22px; }
|
||||||
|
|
||||||
|
/* === Alertes === */
|
||||||
|
.alert {
|
||||||
|
padding: 12px 16px; border-radius: 8px; font-size: 13px;
|
||||||
|
margin-bottom: 15px; display: flex; align-items: center; gap: 10px;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
background: #78350f; color: #fcd34d; border: 1px solid #92400e;
|
||||||
|
}
|
||||||
|
.alert-info {
|
||||||
|
background: #1e3a5f; color: #93c5fd; border: 1px solid #1e40af;
|
||||||
|
}
|
||||||
|
.alert-success {
|
||||||
|
background: #064e3b; color: #6ee7b7; border: 1px solid #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Tableau === */
|
||||||
|
.table-wrapper { overflow-x: auto; margin-top: 15px; }
|
||||||
|
.kb-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
.kb-table thead th {
|
||||||
|
background: #334155; color: #94a3b8; padding: 12px 10px;
|
||||||
|
text-align: left; font-weight: 600; font-size: 11px;
|
||||||
|
text-transform: uppercase; letter-spacing: 0.5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.kb-table tbody tr { border-bottom: 1px solid #1e293b; transition: background 0.15s; }
|
||||||
|
.kb-table tbody tr:hover { background: #334155; }
|
||||||
|
.kb-table td { padding: 10px; vertical-align: middle; }
|
||||||
|
|
||||||
|
/* === Badges catégories === */
|
||||||
|
.category-list { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 10px; }
|
||||||
|
.category-item {
|
||||||
|
display: inline-flex; align-items: center; gap: 8px;
|
||||||
|
padding: 8px 14px; background: #0f172a; border-radius: 8px;
|
||||||
|
border: 1px solid #334155; font-size: 13px;
|
||||||
|
}
|
||||||
|
.category-count {
|
||||||
|
background: #3b82f6; color: white; border-radius: 50%;
|
||||||
|
width: 24px; height: 24px; display: flex; align-items: center;
|
||||||
|
justify-content: center; font-size: 11px; font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Loading === */
|
||||||
|
.loading {
|
||||||
|
text-align: center; padding: 40px; color: #64748b; font-size: 14px;
|
||||||
|
}
|
||||||
|
.loading::after {
|
||||||
|
content: ''; display: inline-block; width: 20px; height: 20px;
|
||||||
|
border: 2px solid #334155; border-top-color: #3b82f6;
|
||||||
|
border-radius: 50%; animation: spin 0.8s linear infinite;
|
||||||
|
margin-left: 10px; vertical-align: middle;
|
||||||
|
}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="header">
|
||||||
|
<div>
|
||||||
|
<h1>📚 Base de connaissances</h1>
|
||||||
|
<div class="header-subtitle">État de la mémoire et des apprentissages de Léa</div>
|
||||||
|
</div>
|
||||||
|
<nav class="header-nav">
|
||||||
|
<a href="/">🎛️ Dashboard</a>
|
||||||
|
<a href="/audit">⚖️ Audit</a>
|
||||||
|
<a href="/process-mining">🗺️ Cartographie</a>
|
||||||
|
<a href="/knowledge-base" class="active">📚 Connaissances</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Section 1 : Mémoire visuelle (FAISS) -->
|
||||||
|
<div class="section-title"><span class="icon">🧠</span> Mémoire visuelle</div>
|
||||||
|
|
||||||
|
<div class="grid-3" id="faissGrid">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="faissVectors">--</div>
|
||||||
|
<div class="stat-label">Vecteurs indexés</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="faissEmbeddings">--</div>
|
||||||
|
<div class="stat-label">Embeddings calculés</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="faissSize">--</div>
|
||||||
|
<div class="stat-label">Taille de l'index</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="faissAlert" style="display:none;"></div>
|
||||||
|
|
||||||
|
<!-- Section 2 : Sessions observées -->
|
||||||
|
<div class="section-title"><span class="icon">👁️</span> Sessions observées</div>
|
||||||
|
|
||||||
|
<div class="grid-3" id="sessionsGrid">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="sessionCount">--</div>
|
||||||
|
<div class="stat-label">Sessions totales</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="sessionVolume">--</div>
|
||||||
|
<div class="stat-label">Volume de données</div>
|
||||||
|
</div>
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="machineCount">--</div>
|
||||||
|
<div class="stat-label">Machines distinctes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" id="machinesCard">
|
||||||
|
<h2><span class="icon">🖥️</span> Répartition par machine</h2>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table class="kb-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Machine</th>
|
||||||
|
<th>Sessions</th>
|
||||||
|
<th>Dernière activité</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="machinesTableBody">
|
||||||
|
<tr><td colspan="3" class="loading">Chargement...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Section 3 : Réflexes natifs (patterns UI) -->
|
||||||
|
<div class="section-title"><span class="icon">⚡</span> Réflexes natifs</div>
|
||||||
|
|
||||||
|
<div class="grid-3" id="patternsGrid">
|
||||||
|
<div class="card stat-card" style="grid-column: span 1;">
|
||||||
|
<div class="stat-value success" id="patternTotal">--</div>
|
||||||
|
<div class="stat-label">Patterns connus</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" id="patternsCard">
|
||||||
|
<h2><span class="icon">🎯</span> Par catégorie</h2>
|
||||||
|
<ul class="category-list" id="categoryList">
|
||||||
|
<li class="loading">Chargement...</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Section 4 : Workflows -->
|
||||||
|
<div class="section-title"><span class="icon">🔄</span> Workflows</div>
|
||||||
|
|
||||||
|
<div class="grid-3" id="workflowsGrid">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="stat-value" id="workflowCount">--</div>
|
||||||
|
<div class="stat-label">Workflows appris</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', loadKnowledgeBase);
|
||||||
|
|
||||||
|
async function loadKnowledgeBase() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/api/knowledge-base/stats');
|
||||||
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
|
const data = await resp.json();
|
||||||
|
renderFaiss(data.faiss);
|
||||||
|
renderSessions(data.sessions);
|
||||||
|
renderPatterns(data.patterns);
|
||||||
|
renderWorkflows(data.workflows);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erreur chargement base de connaissances:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFaiss(faiss) {
|
||||||
|
document.getElementById('faissVectors').textContent = faiss.vectors_indexed.toLocaleString('fr-FR');
|
||||||
|
document.getElementById('faissEmbeddings').textContent = faiss.embeddings_computed.toLocaleString('fr-FR');
|
||||||
|
document.getElementById('faissSize').textContent = faiss.index_size_mb;
|
||||||
|
|
||||||
|
// Alerte si embeddings non consolidés
|
||||||
|
const diff = faiss.embeddings_computed - faiss.vectors_indexed;
|
||||||
|
const alertEl = document.getElementById('faissAlert');
|
||||||
|
if (diff > 10) {
|
||||||
|
alertEl.style.display = 'block';
|
||||||
|
alertEl.innerHTML = `<div class="alert alert-warning">⚠️ ${diff.toLocaleString('fr-FR')} embeddings non consolidés dans l'index FAISS</div>`;
|
||||||
|
} else if (faiss.vectors_indexed === 0 && !faiss.available) {
|
||||||
|
alertEl.style.display = 'block';
|
||||||
|
alertEl.innerHTML = `<div class="alert alert-info">ℹ️ FAISS non disponible sur ce système</div>`;
|
||||||
|
} else {
|
||||||
|
alertEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSessions(sessions) {
|
||||||
|
document.getElementById('sessionCount').textContent = sessions.total_sessions.toLocaleString('fr-FR');
|
||||||
|
document.getElementById('sessionVolume').textContent = sessions.total_volume;
|
||||||
|
document.getElementById('machineCount').textContent = sessions.machines.length.toLocaleString('fr-FR');
|
||||||
|
|
||||||
|
const tbody = document.getElementById('machinesTableBody');
|
||||||
|
if (sessions.machines.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="3" style="color:#64748b;text-align:center;padding:20px;">Aucune session enregistrée</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tbody.innerHTML = sessions.machines.map(m => `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${escapeHtml(m.machine_id)}</strong></td>
|
||||||
|
<td>${m.session_count}</td>
|
||||||
|
<td style="color:#94a3b8;font-size:12px;">${m.last_activity || '—'}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPatterns(patterns) {
|
||||||
|
document.getElementById('patternTotal').textContent = patterns.total.toLocaleString('fr-FR');
|
||||||
|
|
||||||
|
const list = document.getElementById('categoryList');
|
||||||
|
const cats = patterns.by_category;
|
||||||
|
if (!cats || Object.keys(cats).length === 0) {
|
||||||
|
list.innerHTML = '<li style="color:#64748b;">Aucun pattern chargé</li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.innerHTML = Object.entries(cats)
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.map(([cat, count]) => `
|
||||||
|
<li class="category-item">
|
||||||
|
<span class="category-count">${count}</span>
|
||||||
|
<span>${escapeHtml(cat)}</span>
|
||||||
|
</li>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWorkflows(workflows) {
|
||||||
|
document.getElementById('workflowCount').textContent = workflows.total.toLocaleString('fr-FR');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.appendChild(document.createTextNode(text));
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -148,6 +148,7 @@
|
|||||||
<a href="/">🎛️ Dashboard</a>
|
<a href="/">🎛️ Dashboard</a>
|
||||||
<a href="/audit">⚖️ Audit</a>
|
<a href="/audit">⚖️ Audit</a>
|
||||||
<a href="/process-mining" class="active">🗺️ Cartographie</a>
|
<a href="/process-mining" class="active">🗺️ Cartographie</a>
|
||||||
|
<a href="/knowledge-base">📚 Connaissances</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user