3679465956
Lokaler KI-Assistent auf dem Bosgame: Text + Sprache, Tool Calling, WebSocket-Streaming. Steuerzentrale des Agentic OS. - hermes_agent.py: ReAct-Agent mit Tool-Set (read_file, list_dir, run_command, system_status, memory r/w, web_search) - routers/hermes.py: WS /chat, POST /transcribe (Whisper), POST /tts (Piper), GET /status, GET /pubkey - HermesPanel.svelte: Chat-UI mit Token-Streaming, Tool-Anzeige, Mikrofon-Button (MediaRecorder), Setup-Wizard (Windows SSH) - Modell-Routing: scout fuer einfache Tasks, coder fuer komplexe - HERMES_* Env-Vars in config.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
"""
|
|
Mission Control - eine schlanke Steuerzentrale fuer einen lokalen llama-swap Stack.
|
|
|
|
Dieser Einstieg haelt nur noch das Geruest zusammen: er baut die FastAPI-App,
|
|
haengt die Router ein und liefert das statische UI aus. Die eigentliche Logik
|
|
liegt nach Concern getrennt in:
|
|
- config.py Env-Vars / Konstanten
|
|
- auth.py optionale Token-Auth
|
|
- jobengine.py Hintergrund-Jobs mit Live-Log
|
|
- llamaswap.py Reden mit llama-swap + config.yaml lesen/schreiben
|
|
- routers/* ein Router je Bereich (models, jobs, maintenance, ...)
|
|
|
|
Bewusst KISS: kein Build-Schritt, kein Framework ueber FastAPI hinaus, keine DB.
|
|
Neue Bereiche kommen als routers/<bereich>.py + static/js/panels/<bereich>.js dazu.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.responses import FileResponse, JSONResponse, Response
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from routers import jobs, hermes, maintenance, memory, models, system, cookbook, integration, news
|
|
|
|
app = FastAPI(title="Mission Control")
|
|
|
|
|
|
@app.middleware("http")
|
|
async def _no_cache_static(request, call_next):
|
|
"""UI + statische Module immer revalidieren lassen (304 wenn unveraendert),
|
|
damit Aenderungen nach einem rsync sofort wirken und kein Stale-JS haengen bleibt."""
|
|
response = await call_next(request)
|
|
path = request.url.path
|
|
if path == "/" or path.startswith("/static"):
|
|
response.headers["Cache-Control"] = "no-cache"
|
|
return response
|
|
|
|
|
|
app.include_router(models.router)
|
|
app.include_router(jobs.router)
|
|
app.include_router(maintenance.router)
|
|
app.include_router(system.router)
|
|
app.include_router(cookbook.router)
|
|
app.include_router(integration.router)
|
|
app.include_router(news.router)
|
|
app.include_router(memory.router)
|
|
app.include_router(hermes.router)
|
|
|
|
_STATIC = Path(__file__).parent / "static"
|
|
|
|
|
|
@app.get("/")
|
|
def index():
|
|
return FileResponse(_STATIC / "index.html")
|
|
|
|
|
|
_FAVICON = (
|
|
b"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'>"
|
|
b"<rect width='32' height='32' rx='7' fill='#0f1720'/>"
|
|
b"<circle cx='16' cy='16' r='8' fill='none' stroke='#2dd4bf' stroke-width='3'/>"
|
|
b"<circle cx='16' cy='16' r='2.5' fill='#2dd4bf'/></svg>"
|
|
)
|
|
|
|
|
|
@app.get("/favicon.ico")
|
|
def favicon():
|
|
return Response(content=_FAVICON, media_type="image/svg+xml")
|
|
|
|
|
|
app.mount("/static", StaticFiles(directory=_STATIC), name="static")
|
|
|
|
|
|
@app.exception_handler(HTTPException)
|
|
def _http_exc(_req, exc: HTTPException):
|
|
return JSONResponse(status_code=exc.status_code, content={"error": exc.detail})
|
|
|
|
|
|
@app.exception_handler(Exception)
|
|
def _any_exc(_req, exc: Exception):
|
|
"""Unerwartete Fehler als lesbare Meldung ans (vertrauenswuerdige LAN-)UI geben,
|
|
statt nur einen generischen 500 ohne Hinweis. Erleichtert Anfaengern die Diagnose."""
|
|
return JSONResponse(status_code=500, content={"error": str(exc) or exc.__class__.__name__})
|