Files
FabledScribe/src/fabledassistant/app.py
T
bvandeusen f4c7deba66 Run model pull as background task to avoid startup timeout
ensure_model() can take minutes to pull a multi-GB model, which
exceeds Hypercorn's startup timeout. Now fires as an asyncio
background task so the app starts immediately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 19:16:54 -05:00

79 lines
2.5 KiB
Python

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.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(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