Files
foodlinkk-command-center/cockpit/static/js/agents-mesh.js
T
Aissa 5d60d33db1 Platform bundle: marketing publish, IT ops, packaging, agents mesh.
Volledige Foodlinkk Command Center uitbreiding met social automatisering,
reclamefolder filters, Proxmox monitoring en documentatie.
2026-06-09 00:41:27 +00:00

110 lines
3.8 KiB
JavaScript

(function () {
const NS = 'http://www.w3.org/2000/svg';
function make(tag, attrs = {}) {
const el = document.createElementNS(NS, tag);
Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, String(v)));
return el;
}
function clear(el) {
while (el.firstChild) el.removeChild(el.firstChild);
}
function curvePath(x1, y1, x2, y2) {
const cx1 = x1 + (x2 - x1) * 0.35;
const cy1 = y1;
const cx2 = x1 + (x2 - x1) * 0.72;
const cy2 = y2;
return `M ${x1} ${y1} C ${cx1} ${cy1}, ${cx2} ${cy2}, ${x2} ${y2}`;
}
async function loadMeshData() {
const r = await fetch('/api/agents/mesh');
if (!r.ok) throw new Error('Mesh API unavailable');
return r.json();
}
function drawMesh(svg, data) {
clear(svg);
const vb = svg.viewBox.baseVal;
const width = vb && vb.width ? vb.width : 1000;
const height = vb && vb.height ? vb.height : 620;
const cx = width / 2;
const cy = height / 2;
const radius = Math.min(width, height) * 0.35;
const souls = (data.nodes || []).filter((n) => (n.agent_key || '').toLowerCase() !== 'herman');
const edgesBySource = {};
(data.edges || []).forEach((e) => { edgesBySource[e.source] = e; });
souls.forEach((node, i) => {
const a = (Math.PI * 2 * i) / Math.max(1, souls.length) - Math.PI / 2;
node._x = cx + Math.cos(a) * radius;
node._y = cy + Math.sin(a) * radius;
});
const edgesLayer = make('g');
const nodesLayer = make('g');
svg.appendChild(edgesLayer);
svg.appendChild(nodesLayer);
souls.forEach((node) => {
const pathDef = curvePath(node._x, node._y, cx, cy);
const edge = make('path', { d: pathDef, class: 'mesh-edge' });
edgesLayer.appendChild(edge);
if (edgesBySource[node.agent_key]) {
const pulse = make('path', { d: pathDef, class: 'mesh-pulse' });
pulse.style.animationDuration = `${Math.max(1.2, 3.5 - Math.min(2.2, edgesBySource[node.agent_key].weight / 8))}s`;
edgesLayer.appendChild(pulse);
}
});
const herman = make('g', { class: 'mesh-node mesh-herman' });
herman.appendChild(make('circle', { class: 'ring', cx, cy, r: 62 }));
herman.appendChild(make('circle', { class: 'main', cx, cy, r: 46 }));
const crown = make('text', { x: cx, y: cy - 2, 'font-size': 24, 'text-anchor': 'middle' });
crown.textContent = '👑';
herman.appendChild(crown);
const label = make('text', { x: cx, y: cy + 22 });
label.textContent = 'Herman · Co-CEO';
herman.appendChild(label);
nodesLayer.appendChild(herman);
souls.forEach((node) => {
const group = make('g', { class: `mesh-node ${node.health || 'idle'}` });
group.appendChild(make('circle', { cx: node._x, cy: node._y, r: 24 }));
const shortName = (node.display_name || node.agent_key || '?').split(' ')[0];
const t = make('text', { x: node._x, y: node._y + 4 });
t.textContent = shortName;
group.appendChild(t);
const role = make('text', { x: node._x, y: node._y + 42, 'font-size': 10, opacity: 0.82 });
role.textContent = node.role_title || node.agent_key;
group.appendChild(role);
group.addEventListener('click', () => {
if (window.Cockpit && Cockpit.toast) {
Cockpit.toast(`${node.display_name || node.agent_key}: ${node.health || 'idle'}`, 'success');
}
});
nodesLayer.appendChild(group);
});
}
async function mount(targetId) {
const svg = document.getElementById(targetId);
if (!svg) return;
try {
const data = await loadMeshData();
drawMesh(svg, data);
} catch (e) {
clear(svg);
const msg = make('text', { x: 32, y: 40, fill: '#ef4444', 'font-size': 14 });
msg.textContent = 'Mesh kon niet geladen worden';
svg.appendChild(msg);
}
}
window.AgentsMesh = { mount };
})();