🚀 Initial commit: Rose's custom WebUI with modernization + agent attribution
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
<button class="nav-tab" data-panel="workspaces" data-label="Spaces" onclick="switchPanel('workspaces')" title="Spaces" data-i18n-title="tab_workspaces"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
|
||||
<button class="nav-tab" data-panel="todos" data-label="Todos" onclick="switchPanel('todos')" title="Current task list" data-i18n-title="tab_todos"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg></button>
|
||||
<button class="nav-tab" data-panel="missioncontrol" data-label="MC" onclick="switchPanel('missioncontrol')" title="Mission Control" data-i18n-title="tab_mc"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></button>
|
||||
<button class="nav-tab" data-panel="agents" data-label="Agents" onclick="switchPanel('agents')" title="Rose + Tier-2 Agents" data-i18n-title="tab_agents"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></button>
|
||||
</div>
|
||||
<!-- Chat panel -->
|
||||
<div class="panel-view active" id="panelChat">
|
||||
@@ -181,6 +182,25 @@
|
||||
</div>
|
||||
<div id="mcFeed" style="flex-shrink:0;max-height:100px;overflow-y:auto;padding:0 16px 12px;font-size:10px;color:var(--muted)"></div>
|
||||
</div>
|
||||
<!-- Agents panel (Rose + Tier-2) -->
|
||||
<div class="panel-view" id="panelAgents">
|
||||
<div style="padding:14px 16px 10px;flex-shrink:0;border-bottom:1px solid var(--border)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<span style="font-size:16px">🌹</span>
|
||||
<span style="font-size:14px;font-weight:700;color:var(--text)">Rose Agent Network</span>
|
||||
</div>
|
||||
<button onclick="refreshAgents()" title="Refresh" style="background:none;border:none;cursor:pointer;color:var(--muted);font-size:12px;padding:2px">↻</button>
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--muted);margin-top:4px">Rose + 7 Tier-2 Domain Agents</div>
|
||||
</div>
|
||||
<!-- Agents list -->
|
||||
<div style="flex:1;overflow-y:auto;padding:10px 16px" id="agentsList">
|
||||
<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px">Loading...</div>
|
||||
</div>
|
||||
<!-- Agent inbox slide-in panel -->
|
||||
<div id="agentInbox" style="display:none;position:absolute;top:0;right:0;bottom:0;width:320px;background:var(--surface);border-left:1px solid var(--border);z-index:100;overflow-y:auto;padding:16px;box-shadow:-4px 0 20px rgba(0,0,0,.3)"></div>
|
||||
</div>
|
||||
<!-- Workspaces panel -->
|
||||
<div class="panel-view" id="panelWorkspaces">
|
||||
<div style="padding:10px 12px 4px;font-size:11px;color:var(--muted)" data-i18n="workspace_desc">Add and switch workspaces for your sessions.</div>
|
||||
|
||||
144
static/panels.js
144
static/panels.js
@@ -17,6 +17,7 @@ async function switchPanel(name) {
|
||||
if (name === 'profiles') await loadProfilesPanel();
|
||||
if (name === 'todos') loadTodos();
|
||||
if (name === 'missioncontrol') await loadMissionControl();
|
||||
if (name === 'agents') await loadAgentsPanel();
|
||||
}
|
||||
|
||||
// ── Cron panel ──
|
||||
@@ -1764,4 +1765,147 @@ async function deleteMCPriority(id) {
|
||||
await refreshMC();
|
||||
}
|
||||
|
||||
// ── Agents Panel (Rose + Tier-2) ─────────────────────────────────────────────
|
||||
let _agentsInterval = null;
|
||||
let _selectedAgent = null;
|
||||
|
||||
async function loadAgentsPanel() {
|
||||
clearInterval(_agentsInterval);
|
||||
await refreshAgents();
|
||||
_agentsInterval = setInterval(refreshAgents, 15000);
|
||||
}
|
||||
|
||||
async function refreshAgents() {
|
||||
try {
|
||||
const data = await api('/api/agents');
|
||||
renderAgentsList(data.agents || []);
|
||||
} catch(e) {
|
||||
const box = $('agentsList');
|
||||
if (box) box.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderAgentsList(agents) {
|
||||
const box = $('agentsList');
|
||||
if (!box) return;
|
||||
|
||||
const html = agents.map(a => {
|
||||
const statusColor = a.running ? '#4caf50' : '#9e9e9e';
|
||||
const statusLabel = a.running ? 'Active' : 'Inactive';
|
||||
const inboxBadge = a.inbox_count > 0
|
||||
? `<span style="background:#ff5722;color:white;border-radius:10px;padding:1px 6px;font-size:9px;font-weight:600">${a.inbox_count}</span>`
|
||||
: '';
|
||||
return `<div class="agent-card" onclick="selectAgent('${a.id}')" style="cursor:pointer">
|
||||
<div class="agent-card-left">
|
||||
<span style="font-size:24px;line-height:1">${a.emoji}</span>
|
||||
</div>
|
||||
<div class="agent-card-body">
|
||||
<div class="agent-card-name">${esc(a.name)}</div>
|
||||
<div class="agent-card-domain">${esc(a.domain)}</div>
|
||||
<div class="agent-card-meta">
|
||||
<span class="agent-status-dot" style="background:${statusColor}"></span>
|
||||
<span style="color:${statusColor};font-size:10px">${statusLabel}</span>
|
||||
${a.tier === 'orchestrator' ? '<span style="opacity:0.5;font-size:9px;margin-left:4px">Tier 0</span>' : '<span style="opacity:0.5;font-size:9px;margin-left:4px">Tier 2</span>'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="agent-card-right">
|
||||
${inboxBadge}
|
||||
<span style="opacity:0.3;font-size:18px">›</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
box.innerHTML = html;
|
||||
}
|
||||
|
||||
async function selectAgent(agentId) {
|
||||
_selectedAgent = agentId;
|
||||
// Highlight selected
|
||||
document.querySelectorAll('.agent-card').forEach(el => el.classList.remove('selected'));
|
||||
const cards = document.querySelectorAll('.agent-card');
|
||||
const agents_data = await api('/api/agents');
|
||||
const idx = agents_data.agents.findIndex(a => a.id === agentId);
|
||||
if (cards[idx]) cards[idx].classList.add('selected');
|
||||
|
||||
// Show inbox panel
|
||||
const inboxBox = $('agentInbox');
|
||||
const agentName = agents_data.agents[idx]?.name || agentId;
|
||||
const emoji = agents_data.agents[idx]?.emoji || '🤖';
|
||||
const domain = agents_data.agents[idx]?.domain || '';
|
||||
|
||||
inboxBox.innerHTML = `
|
||||
<div class="inbox-header">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
|
||||
<span style="font-size:20px">${emoji}</span>
|
||||
<div>
|
||||
<div style="font-weight:600;font-size:13px">${esc(agentName)}</div>
|
||||
<div style="font-size:10px;opacity:0.5">${esc(domain)}</div>
|
||||
</div>
|
||||
<button onclick="closeAgentInbox()" style="margin-left:auto;background:rgba(255,255,255,.05);border:1px solid var(--border);border-radius:6px;padding:4px 8px;cursor:pointer;color:var(--muted);font-size:11px">× Close</button>
|
||||
</div>
|
||||
<div style="color:var(--muted);font-size:11px;text-align:center;padding:20px">Loading inbox...</div>
|
||||
</div>
|
||||
`;
|
||||
inboxBox.style.display = 'block';
|
||||
|
||||
// Fetch inbox
|
||||
try {
|
||||
const data = await api(`/api/agents/inbox/${agentId}`);
|
||||
renderAgentInbox(data);
|
||||
} catch(e) {
|
||||
inboxBox.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderAgentInbox(data) {
|
||||
const inboxBox = $('agentInbox');
|
||||
const agentName = data.agent_name || _selectedAgent;
|
||||
const messages = data.messages || [];
|
||||
|
||||
if (messages.length === 0) {
|
||||
inboxBox.innerHTML = `
|
||||
<div class="inbox-header">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||||
<span style="font-size:18px">📭</span>
|
||||
<span style="font-weight:600;font-size:13px">${esc(agentName)} — Inbox</span>
|
||||
<button onclick="closeAgentInbox()" style="margin-left:auto;background:rgba(255,255,255,.05);border:1px solid var(--border);border-radius:6px;padding:4px 8px;cursor:pointer;color:var(--muted);font-size:11px">× Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
|
||||
<div style="font-size:28px;margin-bottom:8px">📭</div>
|
||||
<div>No messages in inbox</div>
|
||||
<div style="font-size:10px;margin-top:4px;opacity:0.5">Messages from other agents appear here</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
inboxBox.innerHTML = `
|
||||
<div class="inbox-header">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||||
<span style="font-size:18px">📥</span>
|
||||
<span style="font-weight:600;font-size:13px">${esc(agentName)} — Inbox</span>
|
||||
<span style="opacity:0.5;font-size:10px">(${messages.length} messages)</span>
|
||||
<button onclick="closeAgentInbox()" style="margin-left:auto;background:rgba(255,255,255,.05);border:1px solid var(--border);border-radius:6px;padding:4px 8px;cursor:pointer;color:var(--muted);font-size:11px">× Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inbox-messages">
|
||||
${messages.map(m => {
|
||||
const ts = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
|
||||
const content = typeof m === 'string' ? m : (m.content || JSON.stringify(m));
|
||||
return `<div class="inbox-msg">
|
||||
<div class="inbox-msg-ts">${esc(ts)}</div>
|
||||
<div class="inbox-msg-content">${esc(String(content).slice(0, 300))}</div>
|
||||
</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function closeAgentInbox() {
|
||||
$('agentInbox').style.display = 'none';
|
||||
_selectedAgent = null;
|
||||
document.querySelectorAll('.agent-card').forEach(el => el.classList.remove('selected'));
|
||||
}
|
||||
|
||||
// Event wiring
|
||||
|
||||
@@ -1200,3 +1200,83 @@ body.resizing{user-select:none;cursor:col-resize;}
|
||||
.session-item.cli-session[data-source="discord"]::after { color: #5865F2; }
|
||||
.session-item.cli-session[data-source="slack"] { border-left-color: #4A154B; }
|
||||
.session-item.cli-session[data-source="slack"]::after { color: #4A154B; }
|
||||
|
||||
/* ── Agents Panel ── */
|
||||
.agent-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 8px;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.agent-card:hover {
|
||||
border-color: var(--accent);
|
||||
background: rgba(255,255,255,.03);
|
||||
}
|
||||
.agent-card.selected {
|
||||
border-color: var(--accent);
|
||||
background: rgba(255,255,255,.05);
|
||||
}
|
||||
.agent-card-left {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.agent-card-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.agent-card-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.agent-card-domain {
|
||||
font-size: 10px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.agent-card-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.agent-status-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.agent-card-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.inbox-messages {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 140px);
|
||||
}
|
||||
.inbox-msg {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.inbox-msg:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.inbox-msg-ts {
|
||||
font-size: 9px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.inbox-msg-content {
|
||||
font-size: 11px;
|
||||
color: var(--text);
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user