/**
* dashboard-client-repos.ts — verbatim JS chunk: multi-repo index rendering.
*
* Extracted from html.ts (PR0 split) — ZERO behavior change. Second of three
* chunks concatenated into the dashboard IIFE (core → repos → game). Shares
* the closure with core + game: uses sanitize() from core, fmtSec/renderGameScores
* from core/game.
*/
/** Multi-repo index rendering, repo detail modal, active-repos table. */
export function dashboardClientReposJs(): string {
return `// --- Multi-repo (index.sqlite via /api/index) ---------------------------
function fmtBytesTop(b) {
b = b || 0;
if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
return b + ' B';
}
function renderIndex(d) {
d = d || { updatedAt: null, summary: null, repos: [] };
var repos = d.repos || [];
var s = d.summary || { totalRepos: 0, totalCheckpoints: 0, totalTokensSaved: 0, totalCompressedOriginalBytes: 0 };
document.getElementById('sm-repos').textContent = (s.totalRepos || 0).toLocaleString();
document.getElementById('sm-checkpoints').textContent = (s.totalCheckpoints || 0).toLocaleString();
document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
// Shared clickable-row renderer for both the in-current table and the
// All-repos tab — each row opens the per-repo detail modal.
function rowsHtml() {
if (!repos.length) return '
| No repositories registered yet. |
';
return repos.map(function(r) {
var model = r.modelName
? '' + sanitize(r.modelName) + ''
: '—';
var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
return '' +
'| ' + sanitize(r.displayName || r.repoRoot) + ' | ' +
'' + model + ' | ' +
'' + (r.checkpointCount || 0).toLocaleString() + ' | ' +
'' + (r.tokensSaved || 0).toLocaleString() + ' | ' +
'' + fmtBytesTop(r.compressedOriginalBytes) + ' | ' +
'' + sanitize(when) + ' | ' +
'
';
}).join('');
}
document.getElementById('cur-rows').innerHTML = rowsHtml();
document.getElementById('all-rows').innerHTML = rowsHtml();
bindRepoRows();
var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
document.getElementById('cur-updated').textContent = stamp;
document.getElementById('all-updated').textContent = stamp;
document.getElementById('sm-updated').textContent = stamp;
renderByModel(repos);
}
// Savings-by-model aggregation for the Summary tab — groups the machine-
// wide repo registry by (modelName || '(unknown)') so the user can see how
// much context + cost mega-compact has reclaimed, broken down by which model
// they were running. $ Saved = Σ(tokensSaved × inputRate) per model. Sorted
// by tokens saved descending so the biggest-reclaim model wins the top row.
function renderByModel(repos) {
var rows = document.getElementById('bm-rows');
if (!rows) return;
if (!repos || !repos.length) {
rows.innerHTML = '| No repositories registered yet. |
';
return;
}
var groups = {};
for (var i = 0; i < repos.length; i++) {
var r = repos[i];
var key = (r.modelName && String(r.modelName).trim()) || '(unknown)';
if (!groups[key]) groups[key] = {
model: key, provider: r.providerName || r.provider || '—', repos: 0, checkpoints: 0,
tokensSaved: 0, tokensIn: 0, tokensOut: 0, sessions: 0, usd: 0, lastAt: 0,
inRates: [], outRates: [], ctxWindows: [], maxTokens: [], reasoning: null,
};
var g = groups[key];
g.repos++;
g.checkpoints += (r.checkpointCount || 0);
g.tokensSaved += (r.tokensSaved || 0);
g.tokensIn += (r.tokensDropped || 0);
g.tokensOut += (r.tokensKept || 0);
g.sessions += (r.sessions || 0);
if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.inRates.push(r.inputRate); }
if (r.outputRate) g.outRates.push(r.outputRate);
if (r.contextWindow) g.ctxWindows.push(r.contextWindow);
if (r.maxTokens) g.maxTokens.push(r.maxTokens);
if (r.reasoning != null) g.reasoning = r.reasoning;
if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt;
}
var arr = [];
for (var k in groups) { if (Object.prototype.hasOwnProperty.call(groups, k)) arr.push(groups[k]); }
arr.sort(function(a, b) { return b.tokensSaved - a.tokensSaved; });
// Helper: a set of numeric samples collapses to a single value when all
// repos in the group agree, otherwise shows the range (min–max) so the
// user can see mixed-config model groups at a glance.
function collapseNum(samples) {
if (!samples || !samples.length) return '—';
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
return lo === hi ? lo.toLocaleString() : lo.toLocaleString() + '–' + hi.toLocaleString();
}
function collapseRate(samples) {
if (!samples || !samples.length) return '—';
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
var fmt = function(v) { return '$' + v.toFixed(6); };
return lo === hi ? fmt(lo) : fmt(lo) + '–' + fmt(hi);
}
// Helper: compute cache hit % from providerCacheRead/Write tokens
function cacheHitPct(cr, cw, inp) {
var denom = cr + cw + (inp || 0);
return denom > 0 ? ((cr / denom) * 100).toFixed(1) + '%' : '—';
}
// Helper: compute cache $ saved (read = 0.9 * inputRate, write = 0.25 * inputRate)
function cacheDollar(cr, cw, rate) {
if (!cr || !rate) return '—';
var saved = cr * rate * 0.9 - cw * rate * 0.25;
return '$' + saved.toFixed(4);
}
// Aggregate providerCacheRead/Write across repos in this group
var groupCacheRead = 0, groupCacheWrite = 0, groupTokensIn = 0;
for (var ii = 0; ii < repos.length; ii++) {
var rr = repos[ii];
var key2 = (rr.modelName && String(rr.modelName).trim()) || '(unknown)';
if (key2 === g.model) {
groupCacheRead += (rr.providerCacheRead || 0);
groupCacheWrite += (rr.providerCacheWrite || 0);
groupTokensIn += (rr.tokensDropped || 0);
}
}
rows.innerHTML = arr.map(function(g) {
var freed = (g.tokensIn || 0) - (g.tokensOut || 0);
// B: always show a dollar value (including $0.0000) — do not use '—' when usd is 0
var usd = '$' + (g.usd || 0).toFixed(4);
var when = g.lastAt ? new Date(g.lastAt).toLocaleString() : '—';
var reas = g.reasoning == null ? '—' : (g.reasoning ? 'yes' : 'no');
var rate0 = g.inRates && g.inRates.length ? g.inRates[0] : 0;
return '' +
'| ' + sanitize(g.model) + ' | ' +
'' + sanitize(g.provider) + ' | ' +
'' + (g.tokensIn || 0).toLocaleString() + ' | ' +
'' + (g.tokensOut || 0).toLocaleString() + ' | ' +
'' + freed.toLocaleString() + ' | ' +
'' + collapseNum(g.ctxWindows) + ' | ' +
'' + collapseNum(g.maxTokens) + ' | ' +
'' + reas + ' | ' +
'' + g.sessions.toLocaleString() + ' | ' +
'' + g.checkpoints.toLocaleString() + ' | ' +
'' + collapseRate(g.inRates) + ' | ' +
'' + collapseRate(g.outRates) + ' | ' +
'' + sanitize(usd) + ' | ' +
'' + cacheHitPct(groupCacheRead, groupCacheWrite, groupTokensIn) + ' | ' +
'' + cacheDollar(groupCacheRead, groupCacheWrite, rate0) + ' | ' +
'' + sanitize(when) + ' | ' +
'
';
}).join('');
}
// Per-repo detail modal ---------------------------------------------------
var detailEl = document.getElementById('repo-detail');
var indexCache = { repos: [] };
function openRepoDetail(root) {
var r = null;
for (var i = 0; i < indexCache.repos.length; i++) {
if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
}
if (!r) return;
document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
document.getElementById('rd-path').textContent = r.repoRoot;
document.getElementById('rd-model').textContent = r.modelName || '—';
document.getElementById('rd-provider').textContent = r.providerName || (r.provider || '—');
document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
detailEl.classList.add('open');
}
document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
function bindRepoRows() {
var rows = document.querySelectorAll('.repo-link');
for (var i = 0; i < rows.length; i++) {
rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
}
}
function pollIndex() {
fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) { // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
indexCache = d && d.repos ? d : indexCache;
renderIndex(d);
}).catch(function() {});
}
pollIndex();
setInterval(pollIndex, 5000);
// --- Active repos (live cache-hit / compaction stats) ---------------------
function fmtSec(s) {
s = s || 0;
if (s >= 3600) return (s / 3600).toFixed(1) + 'h';
if (s >= 60) return Math.round(s / 60) + 'm';
if (s >= 1) return s.toFixed(1) + 's';
return Math.round(s * 1000) + 'ms';
}
function renderActiveRepos(d) {
d = d || { updatedAt: null, servers: [] };
var servers = d.servers || [];
var rowsEl = document.getElementById('active-rows');
if (!rowsEl) return;
if (!servers.length) {
rowsEl.innerHTML = '| No active repositories. |
';
} else {
rowsEl.innerHTML = servers.map(function(r) {
var ch = r.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
var cp = r.compacts || { session: 0, total: 0 };
var ts = r.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
return '' +
'| ' + sanitize(r.displayName || r.repoRoot) + ' | ' +
'' + sanitize(r.model || '—') + ' | ' +
'' + sanitize(r.tier || '—') + ' | ' +
'' + (r.contextPct != null ? Math.round(r.contextPct * 100) + '%' : '—') + ' | ' +
'' + sanitize(r.state || '—') + ' | ' +
'' + (cp.session || 0) + ' / ' + (cp.total || 0) + ' | ' +
'' + (ch.session || 0) + ' / ' + (ch.total || 0) + ' | ' +
'' + fmtSec(ts.compact.sessionSec) + ' / ' + fmtSec(ts.compact.totalSec) + ' | ' +
'' + fmtSec(ts.cacheHit.sessionSec) + ' / ' + fmtSec(ts.cacheHit.totalSec) + ' | ' +
'
';
}).join('');
}
var upd = document.getElementById('active-updated');
if (upd) upd.textContent = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
}
function pollServers() {
fetch('/api/servers').then(function(r) { return r.json(); }).then(renderActiveRepos).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
}
pollServers();
setInterval(pollServers, 5000);
`;
}