function repeatedSeries(count, value) {
var pts = []; for (var i = 0; i < count; i++) pts.push(value); return pts;
}
var PROVIDER_COLORS = { anthropic: '#f59e0b', openai: '#22c55e', google: '#06b6d4', moonshot: '#a78bfa', openrouter: '#ec4899', ollama: '#8b5cf6', 'ollama-gpu': '#7c3aed', mistral: '#f97316', deepseek: '#14b8a6', groq: '#ef4444', xai: '#64748b', together: '#84cc16', 'docker-model-runner': '#6366f1', 'llama-cpp': '#d946ef' };
var PROVIDERS = ['anthropic', 'openai', 'google'];
var sparklineId = 0;
function renderSparkCanvas(series, opts) {
opts = opts || {};
var id = 'spark-' + (++sparklineId);
var color = opts.color || '#c180ff';
var fillFrom = opts.fillFrom != null ? opts.fillFrom : 0.25;
var lineWidth = opts.lineWidth || 2;
var height = opts.height || 64;
var axisTopLabel = opts.axisTopLabel || null;
var axisBottomLabel = opts.axisBottomLabel || null;
setTimeout(function() {
var canvas = document.getElementById(id); if (!canvas) return;
var dpr = window.devicePixelRatio || 1;
var rect = canvas.parentElement ? canvas.parentElement.getBoundingClientRect() : { width: 340 };
var W = rect.width, H = height;
canvas.width = W * dpr; canvas.height = H * dpr;
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
var ctx = canvas.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
var min = opts.min != null ? Number(opts.min) : Math.min.apply(null, series);
var max = opts.max != null ? Number(opts.max) : Math.max.apply(null, series);
if (!isFinite(min)) min = 0;
if (!isFinite(max)) max = 1;
if (max < min) { var t = max; max = min; min = t; }
var range = max - min || 1; var padTop = 6, padBot = 4, usableH = H - padTop - padBot;
function toX(i) { return (i / (series.length - 1)) * W; }
function toY(v) {
var n = (v - min) / range;
if (n < 0) n = 0;
if (n > 1) n = 1;
return padTop + usableH - n * usableH;
}
var grad = ctx.createLinearGradient(0, padTop, 0, H);
grad.addColorStop(0, color + Math.round(fillFrom * 255).toString(16).padStart(2, '0'));
grad.addColorStop(1, color + '00');
ctx.beginPath(); ctx.moveTo(toX(0), H);
for (var i = 0; i < series.length; i++) {
if (i === 0) ctx.lineTo(toX(0), toY(series[0]));
else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(series[i-1]), cx1, toY(series[i]), toX(i), toY(series[i])); }
}
ctx.lineTo(W, H); ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
ctx.beginPath();
for (var i = 0; i < series.length; i++) {
if (i === 0) ctx.moveTo(toX(0), toY(series[0]));
else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(series[i-1]), cx1, toY(series[i]), toX(i), toY(series[i])); }
}
ctx.strokeStyle = color; ctx.lineWidth = lineWidth; ctx.stroke();
var lastX = toX(series.length - 1), lastY = toY(series[series.length - 1]);
ctx.fillStyle = color; ctx.beginPath(); ctx.arc(lastX, lastY, 3, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#060e20'; ctx.beginPath(); ctx.arc(lastX, lastY, 1.5, 0, Math.PI * 2); ctx.fill();
}, 0);
if (axisTopLabel || axisBottomLabel) {
var top = axisTopLabel ? '<span class="cc-chart-axis top">' + esc(axisTopLabel) + '</span>' : '';
var bottom = axisBottomLabel ? '<span class="cc-chart-axis bottom">' + esc(axisBottomLabel) + '</span>' : '';
return '<div class="cc-chart-wrap"><canvas id="' + id + '" class="cc-chart"></canvas>' + top + bottom + '</div>';
}
return '<canvas id="' + id + '" class="cc-chart"></canvas>';
}
var stackedId = 0;
function renderStackedArea(seriesMap, keys, colors, opts) {
opts = opts || {};
var id = 'stacked-' + (++stackedId);
var height = opts.height || 180;
var showLabels = opts.labels !== false;
setTimeout(function() {
var canvas = document.getElementById(id); if (!canvas) return;
var dpr = window.devicePixelRatio || 1;
var rect = canvas.parentElement ? canvas.parentElement.getBoundingClientRect() : { width: 600 };
var W = rect.width, H = height;
canvas.width = W * dpr; canvas.height = H * dpr;
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
var ctx = canvas.getContext('2d'); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
var len = seriesMap[keys[0]].length;
var padTop = 8, padBot = showLabels ? 22 : 6, padLeft = opts.yAxis ? 48 : 6, padRight = 20;
var chartW = W - padLeft - padRight, chartH = H - padTop - padBot;
var stacked = [];
for (var i = 0; i < len; i++) {
var cum = 0, layers = [];
for (var k = 0; k < keys.length; k++) { var val = seriesMap[keys[k]][i]; layers.push({ bottom: cum, top: cum + val, key: keys[k] }); cum += val; }
stacked.push({ layers: layers, total: cum });
}
var maxTotal = opts.fixedMax != null ? opts.fixedMax : Math.max.apply(null, stacked.map(function(s) { return s.total; }));
if (maxTotal === 0) maxTotal = 1;
function toX(i) { return padLeft + (i / (len - 1)) * chartW; }
function toY(v) { return padTop + chartH - (v / maxTotal) * chartH; }
for (var k = keys.length - 1; k >= 0; k--) {
var color = colors[keys[k]] || '#9baad6';
var grad = ctx.createLinearGradient(0, padTop, 0, H - padBot);
grad.addColorStop(0, color + '55'); grad.addColorStop(1, color + '08');
ctx.beginPath(); ctx.moveTo(toX(0), toY(0));
for (var i = 0; i < len; i++) {
var topVal = stacked[i].layers[k].top;
if (i === 0) ctx.lineTo(toX(0), toY(topVal));
else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(stacked[i-1].layers[k].top), cx1, toY(topVal), toX(i), toY(topVal)); }
}
ctx.lineTo(toX(len - 1), toY(0)); ctx.closePath(); ctx.fillStyle = grad; ctx.fill();
ctx.beginPath();
for (var i = 0; i < len; i++) {
var topVal = stacked[i].layers[k].top;
if (i === 0) ctx.moveTo(toX(0), toY(topVal));
else { var cx1 = (toX(i-1)+toX(i))/2; ctx.bezierCurveTo(cx1, toY(stacked[i-1].layers[k].top), cx1, toY(topVal), toX(i), toY(topVal)); }
}
ctx.strokeStyle = color; ctx.lineWidth = 1.5; ctx.stroke();
}
if (opts.yAxis) {
ctx.fillStyle = '#9baad6'; ctx.font = '9px ui-monospace, monospace'; ctx.textAlign = 'right';
var steps = 4;
for (var s = 0; s <= steps; s++) {
var val = (maxTotal / steps) * s, y = toY(val);
ctx.fillText(opts.yFormat ? opts.yFormat(val) : val.toFixed(2), padLeft - 6, y + 3);
if (s > 0 && s < steps) { ctx.strokeStyle = 'rgba(113,113,122,0.1)'; ctx.lineWidth = 0.5; ctx.beginPath(); ctx.moveTo(padLeft, y); ctx.lineTo(W - padRight, y); ctx.stroke(); }
}
}
if (showLabels) {
ctx.fillStyle = '#9baad6'; ctx.font = '9px ui-monospace, monospace'; ctx.textAlign = 'center';
var labelStep = Math.max(1, Math.floor(len / 6));
for (var i = 0; i < len; i += labelStep) { var label = opts.xLabels ? opts.xLabels[i] : (i + 'h'); ctx.fillText(label, toX(i), H - 4); }
}
}, 0);
return '<canvas id="' + id + '" style="width:100%;height:' + height + 'px;display:block"></canvas>';
}
function deltaHtml(current, previous) {
if (previous === 0) return '<span class="cc-delta flat">—</span>';
var pct = ((current - previous) / Math.abs(previous) * 100);
var dir = pct > 1 ? 'up' : pct < -1 ? 'down' : 'flat';
var arrow = dir === 'up' ? '\u2191' : dir === 'down' ? '\u2193' : '\u2192';
return '<span class="cc-delta ' + dir + '">' + arrow + ' ' + Math.abs(pct).toFixed(1) + '%</span>';
}
function formatUptime(sec) {
if (sec == null) return '\u2014';
sec = Math.floor(sec);
var d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60);
if (d > 0) return d + 'd ' + h + 'h ' + m + 'm';
if (h > 0) return h + 'h ' + m + 'm';
return m + 'm';
}
function formatTimestampLabel(value) {
if (!value) return '\u2014';
var normalized = String(value).replace(' ', 'T');
if (!/[zZ]|[+-]\d\d:\d\d$/.test(normalized)) normalized += 'Z';
var parsed = new Date(normalized);
if (Number.isNaN(parsed.getTime())) return String(value);
return parsed.toLocaleString();
}
function seriesLast(s) { return s[s.length - 1]; }
function seriesPrev(s) { return s[s.length - 2] || s[0]; }
function seriesAvg(s) { return s.reduce(function(a, b) { return a + b; }, 0) / s.length; }
function seriesMax(s) { return Math.max.apply(null, s); }
var overviewRefreshTimer = null;
var modelsRefreshTimer = null;
var FLEET_HISTORY_MAX_POINTS = 40;
var _fleetHistory = { labels: [], byAgent: {} };
function pushFleetSnapshot(agents, activityByAgent) {
var now = new Date();
var hh = String(now.getHours()).padStart(2, '0');
var mm = String(now.getMinutes()).padStart(2, '0');
var label = hh + ':' + mm;
_fleetHistory.labels.push(label);
if (_fleetHistory.labels.length > FLEET_HISTORY_MAX_POINTS) _fleetHistory.labels.shift();
var activeKeys = {};
agents.forEach(function(a) {
var k = a.name || a.id;
activeKeys[k] = true;
if (!_fleetHistory.byAgent[k]) _fleetHistory.byAgent[k] = [];
_fleetHistory.byAgent[k].push(activityByAgent[k] || 0);
if (_fleetHistory.byAgent[k].length > FLEET_HISTORY_MAX_POINTS) _fleetHistory.byAgent[k].shift();
});
Object.keys(_fleetHistory.byAgent).forEach(function(k) {
if (!activeKeys[k]) delete _fleetHistory.byAgent[k];
});
var len = _fleetHistory.labels.length;
Object.keys(_fleetHistory.byAgent).forEach(function(k) {
while (_fleetHistory.byAgent[k].length < len) _fleetHistory.byAgent[k].unshift(0);
});
}
function startOverviewRefresh(app) {
if (overviewRefreshTimer) clearInterval(overviewRefreshTimer);
overviewRefreshTimer = setInterval(function() {
if (!app || app.page !== 'overview') return;
app.refreshOverview();
}, 7000);
}
function stopModelsBackgroundRefresh() {
if (modelsRefreshTimer) {
clearInterval(modelsRefreshTimer);
modelsRefreshTimer = null;
}
}
function startModelsBackgroundRefresh(app) {
stopModelsBackgroundRefresh();
modelsRefreshTimer = setInterval(function() {
if (!app || !app._loadAvailableModels) return;
app._loadAvailableModels({
forceRefresh: true,
cacheTtlMs: 0,
timeoutMs: 700,
nonBlocking: true,
validationLevel: 'zero'
}).catch(function() {});
}, 180000);
}
var workspaceRefreshTimer = null;
function stopWorkspaceRefresh() {
if (workspaceRefreshTimer) {
clearInterval(workspaceRefreshTimer);
workspaceRefreshTimer = null;
}
}
function startWorkspaceRefresh(app) {
stopWorkspaceRefresh();
workspaceRefreshTimer = setInterval(function() {
if (app.page !== 'workspace' || !workspace || !workspace.applySnapshot) return;
api('/api/workspace/state')
.then(function(data) {
_cachedWorkspace = data;
workspace.applySnapshot(data);
updateWorkspaceStatusPanel(data);
})
.catch(function() {});
}, 3000);
}