""" Hugging Face Spaces entry point This file serves both the FastAPI backend and React frontend """ import os import subprocess import sys from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from fastapi.middleware.cors import CORSMiddleware from dotenv import load_dotenv from backend.main import app as backend_app, initialize_rag_system # def run_files_upload_script(script_path: Path) -> None: # """Run the files_upload.py helper to ensure documents are available.""" # if not script_path.exists(): # print(f"[root_app] Upload script not found at {script_path}") # return # print(f"[root_app] Running upload script: {script_path}") # try: # completed = subprocess.run( # [sys.executable, str(script_path)], # capture_output=True, # text=True, # check=True, # ) # if completed.stdout: # print(f"[root_app] upload script output:\n{completed.stdout}") # if completed.stderr: # print(f"[root_app] upload script warnings:\n{completed.stderr}") # except subprocess.CalledProcessError as exc: # print(f"[root_app] WARNING: upload script failed with code {exc.returncode}") # if exc.stdout: # print(f"[root_app] upload script stdout:\n{exc.stdout}") # if exc.stderr: # print(f"[root_app] upload script stderr:\n{exc.stderr}") # raise @asynccontextmanager async def lifespan(app: FastAPI): """Lifespan context manager for FastAPI startup and shutdown""" # Startup print("[root_app] Startup event triggered") # Set flag to indicate parent app handles initialization (prevents double init) os.environ["RAG_INIT_BY_PARENT"] = "true" print("[root_app] Set RAG_INIT_BY_PARENT flag to prevent double initialization") # Initialize RAG system here to ensure it runs # The backend's lifespan will skip initialization when this flag is set print("[root_app] Initializing RAG system...") try: # Set up paths (same as backend/main.py) # Calculate paths relative to app.py (project root) project_root = Path(__file__).parent documents_dir = project_root / "documents" processed_json = project_root / "processed_documents.json" # Load environment variables from .env file (if it exists) env_path = project_root / ".env" # print(f"[root_app] .env file path: {env_path}") # print(f"[root_app] .env file exists? {env_path.exists()}") if env_path.exists(): load_dotenv(env_path, override=True) # print("[root_app] Loaded .env file") else: # Try loading anyway in case it's in a different location load_dotenv(env_path, override=True) # print("[root_app] .env file not found, using environment variables") # Check for API keys (from .env or environment variables) openai_key = os.getenv("OPENAI_API_KEY") hf_token = os.getenv("HF_TOKEN") # if openai_key: # print(f"[root_app] OPENAI_API_KEY found (length: {len(openai_key)} characters)") # else: # print("[root_app] WARNING: OPENAI_API_KEY not found in environment") # if hf_token: # print(f"[root_app] HF_TOKEN found (length: {len(hf_token)} characters)") # Set global variables in backend.main module so initialize_rag_system can use them import backend.main as backend_main backend_main.PROJECT_ROOT = project_root backend_main.DOCUMENTS_DIR = documents_dir backend_main.PROCESSED_JSON = processed_json # print(f"[root_app] PROJECT_ROOT: {project_root}") # print(f"[root_app] DOCUMENTS_DIR: {documents_dir} (exists: {documents_dir.exists()})") # print(f"[root_app] PROCESSED_JSON: {processed_json} (exists: {processed_json.exists()})") # Run the upload script before initializing the RAG system # run_files_upload_script(upload_script) # Initialize RAG system with timeout protection # print("[root_app] Calling initialize_rag_system()...") initialize_rag_system() print("[root_app] ✓ RAG system initialization completed successfully") except Exception as e: print(f"[root_app] ERROR: RAG system initialization failed: {e}") import traceback traceback.print_exc() # Don't raise - allow app to start even if RAG init fails # The /ask endpoint will return 503 if RAG is not ready print("[root_app] Continuing startup despite RAG initialization failure") print("[root_app] Mounting backend application at /api") yield # Shutdown (if needed in the future) print("[root_app] FastAPI lifespan shutdown") # Create a new FastAPI app that will serve both backend and frontend app = FastAPI(lifespan=lifespan) # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount the backend API app.mount("/api", backend_app) # Serve React frontend frontend_path = Path(__file__).parent / "frontend" / "build" if frontend_path.exists(): # Serve static files (JS, CSS, images, etc.) static_path = frontend_path / "static" if static_path.exists(): app.mount("/static", StaticFiles(directory=str(static_path)), name="static") # Serve index.html for root and all other routes (React Router) @app.get("/") async def serve_index(): index_file = frontend_path / "index.html" if index_file.exists(): return FileResponse(str(index_file)) return {"message": "Frontend not built. Please run 'npm run build' in the frontend directory."} @app.get("/{full_path:path}") async def serve_react_app(full_path: str, request: Request): # Don't interfere with API routes if full_path.startswith("api"): return # Don't interfere with static files if full_path.startswith("static"): return index_file = frontend_path / "index.html" if index_file.exists(): return FileResponse(str(index_file)) return {"message": "Frontend not built."} else: @app.get("/") async def root(): return {"message": "Frontend not built. Please run 'npm run build' in the frontend directory."} if __name__ == "__main__": import uvicorn port = int(os.getenv("PORT", 7860))# 7860 uvicorn.run(app, host="0.0.0.0", port=port)