from __future__ import annotations import threading from typing import Optional from fastapi import APIRouter, Request from fastapi import Body from pathlib import Path import json as _json from fastapi.responses import HTMLResponse, JSONResponse from ..app import templates from ..services.orchestrator import _ensure_setup_ready # type: ignore router = APIRouter(prefix="/setup") def _kickoff_setup_async(force: bool = False): """Start setup/tagging in a background thread. Previously we passed a no-op output function, which hid downstream steps (e.g., theme export). Using print provides visibility in container logs and helps diagnose export issues. """ def runner(): try: _ensure_setup_ready(print, force=force) # type: ignore[arg-type] except Exception as e: # pragma: no cover - background best effort try: print(f"Setup thread failed: {e}") except Exception: pass t = threading.Thread(target=runner, daemon=True) t.start() @router.get("/running", response_class=HTMLResponse) async def setup_running(request: Request, start: Optional[int] = 0, next: Optional[str] = None, force: Optional[bool] = None) -> HTMLResponse: # type: ignore[override] # Optionally start the setup/tagging in the background if requested try: if start and int(start) != 0: # honor optional force flag from query f = False try: if force is not None: f = bool(force) else: q_force = request.query_params.get('force') if q_force is not None: f = q_force.strip().lower() in {"1", "true", "yes", "on"} except Exception: f = False _kickoff_setup_async(force=f) except Exception: pass return templates.TemplateResponse("setup/running.html", {"request": request, "next_url": next}) @router.post("/start") async def setup_start(request: Request, force: bool = Body(False)): # accept JSON body {"force": true} try: # Allow query string override as well (?force=1) try: q_force = request.query_params.get('force') if q_force is not None: force = q_force.strip().lower() in {"1", "true", "yes", "on"} except Exception: pass # Write immediate status so UI reflects the start try: p = Path("csv_files") p.mkdir(parents=True, exist_ok=True) status = {"running": True, "phase": "setup", "message": "Starting setup/tagging...", "color": None} with (p / ".setup_status.json").open('w', encoding='utf-8') as f: _json.dump(status, f) except Exception: pass _kickoff_setup_async(force=bool(force)) return JSONResponse({"ok": True, "started": True, "force": bool(force)}, status_code=202) except Exception: return JSONResponse({"ok": False}, status_code=500) @router.get("/start") async def setup_start_get(request: Request): """GET alias to start setup/tagging via query string (?force=1). Useful as a fallback from clients that cannot POST JSON. """ try: # Determine force from query params force = False try: q_force = request.query_params.get('force') if q_force is not None: force = q_force.strip().lower() in {"1", "true", "yes", "on"} except Exception: pass # Write immediate status so UI reflects the start try: p = Path("csv_files") p.mkdir(parents=True, exist_ok=True) status = {"running": True, "phase": "setup", "message": "Starting setup/tagging...", "color": None} with (p / ".setup_status.json").open('w', encoding='utf-8') as f: _json.dump(status, f) except Exception: pass _kickoff_setup_async(force=bool(force)) return JSONResponse({"ok": True, "started": True, "force": bool(force)}, status_code=202) except Exception: return JSONResponse({"ok": False}, status_code=500) @router.get("/", response_class=HTMLResponse) async def setup_index(request: Request) -> HTMLResponse: return templates.TemplateResponse("setup/index.html", {"request": request})