mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 06:17:58 +00:00
Show interface, backend and REPL function availability in the dashboard
Configuration endpoint: * ResponseConfigOverview: agent_interface, language_backend and facades (None in tool mode) * facades report each facade and its methods with their enabled state (SerenaReplEntrypoint.get_facade_availability_info) Dashboard UI: * new rows "Interface" and "Backend"; "Languages" is shown for the LSP backend only * new collapsible section "Active Functions" listing the facades and their methods, dimming disabled ones
This commit is contained in:
1 parent
323e855625
commit
988f20d5f5
5 files changed
+141
-12
No files matched your search
@@ -61,11 +61,25 @@ class ResponseToolStats(BaseModel):
|
||||
stats: dict[str, dict[str, int]]
|
||||
|
||||
|
||||
class ResponseFacadeMethod(BaseModel):
|
||||
name: str
|
||||
is_enabled: bool
|
||||
|
||||
|
||||
class ResponseFacade(BaseModel):
|
||||
name: str
|
||||
is_enabled: bool
|
||||
methods: list[ResponseFacadeMethod]
|
||||
|
||||
|
||||
class ResponseConfigOverview(BaseModel):
|
||||
active_project: dict[str, str | None]
|
||||
context: dict[str, str]
|
||||
modes: list[dict[str, str]]
|
||||
active_tools: list[str]
|
||||
agent_interface: str
|
||||
language_backend: str
|
||||
facades: list[ResponseFacade] | None
|
||||
tool_stats_summary: dict[str, dict[str, int]]
|
||||
registered_projects: list[dict[str, str | bool]]
|
||||
available_tools: list[dict[str, str | bool]]
|
||||
@@ -608,6 +622,19 @@ class SerenaDashboardAPI:
|
||||
if self._agent.is_tool_function_available(ReadMemoryTool) and project is not None:
|
||||
available_memories = project.memory_manager.list_memories().get_full_list()
|
||||
|
||||
# Get the availability of the REPL's facades and their methods (REPL interface only)
|
||||
facades = None
|
||||
if self._agent.get_agent_interface().is_repl():
|
||||
availability_info = self._agent.get_repl().entrypoint.get_facade_availability_info()
|
||||
facades = [
|
||||
ResponseFacade(
|
||||
name=facade_info.name,
|
||||
is_enabled=facade_info.is_enabled,
|
||||
methods=[ResponseFacadeMethod(name=m.name, is_enabled=m.is_enabled) for m in facade_info.methods],
|
||||
)
|
||||
for facade_info in availability_info.facades
|
||||
]
|
||||
|
||||
# Get list of languages for the active project
|
||||
ls_ids = []
|
||||
if project is not None:
|
||||
@@ -623,6 +650,9 @@ class SerenaDashboardAPI:
|
||||
context=context_info,
|
||||
modes=modes_info,
|
||||
active_tools=active_tools,
|
||||
agent_interface=self._agent.get_agent_interface().value,
|
||||
language_backend=self._agent.get_language_backend().value,
|
||||
facades=facades,
|
||||
tool_stats_summary=tool_stats_summary,
|
||||
registered_projects=registered_projects,
|
||||
available_tools=available_tools,
|
||||
|
||||
@@ -643,6 +643,12 @@ class Facade:
|
||||
"""
|
||||
return [m for m in self._methods.values() if m.enabled]
|
||||
|
||||
def get_methods(self) -> list[FacadeMethod]:
|
||||
"""
|
||||
:return: the list of all methods, regardless of whether they are enabled (e.g. for changing their enabled state)
|
||||
"""
|
||||
return list(self._methods.values())
|
||||
|
||||
def get_types(self) -> list[ReferencedType]:
|
||||
"""
|
||||
:return: the types referenced by the facade's methods
|
||||
|
||||
+44
-8
@@ -8,6 +8,7 @@ import ast
|
||||
import logging
|
||||
import re
|
||||
import traceback
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..session import SerenaSession
|
||||
@@ -21,6 +22,35 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FacadeAvailabilityInfo:
|
||||
"""
|
||||
Represents information on the availability of facades and the methods therein
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class FacadeInfo:
|
||||
name: str
|
||||
is_enabled: bool
|
||||
methods: list["FacadeAvailabilityInfo.MethodInfo"]
|
||||
|
||||
@dataclass
|
||||
class MethodInfo:
|
||||
name: str
|
||||
is_enabled: bool
|
||||
|
||||
def __init__(self):
|
||||
self.facades: list[FacadeAvailabilityInfo.FacadeInfo] = []
|
||||
|
||||
def add_facade(self, facade: Facade, is_enabled: bool):
|
||||
def is_method_enabled(m: FacadeMethod) -> bool:
|
||||
return is_enabled and m.enabled
|
||||
|
||||
methods_info = [
|
||||
FacadeAvailabilityInfo.MethodInfo(name=method.name, is_enabled=is_method_enabled(method)) for method in facade.get_methods()
|
||||
]
|
||||
self.facades.append(FacadeAvailabilityInfo.FacadeInfo(name=facade.name, is_enabled=is_enabled, methods=methods_info))
|
||||
|
||||
|
||||
class SerenaReplEntrypoint:
|
||||
"""
|
||||
Represents the entrypoint object for the REPL. It holds the configured facades as attributes
|
||||
@@ -35,13 +65,25 @@ class SerenaReplEntrypoint:
|
||||
self._facades: dict[str, Facade] = {}
|
||||
self._current_session: SerenaSession | None = None
|
||||
self._current_namespace: dict[str, Any] | None = None
|
||||
self._facade_availability_info = FacadeAvailabilityInfo()
|
||||
registered_facade_names = []
|
||||
for facade in facades:
|
||||
if api_scope.is_facade_enabled(facade.name):
|
||||
self._register(facade)
|
||||
is_facade_enabled = api_scope.is_facade_enabled(facade.name)
|
||||
self._facade_availability_info.add_facade(facade, is_facade_enabled)
|
||||
if is_facade_enabled:
|
||||
if facade.name in self._facades:
|
||||
raise ValueError(f"Duplicate facade name: {facade.name}")
|
||||
self._facades[facade.name] = facade
|
||||
setattr(self, facade.name, facade)
|
||||
registered_facade_names.append(facade.name)
|
||||
log.info("Registered %d/%d facades: %s", len(registered_facade_names), len(facades), registered_facade_names)
|
||||
|
||||
def get_facade_availability_info(self) -> FacadeAvailabilityInfo:
|
||||
"""
|
||||
:return: the availability of all facades and their methods (enabled or disabled)
|
||||
"""
|
||||
return self._facade_availability_info
|
||||
|
||||
def get_enabled_methods(self) -> list[FacadeMethod]:
|
||||
"""
|
||||
:return: the list of all enabled methods across all facades
|
||||
@@ -117,12 +159,6 @@ class SerenaReplEntrypoint:
|
||||
del self._current_namespace[name]
|
||||
return f"Removed {len(items)} persisted item(s)."
|
||||
|
||||
def _register(self, facade: Facade) -> None:
|
||||
if facade.name in self._facades:
|
||||
raise ValueError(f"Duplicate facade name: {facade.name}")
|
||||
self._facades[facade.name] = facade
|
||||
setattr(self, facade.name, facade)
|
||||
|
||||
def _get_facade(self, name: str) -> Facade:
|
||||
if name not in self._facades:
|
||||
raise ValueError(f"Unknown facade '{name}'. Available facades: {list(self._facades)}")
|
||||
|
||||
@@ -603,6 +603,23 @@ code, pre, kbd, samp,
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.facade-block {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.facade-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.facade-name.disabled,
|
||||
.tool-item.disabled {
|
||||
color: var(--text-secondary);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Projects List */
|
||||
.project-item {
|
||||
padding: 10px 12px;
|
||||
|
||||
@@ -614,6 +614,7 @@ class Dashboard {
|
||||
const $existingToolsContent = $('#tools-content');
|
||||
const $existingMemoriesContent = $('#memories-content');
|
||||
const wasToolsExpanded = $existingToolsContent.is(':visible');
|
||||
const wasFunctionsExpanded = $('#functions-content').is(':visible');
|
||||
const wasMemoriesExpanded = $existingMemoriesContent.is(':visible');
|
||||
|
||||
let html = '<div class="config-grid">';
|
||||
@@ -634,10 +635,14 @@ class Dashboard {
|
||||
html += '<div class="config-value">' + (config.active_project.name || 'None') + '</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-label">Interface:</div>';
|
||||
html += '<div class="config-value">' + config.agent_interface + '</div>';
|
||||
|
||||
html += '<div class="config-label">Backend:</div>';
|
||||
html += '<div class="config-value">' + config.language_backend + '</div>';
|
||||
|
||||
if (!this.jetbrainsMode) {
|
||||
html += '<div class="config-label">Languages:</div>';
|
||||
html += '<div class="config-value">';
|
||||
if (config.languages && config.languages.length > 0) {
|
||||
html += '<div class="languages-container">';
|
||||
@@ -705,6 +710,32 @@ class Dashboard {
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
// Active functions of the REPL's facades - collapsible (REPL interface only)
|
||||
if (config.facades) {
|
||||
const enabledMethodCount = config.facades.reduce(function (count, facade) {
|
||||
return count + facade.methods.filter(function (method) { return method.is_enabled; }).length;
|
||||
}, 0);
|
||||
html += '<div style="margin-top: 20px;">';
|
||||
html += '<h3 class="collapsible-header" id="functions-header" style="font-size: 16px; margin: 0;">';
|
||||
html += '<span>Active Functions (' + enabledMethodCount + ')</span>';
|
||||
html += '<span class="toggle-icon' + (wasFunctionsExpanded ? ' expanded' : '') + '">▼</span>';
|
||||
html += '</h3>';
|
||||
html += '<div class="collapsible-content" id="functions-content" style="' + (wasFunctionsExpanded ? '' : 'display:none;') + ' margin-top: 10px;">';
|
||||
config.facades.forEach(function (facade) {
|
||||
html += '<div class="facade-block">';
|
||||
html += '<div class="facade-name' + (facade.is_enabled ? '' : ' disabled') + '">s.' + facade.name + '</div>';
|
||||
html += '<div class="tools-grid">';
|
||||
facade.methods.forEach(function (method) {
|
||||
const title = facade.name + '.' + method.name + (method.is_enabled ? '' : ' (disabled)');
|
||||
html += '<div class="tool-item' + (method.is_enabled ? '' : ' disabled') + '" title="' + title + '">' + method.name + '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// 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;">';
|
||||
@@ -773,6 +804,15 @@ class Dashboard {
|
||||
$('#create-memory-btn').click(this.openCreateMemoryModal.bind(this));
|
||||
|
||||
// Re-attach collapsible handler for the newly created tools header
|
||||
$('#functions-header').click(function () {
|
||||
const $header = $(this);
|
||||
const $content = $('#functions-content');
|
||||
const $icon = $header.find('.toggle-icon');
|
||||
|
||||
$content.slideToggle(300);
|
||||
$icon.toggleClass('expanded');
|
||||
});
|
||||
|
||||
$('#tools-header').click(function () {
|
||||
const $header = $(this);
|
||||
const $content = $('#tools-content');
|
||||
|
||||
Reference in new issue
Block a user