Added possibility to edit and delete memories from dashboard

Also hide Add Language button when no active project is present
This commit is contained in:
MischaPanch committed 2025-10-30 12:54:36 +01:00
1 parent 6ce7d73130
commit c6eba90460
4 files changed
+384 -3

No files matched your search

+76
View File
@@ -66,6 +66,24 @@ class RequestRemoveLanguage(BaseModel):
language: str
class RequestGetMemory(BaseModel):
memory_name: str
class ResponseGetMemory(BaseModel):
content: str
memory_name: str
class RequestSaveMemory(BaseModel):
memory_name: str
content: str
class RequestDeleteMemory(BaseModel):
memory_name: str
class SerenaDashboardAPI:
log = logging.getLogger(__qualname__)
@@ -170,6 +188,42 @@ class SerenaDashboardAPI:
except Exception as e:
return {"status": "error", "message": str(e)}
@self._app.route("/get_memory", methods=["POST"])
def get_memory() -> dict[str, Any]:
request_data = request.get_json()
if not request_data:
return {"status": "error", "message": "No data provided"}
request_get_memory = RequestGetMemory.model_validate(request_data)
try:
result = self._get_memory(request_get_memory)
return result.model_dump()
except Exception as e:
return {"status": "error", "message": str(e)}
@self._app.route("/save_memory", methods=["POST"])
def save_memory() -> dict[str, str]:
request_data = request.get_json()
if not request_data:
return {"status": "error", "message": "No data provided"}
request_save_memory = RequestSaveMemory.model_validate(request_data)
try:
self._save_memory(request_save_memory)
return {"status": "success", "message": f"Memory {request_save_memory.memory_name} saved successfully"}
except Exception as e:
return {"status": "error", "message": str(e)}
@self._app.route("/delete_memory", methods=["POST"])
def delete_memory() -> dict[str, str]:
request_data = request.get_json()
if not request_data:
return {"status": "error", "message": "No data provided"}
request_delete_memory = RequestDeleteMemory.model_validate(request_data)
try:
self._delete_memory(request_delete_memory)
return {"status": "success", "message": f"Memory {request_delete_memory.memory_name} deleted successfully"}
except Exception as e:
return {"status": "error", "message": str(e)}
def _get_log_messages(self, request_log: RequestLog) -> ResponseLog:
all_messages = self._memory_log_handler.get_log_messages()
requested_messages = all_messages[request_log.start_idx :] if request_log.start_idx <= len(all_messages) else []
@@ -321,6 +375,28 @@ class SerenaDashboardAPI:
return ResponseAvailableLanguages(languages=sorted(available_languages))
def _get_memory(self, request_get_memory: RequestGetMemory) -> ResponseGetMemory:
project = self._agent.get_active_project()
if project is None:
raise ValueError("No active project")
content = project.memories_manager.load_memory(request_get_memory.memory_name)
return ResponseGetMemory(content=content, memory_name=request_get_memory.memory_name)
def _save_memory(self, request_save_memory: RequestSaveMemory) -> None:
project = self._agent.get_active_project()
if project is None:
raise ValueError("No active project")
project.memories_manager.save_memory(request_save_memory.memory_name, request_save_memory.content)
def _delete_memory(self, request_delete_memory: RequestDeleteMemory) -> None:
project = self._agent.get_active_project()
if project is None:
raise ValueError("No active project")
project.memories_manager.delete_memory(request_delete_memory.memory_name)
def _add_language(self, request_add_language: RequestAddLanguage) -> None:
from solidlsp.ls_config import Language
@@ -711,3 +711,79 @@ body {
background: rgba(255, 68, 68, 0.2);
transform: scale(1.1);
}
/* Memory Editor Styles */
.modal-content-large {
max-width: 800px;
width: 90%;
}
.memory-editor {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
tab-size: 4;
-moz-tab-size: 4;
}
.memory-editor:focus {
outline: 2px solid var(--btn-primary);
outline-offset: -1px;
}
/* Memory Item Styles */
.memory-item {
position: relative;
display: inline-flex;
align-items: center;
padding: 8px 12px;
margin: 5px;
border-radius: 4px;
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
transition: background-color 0.2s ease;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.memory-item:hover {
background-color: var(--border-color);
text-decoration: underline;
}
.memory-item.removable {
padding-right: 28px;
}
.memory-remove {
position: absolute;
top: 2px;
right: 2px;
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 68, 68, 0.1);
border-radius: 3px;
cursor: pointer;
color: #ff4444;
font-size: 14px;
font-weight: bold;
line-height: 1;
transition: all 0.2s;
}
.memory-remove:hover {
background: rgba(255, 68, 68, 0.2);
transform: scale(1.1);
text-decoration: none;
}
.memories-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
+205 -3
View File
@@ -55,6 +55,10 @@ class Dashboard {
this.jetbrainsMode = false;
this.activeProjectName = null;
this.languageToRemove = null;
this.currentMemoryName = null;
this.originalMemoryContent = null;
this.memoryContentDirty = false;
this.memoryToDelete = null;
// Tool names and stats
this.toolNames = [];
@@ -95,6 +99,17 @@ class Dashboard {
this.$removeModalOkBtn = $('#remove-modal-ok-btn');
this.$removeModalCancelBtn = $('#remove-modal-cancel-btn');
this.$modalCloseRemove = $('.modal-close-remove');
this.$editMemoryModal = $('#edit-memory-modal');
this.$editMemoryName = $('#edit-memory-name');
this.$editMemoryContent = $('#edit-memory-content');
this.$editMemorySaveBtn = $('#edit-memory-save-btn');
this.$editMemoryCancelBtn = $('#edit-memory-cancel-btn');
this.$modalCloseEditMemory = $('.modal-close-edit-memory');
this.$deleteMemoryModal = $('#delete-memory-modal');
this.$deleteMemoryName = $('#delete-memory-name');
this.$deleteMemoryOkBtn = $('#delete-memory-ok-btn');
this.$deleteMemoryCancelBtn = $('#delete-memory-cancel-btn');
this.$modalCloseDeleteMemory = $('.modal-close-delete-memory');
// Chart references
this.countChart = null;
@@ -119,6 +134,13 @@ class Dashboard {
this.$removeModalOkBtn.click(this.confirmRemoveLanguageOk.bind(this));
this.$removeModalCancelBtn.click(this.closeRemoveLanguageModal.bind(this));
this.$modalCloseRemove.click(this.closeRemoveLanguageModal.bind(this));
this.$editMemorySaveBtn.click(this.saveMemoryFromModal.bind(this));
this.$editMemoryCancelBtn.click(this.closeEditMemoryModal.bind(this));
this.$modalCloseEditMemory.click(this.closeEditMemoryModal.bind(this));
this.$editMemoryContent.on('input', this.trackMemoryChanges.bind(this));
this.$deleteMemoryOkBtn.click(this.confirmDeleteMemoryOk.bind(this));
this.$deleteMemoryCancelBtn.click(this.closeDeleteMemoryModal.bind(this));
this.$modalCloseDeleteMemory.click(this.closeDeleteMemoryModal.bind(this));
// Page navigation
$('[data-page]').click(function(e) {
@@ -147,6 +169,18 @@ class Dashboard {
}
});
this.$editMemoryModal.click(function(e) {
if ($(e.target).hasClass('modal')) {
self.closeEditMemoryModal();
}
});
this.$deleteMemoryModal.click(function(e) {
if ($(e.target).hasClass('modal')) {
self.closeDeleteMemoryModal();
}
});
// Collapsible sections
$('.collapsible-header').click(function() {
const $header = $(this);
@@ -167,6 +201,10 @@ class Dashboard {
self.closeLanguageModal();
} else if (self.$removeLanguageModal.is(':visible')) {
self.closeRemoveLanguageModal();
} else if (self.$editMemoryModal.is(':visible')) {
self.closeEditMemoryModal();
} else if (self.$deleteMemoryModal.is(':visible')) {
self.closeDeleteMemoryModal();
}
}
});
@@ -354,9 +392,12 @@ class Dashboard {
html += '<span>Available Memories (' + config.available_memories.length + ')</span>';
html += '<span class="toggle-icon' + (wasMemoriesExpanded ? ' expanded' : '') + '">▼</span>';
html += '</h3>';
html += '<div class="collapsible-content tools-grid" id="memories-content" style="' + (wasMemoriesExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
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="tool-item" title="' + memory + '">' + memory + '</div>';
html += '<div class="memory-item removable" data-memory="' + memory + '">';
html += memory;
html += '<span class="memory-remove" data-memory="' + memory + '">&times;</span>';
html += '</div>';
});
html += '</div>';
html += '</div>';
@@ -365,7 +406,7 @@ class Dashboard {
this.$configDisplay.html(html);
// Show/hide add language button based on jetbrains mode
if (this.jetbrainsMode) {
if (this.jetbrainsMode || this.activeProjectName === null) {
this.$addLanguageBtn.hide();
} else {
this.$addLanguageBtn.show();
@@ -380,6 +421,21 @@ class Dashboard {
self.confirmRemoveLanguage(language);
});
// Attach event handlers for memory items
$('.memory-item').click(function(e) {
e.preventDefault();
const memoryName = $(this).data('memory');
self.openEditMemoryModal(memoryName);
});
// Attach event handlers for memory remove buttons
$('.memory-remove').click(function(e) {
e.preventDefault();
e.stopPropagation();
const memoryName = $(this).data('memory');
self.confirmDeleteMemory(memoryName);
});
// Re-attach collapsible handler for the newly created tools header
$('#tools-header').click(function() {
const $header = $(this);
@@ -1176,6 +1232,152 @@ class Dashboard {
});
}
// ===== Memory Editing Methods =====
openEditMemoryModal(memoryName) {
const self = this;
this.currentMemoryName = memoryName;
this.memoryContentDirty = false;
// Set memory name in modal
this.$editMemoryName.text(memoryName);
// Load memory content
$.ajax({
url: '/get_memory',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
memory_name: memoryName
}),
success: function(response) {
if (response.status === 'error') {
alert('Error: ' + response.message);
return;
}
self.originalMemoryContent = response.content;
self.$editMemoryContent.val(response.content);
self.memoryContentDirty = false;
self.$editMemoryModal.fadeIn(200);
},
error: function(xhr, status, error) {
console.error('Error loading memory:', error);
alert('Error loading memory: ' + (xhr.responseJSON ? xhr.responseJSON.message : error));
}
});
}
closeEditMemoryModal() {
// Check if there are unsaved changes
if (this.memoryContentDirty) {
if (!confirm('You have unsaved changes. Are you sure you want to close?')) {
return;
}
}
this.$editMemoryModal.fadeOut(200);
this.currentMemoryName = null;
this.originalMemoryContent = null;
this.memoryContentDirty = false;
}
trackMemoryChanges() {
const currentContent = this.$editMemoryContent.val();
this.memoryContentDirty = (currentContent !== this.originalMemoryContent);
}
saveMemoryFromModal() {
const self = this;
const memoryName = this.currentMemoryName;
const content = this.$editMemoryContent.val();
if (!memoryName) {
alert('No memory selected');
return;
}
// Disable button during request
self.$editMemorySaveBtn.prop('disabled', true).text('Saving...');
$.ajax({
url: '/save_memory',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
memory_name: memoryName,
content: content
}),
success: function(response) {
if (response.status === 'success') {
// Update original content and reset dirty flag
self.originalMemoryContent = content;
self.memoryContentDirty = false;
// Close modal
self.$editMemoryModal.fadeOut(200);
self.currentMemoryName = null;
} else {
alert('Error: ' + response.message);
}
},
error: function(xhr, status, error) {
console.error('Error saving memory:', error);
alert('Error saving memory: ' + (xhr.responseJSON ? xhr.responseJSON.message : error));
},
complete: function() {
// Re-enable button
self.$editMemorySaveBtn.prop('disabled', false).text('Save');
}
});
}
confirmDeleteMemory(memoryName) {
// Set memory name to delete
this.memoryToDelete = memoryName;
// Set memory name in modal
this.$deleteMemoryName.text(memoryName);
// Show modal
this.$deleteMemoryModal.fadeIn(200);
}
closeDeleteMemoryModal() {
this.$deleteMemoryModal.fadeOut(200);
this.memoryToDelete = null;
}
confirmDeleteMemoryOk() {
if (this.memoryToDelete) {
this.deleteMemory(this.memoryToDelete);
this.closeDeleteMemoryModal();
}
}
deleteMemory(memoryName) {
const self = this;
$.ajax({
url: '/delete_memory',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
memory_name: memoryName
}),
success: function(response) {
if (response.status === 'success') {
// Reload config to show updated memory list
self.loadConfigOverview();
} else {
alert('Error: ' + response.message);
}
},
error: function(xhr, status, error) {
console.error('Error deleting memory:', error);
alert('Error deleting memory: ' + (xhr.responseJSON ? xhr.responseJSON.message : error));
}
});
}
// ===== Shutdown Method =====
shutdown() {
+27
View File
@@ -185,6 +185,33 @@
</div>
</div>
<!-- Edit Memory Modal -->
<div id="edit-memory-modal" class="modal" style="display:none">
<div class="modal-content modal-content-large">
<span class="modal-close modal-close-edit-memory">&times;</span>
<h3 style="margin-bottom: 20px;">Memory: <span id="edit-memory-name"></span></h3>
<textarea id="edit-memory-content" class="memory-editor" rows="20" style="width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-primary); color: var(--text-primary); font-family: 'Courier New', monospace; font-size: 13px; resize: vertical;"></textarea>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
<button id="edit-memory-cancel-btn" class="btn" style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel</button>
<button id="edit-memory-save-btn" class="btn" style="padding: 8px 16px;">Save</button>
</div>
</div>
</div>
<!-- Delete Memory Confirmation Modal -->
<div id="delete-memory-modal" class="modal" style="display:none">
<div class="modal-content">
<span class="modal-close modal-close-delete-memory">&times;</span>
<p style="margin: 20px 0; color: var(--text-primary); line-height: 1.5; font-size: 15px;">
Delete memory <strong id="delete-memory-name"></strong>?
</p>
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
<button id="delete-memory-cancel-btn" class="btn" style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel</button>
<button id="delete-memory-ok-btn" class="btn" style="padding: 8px 16px;">OK</button>
</div>
</div>
</div>
<script>
$(document).ready(function () {
const dashboard = new Dashboard();