Ship War Room UX, CEO inbox, mobile chat, and Agents NAS workspace.
Make A2A togglable, keep reply context while reading, route agent data to //10.4.7.11/share/Agents, and polish themes plus export-intel sync cancel.
This commit is contained in:
+140
-34
@@ -4,12 +4,12 @@
|
||||
<link rel="stylesheet" href="/static/css/hermes.css" />
|
||||
<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" />
|
||||
<link rel="stylesheet" href="/static/css/agents-warroom.css?v=18" />
|
||||
<script src="/static/js/agent-avatars.js?v=4"></script>
|
||||
<link rel="stylesheet" href="/static/css/agent-characters.css?v=8" />
|
||||
<link rel="stylesheet" href="/static/css/agents-warroom.css?v=40" />
|
||||
<script src="/static/js/agent-avatars.js?v=5"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="hm-neo" x-data="agentsSoulHub()" x-init="init()">
|
||||
<div class="hm-neo" :class="{ 'tab-warroom-active': tab==='warroom' }" x-data="agentsSoulHub()" x-init="init()">
|
||||
<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>
|
||||
@@ -32,22 +32,22 @@
|
||||
<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'">
|
||||
<div class="vtabs-nav-header"><strong>Agents</strong></div>
|
||||
<button class="vtab-btn" :class="{active: tab==='warroom'}" @click="setTab('warroom', $el)" data-tab="warroom">
|
||||
<span class="vtab-icon vtab-icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 6v6c0 5 3.5 8.5 8 10 4.5-1.5 8-5 8-10V6l-8-4Z"/><path d="m9 12 2 2 4-4"/></svg></span><span>War Room</span>
|
||||
</button>
|
||||
<button class="vtab-btn" :class="{active: tab==='souls'}" @click="setTab('souls', $el)">
|
||||
<span class="vtab-icon">🧬</span><span>Souls</span>
|
||||
<span class="vtab-icon vtab-icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg></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>
|
||||
<span class="vtab-icon vtab-icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="2"/><circle cx="5" cy="19" r="2"/><circle cx="19" cy="19" r="2"/><path d="M12 7v4"/><path d="m6.7 17.4 3.6-2.4"/><path d="m17.3 17.4-3.6-2.4"/><circle cx="12" cy="12" r="2"/></svg></span><span>Netwerk</span>
|
||||
</button>
|
||||
<button class="vtab-btn" :class="{active: tab==='terminals'}" @click="setTab('terminals', $el)" data-tab="terminals">
|
||||
<span class="vtab-icon">🖥️</span><span>Terminals</span>
|
||||
<span class="vtab-icon vtab-icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/><path d="m7 8 3 3-3 3"/><path d="M13 14h3"/></svg></span><span>Terminals</span>
|
||||
</button>
|
||||
<button class="vtab-btn" :class="{active: tab==='approvals'}" @click="setTab('approvals', $el)">
|
||||
<span class="vtab-icon">✓</span><span>Goedkeuringen</span>
|
||||
<span class="vtab-icon vtab-icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg></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">
|
||||
@@ -275,11 +275,19 @@
|
||||
<section x-show="tab==='warroom'" x-cloak>
|
||||
<div x-data="warRoomHub()" x-init="init()" class="wr-hub-root">
|
||||
<div class="panel" style="padding:0.75rem">
|
||||
<div class="wr-layout">
|
||||
<div class="wr-layout" :class="{'roster-collapsed': rosterCollapsed}">
|
||||
<div class="wr-mobile-bar">
|
||||
<button type="button" class="btn btn-sm" @click="rosterCollapsed = !rosterCollapsed"
|
||||
x-text="rosterCollapsed ? '👥 Agents' : '▾ Agents'"></button>
|
||||
<button type="button" class="btn btn-sm wr-a2a-btn"
|
||||
:class="autoDebate ? 'is-on' : 'is-off'"
|
||||
@click="toggleA2A()"
|
||||
x-text="autoDebate ? 'A2A · aan' : 'A2A · uit'"></button>
|
||||
</div>
|
||||
<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>
|
||||
<span class="wr-chan-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 6v6c0 5 3.5 8.5 8 10 4.5-1.5 8-5 8-10V6l-8-4Z"/><path d="m9 12 2 2 4-4"/></svg></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">
|
||||
@@ -301,22 +309,62 @@
|
||||
</aside>
|
||||
<div class="wr-main">
|
||||
<div class="wr-head">
|
||||
<div>
|
||||
<div class="wr-head-title">
|
||||
<h2 x-text="room ? (room.title || ('#'+room.kind)) : 'War Room'"></h2>
|
||||
<small class="muted" x-show="status" x-text="status"></small>
|
||||
<small class="wr-msg-count muted" x-show="messages.length"
|
||||
x-text="(screenCleared ? '0' : visibleMessages().length) + ' / ' + messages.length + ' berichten'"></small>
|
||||
</div>
|
||||
<div class="wr-head-actions">
|
||||
<label class="btn btn-sm" style="display:inline-flex;align-items:center;gap:0.3rem;cursor:pointer;margin:0" title="Agents praten onderling door"><input type="checkbox" x-model="autoDebate" @change="autoDebate && scheduleContinue(1500)" style="margin:0" /> A2A</label>
|
||||
<button type="button" class="btn btn-sm btn-pulse" @click="openVoiceRoom()" :disabled="!room" title="Spraakmeeting met agents">🎙️ Voice Room</button><button type="button" class="btn btn-sm" :class="{'is-recording': voiceRecording}" @mousedown.prevent="startVoiceTalk($event)" @mouseup.prevent="stopVoiceTalk($event)" @touchstart.prevent="startVoiceTalk($event)" @touchend.prevent="stopVoiceTalk($event)" :disabled="voiceBusy || busy || !room" title="Snel praten">🎤</button>
|
||||
<button type="button" class="btn btn-sm" @click="popOut()" title="Open op tweede scherm">Pop-out ↗</button>
|
||||
<button type="button" class="btn btn-sm" @click="room && openRoom(room.id)" :disabled="busy">↻</button>
|
||||
<button type="button" class="btn btn-sm wr-tool-btn" :class="{active: searchOpen}" @click="toggleSearch()" title="Zoeken in chat">🔍 Zoek</button>
|
||||
<div class="wr-tools-wrap">
|
||||
<button type="button" class="btn btn-sm wr-tool-btn" @click="toolsOpen=!toolsOpen" title="Meer acties">⋯</button>
|
||||
<div class="wr-tools-menu" x-show="toolsOpen" @click.outside="toolsOpen=false" x-cloak>
|
||||
<button type="button" @click="downloadChat('txt')">⬇ Download .txt</button>
|
||||
<button type="button" @click="downloadChat('md')">⬇ Download .md</button>
|
||||
<button type="button" @click="downloadChat('json')">⬇ Download .json</button>
|
||||
<hr />
|
||||
<button type="button" @click="clearScreen(); toolsOpen=false">🧹 Scherm legen</button>
|
||||
<button type="button" x-show="screenCleared" @click="restoreScreen(); toolsOpen=false">↩ Scherm herstellen</button>
|
||||
<button type="button" class="wr-danger" @click="clearChatHistory(); toolsOpen=false">🗑 Geschiedenis wissen</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm wr-a2a-btn"
|
||||
:class="autoDebate ? 'is-on' : 'is-off'"
|
||||
@click="toggleA2A()"
|
||||
:title="autoDebate ? 'A2A staat aan — klik om uit te zetten' : 'A2A staat uit — klik om aan te zetten'">
|
||||
<span x-text="autoDebate ? 'A2A · aan' : 'A2A · uit'"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm wr-ceo-btn" @click="openCeoInbox()" title="Direct aan jou (@aissa)">
|
||||
📬 CEO-inbox
|
||||
<span class="wr-ceo-badge" x-show="ceoUnread > 0" x-text="ceoUnread"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-pulse" @click="openVoiceRoom()" title="Spraakmeeting met agents">🎙️ Voice</button>
|
||||
<button type="button" class="btn btn-sm" :class="{'is-recording': voiceRecording}" @mousedown.prevent="startVoiceTalk($event)" @mouseup.prevent="stopVoiceTalk($event)" @touchstart.prevent="startVoiceTalk($event)" @touchend.prevent="stopVoiceTalk($event)" :disabled="voiceBusy || busy || !room" title="Snel praten">🎤</button>
|
||||
<button type="button" class="btn btn-sm" @click="popOut()" title="Open op tweede scherm">↗</button>
|
||||
<button type="button" class="btn btn-sm" @click="room && openRoom(room.id)" :disabled="busy" title="Vernieuwen">↻</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wr-log" x-ref="wrLog">
|
||||
<p class="wr-empty" x-show="!messages.length && !typing">Nog geen berichten — tag agents met @marketing of deel een bestand.</p>
|
||||
<template x-for="m in messages" :key="m.id">
|
||||
<div class="wr-searchbar" x-show="searchOpen" x-cloak>
|
||||
<input type="search" class="form-input" x-ref="wrSearch" x-model="chatSearch"
|
||||
placeholder="Zoek in berichten, @agent, tekst…" />
|
||||
<button type="button" class="btn btn-sm" @click="chatSearch=''" x-show="chatSearch">Wis</button>
|
||||
<button type="button" class="btn btn-sm" @click="toggleSearch()">Sluit</button>
|
||||
</div>
|
||||
<div class="wr-log-wrap"><div class="wr-log" x-ref="wrLog" @scroll.passive="onLogScroll()">
|
||||
<p class="wr-empty" x-show="screenCleared && !typing">
|
||||
Scherm is geleegd.
|
||||
<button type="button" class="btn btn-sm" @click="restoreScreen()">Herstellen</button>
|
||||
</p>
|
||||
<p class="wr-empty" x-show="!screenCleared && !visibleMessages().length && !typing && messages.length">Geen treffers voor deze zoekopdracht.</p>
|
||||
<p class="wr-empty" x-show="!screenCleared && !messages.length && !typing">Nog geen berichten — tag agents met @marketing of deel een bestand.</p>
|
||||
<template x-for="(m, mi) in visibleMessages()" :key="'msg-'+m.id">
|
||||
<div class="wr-msg-wrap" :data-msg-id="m.id">
|
||||
<div class="wr-day-sep" x-show="showDaySep(mi)">
|
||||
<span x-text="formatMsgDate(m)"></span>
|
||||
</div>
|
||||
<div class="wr-bubble"
|
||||
:class="{'is-user': m.sender_type==='user', 'is-agent': m.sender_type==='agent', 'is-system': m.sender_type==='system'}">
|
||||
:class="{'is-user': m.sender_type==='user', 'is-agent': m.sender_type==='agent', 'is-system': m.sender_type==='system', 'is-ceo-direct': isCeoDirect(m), 'is-reply-target': replyHighlightId == m.id}">
|
||||
<div class="wr-avatar" x-show="m.sender_type!=='system'">
|
||||
<template x-if="m.sender_type==='agent'">
|
||||
<div class="agent-char-wrap"
|
||||
@@ -333,10 +381,17 @@
|
||||
</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>
|
||||
<button type="button" class="wr-msg-del" title="Verwijderen"
|
||||
@click.stop="deleteMessage(m)">🗑</button>
|
||||
<span class="wr-who" x-text="labelForMessage(m)"></span>
|
||||
<span class="wr-handle" x-show="m.agent_key" x-text="'@'+m.agent_key"></span>
|
||||
<time class="wr-time" x-text="formatMsgTime(m)" :title="formatMsgDate(m) + ' ' + formatMsgTime(m)"></time>
|
||||
<button type="button" class="wr-msg-reply" title="Beantwoorden"
|
||||
@click.stop="setReply(m)">↩ Reply</button>
|
||||
<button type="button" class="wr-msg-del" title="Bericht verwijderen"
|
||||
@click.stop.prevent="deleteMessage(m)">🗑</button>
|
||||
</div>
|
||||
<div class="wr-reply-quote" x-show="replyPreview(m)" x-cloak>
|
||||
<span class="wr-reply-quote-who" x-text="'↩ ' + ((replyPreview(m)||{}).agent_key ? ('@'+(replyPreview(m).agent_key)) : ((replyPreview(m)||{}).username || 'bericht'))"></span>
|
||||
<span class="wr-reply-quote-text" x-text="(replyPreview(m)||{}).excerpt"></span>
|
||||
</div>
|
||||
<div class="wr-bubble-text" x-text="m.content"></div>
|
||||
<div class="wr-attach-list" x-show="attachmentsOf(m).length">
|
||||
@@ -349,6 +404,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="wr-bubble is-agent is-typing" x-show="typing" x-cloak>
|
||||
<div class="wr-avatar">
|
||||
@@ -365,7 +421,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wr-composer">
|
||||
<button type="button" class="wr-jump-latest" x-show="showJumpLatest" x-cloak
|
||||
@click="jumpToLatest()" title="Naar nieuwste berichten">
|
||||
↓ Nieuwste
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="wr-composer" @click="focusComposer()">
|
||||
<div class="wr-composer-tools">
|
||||
<button type="button" class="btn btn-sm" :class="{'is-recording': voiceRecording}" @mousedown.prevent="startVoiceTalk($event)" @mouseup.prevent="stopVoiceTalk($event)" @touchstart.prevent="startVoiceTalk($event)" @touchend.prevent="stopVoiceTalk($event)" :disabled="voiceBusy || busy" title="Houd in om te praten">🎤</button>
|
||||
<button type="button" class="btn btn-sm" @click="emojiOpen=!emojiOpen" :disabled="busy">😊</button>
|
||||
@@ -388,6 +450,13 @@
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="wr-reply-bar" x-show="replyTo" x-cloak>
|
||||
<div class="wr-reply-bar-text">
|
||||
<strong x-text="'↩ Reply aan ' + (replyTo && (replyTo.agent_key ? ('@'+replyTo.agent_key) : (replyTo.label || 'bericht')))"></strong>
|
||||
<span x-text="replyTo && replyTo.excerpt"></span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm" @click="clearReply()" title="Annuleer reply">×</button>
|
||||
</div>
|
||||
<div class="wr-composer-wrap">
|
||||
<div class="wr-mention-pop" x-show="mentionOpen && mentionSuggestions().length" x-cloak>
|
||||
<template x-for="(s, si) in mentionSuggestions()" :key="'m-'+s.agent_key">
|
||||
@@ -400,15 +469,51 @@
|
||||
</template>
|
||||
</div>
|
||||
<textarea class="form-input" rows="2" x-ref="wrInput" x-model="draft"
|
||||
@input="onDraftInput()" @keydown="onKey($event)"
|
||||
placeholder="Typ @bizdev of @finance… agent antwoordt hier in de War Room"
|
||||
:disabled="!room"></textarea>
|
||||
@input="onDraftInput()" @keydown="onKey($event)" @keydown.enter.prevent="onEnter($event)"
|
||||
placeholder="Typ hier… bv. @herman of @retail"
|
||||
></textarea>
|
||||
</div>
|
||||
<button type="button" class="btn btn-pulse-green" @click="send()" :disabled="!canSend()">Stuur</button>
|
||||
<button type="button" class="btn btn-pulse-green" @click="send()" :disabled="!canSend() || _sendLock">Stuur</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="wr-voice-overlay" :class="{ 'is-open': voiceRoomOpen }" x-show="voiceRoomOpen" x-cloak @keydown.escape.window="closeVoiceRoom()">
|
||||
|
||||
<!-- CEO inbox (@aissa) -->
|
||||
<div class="wr-ceo-overlay" x-show="ceoInboxOpen" x-cloak @keydown.escape.window="closeCeoInbox()">
|
||||
<div class="wr-ceo-panel" @click.stop>
|
||||
<div class="wr-ceo-head">
|
||||
<div>
|
||||
<h3>📬 CEO-inbox · @aissa</h3>
|
||||
<small class="muted">Direct van agents aan jou (@aissa) — ook ongelezen van toen je offline was</small>
|
||||
</div>
|
||||
<div class="wr-ceo-head-actions">
|
||||
<button type="button" class="btn btn-sm" @click="markAllCeoRead()" :disabled="!ceoUnread">Alles gelezen</button>
|
||||
<button type="button" class="btn btn-sm" @click="loadCeoInbox()">↻</button>
|
||||
<button type="button" class="btn btn-sm" @click="closeCeoInbox()">Sluit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wr-ceo-list">
|
||||
<p class="wr-empty" x-show="!(ceoInbox||[]).length">Nog geen directe berichten. Agents gebruiken @aissa als ze jou echt nodig hebben.</p>
|
||||
<template x-for="item in ceoInbox" :key="'ceo-'+item.id">
|
||||
<article class="wr-ceo-item" :class="{ unread: !item.read_at }">
|
||||
<div class="wr-ceo-item-meta">
|
||||
<strong x-text="item.agent_display_name || ('@'+(item.agent_key||'agent'))"></strong>
|
||||
<span x-text="'@'+(item.agent_key||'')"></span>
|
||||
<time x-text="formatMsgTime({created_at: item.created_at})"></time>
|
||||
<span class="wr-ceo-unread-dot" x-show="!item.read_at">nieuw</span>
|
||||
</div>
|
||||
<div class="wr-ceo-item-body" x-text="item.content"></div>
|
||||
<div class="wr-ceo-item-actions">
|
||||
<button type="button" class="btn btn-sm" x-show="!item.read_at" @click="markCeoRead(item.id)">Markeer gelezen</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template x-if="voiceRoomOpen">
|
||||
<div class="wr-voice-overlay is-open" @keydown.escape.window="closeVoiceRoom()">
|
||||
<div class="wr-voice-panel" @click.stop>
|
||||
<div class="wr-voice-head">
|
||||
<div>
|
||||
@@ -468,6 +573,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -477,7 +583,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent-events-backdrop" x-show="eventsOpen" x-cloak @click="closeEvents()"></div>
|
||||
<div class="agent-events-backdrop" :class="{ 'is-open': eventsOpen }" x-show="eventsOpen" x-cloak @click="closeEvents()"></div>
|
||||
<aside class="agent-events-drawer" :class="{ open: eventsOpen }" x-cloak>
|
||||
<header class="agent-events-head">
|
||||
<div>
|
||||
@@ -516,7 +622,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=18"></script>
|
||||
<script src="/static/js/agents-warroom.js?v=40"></script>
|
||||
<script>
|
||||
function agentsSoulHub() {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user