diff --git a/bot/static/index.html b/bot/static/index.html index 66c2fa1..1443b59 100644 --- a/bot/static/index.html +++ b/bot/static/index.html @@ -27,6 +27,31 @@ overflow-y: scroll; font-size: 0.85rem; border-left: 2px solid #333; + position: relative; + } + + #logs-content { + white-space: pre-wrap; + word-break: break-word; + } + + .log-line { line-height: 1.4; } + .log-line.log-error { color: #ff6b6b; } + .log-line.log-warning { color: #ffd93d; } + .log-line.log-info { color: #0f0; } + .log-line.log-debug { color: #888; } + + .logs-paused-indicator { + position: sticky; + top: 0; + background: rgba(50, 50, 0, 0.9); + color: #ffd93d; + text-align: center; + padding: 0.25rem; + font-size: 0.75rem; + cursor: pointer; + z-index: 10; + display: none; } select, button, input { @@ -1717,8 +1742,9 @@ -
+

Logs

+
⏸ Auto-scroll paused — click to resume
@@ -1867,6 +1893,7 @@ document.addEventListener('DOMContentLoaded', function() { console.log('🚀 DOMContentLoaded - initializing figurine subscribers list'); refreshFigurineSubscribers(); loadProfilePictureMetadata(); + initLogsScrollDetection(); // Set up log auto-scroll tracking // Set up periodic updates setInterval(loadStatus, 10000); @@ -3994,10 +4021,60 @@ async function loadLastPrompt() { } } +// Log auto-scroll state +let logsAutoScroll = true; + +function initLogsScrollDetection() { + const logsPanel = document.getElementById('logs-panel'); + if (!logsPanel) return; + logsPanel.addEventListener('scroll', function() { + // If user is within 50px of the bottom, re-enable auto-scroll + const atBottom = logsPanel.scrollHeight - logsPanel.scrollTop - logsPanel.clientHeight < 50; + logsAutoScroll = atBottom; + const banner = document.getElementById('logs-paused-banner'); + if (banner) banner.style.display = atBottom ? 'none' : 'block'; + }); +} + +function scrollLogsToBottom() { + const logsPanel = document.getElementById('logs-panel'); + if (logsPanel) { + logsPanel.scrollTop = logsPanel.scrollHeight; + logsAutoScroll = true; + const banner = document.getElementById('logs-paused-banner'); + if (banner) banner.style.display = 'none'; + } +} + +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +function classifyLogLine(line) { + const upper = line.toUpperCase(); + if (upper.includes(' ERROR ') || upper.includes(' CRITICAL ') || upper.startsWith('ERROR') || upper.startsWith('CRITICAL') || upper.includes('TRACEBACK')) return 'log-error'; + if (upper.includes(' WARNING ') || upper.startsWith('WARNING')) return 'log-warning'; + if (upper.includes(' DEBUG ') || upper.startsWith('DEBUG')) return 'log-debug'; + return 'log-info'; +} + async function loadLogs() { try { const result = await apiCall('/logs'); - document.getElementById('logs-content').textContent = result; + const logsContent = document.getElementById('logs-content'); + const lines = (result || '').split('\n'); + logsContent.innerHTML = lines.map(line => { + if (!line.trim()) return ''; + const cls = classifyLogLine(line); + return `
${escapeHtml(line)}
`; + }).join(''); + + // Auto-scroll to bottom if user hasn't scrolled up + if (logsAutoScroll) { + scrollLogsToBottom(); + } } catch (error) { console.error('Failed to load logs:', error); }