5d60d33db1
Volledige Foodlinkk Command Center uitbreiding met social automatisering, reclamefolder filters, Proxmox monitoring en documentatie.
193 lines
11 KiB
HTML
193 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
{% block extra_head %}
|
|
<link rel="stylesheet" href="/static/css/hermes.css" />
|
|
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=5" />
|
|
<link rel="stylesheet" href="/static/css/agents-mesh.css" />
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="hm-neo" x-data="agentsSoulHub()" x-init="init()">
|
|
<header class="hm-neo-header page-header">
|
|
<h1>Agents · Soul Registry</h1>
|
|
<p class="hm-sub">Animated team · duidelijke rollen · soul.md · live activiteit</p>
|
|
</header>
|
|
|
|
<div class="vtabs-layout" style="--vt-color:#ffd700" x-ref="vtabsWrap">
|
|
<nav class="vtabs-nav" x-ref="vtabsNav" :style="'--vt-indicator-top:'+tabIndicatorTop+'px;--vt-indicator-h:'+tabIndicatorH+'px'">
|
|
<button class="vtab-btn" :class="{active: tab==='souls'}" @click="setTab('souls', $el)">
|
|
<span class="vtab-icon">🧬</span><span>Souls</span>
|
|
</button>
|
|
<button class="vtab-btn" :class="{active: tab==='mesh'}" @click="setTab('mesh', $el)">
|
|
<span class="vtab-icon">🕸️</span><span>Netwerk</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<div class="vtabs-content">
|
|
<section x-show="tab==='souls'" x-cloak>
|
|
<div class="hm-neo-kpi-row" style="grid-template-columns:repeat(4,1fr)">
|
|
<div class="hm-neo-kpi"><div class="hm-neo-kpi-top"><div class="hm-neo-ring" style="--ring-color:#00e5ff;--ring-pct:80%"><div class="hm-neo-ring-inner">🤖</div></div><div><div class="hm-neo-kpi-label">Agents</div><div class="hm-neo-kpi-sub">geregistreerd</div></div></div><div class="hm-neo-kpi-value" x-text="souls.length">0</div><div class="hm-eq"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div></div>
|
|
<div class="hm-neo-kpi"><div class="hm-neo-kpi-top"><div class="hm-neo-ring" style="--ring-color:#b8ff3c;--ring-pct:70%"><div class="hm-neo-ring-inner">⚡</div></div><div><div class="hm-neo-kpi-label">Events</div><div class="hm-neo-kpi-sub">totaal gelogd</div></div></div><div class="hm-neo-kpi-value" x-text="totalEvents">0</div><div class="hm-eq"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div></div>
|
|
<div class="hm-neo-kpi"><div class="hm-neo-kpi-top"><div class="hm-neo-ring" style="--ring-color:#ff9f43;--ring-pct:50%"><div class="hm-neo-ring-inner">✓</div></div><div><div class="hm-neo-kpi-label">Actief</div><div class="hm-neo-kpi-sub">agents online</div></div></div><div class="hm-neo-kpi-value" x-text="activeCount">0</div><div class="hm-eq"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div></div>
|
|
<div class="hm-neo-kpi"><div class="hm-neo-kpi-top"><div class="hm-neo-ring" style="--ring-color:#ffd700;--ring-pct:90%"><div class="hm-neo-ring-inner">👑</div></div><div><div class="hm-neo-kpi-label">Herman</div><div class="hm-neo-kpi-sub">orchestrator</div></div></div><div class="hm-neo-kpi-value">CEO</div><div class="hm-eq"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div></div>
|
|
</div>
|
|
|
|
<div class="agents-neo-grid">
|
|
<template x-for="s in souls" :key="s.agent_key">
|
|
<article class="agent-soul-card" :class="{ selected: selected?.agent_key === s.agent_key }" @click="select(s.agent_key)">
|
|
<div class="hm-neo-kpi-top">
|
|
<div class="agent-avatar-wrap">
|
|
<div class="agent-avatar" :style="'--av-color:' + (s.avatar_color || '#00e5ff')">
|
|
<div class="agent-face" :class="[avatarStyle(s.agent_key), 'mood-' + (s.avatar_mood || 'happy')]">
|
|
<span class="hair" x-show="hasHair(s.agent_key)"></span>
|
|
<span class="glasses" x-show="hasGlasses(s.agent_key)"></span>
|
|
<span class="eye left"></span>
|
|
<span class="eye right"></span>
|
|
<span class="mouth"></span>
|
|
</div>
|
|
</div>
|
|
<span class="agent-status-dot" x-show="s.is_active !== false"></span>
|
|
</div>
|
|
<div style="flex:1">
|
|
<strong x-text="s.display_name"></strong>
|
|
<div class="hm-neo-kpi-sub" x-text="s.role_title"></div>
|
|
<span class="agent-tag" x-text="s.agent_key"></span>
|
|
</div>
|
|
</div>
|
|
<p class="agent-role-desc" x-text="s.responsibilities || 'Geen beschrijving'"></p>
|
|
<div class="agent-meta-row">
|
|
<small x-text="(s.event_count||0) + ' events'"></small>
|
|
<small x-text="s.last_event_at ? 'Laatst: '+s.last_event_at.substring(0,10) : 'Nog geen activiteit'"></small>
|
|
</div>
|
|
<div class="hm-eq" style="margin-top:0.5rem"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>
|
|
</article>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="agent-soul-editor" x-show="selected" x-cloak>
|
|
<div class="agent-editor-header">
|
|
<div class="agent-avatar-wrap">
|
|
<div class="agent-avatar" :style="'--av-color:' + (selected?.avatar_color || '#00e5ff')">
|
|
<div class="agent-face" :class="[avatarStyle(selected?.agent_key), 'mood-' + (selected?.avatar_mood || 'happy')]">
|
|
<span class="hair" x-show="hasHair(selected?.agent_key)"></span>
|
|
<span class="glasses" x-show="hasGlasses(selected?.agent_key)"></span>
|
|
<span class="eye left"></span>
|
|
<span class="eye right"></span>
|
|
<span class="mouth"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div style="flex:1">
|
|
<h2><span x-text="selected?.display_name"></span> — <span x-text="selected?.role_title"></span></h2>
|
|
<p style="margin:0.25rem 0 0;color:#94a3b8;font-size:0.85rem" x-text="selected?.responsibilities"></p>
|
|
</div>
|
|
<div style="display:flex;gap:0.5rem;flex-shrink:0">
|
|
<button class="btn btn-pulse-green" @click="save()" :disabled="saving">Opslaan</button>
|
|
<a href="/settings?tab=permissions" class="btn btn-pulse-purple">Herman rechten →</a>
|
|
</div>
|
|
</div>
|
|
<label>Rol titel<input class="form-input" x-model="form.role_title"></label>
|
|
<label>Functie & werkzaamheden<textarea class="form-input" rows="3" x-model="form.responsibilities"></textarea></label>
|
|
<label>Soul.md (karakter, tone of voice, regels)<textarea x-model="form.soul_md"></textarea></label>
|
|
<h3 style="font-size:0.75rem;text-transform:uppercase;color:#64748b;margin-top:1rem">Recente activiteit</h3>
|
|
<template x-for="ev in (selected?.recent_events||[])" :key="ev.id">
|
|
<div class="hub-feed-item"><span x-text="ev.created_at?.substring(0,16)"></span> <span x-text="ev.title||ev.event_type"></span></div>
|
|
</template>
|
|
<p x-show="!(selected?.recent_events||[]).length" class="muted">Nog geen events voor deze agent.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section x-show="tab==='mesh'" x-cloak>
|
|
<div class="panel">
|
|
<h2 style="margin-top:0;">Netwerk</h2>
|
|
<p class="page-tip" style="margin-top:.25rem;">Communication mesh met Herman als centrale Co-CEO hub.</p>
|
|
<div class="mesh-wrap">
|
|
<svg id="agents-mesh-canvas" class="mesh-svg" viewBox="0 0 1100 640"></svg>
|
|
</div>
|
|
<div class="mesh-legend">
|
|
<span>Groen = healthy</span>
|
|
<span>Oranje = waarschuwing</span>
|
|
<span>Blauw = idle</span>
|
|
<span>Grijs = offline</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
{% block scripts %}
|
|
<script src="/static/js/agents-mesh.js"></script>
|
|
<script>
|
|
function agentsSoulHub() {
|
|
return {
|
|
tab: '{{ active_tab }}',
|
|
tabIndicatorTop: 0,
|
|
tabIndicatorH: 40,
|
|
meshLoaded: false,
|
|
souls: [], selected: null, form: {}, saving: false,
|
|
get totalEvents() { return this.souls.reduce((a,s) => a + (s.event_count||0), 0); },
|
|
get activeCount() { return this.souls.filter(s => s.is_active !== false).length; },
|
|
avatarStyle(key) {
|
|
const m = {
|
|
herman: 'style-male-glasses', design: 'style-female-long', marketing: 'style-female-bob',
|
|
retail: 'style-male-short', research: 'style-male-glasses', sales: 'style-male-curl',
|
|
hermes: 'style-male-short', finance: 'style-male-glasses', ops: 'style-male-short',
|
|
quality: 'style-female-bob', logistics: 'style-male-curl', legal: 'style-male-glasses',
|
|
};
|
|
return m[key] || 'style-male-short';
|
|
},
|
|
hasHair(key) { return !!key; },
|
|
hasGlasses(key) { return ['herman','research','finance','legal'].includes(key); },
|
|
async init() {
|
|
await this.load();
|
|
this.$nextTick(() => {
|
|
const activeBtn = this.$refs.vtabsNav?.querySelector('.vtab-btn.active');
|
|
if (activeBtn) this.updateTabIndicator(activeBtn);
|
|
if (this.tab === 'mesh') this.mountMesh();
|
|
});
|
|
},
|
|
updateTabIndicator(el) {
|
|
if (!el || !this.$refs.vtabsNav) return;
|
|
const navRect = this.$refs.vtabsNav.getBoundingClientRect();
|
|
const btnRect = el.getBoundingClientRect();
|
|
this.tabIndicatorTop = Math.max(0, btnRect.top - navRect.top);
|
|
this.tabIndicatorH = btnRect.height || 40;
|
|
},
|
|
setTab(next, el) {
|
|
this.tab = next;
|
|
this.updateTabIndicator(el);
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.set('tab', next);
|
|
window.history.replaceState({}, '', url.toString());
|
|
if (next === 'mesh') this.mountMesh();
|
|
},
|
|
mountMesh() {
|
|
if (this.meshLoaded || !window.AgentsMesh) return;
|
|
this.meshLoaded = true;
|
|
window.AgentsMesh.mount('agents-mesh-canvas');
|
|
},
|
|
async load() {
|
|
const r = await fetch('/api/agents/souls').then(x => x.json());
|
|
this.souls = r.items || [];
|
|
if (this.souls.length && !this.selected) this.select(this.souls[0].agent_key);
|
|
},
|
|
async select(key) {
|
|
const r = await fetch('/api/agents/souls/' + key).then(x => x.json());
|
|
this.selected = r.soul;
|
|
this.form = { role_title: r.soul.role_title, responsibilities: r.soul.responsibilities, soul_md: r.soul.soul_md };
|
|
},
|
|
async save() {
|
|
if (!this.selected) return;
|
|
this.saving = true;
|
|
try {
|
|
await Cockpit.api('/api/agents/souls/' + this.selected.agent_key, { method: 'PUT', body: JSON.stringify(this.form) });
|
|
Cockpit.toast('Soul opgeslagen', 'success');
|
|
await this.load();
|
|
await this.select(this.selected.agent_key);
|
|
} catch (e) { Cockpit.toast(e.message, 'error'); }
|
|
this.saving = false;
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|