Files
CHORUS/chrs-gui/static/index.html

151 lines
7.0 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CHORUS Command Center</title>
<style>
:root { --primary: #00ffcc; --bg: #121212; --card: #1e1e1e; --text: #e0e0e0; --border: #333; }
body { font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); margin: 0; display: flex; height: 100vh; overflow: hidden; }
/* Layout */
.sidebar { width: 250px; background: var(--card); border-right: 1px solid var(--border); display: flex; flex-direction: column; }
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.inspector { width: 400px; background: var(--card); border-left: 1px solid var(--border); display: flex; flex-direction: column; }
/* Sidebar: Agents */
.sidebar-header { padding: 20px; border-bottom: 1px solid var(--border); font-weight: bold; color: var(--primary); font-size: 1.2em; }
.agent-list { flex: 1; overflow-y: auto; list-style: none; padding: 0; margin: 0; }
.agent-item { padding: 12px 20px; cursor: pointer; border-bottom: 1px solid var(--border); transition: 0.2s; }
.agent-item:hover { background: #2a2a2a; }
.agent-item.active { background: #333; border-left: 4px solid var(--primary); }
/* Main: Log Viewer */
.main-header { padding: 15px 20px; background: #1a1a1a; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
.log-container { flex: 1; padding: 20px; background: #000; color: #00ff00; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.9em; overflow-y: auto; white-space: pre-wrap; }
/* Inspector: Messages */
.inspector-header { padding: 15px 20px; border-bottom: 1px solid var(--border); font-weight: bold; }
.msg-list { flex: 1; overflow-y: auto; padding: 10px; }
.msg-item { background: #252525; padding: 10px; border-radius: 4px; margin-bottom: 10px; font-size: 0.85em; border: 1px solid var(--border); }
.msg-meta { color: #888; font-size: 0.8em; margin-bottom: 5px; display: flex; justify-content: space-between; }
.msg-topic { color: var(--primary); font-weight: bold; }
/* Forms / UI */
.project-form { padding: 20px; border-bottom: 1px solid var(--border); }
input { width: 100%; padding: 8px; margin-bottom: 10px; background: #2a2a2a; border: 1px solid #444; color: #fff; border-radius: 4px; }
button { width: 100%; padding: 10px; background: var(--primary); color: #000; border: none; font-weight: bold; border-radius: 4px; cursor: pointer; }
button:hover { opacity: 0.8; }
.badge { font-size: 0.7em; padding: 2px 6px; border-radius: 10px; background: var(--primary); color: #000; margin-left: 5px; }
</style>
</head>
<body>
<div class="sidebar">
<div class="sidebar-header">CHORUS Council</div>
<div class="project-form">
<input type="text" id="projName" placeholder="Project Name">
<input type="text" id="repoUrl" placeholder="Repo URL">
<button onclick="createProject()">Add Project</button>
</div>
<ul id="agentList" class="agent-list">
<!-- Agents here -->
</ul>
</div>
<div class="main">
<div class="main-header">
<span id="activeAgentName">Select an agent</span>
<div style="display:flex; gap: 10px;">
<label style="font-size: 0.8em;"><input type="checkbox" id="autoScroll" checked> Auto-scroll</label>
<button style="width: auto; padding: 2px 10px;" onclick="refreshData()">Refresh</button>
</div>
</div>
<div id="logViewer" class="log-container">Welcome to CHORUS Command Center. Select an agent to view its live log.</div>
</div>
<div class="inspector">
<div class="inspector-header">Communication Thread</div>
<div id="msgList" class="msg-list">
<!-- Messages here -->
</div>
</div>
<script>
let selectedAgentId = null;
async function fetchAgents() {
try {
const res = await fetch('/api/agents');
const agents = await res.json();
const list = document.getElementById('agentList');
list.innerHTML = agents.map(a => `
<li class="agent-item ${a.id === selectedAgentId ? 'active' : ''}" onclick="selectAgent('${a.id}')">
${a.id}
</li>
`).join('');
} catch (e) { console.error(e); }
}
async function selectAgent(id) {
selectedAgentId = id;
document.getElementById('activeAgentName').innerText = `Agent: ${id}`;
fetchAgents(); // Update active class
refreshData();
}
async function refreshData() {
if (!selectedAgentId) return;
// 1. Fetch Log
try {
const logRes = await fetch(`/api/agents/${selectedAgentId}/log`);
const logText = await logRes.text();
const viewer = document.getElementById('logViewer');
viewer.innerText = logText;
if (document.getElementById('autoScroll').checked) {
viewer.scrollTop = viewer.scrollHeight;
}
} catch (e) { console.error(e); }
// 2. Fetch Messages
try {
const msgRes = await fetch(`/api/agents/${selectedAgentId}/messages`);
const msgs = await msgRes.json();
const msgList = document.getElementById('msgList');
msgList.innerHTML = msgs.map(m => `
<div class="msg-item">
<div class="msg-meta">
<span>From: ${m.from_peer}</span>
<span>${new Date(m.sent_at).toLocaleTimeString()}</span>
</div>
<div class="msg-topic">${m.topic}</div>
<div style="font-size: 0.9em; margin-top:5px; color: #bbb;">To: ${m.to_peer}</div>
</div>
`).join('');
} catch (e) { console.error(e); }
}
async function createProject() {
const name = document.getElementById('projName').value;
const repo_url = document.getElementById('repoUrl').value;
if (!name || !repo_url) return alert("Missing data");
await fetch('/api/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, repo_url, architect_id: 'architect-lead' })
});
alert("Project analysis started.");
}
// Loop
setInterval(() => {
fetchAgents();
if (selectedAgentId) refreshData();
}, 3000);
fetchAgents();
</script>
</body>
</html>