SysOps: approved_backup — 2026-08-16 11:01 UTC
This commit is contained in:
@@ -5,17 +5,23 @@
|
||||
<link rel="stylesheet" href="/static/css/herman-dashboard.css?v=13" />
|
||||
<link rel="stylesheet" href="/static/css/agents-mesh.css?v=8" />
|
||||
<link rel="stylesheet" href="/static/css/agent-characters.css?v=4" />
|
||||
<script src="/static/js/agent-avatars.js?v=3"></script>
|
||||
<link rel="stylesheet" href="/static/css/agents-warroom.css?v=3" />
|
||||
<script src="/static/js/agent-avatars.js?v=4"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="hm-neo" x-data="agentsSoulHub()" x-init="init()">
|
||||
<header class="hm-neo-header page-header">
|
||||
<header class="hm-neo-header page-header" x-show="tab!=='warroom'" x-cloak>
|
||||
<h1>Agents · Soul Registry</h1>
|
||||
<p class="hm-sub">Animated team · duidelijke rollen · soul.md · live activiteit</p>
|
||||
</header>
|
||||
<header class="hm-neo-header page-header wr-page-header" x-show="tab==='warroom'" x-cloak>
|
||||
<h1>War Room</h1>
|
||||
<p class="hm-sub">Teamchat · @mentions · 1:1 DM · pop-out voor tweede scherm</p>
|
||||
</header>
|
||||
|
||||
<div id="viz-toolbar" class="viz-toolbar"></div>
|
||||
<div class="hm-chart-panel page-viz-panel viz-mode-chart agents-viz-compact" style="margin-bottom:1rem">
|
||||
<div id="viz-toolbar" class="viz-toolbar" x-show="tab!=='warroom'" x-cloak></div>
|
||||
<div class="hm-chart-panel page-viz-panel viz-mode-chart agents-viz-compact" style="margin-bottom:1rem"
|
||||
x-show="tab!=='warroom'" x-cloak>
|
||||
<div class="hm-chart-head"><h4>Agent activiteit (events)</h4><small>5 visualisatie-modi</small></div>
|
||||
<div class="page-viz-body">
|
||||
<div class="hm-chart-wrap page-viz-canvas"><canvas id="chart-agents-viz"></canvas></div>
|
||||
@@ -39,6 +45,9 @@
|
||||
<span class="vtab-icon">✓</span><span>Goedkeuringen</span>
|
||||
<span class="vtab-badge" x-show="pendingCount" x-text="pendingCount"></span>
|
||||
</button>
|
||||
<button class="vtab-btn" :class="{active: tab==='warroom'}" @click="setTab('warroom', $el)" data-tab="warroom">
|
||||
<span class="vtab-icon">⚔️</span><span>War Room</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="vtabs-content">
|
||||
@@ -262,6 +271,82 @@
|
||||
<p x-show="!items.length" class="muted">Geen approval requests.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section x-show="tab==='warroom'" x-cloak x-data="warRoomHub()" x-init="init()">
|
||||
<div class="panel" style="padding:0.75rem">
|
||||
<div class="wr-layout">
|
||||
<aside class="wr-roster">
|
||||
<h3>Kanalen</h3>
|
||||
<button type="button" class="wr-room-btn" :class="{active: room && room.kind==='warroom'}" @click="openWarroom()">
|
||||
<span>⚔️</span><strong>#war-room</strong>
|
||||
</button>
|
||||
<h3 style="margin-top:0.85rem">Agents</h3>
|
||||
<template x-for="s in souls" :key="'wr-'+s.agent_key">
|
||||
<button type="button" class="wr-agent-btn"
|
||||
:class="{active: room && room.kind==='dm' && room.agent_key===s.agent_key}"
|
||||
@click="openDm(s.agent_key)"
|
||||
@dblclick="insertMention(s.agent_key)">
|
||||
<div class="agent-char-wrap"
|
||||
:data-agent-avatar="s.agent_key"
|
||||
:data-agent-color="s.avatar_color || '#1e3a5f'"
|
||||
x-init="$nextTick(() => window.AgentAvatars && AgentAvatars.paint($el, s.agent_key, s.avatar_color))"></div>
|
||||
<div class="wr-agent-meta">
|
||||
<strong x-text="s.display_name"></strong>
|
||||
<small x-text="'@'+s.agent_key"></small>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
</aside>
|
||||
<div class="wr-main">
|
||||
<div class="wr-head">
|
||||
<div>
|
||||
<h2 x-text="room ? (room.title || ('#'+room.kind)) : 'War Room'"></h2>
|
||||
<small class="muted" x-show="status" x-text="status"></small>
|
||||
</div>
|
||||
<div class="wr-head-actions">
|
||||
<button type="button" class="btn btn-sm" @click="popOut()" title="Open op tweede scherm">Pop-out ↗</button>
|
||||
<button type="button" class="btn btn-sm btn-pulse" @click="room && openRoom(room.id)" :disabled="busy">↻</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wr-log" x-ref="wrLog">
|
||||
<p class="wr-empty" x-show="!messages.length">Nog geen berichten — tag agents met @marketing of open een DM.</p>
|
||||
<template x-for="m in messages" :key="m.id">
|
||||
<div class="wr-bubble"
|
||||
:class="{'is-user': m.sender_type==='user', 'is-agent': m.sender_type==='agent', 'is-system': m.sender_type==='system'}">
|
||||
<div class="wr-avatar" x-show="m.sender_type!=='system'">
|
||||
<template x-if="m.sender_type==='agent'">
|
||||
<div class="agent-char-wrap"
|
||||
:data-agent-avatar="m.agent_key"
|
||||
data-agent-color="#1e3a5f"
|
||||
x-init="$nextTick(() => window.AgentAvatars && AgentAvatars.paint($el, m.agent_key))"></div>
|
||||
</template>
|
||||
<template x-if="m.sender_type==='user' && (m.sender_avatar_url || me.avatar_url)">
|
||||
<img :src="m.sender_avatar_url || me.avatar_url" alt="" />
|
||||
</template>
|
||||
<template x-if="m.sender_type==='user' && !(m.sender_avatar_url || me.avatar_url)">
|
||||
<span class="wr-avatar-fallback" x-text="(m.username||me.username||'?').slice(0,2).toUpperCase()"></span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="wr-bubble-body">
|
||||
<div class="wr-bubble-meta">
|
||||
<span x-text="labelForMessage(m)"></span>
|
||||
<span x-show="m.agent_key" x-text="' · @'+m.agent_key"></span>
|
||||
<span x-show="m.metadata && m.metadata.in_reply_to" x-text="' · reageert op @'+m.metadata.in_reply_to"></span>
|
||||
</div>
|
||||
<div class="wr-bubble-text" x-text="m.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="wr-composer">
|
||||
<textarea class="form-input" rows="2" x-model="draft" @keydown="onKey($event)"
|
||||
placeholder="Bericht… gebruik @agent om iemand te taggen" :disabled="busy || !room"></textarea>
|
||||
<button type="button" class="btn btn-pulse-green" @click="send()" :disabled="busy || !room || !draft.trim()">Stuur</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -304,6 +389,7 @@
|
||||
<script src="/static/js/page-viz.js?v=1"></script>
|
||||
<script src="/static/js/agents-mesh.js?v=11"></script>
|
||||
<script src="/static/js/agents-terminals.js?v=6"></script>
|
||||
<script src="/static/js/agents-warroom.js?v=4"></script>
|
||||
<script>
|
||||
function agentsSoulHub() {
|
||||
return {
|
||||
@@ -384,7 +470,14 @@ function agentsSoulHub() {
|
||||
this.syncMeshMode();
|
||||
await this.load();
|
||||
await this.loadPendingCount();
|
||||
this._poll = setInterval(() => { this.load(true); this.loadPendingCount(); }, 20000);
|
||||
this._poll = setInterval(() => {
|
||||
if (this.tab === 'warroom') {
|
||||
this.loadPendingCount();
|
||||
return;
|
||||
}
|
||||
this.load(true);
|
||||
this.loadPendingCount();
|
||||
}, 20000);
|
||||
if (window.PageViz) {
|
||||
window.PageViz.init('agents', (mode) => this.renderViz(mode));
|
||||
}
|
||||
@@ -399,7 +492,7 @@ function agentsSoulHub() {
|
||||
},
|
||||
});
|
||||
}
|
||||
if (window.AgentsTerminals) AgentsTerminals.startLiveStream();
|
||||
if (window.AgentsTerminals && this.tab !== 'warroom') AgentsTerminals.startLiveStream();
|
||||
if (this.tab === 'mesh') this.mountMesh();
|
||||
if (this.tab === 'terminals') this.startTerminals();
|
||||
});
|
||||
@@ -412,6 +505,7 @@ function agentsSoulHub() {
|
||||
this.tabIndicatorH = btnRect.height || 40;
|
||||
},
|
||||
setTab(next, el) {
|
||||
const prev = this.tab;
|
||||
this.tab = next;
|
||||
this.updateTabIndicator(el);
|
||||
const url = new URL(window.location.href);
|
||||
@@ -419,6 +513,15 @@ function agentsSoulHub() {
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
if (next === 'mesh') this.mountMesh();
|
||||
if (next === 'terminals') this.startTerminals();
|
||||
if (next === 'warroom') {
|
||||
if (window.AgentsTerminals && AgentsTerminals.stopLiveStream) AgentsTerminals.stopLiveStream();
|
||||
} else if (prev === 'warroom') {
|
||||
if (window.AgentsTerminals && AgentsTerminals.startLiveStream) AgentsTerminals.startLiveStream();
|
||||
this.$nextTick(() => {
|
||||
this.renderViz(window.__vizMode || 'neo-bars');
|
||||
window.AgentAvatars && AgentAvatars.paintAll(this.$root);
|
||||
});
|
||||
}
|
||||
if (next === 'approvals') this.loadPendingCount();
|
||||
},
|
||||
async loadPendingCount() {
|
||||
@@ -511,16 +614,23 @@ function agentsSoulHub() {
|
||||
},
|
||||
async load(quiet) {
|
||||
const r = await fetch('/api/agents/souls').then(x => x.json());
|
||||
this.souls = r.items || [];
|
||||
const items = r.items || [];
|
||||
// Avoid Alpine thrash: only replace souls when something meaningful changed
|
||||
const sig = items.map(s => [s.agent_key, s.event_count, s.events_6h, s.current_status, s.activity, s.current_task].join(':')).join('|');
|
||||
if (sig !== this._soulsSig) {
|
||||
this._soulsSig = sig;
|
||||
this.souls = items;
|
||||
}
|
||||
if (this.souls.length && !this.selected) this.select(this.souls[0].agent_key);
|
||||
if (this.tab === 'warroom') return; // no chart / global avatar repaint on War Room
|
||||
this.$nextTick(() => {
|
||||
(this.souls || []).forEach((s) => {
|
||||
const el = document.querySelector('[data-agent-avatar="' + s.agent_key + '"]');
|
||||
const el = document.querySelector('.agent-grid [data-agent-avatar="' + s.agent_key + '"], .hm-neo [data-agent-avatar="' + s.agent_key + '"]');
|
||||
if (el && window.AgentAvatars) AgentAvatars.paint(el, s.agent_key, s.avatar_color);
|
||||
});
|
||||
});
|
||||
this.renderViz(window.__vizMode || 'neo-bars');
|
||||
this.$nextTick(() => window.AgentAvatars && AgentAvatars.paintAll(this.$root));
|
||||
if (!quiet) this.$nextTick(() => window.AgentAvatars && AgentAvatars.paintAll(this.$root));
|
||||
},
|
||||
renderViz(mode) {
|
||||
if (!window.VizEngine) return;
|
||||
|
||||
Reference in New Issue
Block a user