# PhysiX-Live demo Space — CPU-only env + UI. # # What this Space hosts: # # :7860 uvicorn _space_app:app # ├─ /reset, /step (OpenEnv stateless API) # ├─ /interactive/* (browser session API) # ├─ /web/ (built React SPA) # └─ /interactive/.../llm-step (LLM-driven episode) # # What this Space does NOT host: # * Inference. The demo is CPU-only — no torch, no vLLM, no GPU. When # the UI calls `/interactive/.../llm-step` the server forwards to # whatever OpenAI-compatible base URL the browser handed us # (HF Router, OpenAI, Ollama, or our sister L4 Space at # `Pratyush-01/physix-infer` for the trained 3B + Qwen baseline). # # Why a separate inference Space: # Keeps this CPU image tiny (sub-second cold-start) so the demo URL # never feels like it's stalled. The L4 Space pays GPU rates only # while it's actually serving requests — its `sleep_time=300s` shuts # it down between sessions. Two Spaces, two failure surfaces; if # inference is broken the verifier-only demo (Custom URL → Ollama # etc.) still works. ############################ # Stage 1: build the SPA ############################ # WORKDIR renamed (was /build) to break HF BuildKit's poisoned cache. # The previous /build mount kept a stale pnpm symlink at # /build/node_modules/@types/katex # from an earlier failed deploy, and every subsequent `COPY frontend/ ./` # blew up with `cannot copy to non-directory`. Switching paths gets us # a fresh cache bucket; nothing in the project depends on /build itself. FROM node:20-alpine AS frontend WORKDIR /spa RUN corepack enable # Copy ALL of frontend/ first — including package.json/pnpm-lock.yaml — # THEN install. Order matters: install runs ON TOP OF the source tree # instead of the source tree being overlaid on top of a pre-installed # node_modules, eliminating the directory-vs-symlink collision class # of failure entirely. COPY frontend/ ./ # Same-origin API fetches (relative paths). The Space serves both API and UI. ENV VITE_PHYSIX_API_URL="" # Cache-bust marker. Bump when an SPA change isn't taking on the Space. # physix-spa-rebuild: 9 RUN pnpm install --frozen-lockfile \ && pnpm exec tsc -b \ && pnpm exec vite build --base=/web/ ############################ # Stage 2: runtime (FastAPI + SPA) ############################ FROM python:3.11-slim AS runtime ENV PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ HOME=/tmp/home \ HF_HOME=/tmp/hf_cache \ XDG_CACHE_HOME=/tmp/xdg-cache \ PORT=7860 \ PHYSIX_HOST=0.0.0.0 \ PHYSIX_CORS_ORIGINS=* # curl for healthchecks; the slim image has neither curl nor build tools # by default. Everything else (numpy, scipy, sympy) is a wheel install. RUN apt-get update \ && apt-get install -y --no-install-recommends curl \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # Pin the server-side runtime stack. NO torch / unsloth / trl here — # this Space never trains and never runs a model locally. RUN pip install \ "openenv-core[core]>=0.2.2" \ "numpy>=1.24" \ "scipy>=1.10" \ "sympy>=1.12" \ "fastapi>=0.110" \ "uvicorn[standard]>=0.29" \ "pydantic>=2.5" \ "openai>=1.40" \ "requests>=2.31" COPY pyproject.toml ./ COPY physix ./physix COPY README.md ./ RUN pip install --no-deps -e . # Built SPA from stage 1. COPY --from=frontend /spa/dist /app/static # Space wrapper — mounts the React SPA at /web/, registers / -> /web/ # redirect (OpenEnv's create_fastapi_app doesn't add one for us). COPY scripts/space_app.py /app/_space_app.py # Pre-create writable dirs. HF Spaces runs containers as a non-root UID # with no /etc/passwd entry, so any cache path under $HOME must exist # and be world-writable BEFORE the runtime user shows up. RUN mkdir -p "$HOME" "$HF_HOME" "$XDG_CACHE_HOME" \ && chmod -R 0777 /tmp /app EXPOSE 7860 # /health is OpenEnv's stock endpoint and turns 200 once uvicorn binds. HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ CMD curl -fsS "http://127.0.0.1:${PORT}/health" || exit 1 ENV ENABLE_WEB_INTERFACE=true CMD ["python3", "-m", "uvicorn", "_space_app:app", "--host", "0.0.0.0", "--port", "7860"]