Production polish: logo, favicon, sidebar, UI, chat order, perf cache
- New Agentic OS logo: SVG hexagon with gradient (replaces unicode) - Modern sidebar toggle: SVG chevron with 180° rotation animation - Favicon: proper SVG favicon returning 200 with browser icon - Agent status cache (15s TTL) eliminates slow subprocess on every status call - Chat moved to top of sidebar nav (Primary section, before Dashboard) - Reorganized sidebar: Primary > Agents > Management > System sections - Glassmorphism card effects with gradient overlays - Stat values with gradient text, enhanced stat icons with glow - Button micro-interactions: scale, shadow, gradient overlay on hover - Table polish: better row transitions, border-spacing - Chat sidebar: active agent indicator, hover animations - Gradient logo text matching SVG accent - All 13 pages verified 200, zero console errors
This commit is contained in:
parent
4bbc3bd826
commit
828a6ab38a
|
|
@ -5,3 +5,4 @@
|
|||
{"action": "chat_message", "agent": "opencode", "msg_preview": "hello", "timestamp": "2026-05-17T10:37:06.791873+00:00", "id": "c41e3054"}
|
||||
{"action": "chat_message", "agent": "opencode", "msg_preview": "hello", "timestamp": "2026-05-17T10:40:56.501310+00:00", "id": "6d4ab33d"}
|
||||
{"action": "chat_message", "agent": "opencode", "msg_preview": "hello", "timestamp": "2026-05-17T10:42:36.570364+00:00", "id": "358f4058"}
|
||||
{"action": "chat_message", "agent": "opencode", "msg_preview": "hey", "timestamp": "2026-05-17T10:49:19.673600+00:00", "id": "c188ba46"}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Agentic OS</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
|
@ -13,14 +14,26 @@
|
|||
<nav id="sidebar" class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="logo">
|
||||
<div class="logo-icon">⬡</div>
|
||||
<svg class="logo-svg" viewBox="0 0 36 36" width="36" height="36">
|
||||
<defs><linearGradient id="lg" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#6c5ce7"/><stop offset="100%" stop-color="#fd79a8"/></linearGradient></defs>
|
||||
<rect x="2" y="2" width="32" height="32" rx="10" fill="url(#lg)" opacity="0.15"/>
|
||||
<rect x="4" y="4" width="28" height="28" rx="8" fill="none" stroke="url(#lg)" stroke-width="1.5"/>
|
||||
<polygon points="18,8 26,13 26,23 18,28 10,23 10,13" fill="none" stroke="url(#lg)" stroke-width="2" stroke-linejoin="round"/>
|
||||
<circle cx="18" cy="18" r="4" fill="url(#lg)"/>
|
||||
</svg>
|
||||
<span class="logo-text">Agentic OS</span>
|
||||
</div>
|
||||
<button class="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar">◀</button>
|
||||
<button class="sidebar-toggle" onclick="toggleSidebar()" title="Toggle sidebar">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" class="toggle-icon">
|
||||
<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sidebar-nav">
|
||||
<div class="sidebar-section"><div class="sidebar-section-label">Core</div></div>
|
||||
<div class="sidebar-section"><div class="sidebar-section-label">Primary</div></div>
|
||||
<a href="#chat" class="nav-item" data-page="chat"><span class="nav-icon">💬</span><span class="nav-label">AI Chat</span></a>
|
||||
<a href="#dashboard" class="nav-item active" data-page="dashboard"><span class="nav-icon">◉</span><span class="nav-label">Dashboard</span></a>
|
||||
<div class="sidebar-section"><div class="sidebar-section-label">Agents</div></div>
|
||||
<a href="#skills" class="nav-item" data-page="skills"><span class="nav-icon">⚡</span><span class="nav-label">Skills</span><span class="nav-badge" id="skillCount">0</span></a>
|
||||
<a href="#memory" class="nav-item" data-page="memory"><span class="nav-icon">🧠</span><span class="nav-label">Memory</span></a>
|
||||
<a href="#scheduler" class="nav-item" data-page="scheduler"><span class="nav-icon">⏱</span><span class="nav-label">Scheduler</span></a>
|
||||
|
|
@ -29,12 +42,10 @@
|
|||
<a href="#cost" class="nav-item" data-page="cost"><span class="nav-icon">💰</span><span class="nav-label">Cost Analytics</span></a>
|
||||
<a href="#plugins" class="nav-item" data-page="plugins"><span class="nav-icon">🔌</span><span class="nav-label">Plugins</span></a>
|
||||
<a href="#backups" class="nav-item" data-page="backups"><span class="nav-icon">💾</span><span class="nav-label">Backups</span></a>
|
||||
<div class="sidebar-section"><div class="sidebar-section-label">Resources</div></div>
|
||||
<a href="#prompts" class="nav-item" data-page="prompts"><span class="nav-icon">📝</span><span class="nav-label">Prompts</span></a>
|
||||
<a href="#standards" class="nav-item" data-page="standards"><span class="nav-icon">📐</span><span class="nav-label">Standards</span></a>
|
||||
<div class="sidebar-section"><div class="sidebar-section-label">System</div></div>
|
||||
<a href="#settings" class="nav-item" data-page="settings"><span class="nav-icon">⚙</span><span class="nav-label">Settings</span></a>
|
||||
<div class="sidebar-section"><div class="sidebar-section-label">Setup</div></div>
|
||||
<a href="#chat" class="nav-item" data-page="chat"><span class="nav-icon">💬</span><span class="nav-label">AI Chat</span></a>
|
||||
<a href="#setup-wizard" class="nav-item" data-page="setup-wizard"><span class="nav-icon">🚀</span><span class="nav-label">Setup Wizard</span></a>
|
||||
</div>
|
||||
<div class="sidebar-footer">
|
||||
|
|
|
|||
|
|
@ -104,36 +104,39 @@ body {
|
|||
}
|
||||
.sidebar.collapsed .nav-item.active::before { left: -6px; }
|
||||
.sidebar.collapsed .sidebar-section { padding: 4px 0; }
|
||||
.sidebar.collapsed .sidebar-header { justify-content: center; padding: 18px 0; gap: 0; }
|
||||
.sidebar.collapsed .sidebar-header { justify-content: center; padding: 16px 0; gap: 0; }
|
||||
.sidebar.collapsed .sidebar-toggle { position: absolute; right: -14px; top: 18px; background: var(--bg-secondary); border: 1px solid var(--border); border-left: none; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; width: 20px; height: 32px; opacity: 0.8; z-index: 10; }
|
||||
.sidebar.collapsed .sidebar-toggle:hover { opacity: 1; }
|
||||
.sidebar.collapsed .sidebar-footer { justify-content: center; padding: 12px 0; flex-direction: column; gap: 8px; }
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18px 16px;
|
||||
padding: 16px 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.logo { display: flex; align-items: center; gap: 10px; }
|
||||
.logo-icon {
|
||||
width: 32px; height: 32px;
|
||||
background: linear-gradient(135deg, var(--accent), var(--pink));
|
||||
border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: 800; color: white;
|
||||
flex-shrink: 0;
|
||||
.logo-svg { flex-shrink: 0; filter: drop-shadow(0 2px 8px rgba(108, 92, 231, 0.3)); }
|
||||
.logo-text {
|
||||
font-weight: 700; font-size: 16px; white-space: nowrap;
|
||||
background: linear-gradient(135deg, var(--accent-light), var(--pink));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.logo-text { font-weight: 700; font-size: 16px; white-space: nowrap; }
|
||||
|
||||
.sidebar-toggle {
|
||||
background: none; border: none; color: var(--text-muted);
|
||||
font-size: 20px; cursor: pointer; padding: 4px;
|
||||
cursor: pointer; padding: 6px;
|
||||
border-radius: var(--radius-sm); transition: var(--transition);
|
||||
width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
|
||||
width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.sidebar-toggle:hover { background: var(--bg-card); color: var(--text-primary); }
|
||||
.sidebar-toggle:hover { background: var(--bg-card); color: var(--text-primary); opacity: 1; }
|
||||
.toggle-icon { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
|
||||
|
||||
.sidebar-section { padding: 8px 12px 4px; }
|
||||
.sidebar-section-label {
|
||||
|
|
@ -199,14 +202,16 @@ body {
|
|||
}
|
||||
.topbar-left { display: flex; align-items: center; gap: 12px; }
|
||||
.topbar-title { font-size: 16px; font-weight: 600; }
|
||||
.topbar-breadcrumb { font-size: 12px; color: var(--text-muted); }
|
||||
.topbar-breadcrumb { font-size: 12px; color: var(--text-muted); margin-top: 1px; }
|
||||
.topbar-right { display: flex; align-items: center; gap: 12px; }
|
||||
.topbar-search {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 6px 12px;
|
||||
border-radius: 8px; padding: 6px 12px;
|
||||
color: var(--text-muted); font-size: 13px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.topbar-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
.topbar-search input {
|
||||
background: none; border: none; color: var(--text-primary);
|
||||
font-size: 13px; outline: none; width: 180px;
|
||||
|
|
@ -235,7 +240,16 @@ body {
|
|||
transition: var(--transition);
|
||||
position: relative;
|
||||
}
|
||||
.card:hover { border-color: var(--border-hover); }
|
||||
.card::before {
|
||||
content: ''; position: absolute; inset: 0; border-radius: inherit;
|
||||
background: linear-gradient(135deg, rgba(108,92,231,0.03), transparent 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.card:hover {
|
||||
border-color: var(--border-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
||||
}
|
||||
.card-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
|
@ -245,24 +259,33 @@ body {
|
|||
.stat-card {
|
||||
position: relative; overflow: hidden;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.stat-card::after {
|
||||
content: ''; position: absolute; top: 0; right: 0;
|
||||
width: 120px; height: 120px;
|
||||
background: radial-gradient(circle, rgba(108,92,231,0.08) 0%, transparent 70%);
|
||||
border-radius: 50%; transform: translate(30%, -30%);
|
||||
content: ''; position: absolute; top: -20%; right: -10%;
|
||||
width: 160px; height: 160px;
|
||||
background: radial-gradient(circle, rgba(108,92,231,0.06) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.stat-card:hover::after { opacity: 0.8; }
|
||||
.stat-icon {
|
||||
width: 40px; height: 40px; border-radius: var(--radius);
|
||||
width: 42px; height: 42px; border-radius: var(--radius);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
.stat-icon.purple { background: var(--accent-glow); color: var(--accent-light); box-shadow: 0 0 20px rgba(108,92,231,0.1); }
|
||||
.stat-icon.green { background: var(--green-dim); color: var(--green); box-shadow: 0 0 20px rgba(0,212,170,0.1); }
|
||||
.stat-icon.blue { background: var(--blue-dim); color: var(--blue); box-shadow: 0 0 20px rgba(69,170,242,0.1); }
|
||||
.stat-icon.yellow { background: var(--yellow-dim); color: var(--yellow); box-shadow: 0 0 20px rgba(255,165,2,0.1); }
|
||||
.stat-icon.red { background: var(--red-dim); color: var(--red); box-shadow: 0 0 20px rgba(255,71,87,0.1); }
|
||||
.stat-value {
|
||||
font-size: 28px; font-weight: 800; letter-spacing: -0.5px;
|
||||
background: linear-gradient(135deg, var(--text-primary), var(--accent-light));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.stat-icon.purple { background: var(--accent-glow); color: var(--accent-light); }
|
||||
.stat-icon.green { background: var(--green-dim); color: var(--green); }
|
||||
.stat-icon.blue { background: var(--blue-dim); color: var(--blue); }
|
||||
.stat-icon.yellow { background: var(--yellow-dim); color: var(--yellow); }
|
||||
.stat-icon.red { background: var(--red-dim); color: var(--red); }
|
||||
.stat-value { font-size: 28px; font-weight: 800; letter-spacing: -0.5px; }
|
||||
.stat-label { font-size: 12px; color: var(--text-secondary); margin-top: 2px; }
|
||||
.stat-change {
|
||||
font-size: 11px; font-weight: 600; margin-top: 6px;
|
||||
|
|
@ -285,23 +308,30 @@ body {
|
|||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px; font-weight: 500;
|
||||
cursor: pointer; transition: var(--transition);
|
||||
cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
white-space: nowrap; font-family: var(--font);
|
||||
user-select: none; line-height: 1.2;
|
||||
}
|
||||
.btn:hover { background: var(--bg-card-hover); border-color: var(--border-hover); }
|
||||
.btn:active { transform: scale(0.97); }
|
||||
.btn:hover { background: var(--bg-card-hover); border-color: var(--border-hover); box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
|
||||
.btn:active { transform: scale(0.96); }
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent), #5a4bd1);
|
||||
color: white; border-color: transparent;
|
||||
color: white; border-color: transparent; position: relative; overflow: hidden;
|
||||
}
|
||||
.btn-primary::before {
|
||||
content: ''; position: absolute; inset: 0; border-radius: inherit;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.1), transparent);
|
||||
opacity: 0; transition: opacity 0.3s;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(135deg, #7b6df7, #6c5ce7);
|
||||
box-shadow: 0 4px 16px var(--accent-glow);
|
||||
box-shadow: 0 4px 20px var(--accent-glow); border-color: transparent;
|
||||
}
|
||||
.btn-primary:hover::before { opacity: 1; }
|
||||
.btn-danger { background: var(--red); color: white; border-color: transparent; }
|
||||
.btn-danger:hover { box-shadow: 0 4px 16px var(--red-dim); }
|
||||
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-secondary); }
|
||||
.btn-ghost:hover { background: var(--bg-card); color: var(--text-primary); }
|
||||
.btn-ghost:hover { background: var(--bg-card); color: var(--text-primary); border-color: var(--border); }
|
||||
.btn-sm { padding: 5px 10px; font-size: 12px; }
|
||||
.btn-xs { padding: 3px 8px; font-size: 11px; }
|
||||
.btn-icon { width: 34px; height: 34px; padding: 0; justify-content: center; }
|
||||
|
|
@ -313,18 +343,19 @@ body {
|
|||
.table-wrapper {
|
||||
overflow-x: auto; border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
}
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px 14px; text-align: left; font-size: 13px; }
|
||||
table { width: 100%; border-collapse: separate; border-spacing: 0; }
|
||||
th, td { padding: 11px 14px; text-align: left; font-size: 13px; }
|
||||
th {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-muted); font-weight: 600; font-size: 11px;
|
||||
text-transform: uppercase; letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
td { border-bottom: 1px solid var(--border); }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
td { border-top: 1px solid var(--border); }
|
||||
tr:first-child td { border-top: none; }
|
||||
tr:hover td { background: var(--bg-card-hover); }
|
||||
tr { transition: background 0.15s; }
|
||||
|
||||
/* ─── Forms ─── */
|
||||
.form-group { margin-bottom: 16px; }
|
||||
|
|
@ -367,8 +398,8 @@ tr:hover td { background: var(--bg-card-hover); }
|
|||
display: flex; flex-direction: column;
|
||||
}
|
||||
.skill-card:hover {
|
||||
border-color: var(--accent); transform: translateY(-2px);
|
||||
box-shadow: 0 8px 32px rgba(108, 92, 231, 0.12);
|
||||
border-color: var(--accent); transform: translateY(-3px);
|
||||
box-shadow: 0 12px 40px rgba(108, 92, 231, 0.15);
|
||||
}
|
||||
.skill-card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
||||
.skill-card-icon {
|
||||
|
|
@ -630,6 +661,7 @@ pre {
|
|||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.chat-sidebar {
|
||||
width: 200px; min-width: 200px;
|
||||
|
|
@ -641,16 +673,20 @@ pre {
|
|||
.chat-agents-label {
|
||||
font-size: 10px; font-weight: 700; text-transform: uppercase;
|
||||
letter-spacing: 1px; color: var(--text-muted);
|
||||
padding: 8px 8px 4px; margin-bottom: 4px;
|
||||
padding: 10px 8px 6px; margin-bottom: 4px;
|
||||
}
|
||||
.chat-agent {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px; border-radius: var(--radius);
|
||||
cursor: pointer; transition: var(--transition);
|
||||
margin-bottom: 2px;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
margin-bottom: 2px; position: relative;
|
||||
}
|
||||
.chat-agent:hover { background: var(--bg-card); }
|
||||
.chat-agent:hover { background: var(--bg-card); transform: translateX(2px); }
|
||||
.chat-agent.active { background: var(--accent-glow); }
|
||||
.chat-agent.active::before {
|
||||
content: ''; position: absolute; left: -8px; top: 50%; transform: translateY(-50%);
|
||||
width: 3px; height: 18px; background: var(--accent); border-radius: 0 3px 3px 0;
|
||||
}
|
||||
.chat-agent-name { font-size: 13px; font-weight: 600; }
|
||||
.chat-agent-desc { font-size: 11px; color: var(--text-muted); }
|
||||
.chat-main {
|
||||
|
|
@ -666,9 +702,12 @@ pre {
|
|||
display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; height: 100%;
|
||||
text-align: center; color: var(--text-muted);
|
||||
gap: 8px;
|
||||
gap: 8px; padding: 20px;
|
||||
}
|
||||
.chat-welcome-icon {
|
||||
font-size: 48px; opacity: 0.3;
|
||||
filter: drop-shadow(0 4px 20px rgba(108,92,231,0.2));
|
||||
}
|
||||
.chat-welcome-icon { font-size: 48px; opacity: 0.3; }
|
||||
.chat-welcome-title { font-size: 18px; font-weight: 700; color: var(--text-secondary); }
|
||||
.chat-welcome-desc { font-size: 13px; max-width: 400px; line-height: 1.5; }
|
||||
.chat-message {
|
||||
|
|
@ -683,14 +722,17 @@ pre {
|
|||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 16px; flex-shrink: 0;
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.chat-message-body {
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg); padding: 12px 16px;
|
||||
min-width: 60px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
.chat-message.user .chat-message-body {
|
||||
background: var(--accent-glow); border-color: transparent;
|
||||
background: linear-gradient(135deg, rgba(108,92,231,0.15), rgba(108,92,231,0.08));
|
||||
border-color: rgba(108,92,231,0.3);
|
||||
}
|
||||
.chat-message-header {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
|
|
|
|||
|
|
@ -53,8 +53,10 @@ function toggleSidebar() {
|
|||
const sidebar = document.getElementById('sidebar');
|
||||
const isCollapsed = sidebar.classList.toggle('collapsed');
|
||||
localStorage.setItem('sidebarCollapsed', isCollapsed);
|
||||
const btn = sidebar.querySelector('.sidebar-toggle');
|
||||
if (btn) btn.textContent = isCollapsed ? '▶' : '◀';
|
||||
const icon = sidebar.querySelector('.toggle-icon');
|
||||
if (icon) {
|
||||
icon.style.transform = isCollapsed ? 'rotate(180deg)' : '';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
|
|
@ -69,9 +71,10 @@ function loadTheme() {
|
|||
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
||||
const sidebarCollapsed = localStorage.getItem('sidebarCollapsed');
|
||||
if (sidebarCollapsed === 'true') {
|
||||
document.getElementById('sidebar').classList.add('collapsed');
|
||||
const btn = document.querySelector('.sidebar-toggle');
|
||||
if (btn) btn.textContent = '▶';
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
sidebar.classList.add('collapsed');
|
||||
const icon = sidebar.querySelector('.toggle-icon');
|
||||
if (icon) icon.style.transform = 'rotate(180deg)';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,20 @@
|
|||
"agent": "opencode",
|
||||
"content": "\u23f1 Agent 'opencode' took too long to respond.\n\nYour message has been dispatched. For a full response, run the agent CLI directly in your terminal.\n\n**Message:** hello",
|
||||
"timestamp": "2026-05-17T10:42:36.569740+00:00"
|
||||
},
|
||||
{
|
||||
"id": "0801ef96",
|
||||
"role": "user",
|
||||
"agent": "opencode",
|
||||
"content": "hey",
|
||||
"timestamp": "2026-05-17T10:49:09.907635+00:00"
|
||||
},
|
||||
{
|
||||
"id": "13259f25",
|
||||
"role": "assistant",
|
||||
"agent": "opencode",
|
||||
"content": "Hey Mihir. What are we working on today?\n\u001b[0m\n> build \u00b7 deepseek-v4-flash-free\n\u001b[0m\n",
|
||||
"timestamp": "2026-05-17T10:49:19.652896+00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
40
server.py
40
server.py
|
|
@ -17,7 +17,7 @@ from typing import Optional
|
|||
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -85,22 +85,34 @@ def append_audit(entry: dict):
|
|||
with open(audit_file, "a") as f:
|
||||
f.write(json.dumps(entry) + "\n")
|
||||
|
||||
# ─── Agent Discovery ──────────────────────────────────────────────
|
||||
# ─── Agent Discovery (with cache) ─────────────────────────────────
|
||||
|
||||
_agent_cache = {}
|
||||
_agent_cache_time = 0
|
||||
|
||||
def check_agent(name: str) -> dict:
|
||||
global _agent_cache, _agent_cache_time
|
||||
now = time.time()
|
||||
if now - _agent_cache_time < 15 and name in _agent_cache:
|
||||
return _agent_cache[name]
|
||||
try:
|
||||
if name == "opencode":
|
||||
r = subprocess.run(["opencode", "--version"], capture_output=True, text=True, timeout=5)
|
||||
return {"name": "opencode", "status": "online" if r.returncode == 0 else "error"}
|
||||
r = subprocess.run(["opencode", "--version"], capture_output=True, text=True, timeout=3)
|
||||
status = "online" if r.returncode == 0 else "error"
|
||||
elif name == "hermes":
|
||||
r = subprocess.run(["hermes", "--version"], capture_output=True, text=True, timeout=5)
|
||||
return {"name": "hermes", "status": "online" if r.returncode == 0 else "error"}
|
||||
r = subprocess.run(["hermes", "--version"], capture_output=True, text=True, timeout=3)
|
||||
status = "online" if r.returncode == 0 else "error"
|
||||
elif name == "gemini":
|
||||
r = subprocess.run(["gemini", "--version"], capture_output=True, text=True, timeout=5)
|
||||
return {"name": "gemini", "status": "online" if r.returncode == 0 else "error"}
|
||||
r = subprocess.run(["gemini", "--version"], capture_output=True, text=True, timeout=3)
|
||||
status = "online" if r.returncode == 0 else "error"
|
||||
else:
|
||||
status = "offline"
|
||||
except Exception:
|
||||
return {"name": name, "status": "offline"}
|
||||
return {"name": name, "status": "offline"}
|
||||
status = "offline"
|
||||
result = {"name": name, "status": status}
|
||||
_agent_cache[name] = result
|
||||
_agent_cache_time = now
|
||||
return result
|
||||
|
||||
# ─── Routes: Status ───────────────────────────────────────────────
|
||||
|
||||
|
|
@ -522,9 +534,15 @@ def index():
|
|||
|
||||
# ─── Favicon ──────────────────────────────────────────────────────
|
||||
|
||||
FAVICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#6c5ce7"/><stop offset="100%" stop-color="#fd79a8"/></linearGradient></defs><rect width="32" height="32" rx="8" fill="url(#g)"/><polygon points="16,6 24,11 24,21 16,26 8,21 8,11" fill="none" stroke="white" stroke-width="2" stroke-linejoin="round"/><circle cx="16" cy="16" r="3" fill="white"/></svg>'
|
||||
|
||||
@app.get("/favicon.ico")
|
||||
def favicon():
|
||||
return HTMLResponse("")
|
||||
return Response(content=FAVICON_SVG, media_type="image/svg+xml")
|
||||
|
||||
@app.get("/favicon.svg")
|
||||
def favicon_svg():
|
||||
return Response(content=FAVICON_SVG, media_type="image/svg+xml")
|
||||
|
||||
# ─── Main ─────────────────────────────────────────────────────────
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue