Files
foodlinkk-command-center/cockpit/hermes.html
T

245 lines
12 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% block title %}Hermes · Telegram Command Center{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/hermes.css" />
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
{% endblock %}
{% block content %}
<div x-data="hermesControl()" x-init="init()" class="hermes-shell hm-neo">
<header class="hm-neo-header page-header">
<h1>Hermes — Telegram Command Center</h1>
<p class="hm-sub">Live feed · 4 PA browsers · second brain · volledige controle</p>
</header>
<!-- Neo KPI row -->
<div class="hm-neo-kpi-row">
<div class="hm-neo-kpi" :class="{ highlight: tab === 'feed' }">
<div class="hm-neo-kpi-top">
<div class="hm-neo-ring" style="--ring-color:#00e5ff;--ring-pct:72%"><div class="hm-neo-ring-inner">💬</div></div>
<div><div class="hm-neo-kpi-label">Conversaties</div><div class="hm-neo-kpi-sub">Telegram chats</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="stats.conversations">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:85%"><div class="hm-neo-ring-inner">📨</div></div>
<div><div class="hm-neo-kpi-label">Berichten</div><div class="hm-neo-kpi-sub">in + out</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="stats.messages">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:60%"><div class="hm-neo-ring-inner">🕸</div></div>
<div><div class="hm-neo-kpi-label">Relaties</div><div class="hm-neo-kpi-sub">graph edges</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="stats.edges">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:#a855f7;--ring-pct:55%"><div class="hm-neo-ring-inner">🧠</div></div>
<div><div class="hm-neo-kpi-label">Vectors</div><div class="hm-neo-kpi-sub">pgvector</div></div>
</div>
<div class="hm-neo-kpi-value" x-text="stats.embeddings">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>
<!-- Neo charts -->
<div class="hm-neo-charts">
<div class="hm-chart-panel">
<div class="hm-chart-head">
<h3><span class="hm-live-dot"></span> Berichten per richting</h3>
<small>klik voor detail</small>
</div>
<div class="hm-chart-wrap"><canvas id="hm-chart-direction"></canvas></div>
</div>
<div class="hm-chart-panel">
<div class="hm-chart-head">
<h3><span class="hm-live-dot"></span> Agent activiteit</h3>
<small>laatste events</small>
</div>
<div class="hm-chart-wrap"><canvas id="hm-chart-agents"></canvas></div>
</div>
</div>
<div class="hermes-tabs">
<button type="button" class="hermes-tab" :class="{ active: tab === 'feed' }" @click="setTab('feed')">📨 Live Feed</button>
<button type="button" class="hermes-tab" :class="{ active: tab === 'pa' }" @click="setTab('pa')">🌐 PA Live (4×)</button>
<button type="button" class="hermes-tab" :class="{ active: tab === 'graph' }" @click="setTab('graph')">🕸 Graph</button>
<button type="button" class="hermes-tab" :class="{ active: tab === 'search' }" @click="setTab('search')">🔍 Memory</button>
<button type="button" class="hermes-tab" :class="{ active: tab === 'control' }" @click="setTab('control')">⚙️ Control</button>
</div>
<!-- LIVE FEED -->
<div x-show="tab === 'feed'" class="hermes-grid">
<aside class="panel hermes-sidebar">
<div class="hm-team">
<h3>Team — Foodlinkk leadership</h3>
<template x-for="m in teamRoster" :key="m.chat_id">
<div class="hm-team-card" :class="{ online: m.online || m.pa_mode }">
<span class="hm-status-dot" :class="m.online ? 'on' : 'off'"></span>
<span class="hm-role-badge" :class="m.role === 'CEO' ? 'hm-role-ceo' : 'hm-role-cto'" x-text="m.role"></span>
<div class="hm-person">
<strong x-text="m.name"></strong>
<small>
<span x-text="m.chat_id"></span>
<span x-show="m.pa_mode" class="tag tag-pa">PA</span>
<span x-show="m.message_count" x-text="' · ' + m.message_count + ' msgs'"></span>
</small>
</div>
</div>
</template>
</div>
<h3 style="font-size:0.7rem;letter-spacing:0.12em;text-transform:uppercase;color:#64748b;margin:1rem 0 0.5rem">Filter chat</h3>
<button class="btn btn-sm" @click="selectChat(null)" :class="{ 'btn-primary': !selectedChatId }">Alle chats</button>
<template x-for="m in teamRoster" :key="'c'+m.chat_id">
<button class="hermes-conv-btn" @click="selectChat(m.chat_id)" :class="{ active: selectedChatId == m.chat_id }">
<span class="conv-role hm-role-badge" :class="m.role === 'CEO' ? 'hm-role-ceo' : 'hm-role-cto'" x-text="m.role"></span>
<span x-text="m.name"></span>
</button>
</template>
</aside>
<section class="panel hermes-feed">
<div class="panel-header">
<h3>Telegram live feed</h3>
<span class="text-muted" x-text="live ? '🟢 Live (5s)' : '⏸'"></span>
<button class="btn btn-sm" @click="refreshFeed()">↻ Refresh</button>
</div>
<div class="hermes-messages">
<template x-for="msg in messages" :key="msg.id">
<div class="hermes-msg" :class="msg.direction === 'in' ? 'msg-in' : 'msg-out'">
<div class="hermes-msg-meta">
<span x-text="msg.direction === 'in' ? '← IN' : '→ OUT'"></span>
<span x-text="msgLabel(msg)"></span>
<span x-text="formatTime(msg.created_at)"></span>
<span class="badge" x-show="msg.has_embedding">vector</span>
<span class="badge" x-show="msg.agent_name" x-text="msg.agent_name"></span>
</div>
<div class="hermes-msg-body" x-text="msg.content_text || '(' + (msg.content_type || 'empty') + ')'"></div>
</div>
</template>
<p x-show="!messages.length" class="text-muted">Nog geen berichten — stuur iets via @klaploper_bot</p>
</div>
</section>
</div>
<!-- PA LIVE -->
<div x-show="tab === 'pa'" class="panel">
<div class="hermes-pa-header">
<div>
<strong>Personal PA — 4 site browsers</strong>
<p class="text-muted" style="margin:0.25rem 0 0" x-text="paLive.query ? 'Zoekt: ' + paLive.query : 'Wacht op @PA via Telegram'"></p>
</div>
<span class="status-badge" :class="paLive.status || 'idle'" x-text="paStatusLabel()"></span>
<span class="text-muted" x-show="paLiveUser" x-text="paLiveUser"></span>
<button class="btn btn-sm btn-primary" @click="refreshPaLive()">↻ Refresh</button>
</div>
<div class="hermes-pa-grid">
<template x-for="slot in paSlots" :key="slot.label">
<div class="hermes-pa-slot" :class="'status-' + (slot.status || 'idle')">
<div class="hermes-pa-slot-head">
<strong x-text="slot.label"></strong>
<span class="status-badge" :class="slot.status || 'idle'" x-text="slotStatusLabel(slot.status)"></span>
</div>
<div class="hermes-pa-slot-body">
<img x-show="slot.has_screenshot" :src="slotScreenshotUrl(slot.label)" :alt="slot.label" />
<div class="hermes-pa-placeholder" x-show="!slot.has_screenshot && slot.status === 'waiting'">⏳ Wacht…</div>
<div class="hermes-pa-placeholder" x-show="!slot.has_screenshot && slot.status === 'loading'">🔄 Bezig…</div>
<div class="hermes-pa-placeholder" x-show="!slot.has_screenshot && (!slot.status || slot.status === 'idle')">— idle —</div>
<div class="hermes-pa-placeholder" x-show="slot.status === 'failed'" x-text="slot.error || 'Mislukt'"></div>
</div>
<div class="hermes-pa-slot-foot" x-text="slot.title || slot.url || slot.label"></div>
</div>
</template>
</div>
</div>
<!-- GRAPH -->
<div x-show="tab === 'graph'" class="panel">
<div class="panel-header">
<h3>Second brain — message graph</h3>
<button class="btn btn-sm btn-primary" @click="loadGraph()">↻ Herlaad</button>
<select class="form-input hermes-select" x-model="graphChatFilter" @change="loadGraph()">
<option value="">Alle chats</option>
<template x-for="m in teamRoster" :key="'g'+m.chat_id">
<option :value="m.chat_id" x-text="m.role + ' — ' + m.name"></option>
</template>
</select>
</div>
<div id="hermes-graph" class="hermes-graph-canvas"></div>
</div>
<!-- SEARCH -->
<div x-show="tab === 'search'" class="panel">
<h3>Vector + full-text memory search</h3>
<div class="hermes-search-bar">
<input class="form-input" type="search" placeholder="Zoek in Telegram-geheugen…" x-model="searchQuery" @keydown.enter="runSearch()" />
<button class="btn btn-primary" @click="runSearch()">Zoeken</button>
</div>
<div class="hermes-search-results">
<template x-for="r in searchResults" :key="r.id">
<div class="hermes-search-hit">
<div class="hermes-msg-meta">
<span x-text="msgLabel(r)"></span>
<span x-text="formatTime(r.created_at)"></span>
<span class="badge" x-text="'score ' + (r.score || 0).toFixed(2)"></span>
</div>
<p x-text="r.content_text"></p>
</div>
</template>
</div>
</div>
<!-- CONTROL -->
<div x-show="tab === 'control'" class="hermes-control-grid">
<div class="panel">
<h3>Hermes status</h3>
<ul class="hermes-info-list">
<li>Bot: <strong>@klaploper_bot</strong></li>
<li>CEO: <strong>Aïssa</strong> · CTO: <strong>Mo</strong></li>
<li>Control API: <span x-text="hermesOnline ? '🟢 Online' : '🔴 Offline'"></span></li>
<li>Inbound: <span x-text="stats.inbound || 0"></span> · Outbound: <span x-text="stats.outbound || 0"></span></li>
</ul>
<div class="hermes-actions">
<button class="btn btn-primary" @click="runAction('evening-briefing')" :disabled="actionBusy">🌙 Avondbriefing → CEO</button>
<button class="btn" @click="runAction('morning-briefing')" :disabled="actionBusy">☀️ Ochtendbriefing</button>
<button class="btn" @click="runAction('resume-jobs')" :disabled="actionBusy">🔧 Herstel jobs</button>
</div>
<p x-show="actionMsg" class="text-muted" style="margin-top:0.5rem" x-text="actionMsg"></p>
</div>
<div class="panel">
<h3>Team</h3>
<template x-for="m in teamRoster" :key="'t'+m.chat_id">
<div class="hm-team-card" :class="{ online: m.online }">
<span class="hm-role-badge" :class="m.role === 'CEO' ? 'hm-role-ceo' : 'hm-role-cto'" x-text="m.role"></span>
<div class="hm-person"><strong x-text="m.name"></strong><small x-text="m.chat_id"></small></div>
</div>
</template>
</div>
<div class="panel hermes-events">
<h3>Agent events</h3>
<template x-for="ev in agentEvents" :key="ev.id">
<div class="hermes-event">
<strong x-text="ev.title"></strong>
<span class="badge" x-text="ev.agent_name"></span>
<small x-text="formatTime(ev.created_at)"></small>
</div>
</template>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/hermes-ui.js"></script>
{% endblock %}