File size: 4,348 Bytes
0e24aff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74ddb14
 
 
 
 
 
0e24aff
74ddb14
0e24aff
 
74ddb14
 
 
 
 
0e24aff
74ddb14
0e24aff
 
74ddb14
 
 
 
 
0e24aff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74ddb14
0e24aff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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"]