diff --git a/src/gateway/ui/pages/chat.js b/src/gateway/ui/pages/chat.js index 1d5453d..43aaa0c 100644 --- a/src/gateway/ui/pages/chat.js +++ b/src/gateway/ui/pages/chat.js @@ -36,7 +36,7 @@ function escapeHtml(text) { function highlightCode() { if (typeof hljs !== 'undefined') { - document.querySelectorAll('.chat-messages pre code').forEach(block => { + document.querySelectorAll('#chat-messages pre code').forEach(block => { hljs.highlightElement(block); }); } @@ -44,10 +44,26 @@ function highlightCode() { function createMessageEl(role, content) { const wrapper = document.createElement('div'); - wrapper.className = 'message-wrapper'; + + const roleClasses = { + user: 'flex flex-col gap-1.5 max-w-[85%] md:max-w-[75%] self-end group', + assistant: 'flex flex-col gap-1.5 max-w-[85%] md:max-w-[75%] self-start group', + system: 'flex flex-col gap-1.5 self-stretch max-w-full group', + error: 'flex flex-col gap-1.5 max-w-[85%] md:max-w-[75%] self-start group', + }; + wrapper.className = roleClasses[role] || roleClasses.assistant; const div = document.createElement('div'); - div.className = `message ${role}`; + + const messageClasses = { + user: 'rounded-lg px-3.5 py-2.5 text-sm leading-relaxed break-words whitespace-pre-wrap bg-blue-500/15 border border-blue-500/25 text-zinc-50', + assistant: 'rounded-lg px-3.5 py-2.5 text-sm leading-relaxed break-words whitespace-pre-wrap bg-zinc-900 border border-zinc-800 text-zinc-50', + system: 'rounded-lg px-3.5 py-2.5 leading-relaxed break-words whitespace-pre-wrap bg-zinc-800 text-zinc-400 border border-zinc-700 text-xs', + error: 'rounded-lg px-3.5 py-2.5 text-sm leading-relaxed break-words whitespace-pre-wrap bg-red-500/15 border border-red-500/30 text-zinc-50', + }; + div.className = messageClasses[role] || messageClasses.assistant; + // Keep role as a data attribute for querySelector compatibility + div.dataset.role = role; if (role === 'assistant' || role === 'system') { div.innerHTML = renderSafeMarkdown(content); @@ -67,16 +83,16 @@ function createMessageEl(role, content) { function createToolEventEl(event, data) { const group = document.createElement('div'); - group.className = 'tool-event-group'; + group.className = 'border border-zinc-800 rounded-md my-1 overflow-hidden'; const header = document.createElement('div'); - header.className = 'tool-event-header'; + header.className = 'flex items-center gap-2 px-3 py-1.5 bg-zinc-800 cursor-pointer text-xs text-zinc-500 hover:text-zinc-400 select-none'; if (event === 'tool_start') { header.innerHTML = ` ${escapeHtml(data.tool)}`; } else if (event === 'tool_end') { const icon = data.result?.success ? '✓' : '✗'; - const cls = data.result?.success ? 'status-ok' : 'status-error'; + const cls = data.result?.success ? 'text-green-500' : 'text-red-500'; header.innerHTML = `${icon} ${escapeHtml(data.tool)}`; } @@ -85,7 +101,7 @@ function createToolEventEl(event, data) { }); const body = document.createElement('div'); - body.className = 'tool-event-body'; + body.className = 'tool-event-body px-3 py-2 text-xs text-zinc-400 bg-zinc-900 whitespace-pre-wrap break-all max-h-48 overflow-y-auto font-mono'; if (event === 'tool_start' && data.args) { body.textContent = JSON.stringify(data.args, null, 2); @@ -144,17 +160,17 @@ function renderPendingAttachments() { for (let i = 0; i < _pendingAttachments.length; i++) { const att = _pendingAttachments[i]; const chip = document.createElement('div'); - chip.className = 'attachment-chip'; + chip.className = 'inline-flex items-center gap-2 px-2.5 py-1 rounded-full bg-zinc-900 border border-zinc-800 text-zinc-400 text-xs'; const name = document.createElement('span'); - name.className = 'attachment-name'; + name.className = 'max-w-[220px] overflow-hidden text-ellipsis whitespace-nowrap'; name.textContent = att.filename || 'attachment'; const rm = document.createElement('button'); - rm.className = 'attachment-remove'; + rm.className = 'text-zinc-500 hover:text-zinc-50 cursor-pointer text-base leading-none appearance-none border-0 bg-transparent'; rm.type = 'button'; rm.title = 'Remove attachment'; - rm.textContent = '×'; + rm.textContent = '\u00d7'; rm.addEventListener('click', () => { _pendingAttachments.splice(i, 1); renderPendingAttachments(); @@ -184,23 +200,23 @@ function getMessageText(el) { function createMessageActions(role) { const bar = document.createElement('div'); - bar.className = 'message-actions'; + bar.className = 'flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity'; // Copy button — all messages const copyBtn = document.createElement('button'); - copyBtn.className = 'msg-action-btn'; + copyBtn.className = 'inline-flex items-center justify-center w-6 h-6 rounded text-zinc-500 hover:text-zinc-50 hover:bg-zinc-800 transition-colors'; copyBtn.title = 'Copy'; copyBtn.innerHTML = COPY_ICON; copyBtn.addEventListener('click', () => { - const msg = bar.closest('.message'); - if (!msg) {return;} - const text = getMessageText(msg); + const msgEl = bar.previousElementSibling; + if (!msgEl) {return;} + const text = getMessageText(msgEl); navigator.clipboard.writeText(text).then(() => { copyBtn.innerHTML = CHECK_ICON; - copyBtn.classList.add('copied'); + copyBtn.classList.add('text-green-500'); setTimeout(() => { copyBtn.innerHTML = COPY_ICON; - copyBtn.classList.remove('copied'); + copyBtn.classList.remove('text-green-500'); }, 1500); }); }); @@ -209,13 +225,13 @@ function createMessageActions(role) { // Edit button — user messages only if (role === 'user') { const editBtn = document.createElement('button'); - editBtn.className = 'msg-action-btn'; + editBtn.className = 'inline-flex items-center justify-center w-6 h-6 rounded text-zinc-500 hover:text-zinc-50 hover:bg-zinc-800 transition-colors'; editBtn.title = 'Edit'; editBtn.innerHTML = EDIT_ICON; editBtn.addEventListener('click', () => { - const msg = bar.closest('.message'); - if (!msg) {return;} - const text = getMessageText(msg); + const msgEl = bar.previousElementSibling; + if (!msgEl) {return;} + const text = getMessageText(msgEl); const input = _elements.input; if (input) { input.value = text; @@ -268,8 +284,8 @@ function showSlashPopup(filtered) { for (let i = 0; i < filtered.length; i++) { const item = document.createElement('div'); - item.className = 'slash-popup-item' + (i === _slashPopupIndex ? ' selected' : ''); - item.innerHTML = `${escapeHtml(filtered[i].name)}${escapeHtml(filtered[i].desc)}`; + item.className = 'flex items-baseline gap-3 px-3 py-2 cursor-pointer transition-colors' + (i === _slashPopupIndex ? ' bg-zinc-800' : ''); + item.innerHTML = `${escapeHtml(filtered[i].name)}${escapeHtml(filtered[i].desc)}`; item.addEventListener('click', () => { selectSlashCommand(filtered[i].name); }); @@ -292,10 +308,14 @@ function hideSlashPopup() { function updatePopupSelection(_filtered) { const popup = _elements.slashPopup; if (!popup) {return;} - const items = popup.querySelectorAll('.slash-popup-item'); - items.forEach((el, i) => { - el.classList.toggle('selected', i === _slashPopupIndex); - }); + const items = popup.children; + for (let i = 0; i < items.length; i++) { + if (i === _slashPopupIndex) { + items[i].classList.add('bg-zinc-800'); + } else { + items[i].classList.remove('bg-zinc-800'); + } + } } function selectSlashCommand(name) { @@ -515,7 +535,7 @@ async function loadHistory(client) { scrollToBottom(); } catch { - msgs.innerHTML = '
Could not load history
'; + msgs.innerHTML = '
Could not load history
'; } } @@ -559,8 +579,8 @@ async function sendMessage(client, overrideText) { // Create placeholder for assistant response const placeholder = document.createElement('div'); - placeholder.className = 'message assistant streaming-cursor'; - placeholder.innerHTML = 'Thinking...'; + placeholder.className = 'rounded-lg px-3.5 py-2.5 text-sm leading-relaxed break-words whitespace-pre-wrap bg-zinc-900 border border-zinc-800 text-zinc-50 streaming-cursor'; + placeholder.innerHTML = 'Thinking...'; _elements.messages.appendChild(placeholder); scrollToBottom(); @@ -575,17 +595,17 @@ async function sendMessage(client, overrideText) { stream.on('tool_end', (data) => { // Replace the last tool_start spinner with completion marker - const events = _elements.messages.querySelectorAll('.tool-event-group'); + const events = _elements.messages.querySelectorAll('.border.border-zinc-800.rounded-md'); const last = events[events.length - 1]; if (last) { - const header = last.querySelector('.tool-event-header'); + const header = last.firstElementChild; if (header && data.tool) { const icon = data.result?.success !== false ? '✓' : '✗'; - const cls = data.result?.success !== false ? 'status-ok' : 'status-error'; + const cls = data.result?.success !== false ? 'text-green-500' : 'text-red-500'; header.innerHTML = `${icon} ${escapeHtml(data.tool)}`; } // Add result body - const body = last.querySelector('.tool-event-body'); + const body = last.lastElementChild; if (body && data.result) { body.textContent = data.result.output || data.result.error || '(no output)'; } @@ -595,7 +615,7 @@ async function sendMessage(client, overrideText) { stream.on('context_warning', (data) => { const note = document.createElement('div'); - note.className = 'message assistant'; + note.className = 'rounded-lg px-3.5 py-2.5 text-sm leading-relaxed break-words whitespace-pre-wrap bg-zinc-900 border border-zinc-800 text-zinc-50'; const text = data?.message || 'Context usage is getting high.'; note.innerHTML = renderSafeMarkdown(`> ${text}`); _elements.messages.insertBefore(note, placeholder); @@ -607,11 +627,18 @@ async function sendMessage(client, overrideText) { placeholder.classList.remove('streaming-cursor'); const content = done?.content ?? done?.text ?? '(no response)'; placeholder.innerHTML = renderSafeMarkdown(content); - placeholder.appendChild(createMessageActions('assistant')); + + // Wrap placeholder in a proper message wrapper for action buttons + const wrapper = document.createElement('div'); + wrapper.className = 'flex flex-col gap-1.5 max-w-[85%] md:max-w-[75%] self-start group'; + placeholder.parentNode.insertBefore(wrapper, placeholder); + wrapper.appendChild(placeholder); + wrapper.appendChild(createMessageActions('assistant')); + setTimeout(highlightCode, 0); } catch (err) { placeholder.classList.remove('streaming-cursor'); - placeholder.className = 'message error'; + placeholder.className = 'rounded-lg px-3.5 py-2.5 text-sm leading-relaxed break-words whitespace-pre-wrap bg-red-500/15 border border-red-500/30 text-zinc-50'; placeholder.textContent = `Error: ${err.message}`; } finally { _sending = false; @@ -623,41 +650,41 @@ async function sendMessage(client, overrideText) { // ── Search SVG Icon ───────────────────────────────────────── -const SEARCH_ICON = ''; -const ATTACH_ICON = ''; -const COPY_ICON = ''; -const CHECK_ICON = ''; -const EDIT_ICON = ''; +const SEARCH_ICON = ''; +const ATTACH_ICON = ''; +const COPY_ICON = ''; +const CHECK_ICON = ''; +const EDIT_ICON = ''; // ── Page Export ────────────────────────────────────────────── export const ChatPage = { async render(el, client) { el.innerHTML = ` -
-
- - - +
+
+ + +
-
-
- - - +
-
- -
- - +
+ +
+ +
@@ -691,7 +718,7 @@ export const ChatPage = { await loadSessions(client); _elements.messages.innerHTML = ''; } catch (err) { - _elements.messages.innerHTML = `
Failed to create session: ${err.message}
`; + _elements.messages.innerHTML = `
Failed to create session: ${err.message}
`; } }); @@ -795,14 +822,14 @@ export const ChatPage = { // Dismiss slash popup on outside click el.addEventListener('click', (e) => { - if (!e.target.closest('.chat-input-wrapper')) { + if (!e.target.closest('.relative')) { hideSlashPopup(); } }); // If there's a current session, show welcome if (!_currentSession) { - _elements.messages.innerHTML = '
Select a session or create a new one to start chatting
'; + _elements.messages.innerHTML = '
Select a session or create a new one to start chatting
'; } }, diff --git a/src/gateway/ui/pages/dashboard.js b/src/gateway/ui/pages/dashboard.js index 3f6c11f..c7711b9 100644 --- a/src/gateway/ui/pages/dashboard.js +++ b/src/gateway/ui/pages/dashboard.js @@ -126,46 +126,46 @@ function buildRollbackPatchesFromSnapshot(snapshot) { function renderSkeleton(el) { el.innerHTML = ` -

Live Ops Dashboard

+

Live Ops Dashboard

-

Core Counters

-
-
Loading...
+

Core Counters

+
+
Loading...
-

Model Performance

+

Model Performance

-
Loading...
+
Loading...
-

Session Analytics

+

Session Analytics

-
Loading...
+
Loading...
-

Context Health

+

Context Health

-
Loading...
+
Loading...
-

Assistant Health

+

Assistant Health

-
Loading...
+
Loading...
-

Event Stream

-
-
Loading events...
+

Event Stream

+
+
Loading events...
-

Active Requests

+

Active Requests

-
Loading...
+
Loading...
-

Services

-
-
Loading...
+

Services

+
+
Loading...
`; } @@ -185,13 +185,13 @@ function updateCounters(metrics, health) { { label: 'Queue Depth', value: String(metrics?.queueDepth ?? 0), cls: '' }, { label: 'Uptime', value: formatUptime(metrics?.uptime ?? 0), cls: '' }, { label: 'Active Requests', value: String(metrics?.activeRequests ?? 0), cls: '' }, - { label: 'Errors', value: String(errCount), cls: errCount > 0 ? 'error' : '' }, + { label: 'Errors', value: String(errCount), cls: errCount > 0 ? 'text-red-500' : '' }, ]; el.innerHTML = cards.map(c => - `
-
${c.label}
-
${c.value}
+ `
+
${c.label}
+
${c.value}
`, ).join(''); } @@ -204,7 +204,7 @@ function updateModelTable(metrics) { const calls = mc?.recentCalls ?? []; if (calls.length === 0) { - el.innerHTML = '
No model calls recorded yet
'; + el.innerHTML = '
No model calls recorded yet
'; return; } @@ -213,41 +213,43 @@ function updateModelTable(metrics) { const errorRate = mc.errorRate ?? 0; const summaryHtml = ` -
-
Total Calls: ${totalCalls}
-
Avg Latency: ${avgLatency}ms
-
Error Rate: ${(errorRate * 100).toFixed(2)}%
+
+
Total Calls: ${totalCalls}
+
Avg Latency: ${avgLatency}ms
+
Error Rate: ${(errorRate * 100).toFixed(2)}%
`; // Show newest first const rows = [...calls].reverse().map(c => { - const status = c.error ? '' : ''; - return ` - ${timeAgo(c.timestamp)} - ${escapeHtml(c.provider)} - ${c.latency}ms - ${c.tokensPerSec.toFixed(1)} - ${c.inputTokens}/${c.outputTokens} - ${status} + const status = c.error ? '' : ''; + return ` + ${timeAgo(c.timestamp)} + ${escapeHtml(c.provider)} + ${c.latency}ms + ${c.tokensPerSec.toFixed(1)} + ${c.inputTokens}/${c.outputTokens} + ${status} `; }).join(''); el.innerHTML = ` ${summaryHtml} - - - - - - - - - - - - ${rows} -
TimeProviderLatencyTokens/secIn/OutStatus
+
+ + + + + + + + + + + + ${rows} +
TimeProviderLatencyTokens/secIn/OutStatus
+
`; } @@ -258,7 +260,7 @@ function updateEvents(eventsData) { const events = eventsData?.events ?? []; if (events.length === 0) { - el.innerHTML = '
No events recorded yet
'; + el.innerHTML = '
No events recorded yet
'; return; } @@ -268,8 +270,8 @@ function updateEvents(eventsData) { el.innerHTML = reversed.map(e => { const time = formatTime(e.timestamp); const level = (e.level || 'info').toUpperCase(); - const cls = `event-level-${e.level || 'info'}`; - return `
[${time}] [${level}] ${escapeHtml(e.source)}: ${escapeHtml(e.message)}
`; + const levelColor = e.level === 'error' ? 'text-red-500' : e.level === 'warn' ? 'text-amber-500' : 'text-zinc-400'; + return `
[${time}] [${level}] ${escapeHtml(e.source)}: ${escapeHtml(e.message)}
`; }).join(''); // Auto-scroll to bottom @@ -283,7 +285,7 @@ function updateActiveRequests(requestsData) { const requests = requestsData?.requests ?? []; if (requests.length === 0) { - el.innerHTML = '
No active requests
'; + el.innerHTML = '
No active requests
'; return; } @@ -292,26 +294,28 @@ function updateActiveRequests(requestsData) { ? `${r.durationMs}ms` : `${(r.durationMs / 1000).toFixed(1)}s`; const started = formatTime(r.startedAt); - return ` - ${escapeHtml(r.sessionId)} - ${escapeHtml(r.channel)} - ${duration} - ${started} + return ` + ${escapeHtml(r.sessionId)} + ${escapeHtml(r.channel)} + ${duration} + ${started} `; }).join(''); el.innerHTML = ` - - - - - - - - - - ${rows} -
SessionChannelDurationStarted
+
+ + + + + + + + + + ${rows} +
SessionChannelDurationStarted
+
`; } @@ -329,95 +333,95 @@ function updateSessionAnalytics(analyticsData) { const avgMessagesPerSession = analyticsData?.averageMessagesPerSession ?? 0; const summaryHtml = ` -
-
-
Sessions (Window)
-
${formatNumber(totalSessions)}
+
+
+
Sessions (Window)
+
${formatNumber(totalSessions)}
-
-
Messages (Window)
-
${formatNumber(totalMessages)}
+
+
Messages (Window)
+
${formatNumber(totalMessages)}
-
-
Avg Session
-
${formatSessionDurationFromMessages(avgMessagesPerSession)}
+
+
Avg Session
+
${formatSessionDurationFromMessages(avgMessagesPerSession)}
`; const topToolsHtml = topTools.length > 0 - ? `
    ${topTools.map((tool) => - `
  • ${escapeHtml(tool.toolName)} (${formatNumber(tool.executions)})
  • `, - ).join('')}
` - : '
No tool usage captured in this window
'; + ? `
${topTools.map((tool) => + `
${escapeHtml(tool.toolName)} (${formatNumber(tool.executions)})
`, + ).join('')}
` + : '
No tool usage captured in this window
'; const topTopicsHtml = topTopics.length > 0 - ? `
    ${topTopics.map((topic) => - `
  • ${escapeHtml(topic.topic)} (${formatNumber(topic.occurrences)})
  • `, - ).join('')}
` - : '
No indexed topics captured in this window
'; + ? `
${topTopics.map((topic) => + `
${escapeHtml(topic.topic)} (${formatNumber(topic.occurrences)})
`, + ).join('')}
` + : '
No indexed topics captured in this window
'; const topSessionsHtml = topSessions.length > 0 - ? ` + ? `
- - - + + + ${topSessions.map((session) => ` - - - - + + + + `).join('')} -
SessionMessagesLast ActiveSessionMessagesLast Active
${escapeHtml(session.sessionId)}${formatNumber(session.messages)}${timeAgo(session.lastActivity * 1000)}
${escapeHtml(session.sessionId)}${formatNumber(session.messages)}${timeAgo(session.lastActivity * 1000)}
` - : '
No session activity in this window
'; +
` + : '
No session activity in this window
'; const dailyHtml = daily.length > 0 - ? ` + ? `
- - - + + + ${daily.slice(0, 7).map((row) => ` - - - - + + + + `).join('')} -
DaySessionsMessagesDaySessionsMessages
${formatDay(row.day)}${formatNumber(row.sessions)}${formatNumber(row.messages)}
${formatDay(row.day)}${formatNumber(row.sessions)}${formatNumber(row.messages)}
` - : '
No daily activity in this window
'; +
` + : '
No daily activity in this window
'; el.innerHTML = ` ${summaryHtml} -
-
- Top Tools +
+
+
Top Tools
${topToolsHtml}
-
- Top Topics +
+
Top Topics
${topTopicsHtml}
-
-
- Top Sessions +
+
+
Top Sessions
${topSessionsHtml}
-
- Daily Trend (Last 7 Rows) +
+
Daily Trend (Last 7 Rows)
${dailyHtml}
@@ -430,7 +434,7 @@ function updateContextHealth(contextData) { const sessions = contextData?.sessions ?? []; if (sessions.length === 0) { - el.innerHTML = '
No active context usage snapshots
'; + el.innerHTML = '
No active context usage snapshots
'; return; } @@ -440,18 +444,18 @@ function updateContextHealth(contextData) { const overThreshold = sessions.filter(s => (s.budget?.shouldCompact ?? false)).length; const summary = ` -
-
-
Highest Usage
-
${highest.toFixed(1)}%
+
+
+
Highest Usage
+
${highest.toFixed(1)}%
-
-
Sessions Near Limit
-
${overThreshold}
+
+
Sessions Near Limit
+
${overThreshold}
-
-
Active Snapshots
-
${sessions.length}
+
+
Active Snapshots
+
${sessions.length}
`; @@ -459,28 +463,30 @@ function updateContextHealth(contextData) { const rows = top.map((entry) => { const budget = entry.budget ?? {}; const usage = budget.usagePct ?? 0; - const cls = usage >= 95 ? 'text-error' : usage >= 85 ? 'status-warning' : ''; - return ` - ${escapeHtml(entry.sessionId)} - ${usage.toFixed(1)}% - ${formatNumber(budget.estimatedTokens ?? 0)} / ${formatNumber(budget.contextWindow ?? 0)} - ${budget.shouldCompact ? 'yes' : 'no'} + const cls = usage >= 95 ? 'text-red-500' : usage >= 85 ? 'text-amber-500' : ''; + return ` + ${escapeHtml(entry.sessionId)} + ${usage.toFixed(1)}% + ${formatNumber(budget.estimatedTokens ?? 0)} / ${formatNumber(budget.contextWindow ?? 0)} + ${budget.shouldCompact ? 'yes' : 'no'} `; }).join(''); el.innerHTML = ` ${summary} - - - - - - - - - - ${rows} -
SessionUsageEstimated TokensShould Compact
+
+ + + + + + + + + + ${rows} +
SessionUsageEstimated TokensShould Compact
+
`; } @@ -488,7 +494,7 @@ async function applyAssistantPatch(patches, statusEl) { if (!_dashboardClient) {return;} if (statusEl) { statusEl.textContent = 'Saving...'; - statusEl.className = 'text-sm text-muted'; + statusEl.className = 'text-sm text-zinc-500'; } try { const result = await _dashboardClient.call('config.patch', { patches }); @@ -500,22 +506,22 @@ async function applyAssistantPatch(patches, statusEl) { if (statusEl) { if (persistError) { statusEl.textContent = `Save failed: ${persistError}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } else if (rejected.length > 0) { statusEl.textContent = `Rejected: ${rejected.join(', ')}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } else if (!persisted) { statusEl.textContent = `Runtime saved (${applied.length} updated)`; - statusEl.className = 'text-sm text-muted'; + statusEl.className = 'text-sm text-zinc-500'; } else { statusEl.textContent = `Saved (${applied.length} updated)`; - statusEl.className = 'text-sm text-success'; + statusEl.className = 'text-sm text-green-500'; } } } catch (error) { if (statusEl) { statusEl.textContent = `Save error: ${error instanceof Error ? error.message : String(error)}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } } } @@ -524,7 +530,7 @@ async function triggerDailyBriefingTest(jobName, statusEl) { if (!_dashboardClient) {return;} if (statusEl) { statusEl.textContent = 'Triggering test briefing...'; - statusEl.className = 'text-sm text-muted'; + statusEl.className = 'text-sm text-zinc-500'; } try { const result = await _dashboardClient.call('tools.invoke', { @@ -536,7 +542,7 @@ async function triggerDailyBriefingTest(jobName, statusEl) { const output = typeof result.output === 'string' ? result.output : 'Triggered.'; if (statusEl) { statusEl.textContent = output; - statusEl.className = 'text-sm text-success'; + statusEl.className = 'text-sm text-green-500'; } _lastBriefingTestAt = Date.now(); return true; @@ -544,13 +550,13 @@ async function triggerDailyBriefingTest(jobName, statusEl) { if (statusEl) { statusEl.textContent = result?.error ?? 'Failed to trigger briefing.'; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } return false; } catch (error) { if (statusEl) { statusEl.textContent = `Trigger error: ${error instanceof Error ? error.message : String(error)}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } return false; } @@ -592,103 +598,103 @@ function updateAssistantHealth(configData) { ]; const chip = (label, value) => ` -
- ${escapeHtml(label)} - ${value ? 'ON' : 'OFF'} +
+ ${escapeHtml(label)} + ${value ? 'ON' : 'OFF'}
`; el.innerHTML = ` -
+
${chip('Announce Mode', announce)} ${chip('Daily Briefing', dailyBriefing)} ${chip('Memory Daily Log', memoryDaily)} ${chip('Proactive Extract', memoryProactive)} ${chip('TTS Replies', ttsEnabled)} -
- Extract Threshold - ${Number.isFinite(proactiveThreshold) ? proactiveThreshold : 1} +
+ Extract Threshold + ${Number.isFinite(proactiveThreshold) ? proactiveThreshold : 1}
-
- - - - -
-
-
Assistant Playbooks
-
- - - -
-
Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.
+
Executive: announce + voice + aggressive interrupt. Operator: announce + memory-first + steer backlog. Focus: reactive, quieter mode.
-
-
Assistant Activation Checklist
-
    +
    +
    Assistant Activation Checklist
    +
    ${checklistRows.map((row) => ` -
  • - ${row.done ? '✓' : '○'} +
    + ${row.done ? '✓' : '○'} ${escapeHtml(row.label)} -
  • +
    `).join('')} -
-
-
+
+ -
-
-
-
-
-
Morning Brief Preview
-
-
- name: ${escapeHtml(briefingName)} - schedule: ${escapeHtml(briefingSchedule)} - timezone: ${escapeHtml(briefingTimezone)} - tier: ${escapeHtml(briefingModelTier)} - output: ${escapeHtml(briefingOutputLabel)} +
+ name: ${escapeHtml(briefingName)} + schedule: ${escapeHtml(briefingSchedule)} + timezone: ${escapeHtml(briefingTimezone)} + tier: ${escapeHtml(briefingModelTier)} + output: ${escapeHtml(briefingOutputLabel)}
-
${escapeHtml(briefingPrompt || 'No daily briefing prompt configured.')}
- ${briefingReady ? '' : '
Enable daily briefing and set output channel/peer to test-send.
'} +
${escapeHtml(briefingPrompt || 'No daily briefing prompt configured.')}
+ ${briefingReady ? '' : '
Enable daily briefing and set output channel/peer to test-send.
'}
-
+
`; const statusEl = el.querySelector('#ops-assistant-status'); @@ -722,7 +728,7 @@ function updateAssistantHealth(configData) { if (!_lastPlaybookRollbackPatches) { if (statusEl) { statusEl.textContent = 'No playbook changes to undo.'; - statusEl.className = 'text-sm text-muted'; + statusEl.className = 'text-sm text-zinc-500'; } return; } @@ -734,7 +740,7 @@ function updateAssistantHealth(configData) { if (!channel || !peer) { if (statusEl) { statusEl.textContent = 'Briefing output channel and peer are required.'; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } return; } @@ -765,7 +771,7 @@ function _updateChannels(channelsData) { const channels = channelsData?.channels ?? []; if (channels.length === 0) { - el.innerHTML = '
No channels registered
'; + el.innerHTML = '
No channels registered
'; return; } @@ -784,27 +790,34 @@ function updateServices(servicesData) { const services = servicesData?.services ?? []; if (services.length === 0) { - el.innerHTML = '
No services configured
'; + el.innerHTML = '
No services configured
'; return; } el.innerHTML = services.map(svc => { const typeIcon = svc.type === 'channel' ? '📡' : svc.type === 'automation' ? '⚙️' : '🔧'; - const statusClass = svc.status === 'connected' - ? 'connected' + const borderColor = svc.status === 'connected' + ? 'border-l-green-500' : svc.status === 'configured' - ? 'configured' + ? 'border-l-blue-500' : svc.status === 'error' - ? 'error' - : svc.status === 'not_configured' - ? 'not-configured' - : 'disconnected'; + ? 'border-l-red-500' + : 'border-l-zinc-600 opacity-60'; + const statusColor = svc.status === 'connected' + ? 'text-green-500' + : svc.status === 'configured' + ? 'text-blue-500' + : svc.status === 'error' + ? 'text-red-500' + : 'text-zinc-500'; const itemCount = svc.itemCount ? ` (${svc.itemCount})` : ''; - return `
- ${typeIcon} - ${escapeHtml(svc.name)}${itemCount} - ${escapeHtml(svc.status)} - ${escapeHtml(svc.description)} + return `
+
+ ${typeIcon} + ${escapeHtml(svc.name)}${itemCount} +
+ ${escapeHtml(svc.status)} + ${escapeHtml(svc.description)}
`; }).join(''); } diff --git a/src/gateway/ui/pages/sessions.js b/src/gateway/ui/pages/sessions.js index a002784..193d707 100644 --- a/src/gateway/ui/pages/sessions.js +++ b/src/gateway/ui/pages/sessions.js @@ -53,21 +53,22 @@ async function loadSessionList() { const sessions = result.sessions ?? []; if (sessions.length === 0) { - listContainer.innerHTML = '
No sessions found
'; + listContainer.innerHTML = '
No sessions found
'; return; } let html = ` - +
+
- - - - - - - + + + + + + + @@ -75,22 +76,24 @@ async function loadSessionList() { for (const s of sessions) { html += ` - - - - - - - - + + + + + + + `; } - html += '
Session IDFrontendMessagesModelQueueLast ActivityActionsSession IDFrontendMessagesModelQueueLast ActivityActions
${escapeHtml(s.id)}${escapeHtml(s.frontend ?? (String(s.id).split(':')[0] || 'unknown'))}${s.messageCount ?? 0}${escapeHtml(s.config?.modelTier ?? 'default')}${escapeHtml(formatQueue(s.config))}${escapeHtml(formatTime(s.lastMessageAt))} - - +
${escapeHtml(s.id)}${escapeHtml(s.frontend ?? (String(s.id).split(':')[0] || 'unknown'))}${s.messageCount ?? 0}${escapeHtml(s.config?.modelTier ?? 'default')}${escapeHtml(formatQueue(s.config))}${escapeHtml(formatTime(s.lastMessageAt))} +
+ + +
'; + html += '
'; listContainer.innerHTML = html; // Bind view buttons @@ -108,7 +111,7 @@ async function loadSessionList() { }); }); } catch (err) { - listContainer.innerHTML = `
Failed to load sessions: ${err.message}
`; + listContainer.innerHTML = `
Failed to load sessions: ${err.message}
`; } } @@ -116,35 +119,42 @@ async function viewSession(sessionId) { const detailContainer = _el.querySelector('#session-detail'); if (!detailContainer) {return;} - detailContainer.innerHTML = '
Loading...
'; + detailContainer.innerHTML = '
Loading...
'; try { const result = await _client.call('sessions.history', { sessionId }); const messages = result.messages ?? []; + const roleClasses = { + user: 'rounded-lg px-3 py-2 text-sm bg-blue-500/15 border border-blue-500/25 text-zinc-50', + assistant: 'rounded-lg px-3 py-2 text-sm bg-zinc-900 border border-zinc-800 text-zinc-50', + system: 'rounded-lg px-3 py-2 text-sm bg-zinc-800 text-zinc-400 border border-zinc-700', + }; + let html = ` -
-
-

${escapeHtml(sessionId)}

- ${messages.length} messages +
+
+

${escapeHtml(sessionId)}

+ ${messages.length} messages
-
+
`; if (messages.length === 0) { - html += '
No messages in this session
'; + html += '
No messages in this session
'; } else { for (const msg of messages) { const role = msg.role ?? 'system'; const content = msg.content ?? msg.text ?? ''; - html += `
${escapeHtml(content)}
`; + const cls = roleClasses[role] ?? roleClasses.system; + html += `
${escapeHtml(content)}
`; } } html += '
'; detailContainer.innerHTML = html; } catch (err) { - detailContainer.innerHTML = `
Failed to load session: ${err.message}
`; + detailContainer.innerHTML = `
Failed to load session: ${err.message}
`; } } @@ -167,10 +177,10 @@ export const SessionsPage = { _el = el; el.innerHTML = ` -

Sessions

-
- -
diff --git a/src/gateway/ui/pages/settings.js b/src/gateway/ui/pages/settings.js index 719c8c4..31eefbb 100644 --- a/src/gateway/ui/pages/settings.js +++ b/src/gateway/ui/pages/settings.js @@ -51,12 +51,12 @@ async function renderPushStatus() { try { const status = await getPushStatus(); statusEl.textContent = describePushStatus(status); - statusEl.className = status.subscribed ? 'text-sm text-success' : 'text-sm text-muted'; + statusEl.className = status.subscribed ? 'text-sm text-green-500' : 'text-sm text-zinc-500'; enableBtn.disabled = !status.supported || !status.enabled || !status.configured || status.subscribed; disableBtn.disabled = !status.supported || !status.subscribed; } catch (err) { statusEl.textContent = `Push status error: ${err.message}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; enableBtn.disabled = true; disableBtn.disabled = true; } @@ -66,13 +66,13 @@ async function onEnablePush() { const statusEl = _el.querySelector('#push-status'); if (!statusEl) {return;} statusEl.textContent = 'Enabling push notifications...'; - statusEl.className = 'text-sm text-muted'; + statusEl.className = 'text-sm text-zinc-500'; try { await enablePushNotifications(); await renderPushStatus(); } catch (err) { statusEl.textContent = `Enable failed: ${err.message}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } } @@ -80,13 +80,13 @@ async function onDisablePush() { const statusEl = _el.querySelector('#push-status'); if (!statusEl) {return;} statusEl.textContent = 'Disabling push notifications...'; - statusEl.className = 'text-sm text-muted'; + statusEl.className = 'text-sm text-zinc-500'; try { await disablePushNotifications(); await renderPushStatus(); } catch (err) { statusEl.textContent = `Disable failed: ${err.message}`; - statusEl.className = 'text-sm text-error'; + statusEl.className = 'text-sm text-red-500'; } } @@ -104,8 +104,8 @@ async function loadSettings() { ]); } catch (err) { _el.innerHTML = ` -

Settings

-
Failed to load settings: ${err.message}
+

Settings

+
Failed to load settings: ${err.message}
`; return; } @@ -140,140 +140,147 @@ async function loadSettings() { const serviceList = services?.services ?? []; _el.innerHTML = ` -

Settings

+

Settings

-

Personal Assistant Mode

-
-
-