""" Pure HTML investigation dashboard: persistent env + JSON API. OpenEnv's HTTP POST /reset and /step spin up a new environment each call, so this UI uses a singleton AdFraudEnvironment for multi-step episodes. Does not replace /ws or competition endpoints. """ from __future__ import annotations from pathlib import Path from typing import Any, Dict, Optional from fastapi import Body, FastAPI, HTTPException from fastapi.responses import FileResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from openenv.core.env_server import serialize_observation from pydantic import BaseModel, Field try: from ..models import AdReviewAction from .environment import AdFraudEnvironment except ImportError: from models import AdReviewAction from server.environment import AdFraudEnvironment _ui_env: Optional[AdFraudEnvironment] = None def _get_ui_env() -> AdFraudEnvironment: global _ui_env if _ui_env is None: _ui_env = AdFraudEnvironment() return _ui_env class UIResetBody(BaseModel): task_id: str = Field(default="task_1") seed: int = Field(default=42, ge=0) def register_investigate_ui(app: FastAPI) -> None: static_dir = Path(__file__).resolve().parent / "static" @app.get("/", include_in_schema=False) async def root_to_investigate() -> RedirectResponse: return RedirectResponse(url="/investigate", status_code=302) @app.get("/web", include_in_schema=False) async def web_to_investigate() -> RedirectResponse: return RedirectResponse(url="/investigate", status_code=302) app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") @app.get("/investigate", include_in_schema=False) async def investigate_page() -> FileResponse: path = static_dir / "investigate_hq.html" if not path.is_file(): raise HTTPException(status_code=404, detail="investigate_hq.html missing") return FileResponse(path, media_type="text/html; charset=utf-8") @app.post("/investigate/api/reset", tags=["Investigation UI"]) async def investigate_api_reset(body: UIResetBody) -> Dict[str, Any]: env = _get_ui_env() obs = env.reset(task_id=body.task_id, seed=body.seed) return serialize_observation(obs) @app.post("/investigate/api/step", tags=["Investigation UI"]) async def investigate_api_step(body: Dict[str, Any] = Body(...)) -> Dict[str, Any]: try: action = AdReviewAction(**body) except Exception as e: raise HTTPException(status_code=422, detail=str(e)) from e env = _get_ui_env() obs = env.step(action) return serialize_observation(obs) @app.get("/investigate/api/state", tags=["Investigation UI"]) async def investigate_api_state() -> Dict[str, Any]: return _get_ui_env().state.model_dump()