diff --git a/the-matrix/index.html b/the-matrix/index.html index 9e3624f..cdc98a8 100644 --- a/the-matrix/index.html +++ b/the-matrix/index.html @@ -24,7 +24,6 @@ color: #00ff41; font-size: 11px; line-height: 1.8; text-shadow: 0 0 6px #00ff41; max-width: 240px; } - #status-panel .label { color: #007722; } #chat-panel { position: fixed; bottom: 16px; left: 16px; right: 16px; max-height: 180px; overflow-y: auto; @@ -34,11 +33,24 @@ } .chat-entry { opacity: 0.8; } .chat-entry .agent-name { color: #00ff88; font-weight: bold; } + .chat-ts { color: #004d18; font-size: 10px; } #connection-status { position: fixed; bottom: 16px; right: 16px; font-size: 11px; color: #555; + pointer-events: none; } #connection-status.connected { color: #00ff41; text-shadow: 0 0 6px #00ff41; } + #chat-clear-btn { + position: fixed; bottom: 16px; right: 110px; + font-family: 'Courier New', monospace; + font-size: 10px; color: #004d18; + background: transparent; border: 1px solid #004d18; + padding: 2px 6px; cursor: pointer; + pointer-events: all; z-index: 20; + text-shadow: none; + transition: color 0.2s, border-color 0.2s; + } + #chat-clear-btn:hover { color: #00ff41; border-color: #00ff41; } @@ -55,6 +67,7 @@
OFFLINE
+ diff --git a/the-matrix/js/ui.js b/the-matrix/js/ui.js index 7ea5193..9619df5 100644 --- a/the-matrix/js/ui.js +++ b/the-matrix/js/ui.js @@ -1,18 +1,96 @@ import { getAgentDefs } from './agents.js'; -import { colorToCss } from './agent-defs.js'; +import { AGENT_DEFS, colorToCss } from './agent-defs.js'; -const $agentCount = document.getElementById('agent-count'); -const $activeJobs = document.getElementById('active-jobs'); -const $fps = document.getElementById('fps'); -const $agentList = document.getElementById('agent-list'); -const $connStatus = document.getElementById('connection-status'); -const $chatPanel = document.getElementById('chat-panel'); +const $agentCount = document.getElementById('agent-count'); +const $activeJobs = document.getElementById('active-jobs'); +const $fps = document.getElementById('fps'); +const $agentList = document.getElementById('agent-list'); +const $connStatus = document.getElementById('connection-status'); +const $chatPanel = document.getElementById('chat-panel'); +const $clearBtn = document.getElementById('chat-clear-btn'); const MAX_CHAT_ENTRIES = 12; +const MAX_STORED = 100; +const STORAGE_PREFIX = 'matrix:chat:'; + const chatEntries = []; +const chatHistory = {}; + +function storageKey(agentId) { + return STORAGE_PREFIX + agentId; +} + +export function loadChatHistory(agentId) { + try { + const raw = localStorage.getItem(storageKey(agentId)); + return raw ? JSON.parse(raw) : []; + } catch { + return []; + } +} + +export function saveChatHistory(agentId, messages) { + try { + localStorage.setItem(storageKey(agentId), JSON.stringify(messages.slice(-MAX_STORED))); + } catch { + } +} + +function formatTimestamp(ts) { + const d = new Date(ts); + const hh = String(d.getHours()).padStart(2, '0'); + const mm = String(d.getMinutes()).padStart(2, '0'); + return `${hh}:${mm}`; +} + +function buildChatEntry(agentLabel, message, cssColor, timestamp) { + const color = cssColor || '#00ff41'; + const entry = document.createElement('div'); + entry.className = 'chat-entry'; + const ts = timestamp ? `[${formatTimestamp(timestamp)}] ` : ''; + entry.innerHTML = `${ts}${agentLabel}: ${escapeHtml(message)}`; + return entry; +} + +function loadAllHistories() { + const all = []; + + const agentIds = [...AGENT_DEFS.map(d => d.id), 'sys']; + for (const id of agentIds) { + const msgs = loadChatHistory(id); + chatHistory[id] = msgs; + all.push(...msgs); + } + + all.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); + + for (const msg of all.slice(-MAX_CHAT_ENTRIES)) { + const entry = buildChatEntry(msg.agentLabel, msg.text, msg.cssColor, msg.timestamp); + chatEntries.push(entry); + $chatPanel.appendChild(entry); + } + + $chatPanel.scrollTop = $chatPanel.scrollHeight; +} + +function clearAllHistories() { + const agentIds = [...AGENT_DEFS.map(d => d.id), 'sys']; + for (const id of agentIds) { + localStorage.removeItem(storageKey(id)); + chatHistory[id] = []; + } + while ($chatPanel.firstChild) $chatPanel.removeChild($chatPanel.firstChild); + chatEntries.length = 0; +} + export function initUI() { renderAgentList(); + loadAllHistories(); + + if ($clearBtn) { + $clearBtn.addEventListener('click', clearAllHistories); + } } function renderAgentList() { @@ -55,16 +133,15 @@ export function updateUI({ fps, agentCount, jobCount, connectionState }) { } /** - * Append a line to the chat panel. - * @param {string} agentLabel — display name - * @param {string} message — message text (HTML-escaped before insertion) - * @param {string} cssColor — CSS color string, e.g. '#00ff88' + * Append a message to the chat panel and optionally persist it. + * @param {string} agentLabel — display name + * @param {string} message — raw text (HTML-escaped before insertion) + * @param {string} cssColor — CSS color string e.g. '#00ff88' + * @param {string} [agentId] — storage key; omit to skip persistence */ -export function appendChatMessage(agentLabel, message, cssColor) { - const color = cssColor || '#00ff41'; - const entry = document.createElement('div'); - entry.className = 'chat-entry'; - entry.innerHTML = `${agentLabel}: ${escapeHtml(message)}`; +export function appendChatMessage(agentLabel, message, cssColor, agentId) { + const timestamp = Date.now(); + const entry = buildChatEntry(agentLabel, message, cssColor, timestamp); chatEntries.push(entry); if (chatEntries.length > MAX_CHAT_ENTRIES) { @@ -74,6 +151,12 @@ export function appendChatMessage(agentLabel, message, cssColor) { $chatPanel.appendChild(entry); $chatPanel.scrollTop = $chatPanel.scrollHeight; + + if (agentId) { + if (!chatHistory[agentId]) chatHistory[agentId] = []; + chatHistory[agentId].push({ agentLabel, text: message, cssColor, agentId, timestamp }); + saveChatHistory(agentId, chatHistory[agentId]); + } } function escapeHtml(str) { diff --git a/the-matrix/js/websocket.js b/the-matrix/js/websocket.js index 0a758c8..ea8a0f5 100644 --- a/the-matrix/js/websocket.js +++ b/the-matrix/js/websocket.js @@ -91,7 +91,7 @@ function handleMessage(msg) { case 'chat': { const def = agentById[msg.agentId]; if (def && msg.text) { - appendChatMessage(def.label, msg.text, colorToCss(def.color)); + appendChatMessage(def.label, msg.text, colorToCss(def.color), def.id); } break; } @@ -103,7 +103,7 @@ function handleMessage(msg) { } function logEvent(text) { - appendChatMessage('SYS', text, colorToCss(0x003300)); + appendChatMessage('SYS', text, colorToCss(0x003300), 'sys'); } export function getConnectionState() {