nusaibah0110 commited on
Commit
3b9b54d
·
1 Parent(s): f4e5bc9

Run FastAPI in Space and use relative API URLs

Browse files
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- # Multi-stage build for Vite React app served as static site
2
 
3
  FROM node:18-alpine AS build
4
  WORKDIR /app
@@ -13,18 +13,22 @@ COPY . .
13
  # Build static assets
14
  RUN npm run build
15
 
16
- FROM node:18-alpine AS runtime
17
  WORKDIR /app
18
 
19
- # Install a lightweight static file server
20
- RUN npm install -g serve
 
 
 
 
 
 
 
21
 
22
- # Copy build output from previous stage
23
  COPY --from=build /app/dist ./dist
24
 
25
- # Spaces sets PORT; default to 7860 for local runs
26
  ENV PORT=7860
27
  EXPOSE 7860
28
 
29
- # Serve the built app
30
- CMD ["sh", "-c", "serve -s dist -l ${PORT}"]
 
1
+ # Multi-stage build: frontend + FastAPI backend in one container
2
 
3
  FROM node:18-alpine AS build
4
  WORKDIR /app
 
13
  # Build static assets
14
  RUN npm run build
15
 
16
+ FROM python:3.10-slim AS runtime
17
  WORKDIR /app
18
 
19
+ ENV PYTHONUNBUFFERED=1
20
+
21
+ RUN apt-get update \
22
+ && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 \
23
+ && rm -rf /var/lib/apt/lists/*
24
+
25
+ COPY backend/ ./backend/
26
+ COPY backend/requirements.txt ./backend/requirements.txt
27
+ RUN pip install --no-cache-dir -r backend/requirements.txt
28
 
 
29
  COPY --from=build /app/dist ./dist
30
 
 
31
  ENV PORT=7860
32
  EXPOSE 7860
33
 
34
+ CMD ["uvicorn", "backend.app:app", "--host", "0.0.0.0", "--port", "7860"]
 
backend/app.py CHANGED
@@ -1,6 +1,7 @@
1
  from fastapi import FastAPI, File, UploadFile, HTTPException
2
  from fastapi.responses import JSONResponse
3
  from fastapi.middleware.cors import CORSMiddleware
 
4
  import cv2
5
  import numpy as np
6
  import tempfile
@@ -22,6 +23,14 @@ app.add_middleware(
22
  )
23
 
24
 
 
 
 
 
 
 
 
 
25
  @app.get("/health")
26
  async def health_check():
27
  """Health check endpoint"""
@@ -214,5 +223,11 @@ async def infer_video(file: UploadFile = File(...)):
214
  raise HTTPException(status_code=500, detail=str(e))
215
 
216
 
 
 
 
 
 
 
217
  if __name__ == "__main__":
218
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
  from fastapi import FastAPI, File, UploadFile, HTTPException
2
  from fastapi.responses import JSONResponse
3
  from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.staticfiles import StaticFiles
5
  import cv2
6
  import numpy as np
7
  import tempfile
 
23
  )
24
 
25
 
26
+ class SPAStaticFiles(StaticFiles):
27
+ async def get_response(self, path: str, scope):
28
+ response = await super().get_response(path, scope)
29
+ if response.status_code == 404:
30
+ return await super().get_response("index.html", scope)
31
+ return response
32
+
33
+
34
  @app.get("/health")
35
  async def health_check():
36
  """Health check endpoint"""
 
223
  raise HTTPException(status_code=500, detail=str(e))
224
 
225
 
226
+ # Serve the built frontend if present (Space/Docker runtime)
227
+ frontend_dist = os.path.join(os.path.dirname(__file__), "..", "dist")
228
+ if os.path.isdir(frontend_dist):
229
+ app.mount("/", SPAStaticFiles(directory=frontend_dist, html=True), name="frontend")
230
+
231
+
232
  if __name__ == "__main__":
233
  uvicorn.run(app, host="0.0.0.0", port=8000)
src/components/AceticAnnotator.tsx CHANGED
@@ -210,7 +210,7 @@ const AceticAnnotatorComponent = forwardRef<AceticAnnotatorHandle, AceticAnnotat
210
 
211
  console.log('🚀 Sending to backend with image dimensions:', imageDimensions);
212
 
213
- const backendResponse = await fetch('http://localhost:8000/api/infer-aw-contour', {
214
  method: 'POST',
215
  body: formData,
216
  });
 
210
 
211
  console.log('🚀 Sending to backend with image dimensions:', imageDimensions);
212
 
213
+ const backendResponse = await fetch('/api/infer-aw-contour', {
214
  method: 'POST',
215
  body: formData,
216
  });
src/pages/AcetowhiteExamPage.tsx CHANGED
@@ -178,7 +178,7 @@ export function AcetowhiteExamPage({ goBack, onNext }: Props) {
178
 
179
  const formData = new FormData();
180
  formData.append('file', blob, 'image.jpg');
181
- const backendResponse = await fetch('http://localhost:8000/infer/image', {
182
  method: 'POST',
183
  body: formData,
184
  });
 
178
 
179
  const formData = new FormData();
180
  formData.append('file', blob, 'image.jpg');
181
+ const backendResponse = await fetch('/infer/image', {
182
  method: 'POST',
183
  body: formData,
184
  });
src/pages/GreenFilterPage.tsx CHANGED
@@ -231,7 +231,7 @@ export function GreenFilterPage({ goBack, onNext }: Props) {
231
 
232
  const formData = new FormData();
233
  formData.append('file', blob, 'image.jpg');
234
- const backendResponse = await fetch('http://localhost:8000/infer/image', {
235
  method: 'POST',
236
  body: formData,
237
  });
 
231
 
232
  const formData = new FormData();
233
  formData.append('file', blob, 'image.jpg');
234
+ const backendResponse = await fetch('/infer/image', {
235
  method: 'POST',
236
  body: formData,
237
  });
src/pages/GuidedCapturePage.tsx CHANGED
@@ -243,7 +243,7 @@ export function GuidedCapturePage({ onNext, onGoToPatientRecords, initialMode, o
243
  const formData = new FormData();
244
  formData.append('file', blob, 'frame.jpg');
245
 
246
- const response = await fetch('http://localhost:8000/infer/image', {
247
  method: 'POST',
248
  body: formData,
249
  });
 
243
  const formData = new FormData();
244
  formData.append('file', blob, 'frame.jpg');
245
 
246
+ const response = await fetch('/infer/image', {
247
  method: 'POST',
248
  body: formData,
249
  });
src/pages/LugolExamPage.tsx CHANGED
@@ -149,7 +149,7 @@ export function LugolExamPage({ goBack, onNext }: Props) {
149
 
150
  const formData = new FormData();
151
  formData.append('file', blob, 'image.jpg');
152
- const backendResponse = await fetch('http://localhost:8000/infer/image', {
153
  method: 'POST',
154
  body: formData,
155
  });
 
149
 
150
  const formData = new FormData();
151
  formData.append('file', blob, 'image.jpg');
152
+ const backendResponse = await fetch('/infer/image', {
153
  method: 'POST',
154
  body: formData,
155
  });