v1.8.2: admin overview — library health, rebuild BM25, clear cache, reingest missing endpoints

This commit is contained in:
2026-05-28 19:27:56 +00:00
parent d8d7b6f2da
commit f721ebaf4d
14 changed files with 456 additions and 0 deletions
+145
View File
@@ -370,6 +370,30 @@ a[role=button]:active {
</button>
</div>
<div id="stats-grid" class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4 mb-6"></div>
<!-- ── Library Health ─────────────────────────────────────────────── -->
<div class="glass rounded-2xl p-6 spotlight-card mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-primary text-xs font-bold uppercase tracking-widest flex items-center gap-2">
<span class="msym text-base">library_books</span> Library Health
</h3>
<div class="flex gap-2">
<button onclick="rebuildBM25()" id="btn-bm25" class="glass glass-hover px-3 py-1.5 rounded-xl text-xs font-bold flex items-center gap-1 text-secondary">
<span class="msym text-sm">refresh</span> Rebuild BM25
</button>
<button onclick="clearCache()" id="btn-cache" class="glass glass-hover px-3 py-1.5 rounded-xl text-xs font-bold flex items-center gap-1 text-yellow-400">
<span class="msym text-sm">delete_sweep</span> Clear Cache
</button>
<button onclick="reingestMissing()" id="btn-reingest" class="glass glass-hover px-3 py-1.5 rounded-xl text-xs font-bold flex items-center gap-1 text-red-400 hidden">
<span class="msym text-sm">sync</span> Re-ingest Missing
</button>
</div>
</div>
<div id="library-health-content">
<div class="text-on-surface-variant text-sm">Loading...</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<div class="glass rounded-2xl p-6 spotlight-card">
<h3 class="text-primary text-xs font-bold uppercase tracking-widest mb-4 flex items-center gap-2"><span class="msym text-base">bar_chart</span>Daily Queries</h3>
@@ -949,7 +973,128 @@ function badge(llm) {
}
// ── Page Loaders ──────────────────────────────────────────────────────────────
async function loadLibraryHealth() {
try {
const d = await api('/admin/library-health');
const el = document.getElementById('library-health-content');
const btnReingest = document.getElementById('btn-reingest');
// Show/hide reingest button
if (d.missing_chunks > 0) btnReingest.classList.remove('hidden');
else btnReingest.classList.add('hidden');
// Health summary pills
const health = d.missing_chunks === 0 ? 'text-emerald-400' : 'text-red-400';
const healthIcon = d.missing_chunks === 0 ? 'check_circle' : 'warning';
let html = `<div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div class="bg-surface-high rounded-xl p-3 text-center">
<div class="text-xl font-bold text-primary">${d.total_books}</div>
<div class="text-xs text-on-surface-variant mt-1">Total Books</div>
</div>
<div class="bg-surface-high rounded-xl p-3 text-center">
<div class="text-xl font-bold text-emerald-400">${d.indexed_books}</div>
<div class="text-xs text-on-surface-variant mt-1">Indexed</div>
</div>
<div class="bg-surface-high rounded-xl p-3 text-center">
<div class="text-xl font-bold ${health}">${d.missing_chunks}</div>
<div class="text-xs text-on-surface-variant mt-1">Missing Chunks</div>
</div>
<div class="bg-surface-high rounded-xl p-3 text-center">
<div class="text-xl font-bold text-blue-400">${d.vectors?.toLocaleString()}</div>
<div class="text-xs text-on-surface-variant mt-1">Vectors</div>
</div>
</div>`;
// Books with no chunks
if (d.no_chunks?.length > 0) {
html += `<div class="mb-4">
<div class="text-xs font-bold text-red-400 uppercase tracking-widest mb-2 flex items-center gap-1">
<span class="msym text-sm">warning</span> Books with no chunks (${d.no_chunks.length})
</div>
<div class="space-y-1 max-h-40 overflow-y-auto">
${d.no_chunks.map(b => `<div class="bg-red-900/20 border border-red-900/30 rounded-lg px-3 py-2 text-xs text-on-surface-variant">${b.title || b.filename}</div>`).join('')}
</div>
</div>`;
}
// Failed files
if (d.failed_files?.length > 0) {
html += `<div class="mb-4">
<div class="text-xs font-bold text-orange-400 uppercase tracking-widest mb-2 flex items-center gap-1">
<span class="msym text-sm">error</span> Failed Files (${d.failed_files.length})
</div>
<div class="space-y-1 max-h-40 overflow-y-auto">
${d.failed_files.map(f => `<div class="bg-orange-900/20 border border-orange-900/30 rounded-lg px-3 py-2 text-xs">
<span class="text-on-surface-variant">${f.filename}</span>
<span class="text-red-400 ml-2">${f.error || 'Unknown error'}</span>
</div>`).join('')}
</div>
</div>`;
}
// Pending jobs
if (d.pending_jobs?.length > 0) {
html += `<div>
<div class="text-xs font-bold text-yellow-400 uppercase tracking-widest mb-2 flex items-center gap-1">
<span class="msym text-sm">pending</span> Active Jobs (${d.pending_jobs.length})
</div>
<div class="space-y-1">
${d.pending_jobs.map(j => `<div class="bg-yellow-900/20 border border-yellow-900/30 rounded-lg px-3 py-2 text-xs flex justify-between">
<span class="text-on-surface-variant">${j.status} — ${j.done}/${j.total} done</span>
<span class="text-red-400">${j.errors > 0 ? j.errors + ' errors' : ''}</span>
</div>`).join('')}
</div>
</div>`;
}
if (d.missing_chunks === 0 && d.failed_files?.length === 0 && d.pending_jobs?.length === 0) {
html += `<div class="flex items-center gap-2 text-emerald-400 text-sm">
<span class="msym">check_circle</span> All ${d.total_books} books are fully indexed
</div>`;
}
el.innerHTML = html;
} catch(e) {
document.getElementById('library-health-content').innerHTML = `<div class="text-red-400 text-sm">Error loading health: ${e.message}</div>`;
}
}
async function rebuildBM25() {
const btn = document.getElementById('btn-bm25');
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm animate-spin">refresh</span> Rebuilding...';
try {
const r = await api('/admin/rebuild-bm25', {method:'POST'});
toast(`BM25 rebuilt — ${r.chunks_indexed} chunks indexed`, 'success');
} catch(e) { toast('BM25 rebuild failed: ' + e.message, 'error'); }
btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">refresh</span> Rebuild BM25';
}
async function clearCache() {
const btn = document.getElementById('btn-cache');
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm">delete_sweep</span> Clearing...';
try {
const r = await api('/admin/clear-cache', {method:'POST'});
toast(`Cache cleared — ${r.cleared} entries removed`, 'success');
loadOverview();
} catch(e) { toast('Cache clear failed: ' + e.message, 'error'); }
btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">delete_sweep</span> Clear Cache';
}
async function reingestMissing() {
const btn = document.getElementById('btn-reingest');
btn.disabled = true; btn.innerHTML = '<span class="msym text-sm">sync</span> Queuing...';
try {
const r = await api('/admin/reingest-missing', {method:'POST'});
toast(`${r.queued} books queued for re-ingestion`, 'success');
setTimeout(loadLibraryHealth, 2000);
} catch(e) { toast('Re-ingest failed: ' + e.message, 'error'); }
btn.disabled = false; btn.innerHTML = '<span class="msym text-sm">sync</span> Re-ingest Missing';
}
async function loadOverview() {
loadLibraryHealth();
try {
const d = await api('/admin/overview');
document.getElementById('stats-grid').innerHTML = [