Files
t2a/src/viewer/templates/admin_referentiels.html
dom c838d75174 feat: affichage des référentiels intégrés dans la page admin RAG
Ajout d'une section listant les 6 sources built-in (CIM-10, CCAM, Guide Métho,
dictionnaires) avec compteurs de chunks et statut. Séparation claire entre
référentiels intégrés et référentiels utilisateur uploadés.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 19:00:44 +01:00

267 lines
9.5 KiB
HTML

{% extends "base.html" %}
{% block title %}Référentiels RAG{% endblock %}
{% block sidebar %}
<div class="group-title">Admin</div>
<a href="/admin/referentiels" style="color:#60a5fa;font-weight:600;border-left-color:#3b82f6;">Référentiels RAG</a>
<a href="/">Retour aux dossiers</a>
{% endblock %}
{% block content %}
<h2>Référentiels RAG</h2>
<p style="font-size:0.85rem;color:#64748b;margin-bottom:1.5rem;">
Ajoutez des documents de référence (PDF, CSV, Excel, TXT) pour enrichir la base de connaissances du RAG.
</p>
<!-- Zone upload -->
<div class="card" style="margin-bottom:1.5rem;">
<h3>Ajouter un référentiel</h3>
<form id="upload-form" style="display:flex;gap:0.75rem;align-items:end;flex-wrap:wrap;margin-top:0.75rem;">
<div>
<label style="display:block;font-size:0.7rem;color:#64748b;text-transform:uppercase;letter-spacing:0.05em;font-weight:600;margin-bottom:0.25rem;">Fichier</label>
<input type="file" id="file-input" name="file" accept=".pdf,.csv,.xlsx,.xls,.txt"
style="font-size:0.85rem;padding:0.35rem;">
</div>
<button type="submit" id="upload-btn"
style="padding:0.5rem 1.25rem;border-radius:6px;border:none;background:#3b82f6;color:#fff;font-size:0.85rem;font-weight:600;cursor:pointer;">
Uploader
</button>
<span id="upload-status" style="font-size:0.8rem;"></span>
</form>
<p style="font-size:0.7rem;color:#94a3b8;margin-top:0.5rem;">
Extensions : .pdf, .csv, .xlsx, .xls, .txt — Max {{ max_size }} Mo
</p>
</div>
<!-- Référentiels intégrés (built-in) -->
<div class="card" style="margin-bottom:1.5rem;">
<h3>Référentiels intégrés</h3>
<p style="font-size:0.8rem;color:#64748b;margin-bottom:0.75rem;">
Sources intégrées automatiquement dans l'index FAISS au build.
</p>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Fichier</th>
<th>Type</th>
<th>Taille</th>
<th>Chunks</th>
<th>Statut</th>
</tr>
</thead>
<tbody>
{% for ref in builtin_refs %}
<tr>
<td style="font-weight:600;">{{ ref.name }}</td>
<td style="font-size:0.8rem;color:#64748b;"><code>{{ ref.filename }}</code></td>
<td><span class="badge" style="background:#f1f5f9;color:#334155;">{{ ref.extension }}</span></td>
<td>{{ "%.1f"|format(ref.size_mb) }} Mo</td>
<td>
{% if ref.chunks %}
<strong>{{ ref.chunks }}</strong>
{% else %}
<span style="color:#94a3b8;"></span>
{% endif %}
</td>
<td>
{% if not ref.exists %}
<span class="badge" style="background:#fee2e2;color:#dc2626;">Fichier absent</span>
{% elif ref.chunks %}
<span class="badge" style="background:#dcfce7;color:#16a34a;">Indexé</span>
{% else %}
<span class="badge" style="background:#f1f5f9;color:#64748b;">Dictionnaire</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Tableau référentiels utilisateur -->
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.75rem;">
<h3>Référentiels utilisateur</h3>
<button id="rebuild-btn"
style="padding:0.35rem 0.75rem;border-radius:6px;border:1px solid #e2e8f0;background:#fff;font-size:0.75rem;cursor:pointer;">
Rebuild complet
</button>
</div>
<table>
<thead>
<tr>
<th>Nom</th>
<th>Type</th>
<th>Taille</th>
<th>Date</th>
<th>Chunks</th>
<th>Statut</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="ref-table">
{% for ref in referentiels %}
<tr id="row-{{ ref.id }}">
<td>{{ ref.filename }}</td>
<td><span class="badge" style="background:#f1f5f9;color:#334155;">{{ ref.extension }}</span></td>
<td>{{ "%.1f"|format(ref.size_bytes / 1024 / 1024) }} Mo</td>
<td style="font-size:0.8rem;">{{ ref.date_added[:10] }}</td>
<td>{{ ref.chunks_count }}</td>
<td>
{% if ref.status == 'indexed' %}
<span class="badge" style="background:#dcfce7;color:#16a34a;">Indexé</span>
{% elif ref.status == 'empty' %}
<span class="badge" style="background:#fef9c3;color:#ca8a04;">Vide</span>
{% else %}
<span class="badge" style="background:#f1f5f9;color:#64748b;">Uploadé</span>
{% endif %}
</td>
<td>
<button onclick="indexRef('{{ ref.id }}')" class="action-btn"
style="padding:2px 8px;border-radius:4px;border:1px solid #3b82f6;background:#eff6ff;color:#2563eb;font-size:0.75rem;cursor:pointer;margin-right:4px;">
Indexer
</button>
<button onclick="deleteRef('{{ ref.id }}')" class="action-btn"
style="padding:2px 8px;border-radius:4px;border:1px solid #fca5a5;background:#fef2f2;color:#dc2626;font-size:0.75rem;cursor:pointer;">
Supprimer
</button>
</td>
</tr>
{% endfor %}
{% if not referentiels %}
<tr id="empty-row">
<td colspan="7" style="text-align:center;color:#94a3b8;padding:2rem;">Aucun référentiel</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
<div id="global-status" style="margin-top:1rem;font-size:0.8rem;"></div>
{% endblock %}
{% block scripts %}
<script>
(function() {
const uploadForm = document.getElementById('upload-form');
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const uploadStatus = document.getElementById('upload-status');
const globalStatus = document.getElementById('global-status');
const rebuildBtn = document.getElementById('rebuild-btn');
uploadForm.addEventListener('submit', function(e) {
e.preventDefault();
const file = fileInput.files[0];
if (!file) { uploadStatus.textContent = 'Sélectionnez un fichier'; return; }
const fd = new FormData();
fd.append('file', file);
uploadBtn.disabled = true;
uploadBtn.innerHTML = '<span class="spinner"></span>';
uploadStatus.textContent = '';
fetch('/admin/referentiels/upload', { method: 'POST', body: fd })
.then(r => r.json())
.then(d => {
uploadBtn.disabled = false;
uploadBtn.textContent = 'Uploader';
if (d.ok) {
uploadStatus.style.color = '#16a34a';
uploadStatus.textContent = 'Uploadé';
setTimeout(() => location.reload(), 800);
} else {
uploadStatus.style.color = '#dc2626';
uploadStatus.textContent = d.error || 'Erreur';
}
})
.catch(() => {
uploadBtn.disabled = false;
uploadBtn.textContent = 'Uploader';
uploadStatus.style.color = '#dc2626';
uploadStatus.textContent = 'Erreur réseau';
});
});
window.indexRef = function(id) {
const btn = event.target;
btn.disabled = true;
btn.innerHTML = '<span class="spinner" style="border-color:rgba(37,99,235,0.3);border-top-color:#2563eb;width:10px;height:10px;"></span>';
fetch('/admin/referentiels/' + id + '/index', { method: 'POST' })
.then(r => r.json())
.then(d => {
if (d.ok) {
globalStatus.style.color = '#16a34a';
globalStatus.textContent = d.chunks + ' chunks indexés';
setTimeout(() => location.reload(), 800);
} else {
btn.disabled = false;
btn.textContent = 'Indexer';
globalStatus.style.color = '#dc2626';
globalStatus.textContent = d.error || 'Erreur';
}
})
.catch(() => {
btn.disabled = false;
btn.textContent = 'Indexer';
globalStatus.style.color = '#dc2626';
globalStatus.textContent = 'Erreur réseau';
});
};
window.deleteRef = function(id) {
if (!confirm('Supprimer ce référentiel ?')) return;
fetch('/admin/referentiels/' + id, { method: 'DELETE' })
.then(r => r.json())
.then(d => {
if (d.ok) {
const row = document.getElementById('row-' + id);
if (row) row.remove();
globalStatus.style.color = '#16a34a';
globalStatus.textContent = 'Supprimé';
} else {
globalStatus.style.color = '#dc2626';
globalStatus.textContent = d.error || 'Erreur';
}
})
.catch(() => {
globalStatus.style.color = '#dc2626';
globalStatus.textContent = 'Erreur réseau';
});
};
rebuildBtn.addEventListener('click', function() {
if (!confirm('Reconstruire l\'index FAISS complet ? Cela peut prendre plusieurs minutes.')) return;
rebuildBtn.disabled = true;
rebuildBtn.innerHTML = '<span class="spinner" style="border-color:rgba(0,0,0,0.2);border-top-color:#333;width:10px;height:10px;"></span> Rebuild…';
fetch('/admin/referentiels/rebuild-index', { method: 'POST' })
.then(r => r.json())
.then(d => {
rebuildBtn.disabled = false;
rebuildBtn.textContent = 'Rebuild complet';
if (d.ok) {
globalStatus.style.color = '#16a34a';
globalStatus.textContent = 'Index reconstruit (' + d.reindexed + ' référentiels réindexés)';
} else {
globalStatus.style.color = '#dc2626';
globalStatus.textContent = d.error || 'Erreur';
}
})
.catch(() => {
rebuildBtn.disabled = false;
rebuildBtn.textContent = 'Rebuild complet';
globalStatus.style.color = '#dc2626';
globalStatus.textContent = 'Erreur réseau';
});
});
})();
</script>
{% endblock %}