mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 20:15:20 +00:00
Change dashboard to use Flask instead of fastapi
This commit is contained in:
1 parent
a2855bb8f9
commit
c3bb2e3d77
4 files changed
+87
-40
No files matched your search
+1
-1
@@ -23,7 +23,7 @@ dependencies = [
|
||||
"overrides>=7.7.0,<8",
|
||||
"python-dotenv>=1.0.0, <2",
|
||||
"mcp>=1.5.0",
|
||||
"fastapi>=0.115.12",
|
||||
"flask>=3.0.0",
|
||||
"sensai-utils>=1.4.0",
|
||||
"pydantic>=2.10.6",
|
||||
"types-pyyaml>=6.0.12.20241230",
|
||||
|
||||
+36
-13
@@ -4,9 +4,7 @@ import socket
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from flask import Flask, request, send_from_directory
|
||||
from pydantic import BaseModel
|
||||
from sensai.util import logging
|
||||
|
||||
@@ -74,7 +72,7 @@ class SerenaDashboardAPI:
|
||||
self._memory_log_handler = memory_log_handler
|
||||
self._tool_names = tool_names
|
||||
self._shutdown_callback = shutdown_callback
|
||||
self._app = FastAPI(title="Serena Dashboard")
|
||||
self._app = Flask(__name__)
|
||||
self._setup_routes()
|
||||
|
||||
@property
|
||||
@@ -82,21 +80,46 @@ class SerenaDashboardAPI:
|
||||
return self._memory_log_handler
|
||||
|
||||
def _setup_routes(self) -> None:
|
||||
self._app.mount("/dashboard", StaticFiles(directory=SERENA_DASHBOARD_DIR), name="dashboard")
|
||||
# Static files
|
||||
@self._app.route("/dashboard/<path:filename>")
|
||||
def serve_dashboard(filename):
|
||||
return send_from_directory(SERENA_DASHBOARD_DIR, filename)
|
||||
|
||||
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)
|
||||
self._app.add_api_route("/shutdown", self._shutdown, methods=["PUT"])
|
||||
@self._app.route("/dashboard/")
|
||||
def serve_dashboard_index():
|
||||
return send_from_directory(SERENA_DASHBOARD_DIR, "index.html")
|
||||
|
||||
async def _get_log_messages(self, request: RequestLog) -> ResponseLog:
|
||||
# API routes
|
||||
@self._app.route("/get_log_messages", methods=["POST"])
|
||||
def get_log_messages():
|
||||
request_data = request.get_json()
|
||||
if not request_data:
|
||||
request_log = RequestLog()
|
||||
else:
|
||||
request_log = RequestLog.model_validate(request_data)
|
||||
|
||||
result = self._get_log_messages(request_log)
|
||||
return result.model_dump()
|
||||
|
||||
@self._app.route("/get_tool_names", methods=["GET"])
|
||||
def get_tool_names():
|
||||
result = self._get_tool_names()
|
||||
return result.model_dump()
|
||||
|
||||
@self._app.route("/shutdown", methods=["PUT"])
|
||||
def shutdown():
|
||||
self._shutdown()
|
||||
return {"status": "shutting down"}
|
||||
|
||||
def _get_log_messages(self, request_log: RequestLog) -> ResponseLog:
|
||||
all_messages = self._memory_log_handler.get_log_messages()
|
||||
requested_messages = all_messages[request.start_idx :] if request.start_idx <= len(all_messages) else []
|
||||
requested_messages = all_messages[request_log.start_idx :] if request_log.start_idx <= len(all_messages) else []
|
||||
return ResponseLog(messages=requested_messages, max_idx=len(all_messages) - 1)
|
||||
|
||||
async def _get_tool_names(self) -> ResponseToolNames:
|
||||
def _get_tool_names(self) -> ResponseToolNames:
|
||||
return ResponseToolNames(tool_names=self._tool_names)
|
||||
|
||||
async def _shutdown(self) -> None:
|
||||
def _shutdown(self) -> None:
|
||||
log.info("Shutting down Serena")
|
||||
if self._shutdown_callback:
|
||||
self._shutdown_callback()
|
||||
@@ -125,7 +148,7 @@ class SerenaDashboardAPI:
|
||||
"""
|
||||
Runs the dashboard on the given host and port and returns the port number.
|
||||
"""
|
||||
uvicorn.run(self._app, host=host, port=port, workers=1, log_config=None, log_level="critical")
|
||||
self._app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
|
||||
return port
|
||||
|
||||
def run_in_thread(self) -> tuple[threading.Thread, int]:
|
||||
|
||||
@@ -3,6 +3,7 @@ import contextlib
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
import webbrowser
|
||||
from enum import StrEnum
|
||||
@@ -12,7 +13,6 @@ from multiprocessing.sharedctypes import Synchronized
|
||||
from multiprocessing.synchronize import Event as EventClass
|
||||
from typing import Any, Literal, Self
|
||||
|
||||
import uvicorn
|
||||
from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata
|
||||
|
||||
from serena.agent import SerenaAgent, SerenaConfig, SerenaConfigBase, Tool, ToolInterface, ToolRegistry
|
||||
@@ -79,30 +79,21 @@ def _dashboard_worker(
|
||||
port = api._find_first_free_port(0x5EDA)
|
||||
port_value.value = port
|
||||
|
||||
# Start server first, then log processing
|
||||
config = uvicorn.Config(
|
||||
app=api._app,
|
||||
host="0.0.0.0",
|
||||
port=port,
|
||||
workers=1,
|
||||
log_config=None,
|
||||
log_level="critical",
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
# Start Flask server in a thread
|
||||
def run_flask_server():
|
||||
api._app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False, threaded=True)
|
||||
|
||||
server_thread = threading.Thread(target=run_flask_server, daemon=True)
|
||||
server_thread.start()
|
||||
|
||||
# Serve and await shutdown signal
|
||||
server_task = asyncio.create_task(server.serve())
|
||||
shutdown_task = asyncio.create_task(_monitor_shutdown())
|
||||
logging_loop_task = asyncio.create_task(_process_logs(api))
|
||||
dashboard_ready_event.set()
|
||||
|
||||
done, pending = await asyncio.wait(
|
||||
[server_task, shutdown_task, logging_loop_task],
|
||||
[shutdown_task, logging_loop_task],
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
if shutdown_task in done:
|
||||
server.should_exit = True
|
||||
await server_task
|
||||
|
||||
# Cancel remaining tasks
|
||||
for task in pending:
|
||||
|
||||
@@ -101,6 +101,15 @@ jupyter = [
|
||||
{ name = "tokenize-rt" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.5.2"
|
||||
@@ -210,17 +219,20 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.12"
|
||||
name = "flask"
|
||||
version = "3.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "blinker" },
|
||||
{ name = "click" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "markupsafe" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -394,6 +406,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.2"
|
||||
@@ -959,7 +980,7 @@ source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "dotenv" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "flask" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "joblib" },
|
||||
{ name = "mcp" },
|
||||
@@ -1007,7 +1028,7 @@ requires-dist = [
|
||||
{ name = "black", extras = ["jupyter"], marker = "extra == 'dev'", specifier = ">=23.7.0" },
|
||||
{ name = "docstring-parser", specifier = ">=0.16" },
|
||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||
{ name = "fastapi", specifier = ">=0.115.12" },
|
||||
{ name = "flask", specifier = ">=3.0.0" },
|
||||
{ name = "google-genai", marker = "extra == 'google'", specifier = ">=1.8.0" },
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
{ name = "jinja2", marker = "extra == 'dev'" },
|
||||
@@ -1299,3 +1320,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 },
|
||||
]
|
||||
Reference in new issue
Block a user