diff --git a/CHANGELOG.md b/CHANGELOG.md index 49475679..e0d94210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,10 @@ Status of the `main` branch. Changes prior to the next official version change w * Major extensions to the dashboard, which now serves as a central web interface for Serena * View current configuration * View news which can be marked as read - * View the executions, with the possibility to cancel running/scheduled executions + * View the executions, with the possibility to cancel running/scheduled executions * View tool usage statistics * View and create memories and edit the serena configuration file + * Log page now has save (downloads a snapshot) and clear (resets log view) buttons alongside the existing copy button * New two-tier caching of language server document symbols and considerable performance improvements surrounding symbol retrieval/indexing * Various fixes related to indexing, special paths and determination of ignored paths * Decreased `TOOL_DEFAULT_MAX_ANSWER_LENGTH` to be in accordance with (below) typical max-tokens configurations diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index c324ae98..db7ab4a7 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -184,6 +184,11 @@ class SerenaDashboardAPI: self._clear_tool_stats() return {"status": "cleared"} + @self._app.route("/clear_logs", methods=["POST"]) + def clear_logs() -> dict[str, str]: + self._memory_log_handler.clear_log_messages() + return {"status": "cleared"} + @self._app.route("/get_token_count_estimator_name", methods=["GET"]) def get_token_count_estimator_name() -> dict[str, str]: estimator_name = self._tool_usage_stats.token_estimator_name if self._tool_usage_stats else "unknown" diff --git a/src/serena/resources/dashboard/dashboard.css b/src/serena/resources/dashboard/dashboard.css index e47fc2c2..1c0a8a47 100644 --- a/src/serena/resources/dashboard/dashboard.css +++ b/src/serena/resources/dashboard/dashboard.css @@ -1277,12 +1277,18 @@ body { gap: 8px; } -/* Copy Logs Button */ -.copy-logs-btn { +/* Log Action Buttons (Save, Copy, Clear) */ +.log-action-buttons { position: absolute; top: 15px; - right: 15px; + right: 20px; z-index: 10; + display: flex; + gap: 8px; + align-items: center; +} + +.log-action-btn { display: flex; align-items: center; gap: 6px; @@ -1293,25 +1299,36 @@ body { color: var(--text-primary); cursor: pointer; opacity: 0.8; - transition: opacity 0.2s ease, background-color 0.2s ease; + transition: opacity 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; font-size: 13px; font-weight: 500; } -.copy-logs-btn:hover { +.log-action-btn:hover { opacity: 1; background-color: var(--border-color); } -.copy-logs-btn svg { +.log-action-btn svg { flex-shrink: 0; } -.copy-logs-text { +.log-action-btn-text { display: none; white-space: nowrap; } -.copy-logs-btn:hover .copy-logs-text { +.log-action-btn:hover .log-action-btn-text { display: inline; } + +.log-action-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.log-action-btn-danger:hover { + background-color: var(--border-color); + border-color: rgba(220, 53, 53, 0.9); + color: var(--log-error); +} diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index 934fd8b9..2b4af7fa 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -211,7 +211,9 @@ class Dashboard { // jQuery elements this.$logContainer = $('#log-container'); this.$errorContainer = $('#error-container'); + this.$saveLogsBtn = $('#save-logs-btn'); this.$copyLogsBtn = $('#copy-logs-btn'); + this.$clearLogsBtn = $('#clear-logs-btn'); this.$menuToggle = $('#menu-toggle'); this.$menuDropdown = $('#menu-dropdown'); this.$menuShutdown = $('#menu-shutdown'); @@ -278,7 +280,9 @@ class Dashboard { this.outputChart = null; // Register event handlers + this.$saveLogsBtn.click(this.saveLogs.bind(this)); this.$copyLogsBtn.click(this.copyLogs.bind(this)); + this.$clearLogsBtn.click(this.clearLogs.bind(this)); this.$menuShutdown.click(function (e) { e.preventDefault(); self.shutdown(); @@ -1112,19 +1116,39 @@ class Dashboard { document.title = activeProject ? `${activeProject} – Serena Dashboard` : 'Serena Dashboard'; } + updateLogButtons(hasLogs) { + this.$saveLogsBtn.prop('disabled', !hasLogs); + this.$copyLogsBtn.prop('disabled', !hasLogs); + this.$clearLogsBtn.prop('disabled', !hasLogs); + } + + saveLogs() { + const logText = this.$logContainer.text(); + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + const blob = new Blob([logText], {type: 'text/plain'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `serena-logs-${timestamp}.txt`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + const originalHtml = this.$saveLogsBtn.html(); + const checkmarkSvg = 'save logs'; + this.$saveLogsBtn.html(checkmarkSvg); + setTimeout(() => { this.$saveLogsBtn.html(originalHtml); }, 1500); + } + copyLogs() { const logText = this.$logContainer.text(); - if (!logText) { - alert('No logs to copy'); - return; - } - // Use the Clipboard API to copy text navigator.clipboard.writeText(logText).then(() => { // Visual feedback - temporarily change icon to grey checkmark const originalHtml = this.$copyLogsBtn.html(); - const checkmarkSvg = 'copy logs'; + const checkmarkSvg = 'copy logs'; this.$copyLogsBtn.html(checkmarkSvg); setTimeout(() => { @@ -1132,7 +1156,27 @@ class Dashboard { }, 1500); }).catch(err => { console.error('Failed to copy logs:', err); - alert('Failed to copy logs to clipboard'); + }); + } + + clearLogs() { + let self = this; + $.ajax({ + url: '/clear_logs', + type: 'POST', + success: function () { + self.$logContainer.empty(); + self.currentMaxIdx = -1; + self.updateLogButtons(false); + + const originalHtml = self.$clearLogsBtn.html(); + const checkmarkSvg = 'clear logs'; + self.$clearLogsBtn.html(checkmarkSvg); + setTimeout(() => { self.$clearLogsBtn.html(originalHtml); }, 1500); + }, + error: function (xhr, status, error) { + console.error('Failed to clear logs:', error); + } }); } @@ -1166,6 +1210,7 @@ class Dashboard { $('#log-container').html('