Spaces:
Runtime error
Runtime error
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Any, Literal | |
| from fastapi import HTTPException | |
| from fastapi.responses import FileResponse, HTMLResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from gradio import Server | |
| from pydantic import BaseModel, ConfigDict, Field | |
| from caro5_bot import BotRequest, select_move | |
| from caro5_bot.tactical import BotError, status as bot_status | |
| ROOT = Path(__file__).resolve().parent | |
| DIST_DIR = ROOT / "client" / "dist" | |
| app = Server() | |
| class BotMoveRequest(BaseModel): | |
| model_config = ConfigDict(populate_by_name=True) | |
| board: dict[str, dict[str, Any]] = Field(default_factory=dict) | |
| current_player: int = Field(alias="currentPlayer") | |
| player_symbols: tuple[str, str] = Field(default=("X", "O"), alias="playerSymbols") | |
| rules: dict[str, Any] = Field(default_factory=dict) | |
| board_size: int | None = Field(default=None, alias="boardSize") | |
| profile: Literal["normal", "expert", "ai", "balanced"] = "normal" | |
| move_number: int | None = Field(default=None, alias="moveNumber") | |
| seed: int | None = None | |
| def health() -> dict[str, str]: | |
| return {"status": "ok"} | |
| async def healthz() -> dict[str, str]: | |
| return {"status": "ok"} | |
| async def api_bot_status() -> dict[str, Any]: | |
| status = bot_status() | |
| return { | |
| "available": status.available, | |
| "engine": status.engine, | |
| "profiles": list(status.profiles), | |
| "defaultProfile": status.default_profile, | |
| "modelAvailable": status.model_available, | |
| "modelPath": status.model_path, | |
| "metadataPath": status.metadata_path, | |
| "modelError": status.model_error, | |
| "metadata": status.metadata, | |
| } | |
| async def api_bot_move(request: BotMoveRequest) -> dict[str, Any]: | |
| try: | |
| response = select_move( | |
| BotRequest( | |
| board=request.board, | |
| current_player=request.current_player, | |
| player_symbols=request.player_symbols, | |
| rules=request.rules, | |
| board_size=request.board_size, | |
| profile=request.profile, | |
| move_number=request.move_number, | |
| seed=request.seed, | |
| ) | |
| ) | |
| except BotError as exc: | |
| raise HTTPException(status_code=400, detail=str(exc)) from exc | |
| return { | |
| "x": response.x, | |
| "y": response.y, | |
| "move": {"x": response.x, "y": response.y}, | |
| "source": response.source, | |
| "profile": response.profile, | |
| "elapsedMs": response.elapsed_ms, | |
| "seed": response.seed, | |
| "legal": response.legal, | |
| "modelAvailable": response.model_available, | |
| } | |
| def static_file(name: str) -> FileResponse: | |
| path = DIST_DIR / name | |
| if path.exists() and path.is_file(): | |
| return FileResponse(path) | |
| raise HTTPException(status_code=404) | |
| for route_name in ("assets", "sounds"): | |
| route_dir = DIST_DIR / route_name | |
| if route_dir.exists(): | |
| app.mount(f"/{route_name}", StaticFiles(directory=route_dir), name=route_name) | |
| async def favicon(): | |
| return static_file("favicon.svg") | |
| async def icons(): | |
| return static_file("icons.svg") | |
| async def vite_favicon_alias(): | |
| return static_file("favicon.svg") | |
| async def root(): | |
| if (DIST_DIR / "index.html").exists(): | |
| return FileResponse(DIST_DIR / "index.html") | |
| return missing_build_response() | |
| async def react_app(path: str): | |
| index = DIST_DIR / "index.html" | |
| if index.exists(): | |
| return FileResponse(index) | |
| return missing_build_response() | |
| def missing_build_response() -> HTMLResponse: | |
| return HTMLResponse( | |
| """ | |
| <main style="font-family: system-ui, sans-serif; max-width: 720px; margin: 48px auto; line-height: 1.5"> | |
| <h1>Caro5 build missing</h1> | |
| <p>The React bundle was not found at <code>client/dist</code>.</p> | |
| <p>Copy or build the web bundle before launching this Space.</p> | |
| </main> | |
| """, | |
| status_code=503, | |
| ) | |
| if __name__ == "__main__": | |
| app.launch(server_name="0.0.0.0", server_port=7860) | |