Spaces:
Sleeping
fix(admin): KI-162 + KI-163 — simplify LLM Chain UI + truncate visitor names
Browse filesKI-162 — LLM Chain admin tab redesigned to two simple tables:
- "What's Live" — 3 rows (Brain Fast / Brain Main / Judge) × current model + probed-age
- "What's Available" — NIM-only dedup pool with Healthy + Used By columns
Dropped 124 lines of stale renderLlmHealthChain (chain cards + AVAILABLE/
UNAVAILABLE chips / PRIMARY pills). Added isRetiredModel() filter that
drops any groq:/openrouter:/or: entries even if a stale payload still
carries them.
KI-163 — visitor table no longer blows up vertically when a session
captured a multi-word garbage name (e.g. "Currently Not Having Any" pre-
KI-156 Fix B). Added max-width + nowrap + ellipsis to .name-cell and
.slug-cell, with hover tooltip via title= for the full value. Also
deleted 4 garbage profile JSONs from 40-data/profiles/ ("Just Me" ×2,
"Rohit Sara", "Not Having Any Pre-existing") that pre-dated the name
stop-list fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- frontend/public/admin/llm-control.html +219 -225
|
@@ -347,121 +347,60 @@
|
|
| 347 |
.chain-table .icon-btn { padding: 2px 7px; font-size: 12px; line-height: 1.2; }
|
| 348 |
.chain-table tbody tr.is-primary td { background: rgba(63, 185, 80, 0.05); }
|
| 349 |
|
| 350 |
-
/* KI-
|
| 351 |
-
.llm-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
padding: 12px;
|
| 358 |
-
background: #0b0d12;
|
| 359 |
-
border: 1px solid var(--border);
|
| 360 |
-
border-radius: 8px;
|
| 361 |
-
}
|
| 362 |
-
.llm-health-chain .chain-head {
|
| 363 |
-
display: flex; align-items: center; gap: 8px;
|
| 364 |
-
margin-bottom: 10px;
|
| 365 |
-
}
|
| 366 |
-
.llm-health-chain .chain-head .role-badge { margin-right: 4px; }
|
| 367 |
-
.llm-health-chain .role-block {
|
| 368 |
-
margin-bottom: 8px;
|
| 369 |
-
padding-bottom: 8px;
|
| 370 |
-
border-bottom: 1px dashed var(--border);
|
| 371 |
-
}
|
| 372 |
-
.llm-health-chain .role-block:last-of-type { border-bottom: none; }
|
| 373 |
-
.llm-health-chain .role-block .role-label {
|
| 374 |
-
font-size: 10px;
|
| 375 |
-
text-transform: uppercase;
|
| 376 |
-
letter-spacing: 0.08em;
|
| 377 |
-
color: var(--muted);
|
| 378 |
-
margin-bottom: 4px;
|
| 379 |
}
|
| 380 |
-
.llm-
|
| 381 |
-
|
|
|
|
| 382 |
font-size: 12px;
|
| 383 |
-
word-break: break-all;
|
| 384 |
}
|
| 385 |
-
.llm-
|
| 386 |
-
|
| 387 |
-
|
|
|
|
|
|
|
| 388 |
color: var(--muted);
|
|
|
|
|
|
|
|
|
|
| 389 |
}
|
| 390 |
-
.llm-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
.llm-health-chain .credit-banner {
|
| 395 |
-
margin-top: 8px;
|
| 396 |
-
padding: 8px 10px;
|
| 397 |
-
background: rgba(248, 81, 73, 0.12);
|
| 398 |
-
border: 1px solid rgba(248, 81, 73, 0.45);
|
| 399 |
-
border-radius: 6px;
|
| 400 |
-
font-size: 12px;
|
| 401 |
-
color: var(--red);
|
| 402 |
-
}
|
| 403 |
-
.llm-health-chain .none-elected {
|
| 404 |
-
color: var(--yellow);
|
| 405 |
-
font-style: italic;
|
| 406 |
-
font-size: 12px;
|
| 407 |
-
}
|
| 408 |
-
|
| 409 |
-
/* KI-122 — "Currently in use" header + per-member availability pills */
|
| 410 |
-
.llm-health-chain .in-use-line {
|
| 411 |
-
display: flex;
|
| 412 |
-
align-items: center;
|
| 413 |
-
gap: 8px;
|
| 414 |
-
flex-wrap: wrap;
|
| 415 |
-
padding: 8px 10px;
|
| 416 |
-
margin-bottom: 12px;
|
| 417 |
-
background: rgba(63, 185, 80, 0.06);
|
| 418 |
-
border: 1px solid rgba(63, 185, 80, 0.25);
|
| 419 |
-
border-radius: 6px;
|
| 420 |
-
}
|
| 421 |
-
.llm-health-chain .in-use-line .role-label {
|
| 422 |
-
margin-bottom: 0;
|
| 423 |
-
color: var(--text);
|
| 424 |
}
|
| 425 |
-
.llm-
|
|
|
|
|
|
|
| 426 |
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;
|
| 427 |
-
font-size: 12px;
|
| 428 |
word-break: break-all;
|
| 429 |
}
|
| 430 |
-
.llm-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
gap: 4px;
|
| 434 |
-
margin: 6px 0 12px;
|
| 435 |
-
padding: 8px 10px;
|
| 436 |
-
background: #050709;
|
| 437 |
-
border: 1px solid var(--border);
|
| 438 |
-
border-radius: 6px;
|
| 439 |
-
}
|
| 440 |
-
.llm-health-chain .chain-member-row {
|
| 441 |
-
display: flex;
|
| 442 |
-
align-items: center;
|
| 443 |
-
gap: 8px;
|
| 444 |
-
font-size: 11px;
|
| 445 |
-
}
|
| 446 |
-
.llm-health-chain .chain-member-row .model-name {
|
| 447 |
-
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;
|
| 448 |
-
word-break: break-all;
|
| 449 |
}
|
| 450 |
-
.
|
|
|
|
| 451 |
display: inline-block;
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
|
|
|
|
|
|
|
|
|
| 458 |
white-space: nowrap;
|
|
|
|
| 459 |
}
|
| 460 |
-
|
| 461 |
-
.avail-pill.pill-available { background: rgba(63, 185, 80, 0.10); color: var(--green); border-color: rgba(63, 185, 80, 0.35); }
|
| 462 |
-
.avail-pill.pill-gated { background: rgba(139, 149, 164, 0.15);color: var(--muted); border-color: rgba(139, 149, 164, 0.40); }
|
| 463 |
-
.avail-pill.pill-degraded { background: rgba(210, 153, 34, 0.15); color: var(--yellow); border-color: rgba(210, 153, 34, 0.45); }
|
| 464 |
-
.avail-pill.pill-down { background: rgba(248, 81, 73, 0.15); color: var(--red); border-color: rgba(248, 81, 73, 0.45); }
|
| 465 |
|
| 466 |
/* Status badges — three colors: ✓ green / ⚠ amber / ✗ red */
|
| 467 |
.health-badge {
|
|
@@ -617,11 +556,21 @@
|
|
| 617 |
}
|
| 618 |
|
| 619 |
/* Profiles table */
|
| 620 |
-
.profile-table td.name-cell {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
.profile-table td.slug-cell {
|
| 622 |
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;
|
| 623 |
font-size: 12px;
|
| 624 |
color: var(--muted);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
}
|
| 626 |
.profile-table td.num-cell { text-align: right; font-variant-numeric: tabular-nums; }
|
| 627 |
.profile-table td.time-cell { white-space: nowrap; font-size: 12px; }
|
|
@@ -722,13 +671,13 @@
|
|
| 722 |
<!-- KI-086: LLM Health & Credits snapshot (KI-080..KI-085 telemetry) -->
|
| 723 |
<div class="card" id="llm-health-card">
|
| 724 |
<div class="row-between" style="margin-bottom: 12px;">
|
| 725 |
-
<h2>LLM health
|
| 726 |
<div class="actions">
|
| 727 |
<button id="btn-refresh-llm-health">Refresh</button>
|
| 728 |
</div>
|
| 729 |
</div>
|
| 730 |
-
<!--
|
| 731 |
-
<div id="llm-health-chains" class="llm-
|
| 732 |
<!-- Section B: candidate health grid -->
|
| 733 |
<h3 style="margin-top: 18px;">Candidate health grid</h3>
|
| 734 |
<div id="llm-health-candidates" class="llm-health-candidates"></div>
|
|
@@ -1490,129 +1439,174 @@
|
|
| 1490 |
return { cls: 'unknown', symbol: '?', label: 'UNKNOWN' };
|
| 1491 |
}
|
| 1492 |
|
| 1493 |
-
|
| 1494 |
-
|
| 1495 |
-
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
-
|
| 1499 |
-
|
| 1500 |
-
|
| 1501 |
-
|
| 1502 |
-
|
| 1503 |
-
|
| 1504 |
-
|
| 1505 |
-
|
| 1506 |
-
|
| 1507 |
-
|
| 1508 |
-
|
| 1509 |
-
|
| 1510 |
-
|
| 1511 |
-
|
| 1512 |
-
|
| 1513 |
-
|
| 1514 |
-
|
| 1515 |
-
|
| 1516 |
-
|
| 1517 |
-
|
| 1518 |
-
var
|
| 1519 |
-
|
| 1520 |
-
|
| 1521 |
-
|
| 1522 |
-
|
| 1523 |
-
|
| 1524 |
-
|
| 1525 |
-
|
| 1526 |
-
|
| 1527 |
-
|
| 1528 |
-
|
| 1529 |
-
|
| 1530 |
-
|
| 1531 |
-
|
| 1532 |
-
|
| 1533 |
-
|
| 1534 |
-
|
| 1535 |
-
|
| 1536 |
-
|
| 1537 |
-
|
| 1538 |
-
|
| 1539 |
-
|
| 1540 |
-
|
| 1541 |
-
|
| 1542 |
-
|
| 1543 |
-
|
| 1544 |
-
|
| 1545 |
-
|
| 1546 |
-
|
| 1547 |
-
|
| 1548 |
-
|
| 1549 |
-
|
| 1550 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1551 |
} else {
|
| 1552 |
-
|
|
|
|
| 1553 |
}
|
| 1554 |
-
|
| 1555 |
-
|
| 1556 |
-
|
|
|
|
|
|
|
| 1557 |
});
|
| 1558 |
-
|
| 1559 |
-
}
|
| 1560 |
|
| 1561 |
-
|
| 1562 |
-
|
| 1563 |
-
|
| 1564 |
-
|
| 1565 |
-
|
| 1566 |
-
|
| 1567 |
-
|
| 1568 |
-
block.appendChild(label);
|
| 1569 |
-
if (!name || !snap) {
|
| 1570 |
-
var noneEl = createEl('div', { className: 'none-elected', text: 'none elected (no eligible candidate)' });
|
| 1571 |
-
block.appendChild(noneEl);
|
| 1572 |
-
card.appendChild(block);
|
| 1573 |
-
return;
|
| 1574 |
-
}
|
| 1575 |
-
var nameLine = createEl('div', { className: 'model-name' });
|
| 1576 |
-
var b = candidateBadge(snap);
|
| 1577 |
-
var statusBadge = createEl('span', { className: 'health-badge ' + b.cls, text: b.symbol + ' ' + b.label });
|
| 1578 |
-
statusBadge.style.marginRight = '6px';
|
| 1579 |
-
nameLine.appendChild(statusBadge);
|
| 1580 |
-
nameLine.appendChild(document.createTextNode(name));
|
| 1581 |
-
block.appendChild(nameLine);
|
| 1582 |
-
|
| 1583 |
-
var stats = createEl('div', { className: 'stats-line' });
|
| 1584 |
-
var pushed = false;
|
| 1585 |
-
function pushPart(text) {
|
| 1586 |
-
if (pushed) stats.appendChild(createEl('span', { className: 'sep', text: '·' }));
|
| 1587 |
-
stats.appendChild(document.createTextNode(text));
|
| 1588 |
-
pushed = true;
|
| 1589 |
-
}
|
| 1590 |
-
if (snap.latency_ms != null) pushPart('latency ' + fmtLatency(snap.latency_ms));
|
| 1591 |
-
if (snap.success_rate != null) pushPart('success ' + (Number(snap.success_rate) * 100).toFixed(0) + '%');
|
| 1592 |
-
if (snap.credits_remaining != null) pushPart('credits ' + fmtCredits(snap.credits_remaining, snap.credits_unit, null));
|
| 1593 |
-
if (snap.credits_reset_in_seconds != null && snap.credits_reset_in_seconds > 0) {
|
| 1594 |
-
pushPart('reset in ' + fmtDurationShort(snap.credits_reset_in_seconds));
|
| 1595 |
-
}
|
| 1596 |
-
if (snap.degraded_for_seconds != null && snap.degraded_for_seconds > 0) {
|
| 1597 |
-
pushPart('sin-bin ' + fmtDurationShort(snap.degraded_for_seconds));
|
| 1598 |
-
}
|
| 1599 |
-
if (!pushed) stats.textContent = 'No telemetry yet.';
|
| 1600 |
-
block.appendChild(stats);
|
| 1601 |
-
card.appendChild(block);
|
| 1602 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
|
| 1604 |
-
|
| 1605 |
-
|
| 1606 |
-
// available_for_calls AND at least one is strictly credit-exhausted).
|
| 1607 |
-
// This prevents the historic "HEALTHY 100% + credit-exhausted banner"
|
| 1608 |
-
// contradiction the operator reported.
|
| 1609 |
-
if (chainBlock.chain_credit_exhausted) {
|
| 1610 |
-
var banner = createEl('div', { className: 'credit-banner' });
|
| 1611 |
-
banner.textContent = 'All candidates for ' + (ROLE_LABELS[chainBlock.role] || chainBlock.role).toUpperCase() +
|
| 1612 |
-
'_CHAIN are credit-exhausted; canonical fallback will fire until quotas reset.';
|
| 1613 |
-
card.appendChild(banner);
|
| 1614 |
}
|
| 1615 |
-
|
|
|
|
|
|
|
| 1616 |
}
|
| 1617 |
|
| 1618 |
function renderLlmHealthCandidates(rows) {
|
|
@@ -1754,9 +1748,7 @@
|
|
| 1754 |
}
|
| 1755 |
|
| 1756 |
clearChildren(chainsHost);
|
| 1757 |
-
(STATE.llmHealth.chains || [])
|
| 1758 |
-
chainsHost.appendChild(renderLlmHealthChain(c));
|
| 1759 |
-
});
|
| 1760 |
|
| 1761 |
renderLlmHealthCandidates(STATE.llmHealth.candidates || []);
|
| 1762 |
renderLlmHealthRecent(STATE.llmHealth.recent_turns || []);
|
|
@@ -1912,9 +1904,11 @@
|
|
| 1912 |
var tr = createEl('tr');
|
| 1913 |
|
| 1914 |
var nameCell = createEl('td', { className: 'name-cell', text: p.name_display || '—' });
|
|
|
|
| 1915 |
tr.appendChild(nameCell);
|
| 1916 |
|
| 1917 |
var slugCell = createEl('td', { className: 'slug-cell', text: p.slug || '—' });
|
|
|
|
| 1918 |
tr.appendChild(slugCell);
|
| 1919 |
|
| 1920 |
var firstCell = createEl('td', { className: 'time-cell', text: fmtIST(p.first_seen) });
|
|
|
|
| 347 |
.chain-table .icon-btn { padding: 2px 7px; font-size: 12px; line-height: 1.2; }
|
| 348 |
.chain-table tbody tr.is-primary td { background: rgba(63, 185, 80, 0.05); }
|
| 349 |
|
| 350 |
+
/* KI-162: simple 2-table layout for LLM Chain section (replaces legacy .llm-health-chains grid) */
|
| 351 |
+
.llm-simple-tables { display: flex; flex-direction: column; gap: 18px; }
|
| 352 |
+
.llm-simple-tables .simple-table-block h3 {
|
| 353 |
+
margin: 0 0 8px;
|
| 354 |
+
font-size: 13px;
|
| 355 |
+
font-weight: 600;
|
| 356 |
+
color: var(--text);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
}
|
| 358 |
+
.llm-simple-tables table {
|
| 359 |
+
width: 100%;
|
| 360 |
+
border-collapse: collapse;
|
| 361 |
font-size: 12px;
|
|
|
|
| 362 |
}
|
| 363 |
+
.llm-simple-tables thead th {
|
| 364 |
+
text-align: left;
|
| 365 |
+
padding: 8px;
|
| 366 |
+
border-bottom: 1px solid var(--border);
|
| 367 |
+
font-weight: 600;
|
| 368 |
color: var(--muted);
|
| 369 |
+
text-transform: uppercase;
|
| 370 |
+
letter-spacing: 0.05em;
|
| 371 |
+
font-size: 10px;
|
| 372 |
}
|
| 373 |
+
.llm-simple-tables tbody td {
|
| 374 |
+
padding: 8px;
|
| 375 |
+
border-bottom: 1px solid var(--border);
|
| 376 |
+
vertical-align: middle;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
}
|
| 378 |
+
.llm-simple-tables tbody tr:last-child td { border-bottom: none; }
|
| 379 |
+
.llm-simple-tables td.model-name,
|
| 380 |
+
.llm-simple-tables td.mono {
|
| 381 |
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;
|
|
|
|
| 382 |
word-break: break-all;
|
| 383 |
}
|
| 384 |
+
.llm-simple-tables td.use-label {
|
| 385 |
+
font-weight: 600;
|
| 386 |
+
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
}
|
| 388 |
+
.llm-simple-tables td.healthy-cell { text-align: center; width: 80px; }
|
| 389 |
+
.llm-simple-tables .health-dot {
|
| 390 |
display: inline-block;
|
| 391 |
+
width: 10px;
|
| 392 |
+
height: 10px;
|
| 393 |
+
border-radius: 50%;
|
| 394 |
+
}
|
| 395 |
+
.llm-simple-tables .health-dot.ok { background: var(--green, #3fb950); }
|
| 396 |
+
.llm-simple-tables .health-dot.bad { background: var(--red, #f85149); }
|
| 397 |
+
.llm-simple-tables td.used-by-cell { color: var(--muted); }
|
| 398 |
+
.llm-simple-tables td.probed-cell {
|
| 399 |
+
color: var(--muted);
|
| 400 |
white-space: nowrap;
|
| 401 |
+
font-variant-numeric: tabular-nums;
|
| 402 |
}
|
| 403 |
+
/* KI-162: legacy .llm-health-chain* + .avail-pill CSS removed — replaced by .llm-simple-tables above */
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
/* Status badges — three colors: ✓ green / ⚠ amber / ✗ red */
|
| 406 |
.health-badge {
|
|
|
|
| 556 |
}
|
| 557 |
|
| 558 |
/* Profiles table */
|
| 559 |
+
.profile-table td.name-cell {
|
| 560 |
+
font-weight: 500;
|
| 561 |
+
max-width: 200px;
|
| 562 |
+
white-space: nowrap;
|
| 563 |
+
overflow: hidden;
|
| 564 |
+
text-overflow: ellipsis;
|
| 565 |
+
}
|
| 566 |
.profile-table td.slug-cell {
|
| 567 |
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, monospace;
|
| 568 |
font-size: 12px;
|
| 569 |
color: var(--muted);
|
| 570 |
+
max-width: 140px;
|
| 571 |
+
white-space: nowrap;
|
| 572 |
+
overflow: hidden;
|
| 573 |
+
text-overflow: ellipsis;
|
| 574 |
}
|
| 575 |
.profile-table td.num-cell { text-align: right; font-variant-numeric: tabular-nums; }
|
| 576 |
.profile-table td.time-cell { white-space: nowrap; font-size: 12px; }
|
|
|
|
| 671 |
<!-- KI-086: LLM Health & Credits snapshot (KI-080..KI-085 telemetry) -->
|
| 672 |
<div class="card" id="llm-health-card">
|
| 673 |
<div class="row-between" style="margin-bottom: 12px;">
|
| 674 |
+
<h2>LLM health <span class="small muted" id="llm-health-snapshot-ts"></span></h2>
|
| 675 |
<div class="actions">
|
| 676 |
<button id="btn-refresh-llm-health">Refresh</button>
|
| 677 |
</div>
|
| 678 |
</div>
|
| 679 |
+
<!-- KI-162: simplified 2-table layout (What's Live + What's Available) -->
|
| 680 |
+
<div id="llm-health-chains" class="llm-simple-tables"></div>
|
| 681 |
<!-- Section B: candidate health grid -->
|
| 682 |
<h3 style="margin-top: 18px;">Candidate health grid</h3>
|
| 683 |
<div id="llm-health-candidates" class="llm-health-candidates"></div>
|
|
|
|
| 1439 |
return { cls: 'unknown', symbol: '?', label: 'UNKNOWN' };
|
| 1440 |
}
|
| 1441 |
|
| 1442 |
+
// KI-162 — simplified 2-table renderer for the LLM Chain section.
|
| 1443 |
+
// Table 1: "What's Live" — one row per chain role with the currently-elected model.
|
| 1444 |
+
// Table 2: "What's Available" — NIM-only pool deduped across chains, with Healthy + Used By.
|
| 1445 |
+
// Replaces the per-chain cards / chain-members lists / availability pills.
|
| 1446 |
+
var SIMPLE_USE_LABELS = {
|
| 1447 |
+
fast_brain: 'Brain Fast',
|
| 1448 |
+
brain: 'Brain Main',
|
| 1449 |
+
judge: 'Judge'
|
| 1450 |
+
};
|
| 1451 |
+
var SIMPLE_USE_ORDER = ['fast_brain', 'brain', 'judge'];
|
| 1452 |
+
|
| 1453 |
+
function isRetiredModel(model) {
|
| 1454 |
+
// Post-KI-160 NIM-only: filter out any stale Groq / OpenRouter entries
|
| 1455 |
+
// that might still appear in chain_members[] payloads.
|
| 1456 |
+
if (!model) return true;
|
| 1457 |
+
var m = String(model).toLowerCase();
|
| 1458 |
+
return (
|
| 1459 |
+
m.indexOf('groq:') === 0 ||
|
| 1460 |
+
m.indexOf('openrouter:') === 0 ||
|
| 1461 |
+
m.indexOf('or:') === 0
|
| 1462 |
+
);
|
| 1463 |
+
}
|
| 1464 |
+
|
| 1465 |
+
function renderLlmSimpleTables(host, chains) {
|
| 1466 |
+
// ---- Table 1: What's Live ----
|
| 1467 |
+
var liveBlock = createEl('div', { className: 'simple-table-block' });
|
| 1468 |
+
liveBlock.appendChild(createEl('h3', { text: "What's Live" }));
|
| 1469 |
+
var liveTable = createEl('table');
|
| 1470 |
+
var liveThead = createEl('thead');
|
| 1471 |
+
var liveHr = createEl('tr');
|
| 1472 |
+
['Use', 'Currently In Use', 'Probed'].forEach(function (h) {
|
| 1473 |
+
liveHr.appendChild(createEl('th', { text: h }));
|
| 1474 |
+
});
|
| 1475 |
+
liveThead.appendChild(liveHr);
|
| 1476 |
+
liveTable.appendChild(liveThead);
|
| 1477 |
+
var liveTbody = createEl('tbody');
|
| 1478 |
+
|
| 1479 |
+
// Index chains by role for stable Brain Fast / Brain Main / Judge ordering.
|
| 1480 |
+
var chainsByRole = {};
|
| 1481 |
+
(chains || []).forEach(function (c) { if (c && c.role) chainsByRole[c.role] = c; });
|
| 1482 |
+
|
| 1483 |
+
SIMPLE_USE_ORDER.forEach(function (role) {
|
| 1484 |
+
var c = chainsByRole[role];
|
| 1485 |
+
var tr = createEl('tr');
|
| 1486 |
+
tr.appendChild(createEl('td', { className: 'use-label', text: SIMPLE_USE_LABELS[role] }));
|
| 1487 |
+
|
| 1488 |
+
var modelCell = createEl('td', { className: 'mono' });
|
| 1489 |
+
if (c && c.current_primary && !isRetiredModel(c.current_primary)) {
|
| 1490 |
+
if (c.current_primary_available === false) {
|
| 1491 |
+
var dot = createEl('span', { className: 'health-dot bad' });
|
| 1492 |
+
dot.style.marginRight = '6px';
|
| 1493 |
+
modelCell.appendChild(dot);
|
| 1494 |
+
}
|
| 1495 |
+
modelCell.appendChild(document.createTextNode(c.current_primary));
|
| 1496 |
+
} else {
|
| 1497 |
+
modelCell.textContent = '—';
|
| 1498 |
+
}
|
| 1499 |
+
tr.appendChild(modelCell);
|
| 1500 |
+
|
| 1501 |
+
var probedCell = createEl('td', { className: 'probed-cell' });
|
| 1502 |
+
if (c && c.last_probe_age_seconds != null) {
|
| 1503 |
+
probedCell.textContent = fmtDurationShort(c.last_probe_age_seconds) + ' ago';
|
| 1504 |
+
} else {
|
| 1505 |
+
probedCell.textContent = '—';
|
| 1506 |
+
}
|
| 1507 |
+
tr.appendChild(probedCell);
|
| 1508 |
+
liveTbody.appendChild(tr);
|
| 1509 |
+
});
|
| 1510 |
+
liveTable.appendChild(liveTbody);
|
| 1511 |
+
liveBlock.appendChild(liveTable);
|
| 1512 |
+
host.appendChild(liveBlock);
|
| 1513 |
+
|
| 1514 |
+
// ---- Table 2: What's Available ----
|
| 1515 |
+
// Walk every chain_members[] across the 3 chains, dedupe by model,
|
| 1516 |
+
// and collect: healthy boolean + used-by-role list with primary-first ordering.
|
| 1517 |
+
var modelMap = {}; // model -> { healthy, roleEntries: [{role, isPrimary, idx}] }
|
| 1518 |
+
(chains || []).forEach(function (c) {
|
| 1519 |
+
if (!c || !Array.isArray(c.chain_members)) return;
|
| 1520 |
+
c.chain_members.forEach(function (mi, idx) {
|
| 1521 |
+
if (!mi || !mi.model || isRetiredModel(mi.model)) return;
|
| 1522 |
+
if (!modelMap[mi.model]) {
|
| 1523 |
+
modelMap[mi.model] = {
|
| 1524 |
+
healthy: !!mi.available_for_calls,
|
| 1525 |
+
roleEntries: []
|
| 1526 |
+
};
|
| 1527 |
} else {
|
| 1528 |
+
// If any chain reports the model as available, treat it as healthy.
|
| 1529 |
+
if (mi.available_for_calls) modelMap[mi.model].healthy = true;
|
| 1530 |
}
|
| 1531 |
+
modelMap[mi.model].roleEntries.push({
|
| 1532 |
+
role: c.role,
|
| 1533 |
+
isPrimary: !!mi.is_current_primary,
|
| 1534 |
+
idx: idx
|
| 1535 |
+
});
|
| 1536 |
});
|
| 1537 |
+
});
|
|
|
|
| 1538 |
|
| 1539 |
+
var availBlock = createEl('div', { className: 'simple-table-block' });
|
| 1540 |
+
availBlock.appendChild(createEl('h3', { text: "What's Available" }));
|
| 1541 |
+
var availTable = createEl('table');
|
| 1542 |
+
var availThead = createEl('thead');
|
| 1543 |
+
var availHr = createEl('tr');
|
| 1544 |
+
['Model', 'Healthy', 'Used By'].forEach(function (h) {
|
| 1545 |
+
availHr.appendChild(createEl('th', { text: h }));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1546 |
});
|
| 1547 |
+
availThead.appendChild(availHr);
|
| 1548 |
+
availTable.appendChild(availThead);
|
| 1549 |
+
var availTbody = createEl('tbody');
|
| 1550 |
+
|
| 1551 |
+
var modelNames = Object.keys(modelMap);
|
| 1552 |
+
if (!modelNames.length) {
|
| 1553 |
+
var emptyTr = createEl('tr');
|
| 1554 |
+
var emptyTd = createEl('td', { attrs: { colspan: '3' }, text: 'No models in any chain.' });
|
| 1555 |
+
emptyTd.style.color = 'var(--muted)';
|
| 1556 |
+
emptyTd.style.textAlign = 'center';
|
| 1557 |
+
emptyTr.appendChild(emptyTd);
|
| 1558 |
+
availTbody.appendChild(emptyTr);
|
| 1559 |
+
} else {
|
| 1560 |
+
// Stable sort: models that are anyone's primary first, then by name.
|
| 1561 |
+
modelNames.sort(function (a, b) {
|
| 1562 |
+
var aPrim = modelMap[a].roleEntries.some(function (e) { return e.isPrimary; });
|
| 1563 |
+
var bPrim = modelMap[b].roleEntries.some(function (e) { return e.isPrimary; });
|
| 1564 |
+
if (aPrim !== bPrim) return aPrim ? -1 : 1;
|
| 1565 |
+
return a.localeCompare(b);
|
| 1566 |
+
});
|
| 1567 |
+
modelNames.forEach(function (model) {
|
| 1568 |
+
var info = modelMap[model];
|
| 1569 |
+
var tr = createEl('tr');
|
| 1570 |
+
tr.appendChild(createEl('td', { className: 'model-name', text: model }));
|
| 1571 |
+
|
| 1572 |
+
var healthyCell = createEl('td', { className: 'healthy-cell' });
|
| 1573 |
+
var hd = createEl('span', { className: 'health-dot ' + (info.healthy ? 'ok' : 'bad') });
|
| 1574 |
+
hd.title = info.healthy ? 'Healthy' : 'Unhealthy';
|
| 1575 |
+
healthyCell.appendChild(hd);
|
| 1576 |
+
tr.appendChild(healthyCell);
|
| 1577 |
+
|
| 1578 |
+
// "Used By" — primary first per role, then backups; relabel via SIMPLE_USE_LABELS.
|
| 1579 |
+
// Group by role, identify primary vs. backup using is_current_primary + chain index.
|
| 1580 |
+
var entriesByRole = {};
|
| 1581 |
+
info.roleEntries.forEach(function (e) {
|
| 1582 |
+
if (!entriesByRole[e.role]) entriesByRole[e.role] = [];
|
| 1583 |
+
entriesByRole[e.role].push(e);
|
| 1584 |
+
});
|
| 1585 |
+
// Order: primaries first (in SIMPLE_USE_ORDER), then backups by chain position.
|
| 1586 |
+
var primaryParts = [];
|
| 1587 |
+
var backupParts = [];
|
| 1588 |
+
SIMPLE_USE_ORDER.forEach(function (role) {
|
| 1589 |
+
var roleList = entriesByRole[role] || [];
|
| 1590 |
+
roleList.forEach(function (e) {
|
| 1591 |
+
var label = SIMPLE_USE_LABELS[role] || role;
|
| 1592 |
+
if (e.isPrimary) {
|
| 1593 |
+
primaryParts.push(label);
|
| 1594 |
+
} else {
|
| 1595 |
+
// idx 0 inside members is the primary itself when the chain has it;
|
| 1596 |
+
// anything else is a backup of some rank.
|
| 1597 |
+
backupParts.push(label + ' (backup)');
|
| 1598 |
+
}
|
| 1599 |
+
});
|
| 1600 |
+
});
|
| 1601 |
+
var usedBy = primaryParts.concat(backupParts).join(', ');
|
| 1602 |
+
tr.appendChild(createEl('td', { className: 'used-by-cell', text: usedBy || '—' }));
|
| 1603 |
|
| 1604 |
+
availTbody.appendChild(tr);
|
| 1605 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1606 |
}
|
| 1607 |
+
availTable.appendChild(availTbody);
|
| 1608 |
+
availBlock.appendChild(availTable);
|
| 1609 |
+
host.appendChild(availBlock);
|
| 1610 |
}
|
| 1611 |
|
| 1612 |
function renderLlmHealthCandidates(rows) {
|
|
|
|
| 1748 |
}
|
| 1749 |
|
| 1750 |
clearChildren(chainsHost);
|
| 1751 |
+
renderLlmSimpleTables(chainsHost, STATE.llmHealth.chains || []);
|
|
|
|
|
|
|
| 1752 |
|
| 1753 |
renderLlmHealthCandidates(STATE.llmHealth.candidates || []);
|
| 1754 |
renderLlmHealthRecent(STATE.llmHealth.recent_turns || []);
|
|
|
|
| 1904 |
var tr = createEl('tr');
|
| 1905 |
|
| 1906 |
var nameCell = createEl('td', { className: 'name-cell', text: p.name_display || '—' });
|
| 1907 |
+
if (p.name_display) nameCell.title = p.name_display;
|
| 1908 |
tr.appendChild(nameCell);
|
| 1909 |
|
| 1910 |
var slugCell = createEl('td', { className: 'slug-cell', text: p.slug || '—' });
|
| 1911 |
+
if (p.slug) slugCell.title = p.slug;
|
| 1912 |
tr.appendChild(slugCell);
|
| 1913 |
|
| 1914 |
var firstCell = createEl('td', { className: 'time-cell', text: fmtIST(p.first_seen) });
|