Support for adding memories and copying logs through dashboard

This commit is contained in:
MischaPanch committed 2025-10-30 17:09:01 +01:00
1 parent ba2439bcb7
commit 85af1a3fd6
4 files changed
+208 -23

No files matched your search

-2
View File
@@ -425,7 +425,6 @@ class SerenaDashboardAPI:
# add_language is already thread-safe
self._agent.add_language(language)
def _remove_language(self, request_remove_language: RequestRemoveLanguage) -> None:
from solidlsp.ls_config import Language
@@ -436,7 +435,6 @@ class SerenaDashboardAPI:
# remove_language is already thread-safe
self._agent.remove_language(language)
@staticmethod
def _find_first_free_port(start_port: int) -> int:
port = start_port
@@ -832,3 +832,42 @@ body {
flex-wrap: wrap;
gap: 8px;
}
/* Copy Logs Button */
.copy-logs-btn {
position: absolute;
top: 15px;
right: 15px;
z-index: 10;
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-primary);
cursor: pointer;
opacity: 0.8;
transition: opacity 0.2s ease, background-color 0.2s ease;
font-size: 13px;
font-weight: 500;
}
.copy-logs-btn:hover {
opacity: 1;
background-color: var(--border-color);
}
.copy-logs-btn svg {
flex-shrink: 0;
}
.copy-logs-text {
display: none;
white-space: nowrap;
}
.copy-logs-btn:hover .copy-logs-text {
display: inline;
}
+138 -17
View File
@@ -72,7 +72,7 @@ class Dashboard {
// jQuery elements
this.$logContainer = $('#log-container');
this.$errorContainer = $('#error-container');
this.$loadButton = $('#load-logs');
this.$copyLogsBtn = $('#copy-logs-btn');
this.$menuToggle = $('#menu-toggle');
this.$menuDropdown = $('#menu-dropdown');
this.$menuShutdown = $('#menu-shutdown');
@@ -111,6 +111,12 @@ class Dashboard {
this.$deleteMemoryOkBtn = $('#delete-memory-ok-btn');
this.$deleteMemoryCancelBtn = $('#delete-memory-cancel-btn');
this.$modalCloseDeleteMemory = $('.modal-close-delete-memory');
this.$createMemoryModal = $('#create-memory-modal');
this.$createMemoryProjectName = $('#create-memory-project-name');
this.$createMemoryNameInput = $('#create-memory-name-input');
this.$createMemoryCreateBtn = $('#create-memory-create-btn');
this.$createMemoryCancelBtn = $('#create-memory-cancel-btn');
this.$modalCloseCreateMemory = $('.modal-close-create-memory');
// Chart references
this.countChart = null;
@@ -119,7 +125,7 @@ class Dashboard {
this.outputChart = null;
// Register event handlers
this.$loadButton.click(this.loadLogs.bind(this));
this.$copyLogsBtn.click(this.copyLogs.bind(this));
this.$menuShutdown.click(function(e) {
e.preventDefault();
self.shutdown();
@@ -141,6 +147,15 @@ class Dashboard {
this.$deleteMemoryOkBtn.click(this.confirmDeleteMemoryOk.bind(this));
this.$deleteMemoryCancelBtn.click(this.closeDeleteMemoryModal.bind(this));
this.$modalCloseDeleteMemory.click(this.closeDeleteMemoryModal.bind(this));
this.$createMemoryCreateBtn.click(this.createMemoryFromModal.bind(this));
this.$createMemoryCancelBtn.click(this.closeCreateMemoryModal.bind(this));
this.$modalCloseCreateMemory.click(this.closeCreateMemoryModal.bind(this));
this.$createMemoryNameInput.keypress(function(e) {
if (e.which === 13) { // Enter key
e.preventDefault();
self.createMemoryFromModal();
}
});
// Page navigation
$('[data-page]').click(function(e) {
@@ -181,6 +196,12 @@ class Dashboard {
}
});
this.$createMemoryModal.click(function(e) {
if ($(e.target).hasClass('modal')) {
self.closeCreateMemoryModal();
}
});
// Collapsible sections
$('.collapsible-header').click(function() {
const $header = $(this);
@@ -205,6 +226,8 @@ class Dashboard {
self.closeEditMemoryModal();
} else if (self.$deleteMemoryModal.is(':visible')) {
self.closeDeleteMemoryModal();
} else if (self.$createMemoryModal.is(':visible')) {
self.closeCreateMemoryModal();
}
}
});
@@ -403,20 +426,25 @@ class Dashboard {
html += '</div>';
html += '</div>';
// Available memories - collapsible (only show if memories exist)
if (config.available_memories && config.available_memories.length > 0) {
// Available memories - collapsible (show if memories exist or if project exists)
if (config.active_project && config.active_project.name) {
html += '<div style="margin-top: 20px;">';
html += '<h3 class="collapsible-header" id="memories-header" style="font-size: 16px; margin: 0;">';
html += '<span>Available Memories (' + config.available_memories.length + ')</span>';
const memoryCount = (config.available_memories && config.available_memories.length) || 0;
html += '<span>Available Memories (' + memoryCount + ')</span>';
html += '<span class="toggle-icon' + (wasMemoriesExpanded ? ' expanded' : '') + '">▼</span>';
html += '</h3>';
html += '<div class="collapsible-content memories-container" id="memories-content" style="' + (wasMemoriesExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
config.available_memories.forEach(function(memory) {
html += '<div class="memory-item removable" data-memory="' + memory + '">';
html += memory;
html += '<span class="memory-remove" data-memory="' + memory + '">&times;</span>';
html += '</div>';
});
if (config.available_memories && config.available_memories.length > 0) {
config.available_memories.forEach(function(memory) {
html += '<div class="memory-item removable" data-memory="' + memory + '">';
html += memory;
html += '<span class="memory-remove" data-memory="' + memory + '">&times;</span>';
html += '</div>';
});
}
// Add Create Memory button
html += '<button id="create-memory-btn" class="btn language-add-btn">+ Create Memory</button>';
html += '</div>';
html += '</div>';
}
@@ -456,6 +484,9 @@ class Dashboard {
self.confirmDeleteMemory(memoryName);
});
// Attach event handler for create memory button
$('#create-memory-btn').click(this.openCreateMemoryModal.bind(this));
// Re-attach collapsible handler for the newly created tools header
$('#tools-header').click(function() {
const $header = $(this);
@@ -598,12 +629,34 @@ class Dashboard {
document.title = activeProject ? `${activeProject} – Serena Dashboard` : 'Serena Dashboard';
}
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 = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#888" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg><span class="copy-logs-text">copy logs</span>';
this.$copyLogsBtn.html(checkmarkSvg);
setTimeout(() => {
this.$copyLogsBtn.html(originalHtml);
}, 1500);
}).catch(err => {
console.error('Failed to copy logs:', err);
alert('Failed to copy logs to clipboard');
});
}
loadLogs() {
console.log("Loading logs");
let self = this;
// Disable button and show loading state
self.$loadButton.prop('disabled', true).text('Loading...');
self.$errorContainer.empty();
// Make API call
@@ -643,10 +696,6 @@ class Dashboard {
console.error('Error loading logs:', error);
self.$errorContainer.html('<div class="error-message">Error loading logs: ' +
(xhr.responseJSON ? xhr.responseJSON.detail : error) + '</div>');
},
complete: function() {
// Re-enable button
self.$loadButton.prop('disabled', false).text('Reload Log');
}
});
}
@@ -1407,6 +1456,78 @@ class Dashboard {
});
}
openCreateMemoryModal() {
// Set project name in modal
this.$createMemoryProjectName.text(this.activeProjectName || 'Unknown');
// Clear the input field
this.$createMemoryNameInput.val('');
// Show modal
this.$createMemoryModal.fadeIn(200);
// Focus on the input field
setTimeout(() => {
this.$createMemoryNameInput.focus();
}, 250);
}
closeCreateMemoryModal() {
this.$createMemoryModal.fadeOut(200);
this.$createMemoryNameInput.val('');
this.$createMemoryCreateBtn.prop('disabled', false).text('Create');
}
createMemoryFromModal() {
const memoryName = this.$createMemoryNameInput.val().trim();
if (!memoryName) {
alert('Please enter a memory name');
return;
}
// Validate memory name (alphanumeric and underscores only)
if (!/^[a-zA-Z0-9_]+$/.test(memoryName)) {
alert('Memory name can only contain letters, numbers, and underscores');
return;
}
const self = this;
// Disable button during request
self.$createMemoryCreateBtn.prop('disabled', true).text('Creating...');
$.ajax({
url: '/save_memory',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
memory_name: memoryName,
content: ''
}),
success: function(response) {
if (response.status === 'success') {
// Close the create modal
self.closeCreateMemoryModal();
// Reload config to show the new memory
self.loadConfigOverview();
// Open the edit modal for the newly created memory
setTimeout(() => {
self.openEditMemoryModal(memoryName);
}, 500);
} else {
alert('Error: ' + response.message);
self.$createMemoryCreateBtn.prop('disabled', false).text('Create');
}
},
error: function(xhr, status, error) {
console.error('Error creating memory:', error);
alert('Error creating memory: ' + (xhr.responseJSON ? xhr.responseJSON.message : error));
self.$createMemoryCreateBtn.prop('disabled', false).text('Create');
}
});
}
// ===== Shutdown Method =====
shutdown() {
+31 -4
View File
@@ -106,11 +106,17 @@
<!-- Logs Page -->
<div id="page-logs" class="page-view" style="display:none">
<div class="controls">
<button id="load-logs" class="btn">Reload Log</button>
</div>
<div id="error-container"></div>
<div id="log-container" class="log-container"></div>
<div style="position: relative;">
<button id="copy-logs-btn" class="copy-logs-btn" title="Copy logs">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
<span class="copy-logs-text">copy logs</span>
</button>
<div id="log-container" class="log-container"></div>
</div>
</div>
<!-- Advanced Stats Page -->
@@ -209,6 +215,27 @@
<button id="delete-memory-ok-btn" class="btn" style="padding: 8px 16px;">OK</button>
</div>
</div>
</div>
<!-- Create Memory Modal -->
<div id="create-memory-modal" class="modal" style="display:none">
<div class="modal-content">
<span class="modal-close modal-close-create-memory">&times;</span>
<p id="create-memory-project-info" style="margin: 15px 0; color: var(--text-primary); line-height: 1.5;">
Create a new memory for project <strong id="create-memory-project-name"></strong>.
</p>
<p style="margin: 15px 0; color: var(--text-muted); font-size: 13px; line-height: 1.5;">
Memory names should be descriptive and use underscores instead of spaces (e.g., "api_architecture", "testing_guidelines").
</p>
<div style="margin: 20px 0;">
<label for="create-memory-name-input" style="display: block; margin-bottom: 8px; font-weight: 500;">Memory Name:</label>
<input type="text" id="create-memory-name-input" class="memory-name-input" placeholder="e.g., project_overview" style="width: 100%; padding: 8px 10px; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-primary); color: var(--text-primary); font-size: 14px;">
</div>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
<button id="create-memory-cancel-btn" class="btn" style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel</button>
<button id="create-memory-create-btn" class="btn" style="padding: 8px 16px;">Create</button>
</div>
</div>
</div> <!-- End of Main -->
<script>