mirror of
https://github.com/tiennm99/serena.git
synced 2026-08-14 14:27:01 +00:00
Moved dashboard to src/serena. Stopped using serena_root_path (in favor of constants.py)
This commit is contained in:
@@ -1,12 +1,5 @@
|
||||
__version__ = "2025-05-21"
|
||||
|
||||
|
||||
def serena_root_path() -> str:
|
||||
from pathlib import Path
|
||||
|
||||
return str(Path(__file__).parent.parent.parent.absolute())
|
||||
|
||||
|
||||
def serena_version() -> str:
|
||||
"""
|
||||
:return: the version of the package, including git status if available.
|
||||
|
||||
+3
-3
@@ -34,9 +34,9 @@ from multilspy import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language, MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_types import SymbolKind
|
||||
from serena import serena_root_path, serena_version
|
||||
from serena import serena_version
|
||||
from serena.config import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.constants import PROJECT_TEMPLATE_FILE, SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME
|
||||
from serena.constants import PROJECT_TEMPLATE_FILE, SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME, REPO_ROOT
|
||||
from serena.dashboard import MemoryLogHandler, SerenaDashboardAPI
|
||||
from serena.prompt_factory import PromptFactory, SerenaPromptFactory
|
||||
from serena.symbol import SymbolManager
|
||||
@@ -308,7 +308,7 @@ class SerenaConfig(SerenaConfigBase):
|
||||
|
||||
@classmethod
|
||||
def get_config_file_path(cls) -> str:
|
||||
return os.path.join(serena_root_path(), cls.CONFIG_FILE)
|
||||
return os.path.join(REPO_ROOT, cls.CONFIG_FILE)
|
||||
|
||||
@classmethod
|
||||
def from_config_file(cls, generate_if_missing: bool = True) -> "SerenaConfig":
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ from agno.tools.toolkit import Toolkit
|
||||
from dotenv import load_dotenv
|
||||
from sensai.util.logging import LogTime
|
||||
|
||||
from serena import serena_root_path
|
||||
from serena.constants import REPO_ROOT
|
||||
from serena.agent import SerenaAgent, Tool, show_fatal_exception_safe
|
||||
from serena.config import SerenaAgentContext
|
||||
|
||||
@@ -65,7 +65,7 @@ class SerenaAgnoAgentProvider:
|
||||
return cls._agent
|
||||
|
||||
# change to Serena root
|
||||
os.chdir(serena_root_path())
|
||||
os.chdir(REPO_ROOT)
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -94,7 +94,7 @@ class SerenaAgnoAgentProvider:
|
||||
# If project file path is relative, make it absolute by joining with project root
|
||||
if not project_file.is_absolute():
|
||||
# Get the project root directory (parent of scripts directory)
|
||||
project_root = Path(serena_root_path())
|
||||
project_root = Path(REPO_ROOT)
|
||||
project_file = project_root / args_project_file
|
||||
|
||||
# Ensure the path is normalized and absolute
|
||||
|
||||
@@ -7,6 +7,8 @@ REPO_ROOT = str(_repo_root_path)
|
||||
PROMPT_TEMPLATES_DIR = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
|
||||
CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
|
||||
MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "modes")
|
||||
SERENA_DASHBOARD_DIR = str(_serena_pkg_path / "resources" / "dashboard")
|
||||
SERENA_ICON_DIR = str(_serena_pkg_path / "resources" / "icons")
|
||||
|
||||
SERENA_MANAGED_DIR_NAME = ".serena"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
from sensai.util import logging
|
||||
|
||||
from serena import serena_root_path
|
||||
from serena.constants import SERENA_DASHBOARD_DIR
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,8 +75,7 @@ class SerenaDashboardAPI:
|
||||
self._setup_routes()
|
||||
|
||||
def _setup_routes(self) -> None:
|
||||
static_dir = os.path.join(serena_root_path(), "dashboard")
|
||||
self._app.mount("/dashboard", StaticFiles(directory=static_dir), name="dashboard")
|
||||
self._app.mount("/dashboard", StaticFiles(directory=SERENA_DASHBOARD_DIR), name="dashboard")
|
||||
|
||||
self._app.add_api_route("/get_log_messages", self._get_log_messages, methods=["POST"], response_model=ResponseLog)
|
||||
self._app.add_api_route("/get_tool_names", self._get_tool_names, methods=["GET"], response_model=ResponseToolNames)
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
class LogMessage {
|
||||
constructor(message, toolNames) {
|
||||
const logLevel = this.determineLogLevel(message);
|
||||
const highlightedMessage = this.highlightToolNames(message, toolNames);
|
||||
this.$elem = $('<div>').addClass('log-' + logLevel).html(highlightedMessage + '\n');
|
||||
}
|
||||
|
||||
determineLogLevel(message) {
|
||||
if (message.startsWith('DEBUG')) {
|
||||
return 'debug';
|
||||
} else if (message.startsWith('INFO')) {
|
||||
return 'info';
|
||||
} else if (message.startsWith('WARNING')) {
|
||||
return 'warning';
|
||||
} else if (message.startsWith('ERROR')) {
|
||||
return 'error';
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
highlightToolNames(message, toolNames) {
|
||||
let highlightedMessage = message;
|
||||
toolNames.forEach(function(toolName) {
|
||||
const regex = new RegExp('\\b' + toolName + '\\b', 'gi');
|
||||
highlightedMessage = highlightedMessage.replace(regex, '<span class="tool-name">' + toolName + '</span>');
|
||||
});
|
||||
return highlightedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
class Dashboard {
|
||||
constructor() {
|
||||
let self = this;
|
||||
|
||||
this.toolNames = [];
|
||||
this.currentMaxIdx = -1;
|
||||
this.pollInterval = null;
|
||||
this.$logContainer = $('#log-container');
|
||||
this.$errorContainer = $('#error-container');
|
||||
this.$loadButton = $('#load-logs');
|
||||
this.$shutdownButton = $('#shutdown');
|
||||
|
||||
// register event handlers
|
||||
this.$loadButton.click(this.loadLogs.bind(this));
|
||||
this.$shutdownButton.click(this.shutdown.bind(this));
|
||||
|
||||
// initialize the application
|
||||
this.loadToolNames().then(function() {
|
||||
// Load logs on page load after tool names are loaded
|
||||
self.loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
displayLogMessage(message) {
|
||||
$('#log-container').append(new LogMessage(message, this.toolNames).$elem);
|
||||
}
|
||||
|
||||
loadToolNames() {
|
||||
let self = this;
|
||||
return $.ajax({
|
||||
url: '/get_tool_names',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
self.toolNames = response.tool_names || [];
|
||||
console.log('Loaded tool names:', self.toolNames);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading tool names:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
$.ajax({
|
||||
url: '/get_log_messages',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
start_idx: 0
|
||||
}),
|
||||
success: function(response) {
|
||||
// Clear existing logs
|
||||
self.$logContainer.empty();
|
||||
|
||||
// Update max_idx
|
||||
self.currentMaxIdx = response.max_idx || -1;
|
||||
|
||||
// Display each log message
|
||||
if (response.messages && response.messages.length > 0) {
|
||||
response.messages.forEach(function(message) {
|
||||
self.displayLogMessage(message);
|
||||
});
|
||||
|
||||
// Auto-scroll to bottom
|
||||
const logContainer = $('#log-container')[0];
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
} else {
|
||||
$('#log-container').html('<div class="loading">No log messages found.</div>');
|
||||
}
|
||||
|
||||
// Start periodic polling for new logs
|
||||
self.startPeriodicPolling();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pollForNewLogs() {
|
||||
let self = this;
|
||||
console.log("Polling logs", this.currentMaxIdx);
|
||||
$.ajax({
|
||||
url: '/get_log_messages',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
start_idx: self.currentMaxIdx + 1
|
||||
}),
|
||||
success: function(response) {
|
||||
// Only append new messages if we have any
|
||||
if (response.messages && response.messages.length > 0) {
|
||||
let wasAtBottom = false;
|
||||
const logContainer = $('#log-container')[0];
|
||||
|
||||
// Check if user was at the bottom before adding new logs
|
||||
if (logContainer.scrollHeight > 0) {
|
||||
wasAtBottom = (logContainer.scrollTop + logContainer.clientHeight) >= (logContainer.scrollHeight - 10);
|
||||
}
|
||||
|
||||
// Append new messages
|
||||
response.messages.forEach(function(message) {
|
||||
self.displayLogMessage(message);
|
||||
});
|
||||
|
||||
// Update max_idx
|
||||
self.currentMaxIdx = response.max_idx || self.currentMaxIdx;
|
||||
|
||||
// Auto-scroll to bottom if user was already at bottom
|
||||
if (wasAtBottom) {
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}
|
||||
} else {
|
||||
// Update max_idx even if no new messages
|
||||
self.currentMaxIdx = response.max_idx || self.currentMaxIdx;
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error polling for new logs:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startPeriodicPolling() {
|
||||
// Clear any existing interval
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
}
|
||||
|
||||
// Start polling every second (1000ms)
|
||||
this.pollInterval = setInterval(this.pollForNewLogs.bind(this), 1000);
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
const self = this;
|
||||
const _shutdown = function () {
|
||||
console.log("Triggering shutdown");
|
||||
$.ajax({
|
||||
url: '/shutdown',
|
||||
type: "PUT",
|
||||
contentType: 'application/json',
|
||||
});
|
||||
self.$errorContainer.html('<div class="error-message">Shutting down ...</div>')
|
||||
setTimeout(function() {
|
||||
window.close();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// ask for confirmation using a dialog
|
||||
if (confirm("This will fully terminate the Serena server.")) {
|
||||
_shutdown();
|
||||
} else {
|
||||
console.log("Shutdown cancelled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Serena Dashboard</title>
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="serena-icon-16.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="serena-icon-32.png">
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="serena-icon-48.png">
|
||||
<script src="jquery.min.js"></script>
|
||||
<script src="dashboard.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
padding: 10px;
|
||||
white-space: pre-wrap;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #eaa45d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #dca662;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.log-debug {
|
||||
color: #808080; /* Gray */
|
||||
}
|
||||
.log-info {
|
||||
color: #000000; /* Black */
|
||||
}
|
||||
.log-warning {
|
||||
color: #FF8C00; /* Dark Orange */
|
||||
}
|
||||
.log-error {
|
||||
color: #FF0000; /* Red */
|
||||
}
|
||||
.log-default {
|
||||
color: #000000; /* Black */
|
||||
}
|
||||
|
||||
/* Tool name highlighting */
|
||||
.tool-name {
|
||||
background-color: #ffff00; /* Yellow background */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #FF0000;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<img src="serena-logs.png" alt="Serena" style="max-width: 400px; height: auto;">
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="load-logs" class="btn">Reload Log</button>
|
||||
<button id="shutdown" class="btn">Shutdown Server</button>
|
||||
</div>
|
||||
|
||||
<div id="error-container"></div>
|
||||
<div id="log-container" class="log-container"></div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const dashboard = new Dashboard();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Reference in New Issue
Block a user