from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from .api.v1.endpoints import generate, download, health, auth from .core.config import settings from .core.model_loader import get_generator # Import get_generator from contextlib import asynccontextmanager from pathlib import Path import logging # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Ensure all required directories exist settings.resolved_generated_path.mkdir(parents=True, exist_ok=True) settings.resolved_base_path.mkdir(parents=True, exist_ok=True) settings.resolved_symbols_path.mkdir(parents=True, exist_ok=True) settings.resolved_qr_code_path.mkdir(parents=True, exist_ok=True) # Ensure the static mount directory exists settings.resolved_static_files_mount_dir.mkdir(parents=True, exist_ok=True) # Lifecycle management for the model @asynccontextmanager async def lifespan(app: FastAPI): # Startup: Preload the model (non-blocking) logger.info("Anwendung startet... Starte LLM-Modell-Loading im Hintergrund.") try: # Start model loading in background to avoid blocking app startup import asyncio import threading def load_model_background(): try: logger.info("Hintergrund-Loading des LLM-Modells gestartet...") get_generator() # Calls get_generator to load and cache the model logger.info("✅ LLM-Modell erfolgreich vorab geladen und Pipeline initialisiert.") except Exception as e: logger.error(f"❌ Fehler beim Hintergrund-Loading des LLM-Modells: {e}", exc_info=True) # Start model loading in a separate thread model_thread = threading.Thread(target=load_model_background, daemon=True) model_thread.start() logger.info("🚀 Anwendung gestartet - Modell lädt im Hintergrund...") except Exception as e: logger.error(f"Fehler beim Starten des Hintergrund-Loadings: {e}", exc_info=True) yield # Shutdown: Cleanup actions could go here (not currently needed for the model) logger.info("Anwendung wird heruntergefahren.") app = FastAPI( title=settings.PROJECT_NAME, description="Ein Service zur Generierung personalisierter Horoskopkarten mit LoRa und FastAPI.", version=settings.APP_VERSION, lifespan=lifespan ) # Mount static files with error handling try: static_dir = settings.resolved_static_files_mount_dir logger.info(f"Attempting to mount static directory: {static_dir}") logger.info(f"Static directory exists: {static_dir.exists()}") if static_dir.exists(): app.mount(f"{settings.API_PREFIX}/static", StaticFiles(directory=static_dir), name="static") logger.info("Static files mounted successfully") else: logger.warning(f"Static directory does not exist: {static_dir}") logger.info("Creating static directory structure...") static_dir.mkdir(parents=True, exist_ok=True) # Create basic subdirectories (static_dir / "images").mkdir(exist_ok=True) (static_dir / "fonts").mkdir(exist_ok=True) app.mount(f"{settings.API_PREFIX}/static", StaticFiles(directory=static_dir), name="static") logger.info("Static files mounted with created directory") except Exception as e: logger.error(f"Failed to mount static files: {e}") # Continue without static files mounting in case of error origins = [ "http://localhost", "http://localhost:3000", "https://huggingface.co", "https://ch404-cardserver.hf.space", "https://huggingface.co/spaces/ch404/cardserver", "*", # Allow all origins for debugging ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(health.router, prefix=settings.API_PREFIX, tags=["Health"]) app.include_router(auth.router, prefix=settings.API_PREFIX, tags=["Authentication"]) app.include_router(generate.router, prefix=settings.API_PREFIX, tags=["Generate Card"]) app.include_router(download.router, prefix=settings.API_PREFIX, tags=["Download Card"]) @app.get("/") async def root(): return {"message": f"Welcome to the {settings.PROJECT_NAME} API."}