Version 1.2.2. Added usage overview. Shows token used and cost in $.

This commit is contained in:
2026-04-15 10:00:39 +02:00
parent 752691fe54
commit d4c6420481
18 changed files with 1657 additions and 86 deletions

View File

@@ -2,9 +2,38 @@
main.py — FastAPI application entry point.
Provides:
- HTML pages: /, /agents, /audit, /settings, /login, /setup, /admin/users
- WebSocket: /ws/{session_id} (streaming agent responses)
- REST API: /api/*
- HTML pages: /, /chats, /agents, /models, /audit, /monitors, /files,
/settings, /login, /setup, /admin/users, /help
- WebSocket: /ws/{session_id} (streaming agent responses)
- REST API: /api/* (see server/web/routes.py)
- Brain MCP: /brain-mcp/sse (MCP protocol for 2nd Brain access)
Startup order (lifespan):
1. init_db() — run PostgreSQL migrations, create pool
2. _refresh_brand_globals() — load brand name/logo from DB into Jinja2 globals
3. _ensure_session_secret() — auto-generate HMAC signing secret if not set
4. check user_count() — set _needs_setup flag (redirects to /setup if 0 users)
5. cleanup_stale_runs() — mark any interrupted "running" agent_runs as "error"
6. init_brain_db() — connect to brain PostgreSQL (pgvector)
7. build_registry() — create ToolRegistry with all production tools
8. discover_and_register_mcp_tools() — connect to configured MCP servers and add their tools
9. Agent(registry=...) — create the singleton agent loop
10. agent_runner.init/start() — load agent cron schedules into APScheduler, start scheduler
11. page_monitor / rss_monitor — wire into shared APScheduler, load schedules
12. _migrate_email_accounts() — one-time migration of old inbox:* credentials
13. inbox_listener.start_all() — start IMAP listeners for all email accounts
14. telegram_listener.start() — start Telegram long-polling listener
15. _session_manager.run() — start Brain MCP session manager
Key singletons (module-level, shared across all requests):
_agent: Agent instance — owns in-memory session history
_registry: ToolRegistry — the set of available tools
Both are initialised in lifespan() and referenced by the WebSocket handler.
Auth middleware (_AuthMiddleware):
Runs on every request before route handlers. Validates the session cookie or
API key, stores the resolved CurrentUser in the current_user ContextVar.
Routes use _require_auth() / _require_admin() helpers to enforce access control.
"""
from __future__ import annotations
@@ -716,6 +745,22 @@ async def setup_post(request: Request):
# ── HTML pages ────────────────────────────────────────────────────────────────
async def _user_can_view_usage(user) -> bool:
"""Admins always; non-admins only if they have their own API key and aren't on admin keys."""
if not user:
return False
if user.is_admin:
return True
from .database import user_settings_store
if await user_settings_store.get(user.id, "use_admin_keys"):
return False
return bool(
await user_settings_store.get(user.id, "anthropic_api_key") or
await user_settings_store.get(user.id, "openrouter_api_key") or
await user_settings_store.get(user.id, "openai_api_key")
)
async def _ctx(request: Request, **extra):
"""Build template context with current_user and active theme CSS injected."""
from .web.themes import get_theme_css, DEFAULT_THEME
@@ -734,6 +779,7 @@ async def _ctx(request: Request, **extra):
"current_user": user,
"theme_css": theme_css,
"needs_personality_setup": needs_personality_setup,
"can_view_usage": await _user_can_view_usage(user),
**extra,
}
@@ -765,6 +811,15 @@ async def models_page(request: Request):
return templates.TemplateResponse("models.html", await _ctx(request))
@app.get("/usage", response_class=HTMLResponse)
async def usage_page(request: Request):
user = _get_current_user(request)
if not await _user_can_view_usage(user):
from fastapi.responses import RedirectResponse
return RedirectResponse("/", status_code=303)
return templates.TemplateResponse("usage.html", await _ctx(request))
@app.get("/audit", response_class=HTMLResponse)
async def audit_page(request: Request):
return templates.TemplateResponse("audit.html", await _ctx(request))