Spaces:
Sleeping
Sleeping
fix: Add SPA Static Files handler for HuggingFace deployment
Browse files- Created SPAStaticFiles class to preserve API routes
- Updated main.py to use SPA handler
- Fixes 404 errors on frontend routes in HuggingFace Spaces
- backend/main.py +7 -5
- backend/spa_static_files.py +34 -0
backend/main.py
CHANGED
|
@@ -21,7 +21,6 @@ from dotenv import load_dotenv
|
|
| 21 |
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Depends
|
| 22 |
from fastapi.middleware.cors import CORSMiddleware
|
| 23 |
from fastapi.responses import JSONResponse
|
| 24 |
-
from fastapi.staticfiles import StaticFiles
|
| 25 |
from contextlib import asynccontextmanager
|
| 26 |
from typing import List, Dict, Optional
|
| 27 |
import asyncio
|
|
@@ -225,16 +224,19 @@ def get_websocket_manager() -> WebSocketManager:
|
|
| 225 |
return saap_app.websocket_manager
|
| 226 |
|
| 227 |
# =====================================================
|
| 228 |
-
# STATIC FILES - Serve Vue.js Frontend
|
| 229 |
# =====================================================
|
| 230 |
|
|
|
|
|
|
|
|
|
|
| 231 |
# Mount static files for frontend (must be AFTER all API routes)
|
| 232 |
-
#
|
| 233 |
import os
|
| 234 |
frontend_dist = "/app/frontend/dist"
|
| 235 |
if os.path.exists(frontend_dist):
|
| 236 |
-
app.mount("/",
|
| 237 |
-
logger.info(f"✅ Static files mounted: {frontend_dist}")
|
| 238 |
else:
|
| 239 |
logger.warning(f"⚠️ Frontend dist directory not found: {frontend_dist}")
|
| 240 |
|
|
|
|
| 21 |
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, Depends
|
| 22 |
from fastapi.middleware.cors import CORSMiddleware
|
| 23 |
from fastapi.responses import JSONResponse
|
|
|
|
| 24 |
from contextlib import asynccontextmanager
|
| 25 |
from typing import List, Dict, Optional
|
| 26 |
import asyncio
|
|
|
|
| 224 |
return saap_app.websocket_manager
|
| 225 |
|
| 226 |
# =====================================================
|
| 227 |
+
# STATIC FILES - Serve Vue.js Frontend (SPA Mode)
|
| 228 |
# =====================================================
|
| 229 |
|
| 230 |
+
# Import custom SPA Static Files handler
|
| 231 |
+
from spa_static_files import SPAStaticFiles
|
| 232 |
+
|
| 233 |
# Mount static files for frontend (must be AFTER all API routes)
|
| 234 |
+
# SPAStaticFiles preserves API routes while serving Vue.js SPA
|
| 235 |
import os
|
| 236 |
frontend_dist = "/app/frontend/dist"
|
| 237 |
if os.path.exists(frontend_dist):
|
| 238 |
+
app.mount("/", SPAStaticFiles(directory=frontend_dist, html=True), name="static")
|
| 239 |
+
logger.info(f"✅ SPA Static files mounted: {frontend_dist}")
|
| 240 |
else:
|
| 241 |
logger.warning(f"⚠️ Frontend dist directory not found: {frontend_dist}")
|
| 242 |
|
backend/spa_static_files.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SPA Static Files Handler for FastAPI
|
| 3 |
+
Serves Vue.js SPA while preserving API routes
|
| 4 |
+
"""
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from starlette.responses import FileResponse
|
| 7 |
+
from starlette.types import Scope
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class SPAStaticFiles(StaticFiles):
|
| 12 |
+
"""
|
| 13 |
+
Custom StaticFiles that serves index.html for non-API routes (SPA fallback)
|
| 14 |
+
while allowing API routes to pass through
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
async def get_response(self, path: str, scope: Scope):
|
| 18 |
+
"""
|
| 19 |
+
Handle requests:
|
| 20 |
+
- API routes (/api/*) → 404 (let FastAPI handle)
|
| 21 |
+
- Static files (exist) → serve file
|
| 22 |
+
- Everything else → index.html (SPA routing)
|
| 23 |
+
"""
|
| 24 |
+
# Let API routes pass through to FastAPI
|
| 25 |
+
if path.startswith("api/") or path.startswith("docs") or path.startswith("redoc") or path.startswith("openapi.json"):
|
| 26 |
+
# Return 404 to let FastAPI handle it
|
| 27 |
+
raise RuntimeError("Not found")
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
# Try to serve the file
|
| 31 |
+
return await super().get_response(path, scope)
|
| 32 |
+
except Exception:
|
| 33 |
+
# File not found → serve index.html for SPA routing
|
| 34 |
+
return FileResponse(os.path.join(self.directory, "index.html"))
|