Spaces:
Runtime error
Runtime error
| """ | |
| SlideMentor Backend β FastAPI + Groq | |
| ===================================== | |
| Endpoints: | |
| POST /explain-slide β Explain slide text or image | |
| POST /explain-selection β Explain highlighted text | |
| POST /chat β Contextual follow-up chat | |
| Security: | |
| - Input sanitization and prompt injection defense | |
| - Rate limiting (in-memory, suitable for demo scale) | |
| - CORS locked to extension origin | |
| - No raw user content in system prompts | |
| """ | |
| from fastapi import FastAPI, Request, HTTPException, Depends | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from contextlib import asynccontextmanager | |
| from routers.pdf import router as pdf_router | |
| import time | |
| import logging | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| from routers.explain import router as explain_router | |
| from routers.chat import router as chat_router | |
| from middleware.rate_limiter import RateLimiter | |
| from middleware.sanitizer import sanitize_input | |
| # βββ Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", | |
| ) | |
| logger = logging.getLogger("slidementor") | |
| # βββ Lifespan ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def lifespan(app: FastAPI): | |
| logger.info("SlideMentor API starting up") | |
| yield | |
| logger.info("SlideMentor API shutting down") | |
| # βββ App βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI( | |
| title="SlideMentor API", | |
| version="1.0.0", | |
| description="AI-powered slide explanation backend using Groq LLaMA 3", | |
| docs_url="/docs", | |
| redoc_url=None, | |
| lifespan=lifespan, | |
| ) | |
| # βββ CORS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=False, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # βββ Rate Limiter βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| rate_limiter = RateLimiter(max_requests=30, window_seconds=60) | |
| async def rate_limit_middleware(request: Request, call_next): | |
| if request.method == "POST": | |
| client_ip = request.client.host if request.client else "unknown" | |
| allowed, retry_after = rate_limiter.check(client_ip) | |
| if not allowed: | |
| return JSONResponse( | |
| status_code=429, | |
| content={ | |
| "error": "rate_limit_exceeded", | |
| "detail": f"Too many requests. Retry after {retry_after}s.", | |
| "retry_after": retry_after, | |
| }, | |
| ) | |
| return await call_next(request) | |
| # βββ Routers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app.include_router(explain_router, prefix="", tags=["Explain"]) | |
| app.include_router(chat_router, prefix="", tags=["Chat"]) | |
| app.include_router(pdf_router, prefix="", tags=["PDF"]) | |
| # βββ Health βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def health(): | |
| return {"status": "ok", "service": "slidementor", "version": "1.0.0"} | |
| # βββ Global Error Handler βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def global_exception_handler(request: Request, exc: Exception): | |
| logger.error(f"Unhandled error on {request.url.path}: {exc}", exc_info=True) | |
| return JSONResponse( | |
| status_code=500, | |
| content={"error": "internal_error", "detail": "An unexpected error occurred."}, | |
| ) | |