mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 06:15:43 +00:00
Support for adding memories and copying logs through dashboard
This commit is contained in:
1 parent
ba2439bcb7
commit
85af1a3fd6
4 files changed
+208
-23
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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 + '">×</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 + '">×</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() {
|
||||
|
||||
@@ -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">×</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>
|
||||
|
||||
Reference in new issue
Block a user