Report the owner's model and index on a mirror's Monitor card

A mirror was probing an Ollama it does not have and an index it never builds,
so the card read "unreachable / not built" about the wrong machine while the
model it actually uses was up. The card now names whose figures it is showing.
This commit is contained in:
Gmer4Lfe
2026-08-21 07:59:50 -04:00
parent 111c70d6b4
commit 9c15df8e22
2 changed files with 76 additions and 4 deletions
+61
View File
@@ -547,7 +547,64 @@ function vv_ai_recent_logs(int $max = 40): array {
return $out; return $out;
} }
// The figures every AI surface draws: the banner, the Scheduler dock and the Monitor card.
//
// On a mirror these describe the owner, because on a mirror there is nothing local to describe.
// Collected locally it probed an Ollama that is not installed and an index that is never built,
// and the Monitor card said "Ollama unreachable / Index not built" — both true of that box and
// both the wrong question, since the model it actually uses was up the whole time.
//
// Routed here rather than in the cache writer so that every consumer follows. api/ai.php already
// sends the stats *action* to the owner, so the tab was right while the card was wrong; this is
// what makes the two agree.
function vv_ai_stats(): array { function vv_ai_stats(): array {
return vv_ai_is_owner() ? vv_ai_stats_local() : vv_ai_stats_shared();
}
// What the owner reports about itself, fetched once and cached by the caller. A transport failure
// is reported as a failed health check rather than as absent hardware: "cannot reach the owner"
// and "the owner has no model" are different faults with different fixes, and the local-collection
// version could only ever express the second one.
function vv_ai_stats_shared(): array {
require_once __DIR__ . '/ai_rpc.php';
$status = 200;
$owner = vv_ai_owner_host();
$name = trim((string)(vv_conf_vars()[strtoupper($owner)] ?? $owner));
$r = vv_ai_rpc('stats', [], false, $status);
if (($r['ok'] ?? false) && is_array($r['stats'] ?? null)) {
$stats = $r['stats'];
$stats['served_by'] = $name; // so a surface can say whose numbers these are
return $stats;
}
$why = (string)($r['error'] ?? 'no response from the AI owner');
return [
'enabled' => vv_ai_enabled(),
'model' => '',
'embed_model' => '',
'url' => '',
'k' => 0,
'served_by' => $name,
// Shapes kept identical to the local collection. The Monitor card reads runtime.reachable
// and index.exists directly, and a missing key there renders as a broken card rather than
// as the outage it is.
'index' => ['exists' => false, 'chunks' => 0, 'stale' => false],
'runtime' => ['reachable' => false, 'loaded' => false, 'offload_pct' => null],
'loaded' => null,
'health' => [[
'id' => 'owner',
'label' => 'AI owner',
'state' => 'bad',
'detail' => $why,
'fix' => "Check that $name is up and that this node's SSH key still reaches it",
]],
'ts' => time(),
];
}
function vv_ai_stats_local(): array {
$cfg = vv_ai_config(); $cfg = vv_ai_config();
return [ return [
'enabled' => $cfg['enabled'], 'enabled' => $cfg['enabled'],
@@ -596,6 +653,10 @@ function vv_ai_stats_cached(bool $live = false): array {
function vv_ai_monitor_block(array $stats): array { function vv_ai_monitor_block(array $stats): array {
return [ return [
'model' => $stats['model'] ?? '', 'model' => $stats['model'] ?? '',
// Empty on the owner, the owner's hostname on a mirror. The card draws a model and an
// index that live on another machine, and a dashboard that shows one node's figures under
// another node's heading is the kind of quiet wrongness this project keeps finding.
'served_by' => $stats['served_by'] ?? '',
'runtime' => $stats['runtime'] ?? [], 'runtime' => $stats['runtime'] ?? [],
'index' => $stats['index'] ?? [], 'index' => $stats['index'] ?? [],
]; ];
+15 -4
View File
@@ -31,7 +31,9 @@
// No GPU, no UPS, no VMs — the corresponding card is absent rather than showing zeros // No GPU, no UPS, no VMs — the corresponding card is absent rather than showing zeros
// or an error. The page is built to be correct on hardware lacking any given part. The // or an error. The page is built to be correct on hardware lacking any given part. The
// AI row is the same rule applied to a subsystem rather than a device: it exists only // AI row is the same rule applied to a subsystem rather than a device: it exists only
// where vv_ai_ui_on() is true, which is the AI host with AI_ENABLED set. // where vv_ai_ui_on() is true, which is any node in the mesh with AI_ENABLED set. On a
// node that is not the AI owner the figures describe the owner's model and index, and the
// card names it — see 'served_by' in vv_ai_monitor_block().
// //
// The assistant here starts on General Chat, where the AI tab starts on Varaverk Assistant. // The assistant here starts on General Chat, where the AI tab starts on Varaverk Assistant.
// Different jobs. The tab is where you go to interrogate the installation; this is the // Different jobs. The tab is where you go to interrogate the installation; this is the
@@ -310,10 +312,11 @@ $_vv_doc_vars = array_merge(vv_conf_vars(), ['SCRIPTS_DIR' => SCRIPTS_DIR]);
Cards here size to their content instead. --> Cards here size to their content instead. -->
<div id="vv-monitor-ai"> <div id="vv-monitor-ai">
<!-- AI residency | Saved conversations | Assistant --> <!-- AI residency | Saved conversations | Assistant -->
<!-- Present only on the AI host with AI_ENABLED true, on the same footing as the GPU and UPS <!-- Present on any mesh node with AI_ENABLED true, on the same footing as the GPU and UPS
cards above: a subsystem that is not here does not render an empty card explaining that cards above: a subsystem that is not here does not render an empty card explaining that
it is not here. api/ai.php refuses every action independently, so this is presentation it is not here. On a node that is not the AI owner these figures describe the owner's
rather than access control. --> model and index, and the card names it. api/ai.php gates each action independently, so
this is presentation rather than access control. -->
<!-- Placement for this row lives in css/varaverk.css, not on these cards. It is two rows <!-- Placement for this row lives in css/varaverk.css, not on these cards. It is two rows
deep — AI over Tokens in the first column — and a grid-column span here cannot say which deep — AI over Tokens in the first column — and a grid-column span here cannot say which
@@ -2010,6 +2013,14 @@ function vvRenderAi(ai) {
if (ix.stale) h += `<div style="font-size:10px;color:#8a6a3a;margin:-2px 0 7px;">source newer — reindex</div>`; if (ix.stale) h += `<div style="font-size:10px;color:#8a6a3a;margin:-2px 0 7px;">source newer — reindex</div>`;
} }
// Only set on a node that is not the AI owner. Everything above then describes that owner's
// model and index, not this box's, and the card has to say so — otherwise a mirror looks like
// it is running a model it does not have.
if (ai.served_by) {
h += `<div style="font-size:10px;color:#666;margin-top:6px;border-top:1px solid #1e1e1e;padding-top:5px;">`
+ `served by ${vvEscHtml(ai.served_by)}</div>`;
}
box.innerHTML = h; box.innerHTML = h;
} }