import logging from pathlib import Path from quart import Quart, jsonify, make_response, request, send_from_directory from fabledassistant.config import Config from fabledassistant.routes.api import api from fabledassistant.routes.chat import chat_bp from fabledassistant.routes.notes import notes_bp from fabledassistant.routes.settings import settings_bp from fabledassistant.routes.tasks import tasks_bp STATIC_DIR = Path(__file__).parent / "static" logger = logging.getLogger(__name__) def create_app() -> Quart: app = Quart(__name__, static_folder=None) app.secret_key = Config.SECRET_KEY app.register_blueprint(api) app.register_blueprint(chat_bp) app.register_blueprint(notes_bp) app.register_blueprint(settings_bp) app.register_blueprint(tasks_bp) @app.before_serving async def startup(): import asyncio from fabledassistant.services.llm import ensure_model async def _pull_model(): try: await ensure_model(Config.OLLAMA_MODEL) except Exception: logger.warning( "Failed to ensure model '%s' — chat may not work until model is available", Config.OLLAMA_MODEL, exc_info=True, ) # Fire-and-forget so model pull doesn't block startup asyncio.create_task(_pull_model()) @app.route("/") async def serve_index(): resp = await make_response( await send_from_directory(STATIC_DIR, "index.html") ) resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" return resp @app.errorhandler(404) async def handle_404(error): # Return JSON 404 for API routes if request.path.startswith("/api/"): return jsonify({"error": "Not found"}), 404 # Try to serve static file path = request.path.lstrip("/") file_path = STATIC_DIR / path if path and file_path.is_file(): return await send_from_directory(STATIC_DIR, path) # SPA fallback resp = await make_response( await send_from_directory(STATIC_DIR, "index.html") ) resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" return resp @app.errorhandler(500) async def handle_500(error): import traceback traceback.print_exc() logger.exception("Internal server error on %s %s", request.method, request.path) if request.path.startswith("/api/"): return jsonify({"error": str(error)}), 500 return "Internal Server Error", 500 return app