mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 08:15:59 +00:00
Merge branch 'feature/web-server-logs' of github.com:oraios/serena into feature/web-server-logs
This commit is contained in:
commit
e3815ddfbd
3 files changed
+198
-210
No files matched your search
@@ -0,0 +1,192 @@
|
||||
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.$clearButton = $('#clear-logs');
|
||||
|
||||
// register event handlers
|
||||
this.$loadButton.click(this.loadLogs.bind(this));
|
||||
this.$clearButton.click(this.clearLogs.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:', toolNames);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading tool names:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadLogs() {
|
||||
console.log("load", this);
|
||||
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('Load Logs');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pollForNewLogs() {
|
||||
let self = this;
|
||||
console.log("poll", this);
|
||||
$.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);
|
||||
}
|
||||
|
||||
stopPeriodicPolling() {
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
this.pollInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
clearLogs() {
|
||||
this.stopPeriodicPolling();
|
||||
this.$logContainer.empty();
|
||||
this.$errorContainer.empty();
|
||||
this.currentMaxIdx = -1;
|
||||
}
|
||||
}
|
||||
+4
-210
@@ -3,8 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Serena Log Viewer</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
<title>Serena Dashboard</title>
|
||||
<script src="jquery.min.js"></script>
|
||||
<script src="dashboard.js"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
@@ -18,11 +19,6 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
@@ -65,23 +61,18 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Log level colors matching the GUI viewer */
|
||||
.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 */
|
||||
}
|
||||
@@ -120,204 +111,7 @@
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Variables to track state
|
||||
let toolNames = [];
|
||||
let currentMaxIdx = -1;
|
||||
let pollInterval = null;
|
||||
|
||||
// Determine log level from message
|
||||
function determineLogLevel(message) {
|
||||
const messageUpper = message.toUpperCase();
|
||||
if (messageUpper.startsWith('DEBUG')) {
|
||||
return 'debug';
|
||||
} else if (messageUpper.startsWith('INFO')) {
|
||||
return 'info';
|
||||
} else if (messageUpper.startsWith('WARNING')) {
|
||||
return 'warning';
|
||||
} else if (messageUpper.startsWith('ERROR')) {
|
||||
return 'error';
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight tool names in a message
|
||||
function highlightToolNames(message) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Format and display a log message
|
||||
function displayLogMessage(message) {
|
||||
const logLevel = determineLogLevel(message);
|
||||
const highlightedMessage = highlightToolNames(message);
|
||||
const logEntry = $('<div>').addClass('log-' + logLevel).html(highlightedMessage + '\n');
|
||||
$('#log-container').append(logEntry);
|
||||
}
|
||||
|
||||
// Load tool names from the API
|
||||
function loadToolNames() {
|
||||
return $.ajax({
|
||||
url: '/get_tool_names',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
toolNames = response.tool_names || [];
|
||||
console.log('Loaded tool names:', toolNames);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading tool names:', error);
|
||||
// Use fallback tool names if API fails
|
||||
toolNames = [
|
||||
'find_symbol', 'read_file', 'create_text_file', 'replace_symbol_body',
|
||||
'insert_after_symbol', 'insert_before_symbol', 'search_for_pattern',
|
||||
'execute_shell_command', 'get_symbols_overview', 'find_referencing_symbols'
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load logs from the API (full reload)
|
||||
function loadLogs() {
|
||||
const $loadBtn = $('#load-logs');
|
||||
const $errorContainer = $('#error-container');
|
||||
|
||||
// Disable button and show loading state
|
||||
$loadBtn.prop('disabled', true).text('Loading...');
|
||||
$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
|
||||
$('#log-container').empty();
|
||||
|
||||
// Update max_idx
|
||||
currentMaxIdx = response.max_idx || -1;
|
||||
|
||||
// Display each log message
|
||||
if (response.messages && response.messages.length > 0) {
|
||||
response.messages.forEach(function(message) {
|
||||
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
|
||||
startPeriodicPolling();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error loading logs:', error);
|
||||
$errorContainer.html('<div class="error-message">Error loading logs: ' +
|
||||
(xhr.responseJSON ? xhr.responseJSON.detail : error) + '</div>');
|
||||
},
|
||||
complete: function() {
|
||||
// Re-enable button
|
||||
$loadBtn.prop('disabled', false).text('Load Logs');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Poll for new log messages
|
||||
function pollForNewLogs() {
|
||||
console.log("poll");
|
||||
$.ajax({
|
||||
url: '/get_log_messages',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
start_idx: 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) {
|
||||
displayLogMessage(message);
|
||||
});
|
||||
|
||||
// Update max_idx
|
||||
currentMaxIdx = response.max_idx || 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
|
||||
currentMaxIdx = response.max_idx || currentMaxIdx;
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Error polling for new logs:', error);
|
||||
// Continue polling even on error (API might be temporarily unavailable)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start periodic polling
|
||||
function startPeriodicPolling() {
|
||||
// Clear any existing interval
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
}
|
||||
|
||||
// Start polling every second (1000ms)
|
||||
pollInterval = setInterval(pollForNewLogs, 1000);
|
||||
}
|
||||
|
||||
// Stop periodic polling
|
||||
function stopPeriodicPolling() {
|
||||
if (pollInterval) {
|
||||
clearInterval(pollInterval);
|
||||
pollInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the log display
|
||||
function clearLogs() {
|
||||
stopPeriodicPolling();
|
||||
$('#log-container').empty();
|
||||
$('#error-container').empty();
|
||||
currentMaxIdx = -1;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
$('#load-logs').click(loadLogs);
|
||||
$('#clear-logs').click(clearLogs);
|
||||
|
||||
// Initialize the application
|
||||
loadToolNames().then(function() {
|
||||
// Load logs on page load after tool names are loaded
|
||||
loadLogs();
|
||||
});
|
||||
|
||||
// Stop polling when page is about to unload
|
||||
$(window).on('beforeunload', function() {
|
||||
stopPeriodicPolling();
|
||||
});
|
||||
const dashboard = new Dashboard();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
|
||||
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}function fe(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}ce.fn=ce.prototype={jquery:t,constructor:ce,length:0,toArray:function(){return ae.call(this)},get:function(e){return null==e?ae.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=ce.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return ce.each(this,e)},map:function(n){return this.pushStack(ce.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(ae.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(ce.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(ce.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:oe.sort,splice:oe.splice},ce.extend=ce.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||v(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(ce.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||ce.isPlainObject(n)?n:{},i=!1,a[t]=ce.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},ce.extend({expando:"jQuery"+(t+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==i.call(e))&&(!(t=r(e))||"function"==typeof(n=ue.call(t,"constructor")&&t.constructor)&&o.call(n)===a)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){m(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(c(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},text:function(e){var t,n="",r=0,i=e.nodeType;if(!i)while(t=e[r++])n+=ce.text(t);return 1===i||11===i?e.textContent:9===i?e.documentElement.textContent:3===i||4===i?e.nodeValue:n},makeArray:function(e,t){var n=t||[];return null!=e&&(c(Object(e))?ce.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},isXMLDoc:function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!l.test(t||n&&n.nodeName||"HTML")},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(c(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:le}),"function"==typeof Symbol&&(ce.fn[Symbol.iterator]=oe[Symbol.iterator]),ce.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var pe=oe.pop,de=oe.sort,he=oe.splice,ge="[\\x20\\t\\r\\n\\f]",ve=new RegExp("^"+ge+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ge+"+$","g");ce.contains=function(e,t){var n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(e.contains?e.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))};var f=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function p(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}ce.escapeSelector=function(e){return(e+"").replace(f,p)};var ye=C,me=s;!function(){var e,b,w,o,a,T,r,C,d,i,k=me,S=ce.expando,E=0,n=0,s=W(),c=W(),u=W(),h=W(),l=function(e,t){return e===t&&(a=!0),0},f="checked|selected|async|autofocus|autoplay|conLine truncated
|
||||
Reference in new issue
Block a user