feat(hub): dashboard SSE push, light-theme cards, simplify AI coach
Replace dashboard polling with backend SSE and snapshot refresh. Restyle for light/dark theme with soft card glow instead of neon. Remove Today's Summary from AI page; keep trading and general chat only. Update hub documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -90,6 +90,8 @@ from url_public import browser_url, default_review_url, public_origin
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from hub_board_cache import HUB_BOARD_POLL_INTERVAL, board_store
|
||||
from hub_dashboard_cache import dashboard_store
|
||||
from hub_dashboard import DASHBOARD_POLL_INTERVAL_SEC
|
||||
from hub_chart_cache import (
|
||||
HUB_CHART_POLL_INTERVAL,
|
||||
HUB_CHART_WATCH_TTL_SEC,
|
||||
@@ -271,6 +273,7 @@ async def _run_board_aggregate() -> dict:
|
||||
|
||||
def _schedule_board_refresh() -> None:
|
||||
board_store.request_refresh()
|
||||
dashboard_store.request_refresh()
|
||||
|
||||
|
||||
async def _run_archive_sync_once() -> dict:
|
||||
@@ -470,6 +473,7 @@ async def _archive_sync_loop() -> None:
|
||||
async def _hub_lifespan(_app: FastAPI):
|
||||
global _archive_sync_stop, _archive_sync_task, _volume_rank_stop, _volume_rank_task
|
||||
await board_store.start(_run_board_aggregate)
|
||||
await dashboard_store.start(_run_dashboard_aggregate)
|
||||
await chart_poll_store.start(_run_chart_poll)
|
||||
_archive_sync_stop = asyncio.Event()
|
||||
_archive_sync_task = asyncio.create_task(_archive_sync_loop(), name="hub-archive-sync")
|
||||
@@ -499,6 +503,7 @@ async def _hub_lifespan(_app: FastAPI):
|
||||
_volume_rank_task = None
|
||||
_volume_rank_stop = None
|
||||
await chart_poll_store.stop()
|
||||
await dashboard_store.stop()
|
||||
await board_store.stop()
|
||||
|
||||
|
||||
@@ -667,9 +672,26 @@ from hub_dashboard import build_dashboard_payload, default_trading_day
|
||||
app.include_router(create_hub_ai_router(load_all_exchanges=_all_exchanges_for_ai))
|
||||
|
||||
|
||||
async def _run_dashboard_aggregate() -> dict:
|
||||
try:
|
||||
return await asyncio.to_thread(
|
||||
build_dashboard_payload,
|
||||
_all_exchanges_for_ai(),
|
||||
trading_day=default_trading_day(),
|
||||
)
|
||||
except Exception as exc:
|
||||
return {"ok": False, "msg": str(exc), "error": "aggregate_failed"}
|
||||
|
||||
|
||||
def _schedule_dashboard_refresh() -> None:
|
||||
dashboard_store.request_refresh()
|
||||
|
||||
|
||||
@app.get("/api/dashboard/daily")
|
||||
def api_dashboard_daily(trading_day: str = ""):
|
||||
day = (trading_day or "").strip()[:10] or default_trading_day()
|
||||
if not (trading_day or "").strip():
|
||||
return dashboard_store.snapshot_dict()
|
||||
try:
|
||||
payload = build_dashboard_payload(
|
||||
_all_exchanges_for_ai(),
|
||||
@@ -677,7 +699,28 @@ def api_dashboard_daily(trading_day: str = ""):
|
||||
)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
return payload
|
||||
return {**payload, "dashboard_version": dashboard_store.version}
|
||||
|
||||
|
||||
@app.get("/api/dashboard/stream")
|
||||
async def api_dashboard_stream():
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
return StreamingResponse(
|
||||
dashboard_store.iter_sse(),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/dashboard/refresh")
|
||||
async def api_dashboard_refresh():
|
||||
_schedule_dashboard_refresh()
|
||||
return {"ok": True, "dashboard_version": dashboard_store.version}
|
||||
|
||||
|
||||
@app.get("/trade")
|
||||
@@ -2122,7 +2165,7 @@ def api_ping():
|
||||
"service": "manual-trading-hub",
|
||||
"build": HUB_BUILD,
|
||||
"trade_ui": False,
|
||||
"features": ["monitor", "settings", "auth", "board_sse", "archive", "dashboard", "funds"],
|
||||
"features": ["monitor", "settings", "auth", "board_sse", "dashboard_sse", "archive", "dashboard", "funds"],
|
||||
"board_poll_interval_sec": HUB_BOARD_POLL_INTERVAL,
|
||||
"board_version": board_store.version,
|
||||
"board_aggregating": board_store.aggregating,
|
||||
@@ -2130,6 +2173,13 @@ def api_ping():
|
||||
if isinstance(board_store.payload, dict)
|
||||
else None,
|
||||
"board_error": board_store.last_error,
|
||||
"dashboard_poll_interval_sec": DASHBOARD_POLL_INTERVAL_SEC,
|
||||
"dashboard_version": dashboard_store.version,
|
||||
"dashboard_aggregating": dashboard_store.aggregating,
|
||||
"dashboard_updated_at": (dashboard_store.payload or {}).get("updated_at")
|
||||
if isinstance(dashboard_store.payload, dict)
|
||||
else None,
|
||||
"dashboard_error": dashboard_store.last_error,
|
||||
"password_required": password_required(),
|
||||
"env_disabled_ids": sorted(env_force_disabled_ids()),
|
||||
"hub_disabled_ids_raw": (os.getenv("HUB_DISABLED_IDS") or ""),
|
||||
|
||||
Reference in New Issue
Block a user