mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 06:15:43 +00:00
Dashboard: possibility to add a language.
Also specialize dashboard to jetbrains mode when it is active
This commit is contained in:
1 parent
2c2fcc6a07
commit
31dae80218
7 files changed
+508
-14
No files matched your search
@@ -557,6 +557,15 @@ class SerenaAgent:
|
||||
else:
|
||||
self.issue_task(task, name=task_name)
|
||||
|
||||
def remove_language(self, language: Language) -> None:
|
||||
"""
|
||||
Removes a language from the active project, shutting down the respective language server and updating the project configuration.
|
||||
The removal is scheduled via the agent's task executor and executed asynchronously.
|
||||
|
||||
:param language: the language to remove
|
||||
"""
|
||||
self.issue_task(lambda: self.get_active_project_or_raise().remove_language(language), name=f"RemoveLanguage:{language.value}")
|
||||
|
||||
def get_tool(self, tool_class: type[TTool]) -> TTool:
|
||||
return self._all_tools[tool_class] # type: ignore
|
||||
|
||||
|
||||
@@ -50,6 +50,20 @@ class ResponseConfigOverview(BaseModel):
|
||||
available_modes: list[dict[str, str | bool]]
|
||||
available_contexts: list[dict[str, str | bool]]
|
||||
available_memories: list[str] | None
|
||||
jetbrains_mode: bool
|
||||
languages: list[str]
|
||||
|
||||
|
||||
class ResponseAvailableLanguages(BaseModel):
|
||||
languages: list[str]
|
||||
|
||||
|
||||
class RequestAddLanguage(BaseModel):
|
||||
language: str
|
||||
|
||||
|
||||
class RequestRemoveLanguage(BaseModel):
|
||||
language: str
|
||||
|
||||
|
||||
class SerenaDashboardAPI:
|
||||
@@ -127,6 +141,35 @@ class SerenaDashboardAPI:
|
||||
self._shutdown()
|
||||
return {"status": "shutting down"}
|
||||
|
||||
@self._app.route("/get_available_languages", methods=["GET"])
|
||||
def get_available_languages() -> dict[str, Any]:
|
||||
result = self._get_available_languages()
|
||||
return result.model_dump()
|
||||
|
||||
@self._app.route("/add_language", methods=["POST"])
|
||||
def add_language() -> dict[str, str]:
|
||||
request_data = request.get_json()
|
||||
if not request_data:
|
||||
return {"status": "error", "message": "No data provided"}
|
||||
request_add_language = RequestAddLanguage.model_validate(request_data)
|
||||
try:
|
||||
self._add_language(request_add_language)
|
||||
return {"status": "success", "message": f"Language {request_add_language.language} added successfully"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@self._app.route("/remove_language", methods=["POST"])
|
||||
def remove_language() -> dict[str, str]:
|
||||
request_data = request.get_json()
|
||||
if not request_data:
|
||||
return {"status": "error", "message": "No data provided"}
|
||||
request_remove_language = RequestRemoveLanguage.model_validate(request_data)
|
||||
try:
|
||||
self._remove_language(request_remove_language)
|
||||
return {"status": "success", "message": f"Language {request_remove_language.language} removed 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 []
|
||||
@@ -233,6 +276,11 @@ class SerenaDashboardAPI:
|
||||
if self._agent.tool_is_active("read_memory") and project is not None:
|
||||
available_memories = project.memories_manager.list_memories()
|
||||
|
||||
# Get list of languages for the active project
|
||||
languages = []
|
||||
if project is not None:
|
||||
languages = [lang.value for lang in project.project_config.languages]
|
||||
|
||||
return ResponseConfigOverview(
|
||||
active_project=project_info,
|
||||
context=context_info,
|
||||
@@ -244,6 +292,8 @@ class SerenaDashboardAPI:
|
||||
available_modes=available_modes,
|
||||
available_contexts=available_contexts,
|
||||
available_memories=available_memories,
|
||||
jetbrains_mode=self._agent.serena_config.jetbrains,
|
||||
languages=languages,
|
||||
)
|
||||
|
||||
def _shutdown(self) -> None:
|
||||
@@ -255,6 +305,46 @@ class SerenaDashboardAPI:
|
||||
# noinspection PyUnresolvedReferences
|
||||
os._exit(0)
|
||||
|
||||
def _get_available_languages(self) -> ResponseAvailableLanguages:
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
# Get all non-experimental languages
|
||||
all_languages = [lang.value for lang in Language.iter_all(include_experimental=False)]
|
||||
|
||||
# Filter out already added languages for the active project
|
||||
project = self._agent.get_active_project()
|
||||
if project:
|
||||
current_languages = [lang.value for lang in project.project_config.languages]
|
||||
available_languages = [lang for lang in all_languages if lang not in current_languages]
|
||||
else:
|
||||
available_languages = all_languages
|
||||
|
||||
return ResponseAvailableLanguages(languages=sorted(available_languages))
|
||||
|
||||
def _add_language(self, request_add_language: RequestAddLanguage) -> None:
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
# Convert string to Language enum
|
||||
try:
|
||||
language = Language(request_add_language.language)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid language: {request_add_language.language}")
|
||||
|
||||
# Add the language to the active project
|
||||
self._agent.add_language(language, wait_for_ls_startup=False)
|
||||
|
||||
def _remove_language(self, request_remove_language: RequestRemoveLanguage) -> None:
|
||||
from solidlsp.ls_config import Language
|
||||
|
||||
# Convert string to Language enum
|
||||
try:
|
||||
language = Language(request_remove_language.language)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid language: {request_remove_language.language}")
|
||||
|
||||
# Remove the language from the active project
|
||||
self._agent.remove_language(language)
|
||||
|
||||
@staticmethod
|
||||
def _find_first_free_port(start_port: int) -> int:
|
||||
port = start_port
|
||||
|
||||
@@ -175,6 +175,25 @@ class LanguageServerManager:
|
||||
raise ValueError(f"Language server for language {language.value} already present")
|
||||
return self._create_and_start_language_server(language)
|
||||
|
||||
def remove_language_server(self, language: Language, save_cache: bool = False) -> None:
|
||||
"""
|
||||
Removes the language server for the given language, stopping it if it is running.
|
||||
|
||||
:param language: the language
|
||||
"""
|
||||
if language not in self._language_servers:
|
||||
raise ValueError(f"No language server for language {language.value} present; cannot remove")
|
||||
ls = self._language_servers.pop(language)
|
||||
self._stop_language_server(ls, save_cache=save_cache)
|
||||
|
||||
@staticmethod
|
||||
def _stop_language_server(ls: SolidLanguageServer, save_cache: bool = False) -> None:
|
||||
if ls.is_running():
|
||||
if save_cache:
|
||||
ls.save_cache()
|
||||
log.info(f"Stopping language server for language {ls.language} ...")
|
||||
ls.stop()
|
||||
|
||||
def iter_language_servers(self) -> Iterator[SolidLanguageServer]:
|
||||
for ls in self._language_servers.values():
|
||||
yield self._ensure_functional_ls(ls)
|
||||
@@ -186,11 +205,7 @@ class LanguageServerManager:
|
||||
:param save_cache: whether to save the cache before stopping
|
||||
"""
|
||||
for ls in self.iter_language_servers():
|
||||
if ls.is_running():
|
||||
if save_cache:
|
||||
ls.save_cache()
|
||||
log.info(f"Stopping language server for language {ls.language} ...")
|
||||
ls.stop()
|
||||
self._stop_language_server(ls, save_cache=save_cache)
|
||||
|
||||
def save_all_caches(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -416,6 +416,28 @@ class Project(ToStringMixin):
|
||||
log.info("Adding and starting the language server for new language %s ...", language.value)
|
||||
self.language_server_manager.add_language_server(language)
|
||||
|
||||
def remove_language(self, language: Language) -> None:
|
||||
"""
|
||||
Removes a programming language from the project configuration, stopping the corresponding
|
||||
language server instance if the LS manager is active.
|
||||
The project configuration is saved to disk after removing the language.
|
||||
|
||||
:param language: the programming language to remove
|
||||
"""
|
||||
if language not in self.project_config.languages:
|
||||
log.info(f"Language {language.value} is not present in the project configuration.")
|
||||
return
|
||||
# update the project configuration
|
||||
self.project_config.languages.remove(language)
|
||||
self.save_config()
|
||||
|
||||
# stop the language server (if the LS manager is active)
|
||||
if self.language_server_manager is None:
|
||||
log.info("Language server manager is not active; skipping language server shutdown for the removed language.")
|
||||
else:
|
||||
log.info("Removing and stopping the language server for language %s ...", language.value)
|
||||
self.language_server_manager.remove_language_server(language)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
if self.language_server_manager is not None:
|
||||
self.language_server_manager.stop_all(save_cache=True)
|
||||
|
||||
@@ -618,3 +618,96 @@ body {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--bg-primary);
|
||||
margin: 10% auto;
|
||||
padding: 25px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
color: var(--text-muted);
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover,
|
||||
.modal-close:focus {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Language Badge Styles */
|
||||
.languages-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.language-badge {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.language-badge.removable {
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.language-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;
|
||||
}
|
||||
|
||||
.language-remove:hover {
|
||||
background: rgba(255, 68, 68, 0.2);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
@@ -52,6 +52,9 @@ class Dashboard {
|
||||
// Page state
|
||||
this.currentPage = 'overview';
|
||||
this.configData = null;
|
||||
this.jetbrainsMode = false;
|
||||
this.activeProjectName = null;
|
||||
this.languageToRemove = null;
|
||||
|
||||
// Tool names and stats
|
||||
this.toolNames = [];
|
||||
@@ -80,6 +83,18 @@ class Dashboard {
|
||||
this.$availableToolsDisplay = $('#available-tools-display');
|
||||
this.$availableModesDisplay = $('#available-modes-display');
|
||||
this.$availableContextsDisplay = $('#available-contexts-display');
|
||||
this.$addLanguageBtn = $('#add-language-btn');
|
||||
this.$addLanguageModal = $('#add-language-modal');
|
||||
this.$modalLanguageSelect = $('#modal-language-select');
|
||||
this.$modalProjectName = $('#modal-project-name');
|
||||
this.$modalAddBtn = $('#modal-add-btn');
|
||||
this.$modalCancelBtn = $('#modal-cancel-btn');
|
||||
this.$modalClose = $('.modal-close');
|
||||
this.$removeLanguageModal = $('#remove-language-modal');
|
||||
this.$removeLanguageName = $('#remove-language-name');
|
||||
this.$removeModalOkBtn = $('#remove-modal-ok-btn');
|
||||
this.$removeModalCancelBtn = $('#remove-modal-cancel-btn');
|
||||
this.$modalCloseRemove = $('.modal-close-remove');
|
||||
|
||||
// Chart references
|
||||
this.countChart = null;
|
||||
@@ -97,6 +112,13 @@ class Dashboard {
|
||||
this.$themeToggle.click(this.toggleTheme.bind(this));
|
||||
this.$refreshStats.click(this.loadStats.bind(this));
|
||||
this.$clearStats.click(this.clearStats.bind(this));
|
||||
this.$addLanguageBtn.click(this.openLanguageModal.bind(this));
|
||||
this.$modalAddBtn.click(this.addLanguageFromModal.bind(this));
|
||||
this.$modalCancelBtn.click(this.closeLanguageModal.bind(this));
|
||||
this.$modalClose.click(this.closeLanguageModal.bind(this));
|
||||
this.$removeModalOkBtn.click(this.confirmRemoveLanguageOk.bind(this));
|
||||
this.$removeModalCancelBtn.click(this.closeRemoveLanguageModal.bind(this));
|
||||
this.$modalCloseRemove.click(this.closeRemoveLanguageModal.bind(this));
|
||||
|
||||
// Page navigation
|
||||
$('[data-page]').click(function(e) {
|
||||
@@ -112,6 +134,19 @@ class Dashboard {
|
||||
}
|
||||
});
|
||||
|
||||
// Close modals when clicking outside
|
||||
this.$addLanguageModal.click(function(e) {
|
||||
if ($(e.target).hasClass('modal')) {
|
||||
self.closeLanguageModal();
|
||||
}
|
||||
});
|
||||
|
||||
this.$removeLanguageModal.click(function(e) {
|
||||
if ($(e.target).hasClass('modal')) {
|
||||
self.closeRemoveLanguageModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Collapsible sections
|
||||
$('.collapsible-header').click(function() {
|
||||
const $header = $(this);
|
||||
@@ -125,6 +160,17 @@ class Dashboard {
|
||||
// Initialize theme
|
||||
this.initializeTheme();
|
||||
|
||||
// Add ESC key handler for closing modals
|
||||
$(document).keydown(function(e) {
|
||||
if (e.key === 'Escape' || e.keyCode === 27) {
|
||||
if (self.$addLanguageModal.is(':visible')) {
|
||||
self.closeLanguageModal();
|
||||
} else if (self.$removeLanguageModal.is(':visible')) {
|
||||
self.closeRemoveLanguageModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize the application
|
||||
this.loadToolNames().then(function() {
|
||||
// Start on overview page
|
||||
@@ -187,8 +233,11 @@ class Dashboard {
|
||||
url: '/get_config_overview',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
console.log('Config overview response:', response);
|
||||
self.failureCount = 0;
|
||||
self.configData = response;
|
||||
self.jetbrainsMode = response.jetbrains_mode;
|
||||
self.activeProjectName = response.active_project.name;
|
||||
self.displayConfig(response);
|
||||
self.displayBasicStats(response.tool_stats_summary);
|
||||
self.displayProjects(response.registered_projects);
|
||||
@@ -219,13 +268,14 @@ class Dashboard {
|
||||
}
|
||||
|
||||
displayConfig(config) {
|
||||
// Check if tools and memories sections are currently expanded
|
||||
const $existingToolsContent = $('#tools-content');
|
||||
const $existingMemoriesContent = $('#memories-content');
|
||||
const wasToolsExpanded = $existingToolsContent.is(':visible');
|
||||
const wasMemoriesExpanded = $existingMemoriesContent.is(':visible');
|
||||
try {
|
||||
// Check if tools and memories sections are currently expanded
|
||||
const $existingToolsContent = $('#tools-content');
|
||||
const $existingMemoriesContent = $('#memories-content');
|
||||
const wasToolsExpanded = $existingToolsContent.is(':visible');
|
||||
const wasMemoriesExpanded = $existingMemoriesContent.is(':visible');
|
||||
|
||||
let html = '<div class="config-grid">';
|
||||
let html = '<div class="config-grid">';
|
||||
|
||||
// Project info
|
||||
html += '<div class="config-label">Active Project:</div>';
|
||||
@@ -236,8 +286,28 @@ class Dashboard {
|
||||
html += '<div class="config-value">' + (config.active_project.name || 'None') + '</div>';
|
||||
}
|
||||
|
||||
html += '<div class="config-label">Language:</div>';
|
||||
html += '<div class="config-value">' + (config.active_project.language || 'N/A') + '</div>';
|
||||
html += '<div class="config-label">Languages:</div>';
|
||||
if (this.jetbrainsMode) {
|
||||
html += '<div class="config-value">Using JetBrains backend</div>';
|
||||
} else {
|
||||
html += '<div class="config-value">';
|
||||
if (config.languages && config.languages.length > 0) {
|
||||
html += '<div class="languages-container">';
|
||||
config.languages.forEach(function(language, index) {
|
||||
const isRemovable = config.languages.length > 1;
|
||||
html += '<div class="language-badge' + (isRemovable ? ' removable' : '') + '">';
|
||||
html += language;
|
||||
if (isRemovable) {
|
||||
html += '<span class="language-remove" data-language="' + language + '">×</span>';
|
||||
}
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += 'N/A';
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Context info
|
||||
html += '<div class="config-label">Context:</div>';
|
||||
@@ -294,6 +364,22 @@ class Dashboard {
|
||||
|
||||
this.$configDisplay.html(html);
|
||||
|
||||
// Show/hide add language button based on jetbrains mode
|
||||
if (this.jetbrainsMode) {
|
||||
this.$addLanguageBtn.hide();
|
||||
} else {
|
||||
this.$addLanguageBtn.show();
|
||||
}
|
||||
|
||||
// Attach event handlers for language remove buttons
|
||||
const self = this;
|
||||
$('.language-remove').click(function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const language = $(this).data('language');
|
||||
self.confirmRemoveLanguage(language);
|
||||
});
|
||||
|
||||
// Re-attach collapsible handler for the newly created tools header
|
||||
$('#tools-header').click(function() {
|
||||
const $header = $(this);
|
||||
@@ -313,6 +399,10 @@ class Dashboard {
|
||||
$content.slideToggle(300);
|
||||
$icon.toggleClass('expanded');
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in displayConfig:', error);
|
||||
this.$configDisplay.html('<div class="error-message">Error displaying configuration: ' + error.message + '</div>');
|
||||
}
|
||||
}
|
||||
|
||||
displayBasicStats(stats) {
|
||||
@@ -952,6 +1042,140 @@ class Dashboard {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Language Management Methods =====
|
||||
|
||||
confirmRemoveLanguage(language) {
|
||||
// Store the language to remove
|
||||
this.languageToRemove = language;
|
||||
|
||||
// Set language name in modal
|
||||
this.$removeLanguageName.text(language);
|
||||
|
||||
// Show modal
|
||||
this.$removeLanguageModal.fadeIn(200);
|
||||
}
|
||||
|
||||
closeRemoveLanguageModal() {
|
||||
this.$removeLanguageModal.fadeOut(200);
|
||||
this.languageToRemove = null;
|
||||
}
|
||||
|
||||
confirmRemoveLanguageOk() {
|
||||
if (this.languageToRemove) {
|
||||
this.removeLanguage(this.languageToRemove);
|
||||
this.closeRemoveLanguageModal();
|
||||
}
|
||||
}
|
||||
|
||||
removeLanguage(language) {
|
||||
const self = this;
|
||||
|
||||
$.ajax({
|
||||
url: '/remove_language',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
language: language
|
||||
}),
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
// Reload config to show updated language list
|
||||
self.loadConfigOverview();
|
||||
} else {
|
||||
alert('Error: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error removing language:', error);
|
||||
alert('Error removing language: ' + (xhr.responseJSON ? xhr.responseJSON.message : error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openLanguageModal() {
|
||||
// Set project name in modal
|
||||
this.$modalProjectName.text(this.activeProjectName || 'Unknown');
|
||||
|
||||
// Load available languages into modal dropdown
|
||||
this.loadAvailableLanguages();
|
||||
|
||||
// Show modal
|
||||
this.$addLanguageModal.fadeIn(200);
|
||||
}
|
||||
|
||||
closeLanguageModal() {
|
||||
this.$addLanguageModal.fadeOut(200);
|
||||
this.$modalLanguageSelect.empty();
|
||||
this.$modalAddBtn.prop('disabled', false).text('Add Language');
|
||||
}
|
||||
|
||||
loadAvailableLanguages() {
|
||||
let self = this;
|
||||
$.ajax({
|
||||
url: '/get_available_languages',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
const languages = response.languages || [];
|
||||
// Clear all existing options
|
||||
self.$modalLanguageSelect.empty();
|
||||
|
||||
if (languages.length === 0) {
|
||||
// Show message if no languages available
|
||||
self.$modalLanguageSelect.append($('<option>').val('').text('No languages available to add'));
|
||||
self.$modalAddBtn.prop('disabled', true);
|
||||
} else {
|
||||
// Add language options
|
||||
languages.forEach(function(language) {
|
||||
self.$modalLanguageSelect.append($('<option>').val(language).text(language));
|
||||
});
|
||||
self.$modalAddBtn.prop('disabled', false);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading available languages:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addLanguageFromModal() {
|
||||
const selectedLanguage = this.$modalLanguageSelect.val();
|
||||
if (!selectedLanguage) {
|
||||
alert('No language selected or no languages available to add');
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
// Disable button during request
|
||||
self.$modalAddBtn.prop('disabled', true).text('Adding...');
|
||||
|
||||
$.ajax({
|
||||
url: '/add_language',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
language: selectedLanguage
|
||||
}),
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
// Close modal
|
||||
self.closeLanguageModal();
|
||||
// Reload config to show updated language
|
||||
self.loadConfigOverview();
|
||||
} else {
|
||||
alert('Error: ' + response.message);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error adding language:', error);
|
||||
alert('Error adding language: ' + (xhr.responseJSON ? xhr.responseJSON.message : error));
|
||||
},
|
||||
complete: function() {
|
||||
// Re-enable button
|
||||
self.$modalAddBtn.prop('disabled', false).text('Add Language');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ===== Shutdown Method =====
|
||||
|
||||
shutdown() {
|
||||
|
||||
@@ -44,7 +44,10 @@
|
||||
<div class="overview-container">
|
||||
<div class="overview-left">
|
||||
<section class="config-section">
|
||||
<h2>Current Configuration</h2>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||||
<h2 style="margin: 0;">Current Configuration</h2>
|
||||
<button id="add-language-btn" class="btn" style="padding: 6px 12px; font-size: 14px;">Add Language</button>
|
||||
</div>
|
||||
<div id="config-display">
|
||||
<div class="loading">Loading configuration...</div>
|
||||
</div>
|
||||
@@ -144,6 +147,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Language Modal -->
|
||||
<div id="add-language-modal" class="modal" style="display:none">
|
||||
<div class="modal-content">
|
||||
<span class="modal-close">×</span>
|
||||
<h3>Add Language</h3>
|
||||
<p id="modal-project-info" style="margin: 15px 0; color: var(--text-primary); line-height: 1.5;">
|
||||
Adding a language to serena config of project <strong id="modal-project-name"></strong>.
|
||||
</p>
|
||||
<p style="margin: 15px 0; color: var(--text-muted); font-size: 13px; line-height: 1.5;">
|
||||
Note that this may download some dependencies needed for the language server and then start it,
|
||||
it may take a few seconds before the LS is responsive.
|
||||
</p>
|
||||
<div style="margin: 20px 0;">
|
||||
<label for="modal-language-select" style="display: block; margin-bottom: 8px; font-weight: 500;">Select Language:</label>
|
||||
<select id="modal-language-select" class="language-select" 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;">
|
||||
</select>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
|
||||
<button id="modal-cancel-btn" class="btn" style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel</button>
|
||||
<button id="modal-add-btn" class="btn" style="padding: 8px 16px;">Add Language</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remove Language Confirmation Modal -->
|
||||
<div id="remove-language-modal" class="modal" style="display:none">
|
||||
<div class="modal-content">
|
||||
<span class="modal-close modal-close-remove">×</span>
|
||||
<p style="margin: 20px 0; color: var(--text-primary); line-height: 1.5; font-size: 15px;">
|
||||
Remove language <strong id="remove-language-name"></strong> from configuration?
|
||||
</p>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;">
|
||||
<button id="remove-modal-cancel-btn" class="btn" style="background: var(--bg-secondary); color: var(--text-primary); padding: 8px 16px;">Cancel</button>
|
||||
<button id="remove-modal-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