feat: Add new API endpoints and HTML pages for ML model management

- Implemented HTML pages for datasets, models, training, testing, and results.
- Created API endpoints for managing repositories, results, tests, and training sessions.
- Added functionality for streaming training progress via Server-Sent Events (SSE).
- Introduced a Dockerfile for the ML runner with necessary dependencies.
- Developed an SDK for user code execution within the runner container.
- Enhanced CSS styles for improved UI layout and navigation.
- Established a layout template for consistent HTML structure across pages.
- Added JavaScript for dynamic interactions on the models page.
- Implemented WebSocket handling for real-time communication with kiosk devices and controllers.
- Implemented model registration and management API at /api/models
- Added Gitea proxy API for repository interactions at /api/repos
- Created results API for listing and comparing training results at /api/results
- Developed training management API for enqueueing and retrieving training jobs at /api/trainings
- Introduced SSE endpoint for live training progress updates
- Added HTML pages for models, datasets, and training management
- Created a Dockerfile for the ML runner with necessary dependencies
- Developed SDK for user code execution within the runner container
- Enhanced CSS styles for improved UI/UX
- Implemented WebSocket communication for real-time device and controller interactions in the kiosk system
This commit is contained in:
Giuseppe Raffa
2026-04-28 09:24:38 +02:00
parent ee478e52ef
commit 0ce879aa44
81 changed files with 7491 additions and 746 deletions

View File

@@ -0,0 +1,64 @@
"""SSE endpoint per live progress del training.
GET /api/trainings/{id}/events
Streamma eventi dal Redis stream `ml:train:{id}:events` via Server-Sent Events.
Termina quando lo stato del training è terminale (succeeded/failed/cancelled).
"""
from __future__ import annotations
import asyncio
import json
import uuid
from fastapi import APIRouter, Depends, HTTPException
from sse_starlette.sse import EventSourceResponse
from core import db, redis_client
from core.auth import require_auth
router = APIRouter(prefix="/api/trainings", tags=["trainings-sse"])
_TERMINAL = {"succeeded", "failed", "cancelled"}
@router.get("/{training_id}/events")
async def training_events(training_id: str, user=Depends(require_auth)):
# verifica esistenza
row = await db.fetchrow("SELECT status FROM trainings WHERE id = $1", uuid.UUID(training_id))
if not row:
raise HTTPException(404, "not found")
stream_key = f"ml:train:{training_id}:events"
status_key = f"ml:train:{training_id}"
async def gen():
last_id = "0-0"
r = redis_client.client()
while True:
try:
# XREAD block 5s per non tenere la connessione idle troppo a lungo
resp = await r.xread({stream_key: last_id}, count=50, block=5000)
except Exception as e:
yield {"event": "error", "data": json.dumps({"error": str(e)})}
await asyncio.sleep(1)
continue
if resp:
for _stream, entries in resp:
for entry_id, fields in entries:
last_id = entry_id
yield {"event": "message", "id": entry_id, "data": json.dumps(fields)}
# controlla stato terminale
state = await r.hget(status_key, "status")
if not state:
# fallback su db se redis scaduto
db_row = await db.fetchrow(
"SELECT status FROM trainings WHERE id = $1", uuid.UUID(training_id)
)
state = db_row["status"] if db_row else "unknown"
if state in _TERMINAL:
yield {"event": "end", "data": json.dumps({"status": state})}
return
return EventSourceResponse(gen())