function api() {
return window.LctxApi && window.LctxApi.apiFetch ? window.LctxApi.apiFetch : null;
}
function fmtLib() {
return window.LctxFmt || {};
}
function chartsLib() {
return window.LctxCharts || {};
}
function sharedLib() {
return window.LctxShared || {};
}
function tip(k) {
return window.LctxShared && window.LctxShared.tip ? window.LctxShared.tip(k) : '';
}
var CKO_CHARTS = [
'cko-chartCumSavings',
'cko-chartDailyActivity',
'cko-chartSavingsRate',
'cko-chartMcpShell',
'cko-chartTaskBreak',
];
function lvlTier(level) {
if (level >= 30) return 'lvl-t4';
if (level >= 20) return 'lvl-t3';
if (level >= 10) return 'lvl-t2';
return 'lvl-t1';
}
function miniGauge(val, color) {
var v = Math.max(0, Math.min(100, Number(val) || 0));
var circ = 100;
var gap = circ - v;
return (
'<div class="stat-gauge">' +
'<svg width="36" height="36" viewBox="0 0 36 36">' +
'<circle class="bg" cx="18" cy="18" r="15.91549430918954" />' +
'<circle class="fg" cx="18" cy="18" r="15.91549430918954" ' +
'stroke="' + color + '" ' +
'stroke-dasharray="' + v + ' ' + gap + '" ' +
'stroke-dashoffset="' + gap + '" />' +
'</svg></div>'
);
}
class CockpitOverview extends HTMLElement {
constructor() {
super();
this._range = 30;
this._sortKey = 'saved';
this._sortDir = 'desc';
this._animTimer = null;
this._animFrame = 0;
this._onRefresh = this._onRefresh.bind(this);
this._onViewChange = this._onViewChange.bind(this);
this._data = null;
this._error = null;
this._loading = true;
}
connectedCallback() {
if (this._ready) return;
this._ready = true;
this.style.display = 'block';
document.addEventListener('lctx:refresh', this._onRefresh);
document.addEventListener('lctx:view', this._onViewChange);
this.render();
this.loadData();
}
disconnectedCallback() {
document.removeEventListener('lctx:refresh', this._onRefresh);
document.removeEventListener('lctx:view', this._onViewChange);
this._stopAnim();
this._destroyCharts();
}
_onViewChange(e) {
var viewId = e && e.detail && e.detail.viewId;
if (viewId !== 'overview') this._stopAnim();
}
_onRefresh() {
var v = document.getElementById('view-overview');
if (v && v.classList.contains('active')) this.loadData();
}
_stopAnim() {
if (this._animTimer) {
clearInterval(this._animTimer);
this._animTimer = null;
}
}
_destroyCharts() {
var Ch = chartsLib();
if (!Ch.destroyIfNeeded) return;
for (var i = 0; i < CKO_CHARTS.length; i++) {
Ch.destroyIfNeeded(CKO_CHARTS[i]);
}
}
async loadData() {
var fetchJson = api();
if (!fetchJson) {
this._error = 'API client not loaded';
this._loading = false;
this.render();
return;
}
this._loading = true;
this._error = null;
this.render();
var paths = [
'/api/stats',
'/api/gain',
'/api/buddy',
'/api/session',
'/api/slos',
'/api/verification',
'/api/graph/stats',
];
var results = await Promise.all(
paths.map(function (p) {
return fetchJson(p, { timeoutMs: 12000 }).catch(function (e) {
return { __error: e && e.error ? e.error : String(e || 'error'), __path: p };
});
})
);
var err = [results[0], results[1]].find(function (x) {
return x && x.__error;
});
if (err) {
this._error = String(err.__path) + ': ' + String(err.__error);
}
function ok(r) {
return r && !r.__error ? r : null;
}
this._data = {
stats: ok(results[0]),
gain: ok(results[1]),
buddy: ok(results[2]),
session: ok(results[3]),
slos: ok(results[4]),
verification: ok(results[5]),
graphStats: ok(results[6]),
};
this._loading = false;
this._stopAnim();
this._destroyCharts();
this.render();
this._renderAllCharts();
this._startBuddyAnim();
}
render() {
var F = fmtLib();
var esc = F.esc || function (s) { return String(s); };
var ff = F.ff || function (n) { return String(n); };
var fmt = F.fmt || function (n) { return String(n); };
var pc = F.pc || function (a, b) { return b > 0 ? Math.round((a / b) * 100) : 0; };
var fu = F.fu || function (a) { return '$' + Number(a).toFixed(2); };
if (this._loading) {
this.innerHTML =
'<div class="card"><div class="loading-state">Loading overview\u2026</div></div>';
return;
}
if (this._error && !this._data.stats) {
this.innerHTML =
'<div class="card"><h3>Error</h3>' +
'<p class="hs" style="color:var(--red)">' +
esc(String(this._error)) +
'</p></div>';
return;
}
var body = '';
body += this._renderTimeFilter(esc);
body += this._renderHero(esc, ff, fmt, fu, pc);
body += this._renderBuddy(esc);
body += this._renderChartsRow1(esc, ff, fu);
body += this._renderHealthRow(esc);
body += this._renderChartsRow2();
body += this._renderCommandTable(esc, ff, fmt, pc);
this.innerHTML = body;
this._bind();
}
_renderTimeFilter(esc) {
var ranges = [
{ label: '7d', val: 7 },
{ label: '30d', val: 30 },
{ label: '90d', val: 90 },
{ label: 'All', val: 0 },
];
var html = '<div class="tf-bar">';
for (var i = 0; i < ranges.length; i++) {
var r = ranges[i];
html +=
'<button type="button" class="tf-btn' +
(this._range === r.val ? ' active' : '') +
'" data-range="' + r.val + '">' +
esc(r.label) + '</button>';
}
html += '</div>';
return html;
}
_renderHero(esc, ff, fmt, fu, pc) {
var stats = this._data.stats;
var gain = this._data.gain;
var totalIn = stats ? stats.total_input_tokens || 0 : 0;
var totalOut = stats ? stats.total_output_tokens || 0 : 0;
var saved = totalIn - totalOut;
var compRate = totalIn > 0 ? pc(saved, totalIn) : 0;
var calls = stats ? stats.total_commands || 0 : 0;
var avoidedUsd = gain && gain.summary ? gain.summary.avoided_usd || 0 : 0;
var scoreTotal = gain && gain.summary && gain.summary.score
? gain.summary.score.total || 0 : 0;
var scoreDash = Math.max(0, Math.min(100, scoreTotal));
var scoreGap = 100 - scoreDash;
var scoreCol = scoreDash >= 80
? 'var(--green)' : scoreDash >= 50
? 'var(--yellow)' : 'var(--red)';
return (
'<div class="hero stagger">' +
'<div class="hero-main">' +
'<span class="hl">Total tokens saved' + tip('total_tokens_saved') + '</span>' +
'<div class="hv" id="cko-vSaved">' + esc(ff(saved)) + '</div>' +
'<p class="hs">' +
'From <b>' + esc(ff(totalIn)) + '</b> input to <b>' +
esc(ff(totalOut)) + '</b> output across <b>' +
esc(ff(calls)) + '</b> calls</p>' +
'</div>' +
'<div class="hc">' +
'<span class="hl">Cost saved' + tip('cost_saved') + '</span>' +
'<div class="hv" style="color:var(--yellow)">' + esc(fu(avoidedUsd)) + '</div>' +
'<p class="hs">estimated API cost avoided</p>' +
'</div>' +
'<div class="hc">' +
'<span class="hl">Compression rate' + tip('compression_rate') + '</span>' +
'<div class="hv" style="color:var(--purple)">' + esc(String(compRate)) + '%</div>' +
'<p class="hs">tokens removed before sending</p>' +
'</div>' +
'<div class="hc">' +
'<span class="hl">Gain score' + tip('gain_score') + '</span>' +
'<div class="gauge-ring" style="width:72px;height:72px">' +
'<svg width="72" height="72" viewBox="0 0 36 36" aria-hidden="true">' +
'<circle class="bg" cx="18" cy="18" r="15.91549430918954" />' +
'<circle class="fg" cx="18" cy="18" r="15.91549430918954" ' +
'stroke="' + scoreCol + '" ' +
'stroke-dasharray="' + scoreDash + ' ' + scoreGap + '" ' +
'stroke-dashoffset="' + scoreGap + '" />' +
'</svg>' +
'<span class="gauge-value" style="font-size:15px">' +
Math.round(scoreTotal) + '</span>' +
'</div>' +
'</div>' +
'<div class="hc">' +
'<span class="hl">Total calls' + tip('total_calls') + '</span>' +
'<div class="hv" style="color:var(--blue)">' + esc(ff(calls)) + '</div>' +
'<p class="hs">' +
(stats && stats.first_use
? 'since ' + esc(String(stats.first_use).slice(0, 10))
: '') +
'</p>' +
'</div>' +
'</div>'
);
}
_renderBuddy(esc) {
var b = this._data.buddy;
if (!b || !b.name) return '';
var rarity = b.rarity || 'Common';
var tier = lvlTier(b.level || 1);
var art = Array.isArray(b.ascii_art) ? b.ascii_art.join('\n') : (b.ascii_art || '');
var xp = b.xp || 0;
var xpNext = b.xp_next_level || 1;
var xpPct = xpNext > 0 ? Math.min(100, Math.round((xp / xpNext) * 100)) : 0;
var mood = b.mood || 'Content';
var statNames = ['CMP', 'VIG', 'END', 'WIS', 'EXP'];
var statKeys = ['compression', 'vigilance', 'endurance', 'wisdom', 'experience'];
var statTipKeys = ['buddy_cmp', 'buddy_vig', 'buddy_end', 'buddy_wis', 'buddy_exp'];
var statColors = [
'var(--green)', 'var(--blue)', 'var(--purple)',
'var(--yellow)', 'var(--pink)',
];
var bStats = b.stats || {};
var statsHtml = '<div class="buddy-stats-grid">';
for (var i = 0; i < statNames.length; i++) {
var val = bStats[statKeys[i]] || 0;
statsHtml +=
'<div class="stat-cell">' +
'<div class="stat-label">' + statNames[i] + tip(statTipKeys[i]) + '</div>' +
miniGauge(val, statColors[i]) +
'<div class="stat-val">' + val + '</div>' +
'</div>';
}
statsHtml += '</div>';
return (
'<div class="buddy-card rarity-' + esc(rarity) + ' ' + tier +
'" style="margin-bottom:20px">' +
'<div class="buddy-sprite rarity-' + esc(rarity) + ' ' + tier + '">' +
'<pre id="cko-buddyArt">' + esc(art) + '</pre>' +
'</div>' +
'<div class="buddy-info">' +
'<div class="buddy-name">' + esc(b.name) +
' <span class="rarity-badge r-' + esc(rarity) + '">' +
esc(rarity) + '</span></div>' +
'<div class="buddy-meta">' +
'<span>' + esc(b.species || '') + '</span>' +
'<span>Lv.' + (b.level || 1) + tip('buddy_level') + '</span>' +
'<span class="mood-dot mood-' + esc(mood) + '"></span>' +
'<span>' + esc(mood) + tip('buddy_mood') + '</span>' +
(b.streak_days != null
? '<span>' + b.streak_days + 'd streak' + tip('buddy_streak') + '</span>'
: '') +
'</div>' +
'<div class="xp-wrap">' +
'<div class="xp-label"><span>XP</span><span>' +
xp + ' / ' + xpNext + '</span></div>' +
'<div class="xp-track"><div class="xp-fill" style="width:' +
xpPct + '%"></div></div>' +
'</div>' +
statsHtml +
(b.speech
? '<div class="buddy-speech">' + esc(b.speech) + '</div>'
: '') +
'</div>' +
'</div>'
);
}
_startBuddyAnim() {
var b = this._data && this._data.buddy;
if (!b) return;
var frames = b.ascii_frames;
if (!frames || !Array.isArray(frames) || frames.length < 2) return;
var ms = b.anim_ms || 500;
var self = this;
this._animFrame = 0;
this._animTimer = setInterval(function () {
self._animFrame = (self._animFrame + 1) % frames.length;
var el = document.getElementById('cko-buddyArt');
if (!el) return;
var frame = frames[self._animFrame];
el.textContent = Array.isArray(frame) ? frame.join('\n') : String(frame);
}, ms);
}
_renderChartsRow1(esc, ff, fu) {
var stats = this._data.stats;
var totalIn = stats ? stats.total_input_tokens || 0 : 0;
var totalOut = stats ? stats.total_output_tokens || 0 : 0;
var calls = stats ? stats.total_commands || 0 : 0;
var F = fmtLib();
var gc = F.gc || function () {
return { iW: 0, iC: 0, oW: 0, oC: 0, tW: 0, tC: 0, sv: 0, os: 0 };
};
var c = gc(totalIn, totalOut, calls);
return (
'<div class="row r21" style="margin-bottom:20px">' +
'<div class="card">' +
'<h3>Cumulative token savings' + tip('cumulative_savings') + '</h3>' +
'<canvas id="cko-chartCumSavings" height="220"' +
' aria-label="Cumulative savings chart"></canvas>' +
'</div>' +
'<div class="card">' +
'<h3>Cost analysis' + tip('cost_analysis') + '</h3>' +
'<div class="cost-row">' +
'<div class="cost-box bad">' +
'<div class="amt" style="color:var(--red)">' +
esc(fu(c.tW)) + '</div>' +
'<div class="lb">Without lean-ctx</div></div>' +
'<div class="cost-arrow">\u2192</div>' +
'<div class="cost-box good">' +
'<div class="amt" style="color:var(--green)">' +
esc(fu(c.tC)) + '</div>' +
'<div class="lb">With lean-ctx</div></div>' +
'</div>' +
'<div class="cost-detail">' +
'<div class="cd-item"><div class="v" style="color:var(--green)">' +
esc(fu(c.sv)) + '</div><div class="l">Total saved</div></div>' +
'<div class="cd-item"><div class="v">' +
esc(fu(c.iW - c.iC)) + '</div><div class="l">Input saved</div></div>' +
'<div class="cd-item"><div class="v">' +
esc(fu(c.oW - c.oC)) + '</div><div class="l">Output saved</div></div>' +
'<div class="cd-item"><div class="v">' +
esc(fu(c.tC)) + '</div><div class="l">Actual cost</div></div>' +
'</div>' +
'</div>' +
'</div>'
);
}
_renderHealthRow(esc) {
var session = this._data.session;
var slos = this._data.slos;
var verif = this._data.verification;
var graph = this._data.graphStats;
var taskDesc = session && session.task
? session.task.description || '\u2014' : '\u2014';
var filesCount = session && session.files_touched
? session.files_touched.length : 0;
var sloSnap = slos && slos.snapshot ? slos.snapshot : null;
var sloArr = sloSnap && Array.isArray(sloSnap.slos) ? sloSnap.slos : [];
var sloTotal = sloArr.length;
var sloPassed = sloArr.filter(function (s) { return !s.violated; }).length;
var sloPct = sloTotal > 0
? Math.round((sloPassed / sloTotal) * 100) : 0;
var sloCol = sloPct >= 80
? 'var(--green)' : sloPct >= 50
? 'var(--yellow)' : 'var(--red)';
var vTotal = verif ? verif.total || 0 : 0;
var vPassed = verif ? verif.pass || 0 : 0;
var vPct = vTotal > 0 ? Math.round((vPassed / vTotal) * 100) : 0;
var vCol = vPct >= 80
? 'var(--green)' : vPct >= 50
? 'var(--yellow)' : 'var(--red)';
var gNodes = graph ? graph.node_count || 0 : 0;
var gEdges = graph ? graph.edge_count || 0 : 0;
var shortTask = taskDesc.length > 40
? taskDesc.slice(0, 40) + '\u2026' : taskDesc;
return (
'<div class="row r4" style="margin-bottom:20px">' +
'<div class="card">' +
'<h3>Session' + tip('session_overview') + '</h3>' +
'<div class="sr"><span class="sl">Task</span>' +
'<span class="sv" title="' + esc(taskDesc) +
'" style="max-width:160px;overflow:hidden;' +
'text-overflow:ellipsis;white-space:nowrap">' +
esc(shortTask) + '</span></div>' +
'<div class="sr"><span class="sl">Files touched</span>' +
'<span class="sv">' + filesCount + '</span></div>' +
(session && session.terse_mode
? '<div class="sr"><span class="sl">Terse mode</span>' +
'<span class="sv"><span class="tag tg">on</span></span></div>'
: '') +
'</div>' +
'<div class="card">' +
'<h3>SLO compliance' + tip('slo_compliance') + '</h3>' +
'<div class="hv" style="font-size:28px;color:' + sloCol + '">' +
sloPct + '%</div>' +
'<div class="sr" style="margin-top:8px">' +
'<span class="sl">Passed</span>' +
'<span class="sv">' + sloPassed + ' / ' + sloTotal + '</span></div>' +
'</div>' +
'<div class="card">' +
'<h3>Verification' + tip('verification') + '</h3>' +
'<div class="hv" style="font-size:28px;color:' + vCol + '">' +
vPct + '%</div>' +
'<div class="sr" style="margin-top:8px">' +
'<span class="sl">Checks</span>' +
'<span class="sv">' + vPassed + ' / ' + vTotal + '</span></div>' +
'</div>' +
'<div class="card">' +
'<h3>Property graph' + tip('property_graph') + '</h3>' +
'<div class="sr"><span class="sl">Nodes</span>' +
'<span class="sv">' + gNodes + '</span></div>' +
'<div class="sr"><span class="sl">Edges</span>' +
'<span class="sv">' + gEdges + '</span></div>' +
'</div>' +
'</div>'
);
}
_renderChartsRow2() {
return (
'<div class="row r4" style="margin-bottom:20px">' +
'<div class="card">' +
'<h3>Daily activity' + tip('daily_activity') + '</h3>' +
'<canvas id="cko-chartDailyActivity" height="200"' +
' aria-label="Daily activity chart"></canvas>' +
'</div>' +
'<div class="card">' +
'<h3>Savings rate' + tip('savings_rate') + '</h3>' +
'<canvas id="cko-chartSavingsRate" height="200"' +
' aria-label="Savings rate chart"></canvas>' +
'</div>' +
'<div class="card">' +
'<h3>MCP vs Shell hook' + tip('mcp_vs_shell') + '</h3>' +
'<canvas id="cko-chartMcpShell" height="180"' +
' aria-label="MCP vs Shell chart"></canvas>' +
'<div id="cko-mcpShellGrid"></div>' +
'</div>' +
'<div class="card">' +
'<h3>Task breakdown' + tip('task_breakdown') + '</h3>' +
'<canvas id="cko-chartTaskBreak" height="180"' +
' aria-label="Task breakdown chart"></canvas>' +
'</div>' +
'</div>'
);
}
_renderCommandTable(esc, ff, fmt, pc) {
var stats = this._data.stats;
var cmds = stats && stats.commands ? stats.commands : {};
var keys = Object.keys(cmds);
if (!keys.length) return '';
var F = fmtLib();
var isM = F.isM || function () { return false; };
var sb = F.sb || function () { return ''; };
var rows = [];
var maxSaved = 0;
for (var i = 0; i < keys.length; i++) {
var name = keys[i];
var s = cmds[name];
var saved = (s.input_tokens || 0) - (s.output_tokens || 0);
if (saved > maxSaved) maxSaved = saved;
rows.push({
name: name,
count: s.count || 0,
input: s.input_tokens || 0,
output: s.output_tokens || 0,
saved: saved,
pct: s.input_tokens > 0 ? pc(saved, s.input_tokens) : 0,
});
}
var sk = this._sortKey;
var dir = this._sortDir === 'desc' ? -1 : 1;
rows.sort(function (a, b) {
var av = a[sk];
var bv = b[sk];
if (typeof av === 'string') av = av.toLowerCase();
if (typeof bv === 'string') bv = bv.toLowerCase();
if (av < bv) return -1 * dir;
if (av > bv) return 1 * dir;
return 0;
});
var sortDir = this._sortDir;
function th(key, label, cls) {
var active = sk === key;
var ind = active ? (sortDir === 'asc' ? ' \u25B2' : ' \u25BC') : ' \u25C7';
return (
'<th class="' + (cls || '') + (active ? ' th-sort-active' : '') +
'" data-cko-sort="' + key +
'" style="cursor:pointer;user-select:none">' +
label + '<span class="sort-ind">' + ind + '</span></th>'
);
}
var trs = '';
for (var j = 0; j < rows.length; j++) {
var r = rows[j];
var barW = maxSaved > 0 ? Math.round((r.saved / maxSaved) * 100) : 0;
trs +=
'<tr>' +
'<td>' + sb(r.name) + ' ' + esc(r.name) + '</td>' +
'<td class="r">' + esc(ff(r.count)) + '</td>' +
'<td class="r">' + esc(fmt(r.input)) + '</td>' +
'<td class="r">' + esc(fmt(r.output)) + '</td>' +
'<td class="r">' + esc(fmt(r.saved)) + '</td>' +
'<td class="r">' + r.pct + '%</td>' +
'<td style="min-width:80px">' +
'<div class="bar-bg"><div class="bar-f" style="width:' +
barW + '%;background:var(--green)"></div></div></td>' +
'</tr>';
}
return (
'<div class="card">' +
'<h3>Command breakdown ' +
'<span class="badge">' + keys.length + ' commands</span>' + tip('command_breakdown') + '</h3>' +
'<div class="table-scroll"><table>' +
'<thead><tr>' +
th('name', 'Command') +
th('count', 'Calls', 'r') +
th('input', 'Input', 'r') +
th('output', 'Output', 'r') +
th('saved', 'Saved', 'r') +
th('pct', 'Rate', 'r') +
'<th>Distribution</th>' +
'</tr></thead>' +
'<tbody>' + trs + '</tbody>' +
'</table></div></div>'
);
}
_renderAllCharts() {
var self = this;
requestAnimationFrame(function () {
try { self._chartCumSavings(); } catch (_) {}
try { self._chartDailyActivity(); } catch (_) {}
try { self._chartSavingsRate(); } catch (_) {}
try { self._chartMcpShell(); } catch (_) {}
try { self._chartTaskBreak(); } catch (_) {}
});
}
_filteredDaily() {
var stats = this._data && this._data.stats;
var daily = stats && Array.isArray(stats.daily) ? stats.daily : [];
var F = fmtLib();
var fd = F.fd || function (d, r) {
return !r || r === 0 ? d : d.slice(-r);
};
return fd(daily, this._range);
}
_chartCumSavings() {
var Ch = chartsLib();
if (!Ch.lineChart || typeof Chart === 'undefined') return;
var daily = this._filteredDaily();
if (!daily.length) return;
var labels = [];
var values = [];
var cum = 0;
for (var i = 0; i < daily.length; i++) {
var d = daily[i];
labels.push(String(d.date || '').slice(5));
cum += (d.input_tokens || 0) - (d.output_tokens || 0);
values.push(cum);
}
Ch.lineChart(
'cko-chartCumSavings', labels, values,
'#34d399', 'rgba(52,211,153,.06)'
);
}
_chartDailyActivity() {
var Ch = chartsLib();
if (!Ch.createChart || typeof Chart === 'undefined') return;
var daily = this._filteredDaily();
if (!daily.length) return;
var labels = [];
var savedArr = [];
var sentArr = [];
for (var i = 0; i < daily.length; i++) {
var d = daily[i];
labels.push(String(d.date || '').slice(5));
var inp = d.input_tokens || 0;
var out = d.output_tokens || 0;
savedArr.push(inp - out);
sentArr.push(out);
}
Ch.createChart('cko-chartDailyActivity', 'bar', {
labels: labels,
datasets: [
{
label: 'Saved',
data: savedArr,
backgroundColor: 'rgba(52,211,153,0.6)',
borderRadius: 3,
},
{
label: 'Sent',
data: sentArr,
backgroundColor: 'rgba(129,140,248,0.4)',
borderRadius: 3,
},
],
}, {
scales: { x: { stacked: true }, y: { stacked: true } },
plugins: {
legend: {
display: true,
position: 'bottom',
labels: {
color: '#6b6b88', font: { size: 9 }, padding: 8,
usePointStyle: true, pointStyle: 'circle',
},
},
},
});
}
_chartSavingsRate() {
var Ch = chartsLib();
if (!Ch.lineChart || typeof Chart === 'undefined') return;
var daily = this._filteredDaily();
if (!daily.length) return;
var labels = [];
var values = [];
for (var i = 0; i < daily.length; i++) {
var d = daily[i];
labels.push(String(d.date || '').slice(5));
var inp = d.input_tokens || 0;
var out = d.output_tokens || 0;
values.push(inp > 0 ? Math.round(((inp - out) / inp) * 100) : 0);
}
Ch.lineChart(
'cko-chartSavingsRate', labels, values,
'#818cf8', 'rgba(129,140,248,.06)'
);
}
_chartMcpShell() {
var Ch = chartsLib();
if (!Ch.doughnutChart || typeof Chart === 'undefined') return;
var stats = this._data && this._data.stats;
if (!stats || !stats.commands) return;
var F = fmtLib();
var ss = F.ss || function () {
return { m: { c: 0, i: 0, o: 0, s: 0 }, h: { c: 0, i: 0, o: 0, s: 0 } };
};
var ff = F.ff || function (n) { return String(n); };
var fmt = F.fmt || function (n) { return String(n); };
var entries = [];
var cmds = stats.commands;
var keys = Object.keys(cmds);
for (var i = 0; i < keys.length; i++) {
entries.push([keys[i], cmds[keys[i]]]);
}
var split = ss(entries);
if (split.m.s + split.h.s > 0) {
Ch.doughnutChart(
'cko-chartMcpShell',
['MCP', 'Shell Hook'],
[split.m.s, split.h.s],
['#818cf8', '#38bdf8']
);
}
var grid = document.getElementById('cko-mcpShellGrid');
if (grid) {
grid.innerHTML =
'<div class="src-grid" style="margin-top:12px">' +
'<div class="src-item">' +
'<h4><span class="d" style="background:var(--purple)"></span> MCP</h4>' +
'<div class="sr"><span class="sl">Calls</span>' +
'<span class="sv">' + ff(split.m.c) + '</span></div>' +
'<div class="sr"><span class="sl">Saved</span>' +
'<span class="sv">' + fmt(split.m.s) + '</span></div>' +
'</div>' +
'<div class="src-item">' +
'<h4><span class="d" style="background:var(--blue)"></span> Shell</h4>' +
'<div class="sr"><span class="sl">Calls</span>' +
'<span class="sv">' + ff(split.h.c) + '</span></div>' +
'<div class="sr"><span class="sl">Saved</span>' +
'<span class="sv">' + fmt(split.h.s) + '</span></div>' +
'</div></div>';
}
}
_chartTaskBreak() {
var Ch = chartsLib();
if (!Ch.doughnutChart || typeof Chart === 'undefined') return;
var gain = this._data && this._data.gain;
var tasks = gain && Array.isArray(gain.tasks) ? gain.tasks : [];
if (!tasks.length) return;
var labels = [];
var values = [];
for (var i = 0; i < tasks.length; i++) {
labels.push(tasks[i].category || 'Other');
values.push(tasks[i].tokens_saved || 0);
}
Ch.doughnutChart('cko-chartTaskBreak', labels, values);
}
_bind() {
var self = this;
this.querySelectorAll('.tf-btn[data-range]').forEach(function (btn) {
btn.addEventListener('click', function () {
var val = parseInt(btn.getAttribute('data-range'), 10);
if (isNaN(val)) val = 0;
self._range = val;
self._stopAnim();
self._destroyCharts();
self.render();
self._renderAllCharts();
self._startBuddyAnim();
});
});
this.querySelectorAll('th[data-cko-sort]').forEach(function (h) {
h.addEventListener('click', function () {
var k = h.getAttribute('data-cko-sort');
if (self._sortKey === k) {
self._sortDir = self._sortDir === 'asc' ? 'desc' : 'asc';
} else {
self._sortKey = k;
self._sortDir = 'desc';
}
self._stopAnim();
self._destroyCharts();
self.render();
self._renderAllCharts();
self._startBuddyAnim();
});
});
var S = sharedLib();
if (S.injectExpandButtons) S.injectExpandButtons(this);
if (S.bindHowItWorks) S.bindHowItWorks(this);
}
}
(function registerOverviewLoader() {
var R = window.LctxRouter;
if (R && R.registerLoader) {
R.registerLoader('overview', function () {
var el = document.querySelector('cockpit-overview');
if (el && typeof el.loadData === 'function') return el.loadData();
});
}
})();
customElements.define('cockpit-overview', CockpitOverview);
export { CockpitOverview };