SCGR commited on
Commit
8d0e263
Β·
1 Parent(s): 1682252

Ship frontend

Browse files
Files changed (2) hide show
  1. Dockerfile +40 -35
  2. py_backend/app/main.py +28 -27
Dockerfile CHANGED
@@ -1,35 +1,40 @@
1
- FROM python:3.11-slim
2
-
3
- # Build deps for psycopg2 etc.
4
- RUN apt-get update && apt-get install -y \
5
- build-essential gcc libpq-dev \
6
- && rm -rf /var/lib/apt/lists/*
7
-
8
- WORKDIR /app
9
-
10
- # Install Python deps first for better caching
11
- COPY py_backend/requirements.txt /tmp/requirements.txt
12
- RUN pip install --no-cache-dir -r /tmp/requirements.txt
13
-
14
- # Copy backend code
15
- COPY py_backend/ /app/
16
-
17
- # Copy startup script
18
- COPY entrypoint.sh /app/entrypoint.sh
19
- RUN chmod +x /app/entrypoint.sh
20
-
21
- # Default runtime env (can be overridden by Space Secrets)
22
- ENV DATABASE_URL=sqlite:////data/app.db
23
- ENV STORAGE_PROVIDER=local
24
- ENV STORAGE_DIR=/data/uploads
25
- ENV HF_HOME=/data/.cache/huggingface
26
-
27
- # Create writable data dirs (enable β€œPersistent storage” in Space settings to keep them)
28
- RUN mkdir -p /data/uploads && chmod -R 777 /data
29
-
30
- # Hugging Face sets PORT; default to 7860
31
- ENV PORT=7860
32
- EXPOSE 7860
33
-
34
- # Run via shell script so $PORT is expanded before reaching uvicorn
35
- ENTRYPOINT ["/app/entrypoint.sh"]
 
 
 
 
 
 
1
+ # ---------- Build frontend ----------
2
+ FROM node:20-alpine AS fe
3
+ WORKDIR /fe
4
+ COPY frontend/package*.json ./
5
+ RUN npm ci
6
+ COPY frontend/ .
7
+ RUN npm run build # produces /fe/dist
8
+
9
+ # ---------- Backend image ----------
10
+ FROM python:3.11-slim
11
+
12
+ RUN apt-get update && apt-get install -y \
13
+ build-essential gcc libpq-dev \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ WORKDIR /app
17
+
18
+ # Install backend deps
19
+ COPY py_backend/requirements.txt /tmp/requirements.txt
20
+ RUN pip install --no-cache-dir -r /tmp/requirements.txt
21
+
22
+ # Copy backend code
23
+ COPY py_backend/ /app/
24
+
25
+ # Copy built frontend into the image (served by FastAPI)
26
+ COPY --from=fe /fe/dist /app/static
27
+
28
+ # Data dirs & sensible defaults (you can keep sqlite fallback if you want)
29
+ RUN mkdir -p /data/uploads && chmod -R 777 /data
30
+ ENV STORAGE_PROVIDER=local
31
+ ENV STORAGE_DIR=/data/uploads
32
+ ENV HF_HOME=/data/.cache/huggingface
33
+
34
+ # Spaces provides PORT; default to 7860 locally
35
+ ENV PORT=7860
36
+ EXPOSE 7860
37
+
38
+ # Start backend (serves API + static frontend)
39
+ CMD uvicorn app.main:app --host 0.0.0.0 --port $PORT
40
+
py_backend/app/main.py CHANGED
@@ -1,51 +1,52 @@
1
- from fastapi import FastAPI
 
2
  from fastapi.middleware.cors import CORSMiddleware
3
- from fastapi.responses import HTMLResponse, JSONResponse
4
- from app.routers import upload, caption, metadata, models
 
5
  from app.config import settings
 
6
  from app.routers.images import router as images_router
7
 
8
  app = FastAPI(title="PromptAid Vision")
9
 
 
10
  app.add_middleware(
11
  CORSMiddleware,
12
  allow_origins=[
13
  "http://localhost:3000",
14
  "http://localhost:5173",
15
- "https://huggingface.co",
16
- "https://*.hf.space",
17
- "https://*.hf.space",
18
- "*"
19
  ],
20
- allow_credentials=True,
 
21
  allow_methods=["*"],
22
  allow_headers=["*"],
23
  )
24
 
25
- app.include_router(caption.router, prefix="/api", tags=["captions"])
26
- app.include_router(metadata.router, prefix="/api", tags=["metadata"])
27
- app.include_router(models.router, prefix="/api", tags=["models"])
28
- app.include_router(upload.router, prefix="/api/images", tags=["images"])
29
- app.include_router(images_router, prefix="/api/contribute", tags=["contribute"])
30
-
31
- @app.get("/", include_in_schema=False, response_class=HTMLResponse)
32
- async def root():
33
- # simple page so HF health-check gets 200 OK
34
- return """<!doctype html>
35
- <title>PromptAid Vision</title>
36
- <h1>PromptAid Vision API</h1>
37
- <p>OK</p>
38
- <p>See <a href="/docs">/docs</a> for the API.</p>
39
- """
40
 
 
41
  @app.get("/health", include_in_schema=False, response_class=JSONResponse)
42
  async def health():
43
  return {"status": "ok"}
44
 
 
 
 
 
 
 
 
 
 
 
 
45
  print("πŸš€ PromptAid Vision API server ready")
46
  print("πŸ“Š Available endpoints: /api/images, /api/captions, /api/metadata, /api/models")
47
  print(f"🌍 Environment: {settings.ENVIRONMENT}")
48
- print("πŸ”— CORS enabled for Hugging Face Spaces")
49
-
50
-
51
-
 
1
+ import os
2
+ from fastapi import FastAPI, HTTPException
3
  from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.responses import FileResponse, JSONResponse
6
+
7
  from app.config import settings
8
+ from app.routers import upload, caption, metadata, models
9
  from app.routers.images import router as images_router
10
 
11
  app = FastAPI(title="PromptAid Vision")
12
 
13
+ # CORS: allow localhost dev and all *.hf.space
14
  app.add_middleware(
15
  CORSMiddleware,
16
  allow_origins=[
17
  "http://localhost:3000",
18
  "http://localhost:5173",
 
 
 
 
19
  ],
20
+ allow_origin_regex=r"https://.*\.hf\.space$", # Hugging Face subdomains
21
+ allow_credentials=False, # must be False if using "*" or regex
22
  allow_methods=["*"],
23
  allow_headers=["*"],
24
  )
25
 
26
+ # ---- API routers (keep them under /api) ----
27
+ app.include_router(caption.router, prefix="/api", tags=["captions"])
28
+ app.include_router(metadata.router, prefix="/api", tags=["metadata"])
29
+ app.include_router(models.router, prefix="/api", tags=["models"])
30
+ app.include_router(upload.router, prefix="/api/images", tags=["images"])
31
+ app.include_router(images_router, prefix="/api/contribute", tags=["contribute"])
 
 
 
 
 
 
 
 
 
32
 
33
+ # Simple health endpoint for HF health checks
34
  @app.get("/health", include_in_schema=False, response_class=JSONResponse)
35
  async def health():
36
  return {"status": "ok"}
37
 
38
+ # ---- Serve built frontend (Vite) ----
39
+ STATIC_DIR = os.path.join(os.path.dirname(__file__), "..", "static")
40
+ app.mount("/", StaticFiles(directory=STATIC_DIR, html=True), name="static")
41
+
42
+ # SPA fallback for client-side routes; don't intercept API or docs
43
+ @app.get("/{full_path:path}", include_in_schema=False)
44
+ def spa_fallback(full_path: str):
45
+ if full_path.startswith(("api", "docs", "redoc", "openapi")):
46
+ raise HTTPException(status_code=404, detail="Not Found")
47
+ return FileResponse(os.path.join(STATIC_DIR, "index.html"))
48
+
49
  print("πŸš€ PromptAid Vision API server ready")
50
  print("πŸ“Š Available endpoints: /api/images, /api/captions, /api/metadata, /api/models")
51
  print(f"🌍 Environment: {settings.ENVIRONMENT}")
52
+ print("πŸ”— CORS enabled for localhost and *.hf.space")