from __future__ import annotations import logging from fastapi import APIRouter, File, HTTPException, UploadFile from pydantic import BaseModel, Field from app.services.yolo_detector import get_detector, reload_detector, yolo_available from app.services.yolo_manager import get_yolo_manager logger = logging.getLogger(__name__) router = APIRouter(prefix="/yolo", tags=["yolo"]) class ActivateRequest(BaseModel): name: str = Field(..., description="权重文件名") demo_mode: str | None = Field(default=None, description="demo|real|auto") class DemoModeRequest(BaseModel): demo_mode: str = Field(..., description="demo|real|auto") @router.get("/status") def yolo_status() -> dict: mgr = get_yolo_manager() det = get_detector() return mgr.status(det.info()) @router.get("/weights") def list_weights() -> dict: mgr = get_yolo_manager() return {"items": mgr.list_weights(), "count": len(mgr.list_weights())} @router.post("/weights/upload") async def upload_weight(file: UploadFile = File(...)) -> dict: if not file.filename: raise HTTPException(400, "缺少文件名") raw = await file.read() try: item = get_yolo_manager().save_upload(file.filename, raw) return {"ok": True, "weight": item} except ValueError as e: raise HTTPException(400, str(e)) from e except Exception as e: logger.exception("上传权重失败") raise HTTPException(500, f"上传失败: {e}") from e @router.post("/weights/activate") def activate_weight(body: ActivateRequest) -> dict: try: status = get_yolo_manager().activate(body.name, body.demo_mode) info = reload_detector() status.update(info) status["ok"] = True return status except FileNotFoundError as e: raise HTTPException(404, str(e)) from e except ValueError as e: raise HTTPException(400, str(e)) from e @router.post("/weights/deactivate") def deactivate_weight() -> dict: status = get_yolo_manager().deactivate() info = reload_detector() status.update(info) status["ok"] = True return status @router.delete("/weights/{name}") def delete_weight(name: str) -> dict: """逻辑删除:隐藏权重,本地 .pt 文件仍保留在 data/weights。""" try: get_yolo_manager().delete_weight(name) reload_detector() return { "ok": True, "name": name, "soft_delete": True, "message": "已逻辑删除(本地文件保留,仅从列表隐藏)", } except FileNotFoundError as e: raise HTTPException(404, str(e)) from e except ValueError as e: raise HTTPException(400, str(e)) from e @router.post("/weights/restore") def restore_weight(body: ActivateRequest) -> dict: """恢复逻辑删除的权重。""" try: status = get_yolo_manager().restore_weight(body.name) info = reload_detector() status.update(info) status["ok"] = True return status except FileNotFoundError as e: raise HTTPException(404, str(e)) from e @router.post("/mode") def set_mode(body: DemoModeRequest) -> dict: try: status = get_yolo_manager().set_demo_mode(body.demo_mode) info = reload_detector() status.update(info) status["ok"] = True return status except ValueError as e: raise HTTPException(400, str(e)) from e @router.get("/visualization") def visualization() -> dict: return get_yolo_manager().visualization() @router.post("/stats/reset") def reset_stats() -> dict: get_yolo_manager().reset_stats() return {"ok": True} @router.get("/capability") def capability() -> dict: return { "ultralytics": yolo_available(), "detector": get_detector().info(), "manager": get_yolo_manager().status(), }