/**
* Verbatim port of the v1 budget-panel client JS (old src/server/board.ts,
* lines 1466–1750). Kept byte-identical on purpose: session/total baseline
* logic, donut signature throttling and the reset handler are battle-tested.
*
* Depends on page-scope helpers provided by the columns/data-layer port:
* esc, getJSON, hashColor, agentSpec, toast. Mount points expected in the DOM:
* #budgetRows, #budgetTotal, #budgetReset, [data-budget-mode].
*/
export const v1BudgetJs = `
// ── Roster ──────────────────────────────────────────────────────────────
// AGENTS backs the budget panel + resolves a title's "name:" prefix to a
// known agent when the architect drags a card into In Progress.
var AGENTS = [];
var BUDGET = null;
var budgetMode = localStorage.getItem('agenthub-budget-mode') || 'session';
var donutMetric = localStorage.getItem('agenthub-donut-metric') || 'tokens';
var lastDonutSignature = '';
var lastDonutAt = 0;
var DONUT_REFRESH_MS = 180000;
async function loadAgents() {
try {
AGENTS = await getJSON('/agents');
} catch (_) { AGENTS = []; }
}
// ── Budget panel ────────────────────────────────────────────────────────
function fmtTokens(n) {
n = Number(n) || 0;
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k';
return String(Math.round(n));
}
function fmtEur(n) { return '\\u20ac' + (Number(n) || 0).toFixed(2); }
function maxTok(list) { return list.reduce(function(m, a) { return Math.max(m, a.tokens || 0); }, 0); }
function cloneAgent(a) {
return {
name: a.name,
role: a.role,
model: a.model,
kind: a.kind,
tokens: Number(a.tokens) || 0,
realTokens: Number(a.realTokens) || 0,
estimatedTokens: Number(a.estimatedTokens) || 0,
estimated: !!a.estimated,
costEur: Number(a.costEur) || 0,
taskCount: Number(a.taskCount) || 0
};
}
function currentSnapshot(rep) {
var byAgent = {};
(rep && rep.agents ? rep.agents : []).forEach(function(a) {
byAgent[a.name] = { tokens: Number(a.tokens) || 0, costEur: Number(a.costEur) || 0 };
});
return { createdAt: Date.now(), byAgent: byAgent };
}
function readBaseline() {
try { return JSON.parse(localStorage.getItem('agenthub-budget-baseline') || 'null'); } catch (_) { return null; }
}
function writeBaseline(rep) {
localStorage.setItem('agenthub-budget-baseline', JSON.stringify(currentSnapshot(rep)));
}
function applyBudgetMode(rep) {
var agents = (rep && rep.agents ? rep.agents : []).map(cloneAgent);
if (budgetMode === 'session') {
var base = readBaseline();
if (!base) { writeBaseline(rep); base = readBaseline(); }
agents.forEach(function(a) {
var b = base && base.byAgent ? base.byAgent[a.name] : null;
var baseTokens = b ? Number(b.tokens) || 0 : 0;
var baseCost = b ? Number(b.costEur) || 0 : 0;
a.tokens = Math.max(0, a.tokens - baseTokens);
a.costEur = Math.max(0, a.costEur - baseCost);
a.realTokens = Math.min(a.realTokens, a.tokens);
a.estimatedTokens = Math.max(0, a.tokens - a.realTokens);
a.estimated = a.estimated && a.estimatedTokens > 0;
});
}
var totals = {
tokens: agents.reduce(function(s, a) { return s + (a.tokens || 0); }, 0),
costEur: agents.reduce(function(s, a) { return s + (a.costEur || 0); }, 0),
estimated: agents.some(function(a) { return a.estimated; })
};
return { agents: agents, totals: totals };
}
function companySpec(kind) {
var key = String(kind || '').toLowerCase();
var map = {
anthropic: { name: 'Anthropic', color: '#D97757' },
openai: { name: 'OpenAI', color: '#10A37F' },
moonshot: { name: 'Moonshot', color: '#7C3AED' }
};
return map[key] || { name: key ? key.charAt(0).toUpperCase() + key.slice(1) : 'Unknown', color: hashColor(key || 'unknown') };
}
function donutPolar(cx, cy, r, angleDeg) {
var rad = angleDeg * Math.PI / 180;
return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
}
function donutArc(cx, cy, r, startDeg, endDeg) {
var start = donutPolar(cx, cy, r, startDeg);
var end = donutPolar(cx, cy, r, endDeg);
var largeArc = endDeg - startDeg > 180 ? 1 : 0;
return 'M ' + start.x.toFixed(2) + ' ' + start.y.toFixed(2) + ' A ' + r + ' ' + r + ' 0 ' + largeArc + ' 1 ' + end.x.toFixed(2) + ' ' + end.y.toFixed(2);
}
function aggregateByCompany(agents, field) {
var byKind = {};
(agents || []).forEach(function(a) {
var kind = String(a.kind || '').toLowerCase();
if (!kind || !(a.tokens > 0)) return;
var spec = companySpec(kind);
if (!byKind[kind]) byKind[kind] = { kind: kind, name: spec.name, color: spec.color, value: 0 };
byKind[kind].value += Number(a[field]) || 0;
});
return Object.keys(byKind).map(function(k) { return byKind[k]; }).sort(function(a, b) { return b.value - a.value; });
}
function halfDonut(title, rows, centerValue, centerLabel, formatValue) {
var total = rows.reduce(function(s, x) { return s + (x.value || 0); }, 0);
var safeTotal = Math.max(1, total);
var cx = 110, cy = 122, r = 90, stroke = 18;
var angle = 180;
var paths = '