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:
89
ml/routers/results.py
Normal file
89
ml/routers/results.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""API /api/results — lista trainings/tests + compare multi-training."""
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
||||
from core import db, influx_client
|
||||
from core.auth import require_auth
|
||||
from core.config import settings
|
||||
|
||||
router = APIRouter(prefix="/api/results", tags=["results"])
|
||||
|
||||
|
||||
def _row(r):
|
||||
if r is None:
|
||||
return None
|
||||
d = dict(r)
|
||||
for k in ("queued_at", "started_at", "finished_at", "started_at", "ended_at"):
|
||||
if d.get(k) is not None and hasattr(d[k], "isoformat"):
|
||||
d[k] = d[k].isoformat()
|
||||
return d
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_results(
|
||||
model_id: Optional[str] = Query(None),
|
||||
user=Depends(require_auth),
|
||||
):
|
||||
where = []
|
||||
args: list = []
|
||||
if model_id:
|
||||
args.append(uuid.UUID(model_id))
|
||||
where.append(f"model_id = ${len(args)}")
|
||||
sql = "SELECT * FROM trainings"
|
||||
if where:
|
||||
sql += " WHERE " + " AND ".join(where)
|
||||
sql += " ORDER BY finished_at DESC NULLS LAST, queued_at DESC LIMIT 200"
|
||||
rows = await db.fetch(sql, *args)
|
||||
return {"count": len(rows), "trainings": [_row(r) for r in rows]}
|
||||
|
||||
|
||||
@router.get("/{training_id}")
|
||||
async def get_result(training_id: str, user=Depends(require_auth)):
|
||||
row = await db.fetchrow("SELECT * FROM trainings WHERE id = $1", uuid.UUID(training_id))
|
||||
if not row:
|
||||
raise HTTPException(404, "not found")
|
||||
# timeseries via Influx: loss per iter + cpu/mem
|
||||
flux = (
|
||||
f'from(bucket:"{settings.influx_bucket}") '
|
||||
f'|> range(start:-90d) '
|
||||
f'|> filter(fn: (r) => r._measurement == "ml_training" and r.training_id == "{training_id}")'
|
||||
)
|
||||
try:
|
||||
ts = await influx_client.query_flux(flux)
|
||||
except Exception:
|
||||
ts = []
|
||||
return {"training": _row(row), "timeseries": ts}
|
||||
|
||||
|
||||
@router.get("/compare")
|
||||
async def compare(
|
||||
trainings: str = Query(..., description="comma-separated training IDs"),
|
||||
user=Depends(require_auth),
|
||||
):
|
||||
ids = [s.strip() for s in trainings.split(",") if s.strip()]
|
||||
if len(ids) < 2:
|
||||
raise HTTPException(400, "at least 2 training IDs required")
|
||||
out = []
|
||||
for tid in ids:
|
||||
try:
|
||||
tid_uuid = uuid.UUID(tid)
|
||||
except ValueError:
|
||||
continue
|
||||
row = await db.fetchrow("SELECT * FROM trainings WHERE id = $1", tid_uuid)
|
||||
if not row:
|
||||
continue
|
||||
flux = (
|
||||
f'from(bucket:"{settings.influx_bucket}") '
|
||||
f'|> range(start:-90d) '
|
||||
f'|> filter(fn: (r) => r._measurement == "ml_training" and r.training_id == "{tid}")'
|
||||
)
|
||||
try:
|
||||
ts = await influx_client.query_flux(flux)
|
||||
except Exception:
|
||||
ts = []
|
||||
out.append({"training": _row(row), "timeseries": ts})
|
||||
return {"results": out}
|
||||
Reference in New Issue
Block a user