Add config-vs-reality health checks, loaded models, and log evidence
The failures this subsystem actually has are configuration drift, so each check names the setting to change rather than reporting that retrieval failed. Notably it catches a conf model tag that is no longer installed, and an index built by a different embedder than the one configured — vectors from two models are not comparable, and that failure returns confident nonsense rather than erroring. Diagnostic questions also get recent log warnings, attached only then because they cost budget the passages need.
This commit is contained in:
@@ -104,6 +104,23 @@
|
||||
.vv-ai-src-s { color:#333; font-family:monospace; margin-left:auto; flex-shrink:0; }
|
||||
.vv-ai-meta { font-size:10px; color:#333; margin-top:6px; font-family:monospace; }
|
||||
|
||||
/* ── Health + loaded models ─────────────────────────────────────────────── */
|
||||
.vv-ai-diag { display:grid; grid-template-columns:1fr 1fr; gap:1px; background:#1a1a1a;
|
||||
border:1px solid #262626; border-radius:6px; overflow:hidden; }
|
||||
@media (max-width:900px) { .vv-ai-diag { grid-template-columns:1fr; } }
|
||||
.vv-ai-diag-col { background:#0e0e0e; padding:9px 12px; }
|
||||
.vv-ai-diag-h { font-size:9px; letter-spacing:.08em; text-transform:uppercase; color:#4a4a4a;
|
||||
margin-bottom:6px; display:flex; align-items:center; gap:7px; }
|
||||
.vv-ai-chk { display:flex; align-items:flex-start; gap:7px; font-size:11px; padding:2px 0; line-height:1.5; }
|
||||
.vv-ai-chk-i { flex-shrink:0; font-size:11px; width:12px; }
|
||||
.vv-ai-chk-l { color:#8a8a8a; flex-shrink:0; }
|
||||
.vv-ai-chk-d { color:#5a5a5a; }
|
||||
.vv-ai-chk-f { color:#8a6a3a; display:block; font-size:10px; margin-top:1px; }
|
||||
.vv-ai-mdl { display:flex; align-items:center; gap:7px; font-size:11px; padding:2px 0; }
|
||||
.vv-ai-mdl-n { color:#8a8a8a; font-family:monospace; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.vv-ai-mdl-m { color:#444; font-family:monospace; margin-left:auto; flex-shrink:0; font-size:10px; }
|
||||
.vv-ai-none { font-size:11px; color:#3a3a3a; font-style:italic; }
|
||||
|
||||
.vv-ai-pending { font-size:12px; color:#5a5a5a; display:flex; align-items:center; gap:8px; }
|
||||
.vv-ai-dot { width:6px; height:6px; border-radius:50%; background:#6fcf97; animation:vvAiPulse 1.1s infinite; }
|
||||
@keyframes vvAiPulse { 0%,100%{opacity:.25;} 50%{opacity:1;} }
|
||||
@@ -141,6 +158,17 @@
|
||||
|
||||
<div class="vv-ai-banner" id="vv-ai-banner"></div>
|
||||
|
||||
<div class="vv-ai-diag">
|
||||
<div class="vv-ai-diag-col">
|
||||
<div class="vv-ai-diag-h"><span id="vv-ai-health-sum"></span> System checks</div>
|
||||
<div id="vv-ai-health"></div>
|
||||
</div>
|
||||
<div class="vv-ai-diag-col">
|
||||
<div class="vv-ai-diag-h">Loaded models</div>
|
||||
<div id="vv-ai-models"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vv-ai-chat" id="vv-ai-chat">
|
||||
<div class="vv-ai-empty">
|
||||
Ask Varaverk about itself.<br>
|
||||
@@ -239,6 +267,51 @@
|
||||
rt.gpu.name + ' · ' + rt.gpu.util + '% util');
|
||||
}
|
||||
$('vv-ai-banner').innerHTML = html;
|
||||
renderHealth(s.health || []);
|
||||
renderModels(s.loaded, s.model);
|
||||
}
|
||||
|
||||
// ✓ / ! / ✗ per check. Remedy shown inline on anything not ok — the point is to name the
|
||||
// setting that is wrong, not to report that something failed.
|
||||
const ICON = { ok: ['✓','vv-ai-ok'], warn: ['!','vv-ai-warn'], bad: ['✗','vv-ai-bad'] };
|
||||
function renderHealth(checks) {
|
||||
if (!checks.length) { $('vv-ai-health').innerHTML = '<div class="vv-ai-none">no checks</div>'; return; }
|
||||
let bad = 0, warn = 0, html = '';
|
||||
checks.forEach(c => {
|
||||
if (c.state === 'bad') bad++; else if (c.state === 'warn') warn++;
|
||||
const [ic, cl] = ICON[c.state] || ICON.warn;
|
||||
html += `<div class="vv-ai-chk"><span class="vv-ai-chk-i ${cl}">${ic}</span>`
|
||||
+ `<span><span class="vv-ai-chk-l">${esc(c.label)}</span> `
|
||||
+ `<span class="vv-ai-chk-d">— ${esc(c.detail)}</span>`
|
||||
+ (c.fix ? `<span class="vv-ai-chk-f">→ ${esc(c.fix)}</span>` : '')
|
||||
+ `</span></div>`;
|
||||
});
|
||||
$('vv-ai-health').innerHTML = html;
|
||||
const sum = $('vv-ai-health-sum');
|
||||
if (bad) sum.innerHTML = `<span class="vv-ai-bad">✗ ${bad} problem${bad>1?'s':''}</span>`;
|
||||
else if (warn) sum.innerHTML = `<span class="vv-ai-warn">! ${warn} warning${warn>1?'s':''}</span>`;
|
||||
else sum.innerHTML = `<span class="vv-ai-ok">✓ all good</span>`;
|
||||
}
|
||||
|
||||
function renderModels(loaded, active) {
|
||||
const box = $('vv-ai-models');
|
||||
if (loaded === null || loaded === undefined) {
|
||||
box.innerHTML = '<div class="vv-ai-none">Ollama unreachable</div>'; return;
|
||||
}
|
||||
if (!loaded.length) {
|
||||
box.innerHTML = '<div class="vv-ai-none">none resident — loads on first use</div>'; return;
|
||||
}
|
||||
let html = '';
|
||||
loaded.forEach(m => {
|
||||
const full = m.offload === 100;
|
||||
const [ic, cl] = full ? ICON.ok : ICON.warn;
|
||||
html += `<div class="vv-ai-mdl"><span class="vv-ai-chk-i ${cl}">${ic}</span>`
|
||||
+ `<span class="vv-ai-mdl-n"${m.name===active?' style="color:#a8c8a8"':''}>`
|
||||
+ esc(m.name.replace(/^hf\.co\/[^/]+\//,'')) + `</span>`
|
||||
+ `<span class="vv-ai-mdl-m">${m.offload===null?'—':m.offload+'% GPU'}`
|
||||
+ `${m.context?' · '+m.context.toLocaleString()+' ctx':''} · ${(m.vram/1073741824).toFixed(1)}GB</span></div>`;
|
||||
});
|
||||
box.innerHTML = html;
|
||||
}
|
||||
function loadBanner() {
|
||||
fetch(API + '?action=stats').then(r => r.json())
|
||||
|
||||
Reference in New Issue
Block a user