mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 16:15:29 +00:00
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:
1 parent
6ce7d73130
commit
c6eba90460
4 files changed
+384
-3
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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 + '">×</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() {
|
||||
|
||||
@@ -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">×</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">×</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();
|
||||
|
||||
Reference in new issue
Block a user