# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """ Main FastAPI application for Unsloth UI Backend """ import os import sys from pathlib import Path as _Path # Suppress annoying C-level dependency warnings globally os.environ["PYTHONWARNINGS"] = "ignore" # Ensure backend dir is on sys.path so _platform_compat is importable when # main.py is launched directly (e.g. `uvicorn main:app`). _backend_dir = str(_Path(__file__).parent) if _backend_dir not in sys.path: sys.path.insert(0, _backend_dir) # Fix for Anaconda/conda-forge Python: seed platform._sys_version_cache before # any library imports that trigger attrs -> rich -> structlog -> platform crash. # See: https://github.com/python/cpython/issues/102396 import _platform_compat # noqa: F401 import mimetypes import shutil import warnings from contextlib import asynccontextmanager from importlib.metadata import PackageNotFoundError, version as package_version # Fix broken Windows registry MIME types. Some Windows installs map .js to # "text/plain" in the registry (HKCR\.js\Content Type). Python's mimetypes # module reads from the registry, and FastAPI/Starlette's StaticFiles uses # mimetypes.guess_type() to set Content-Type headers. Browsers enforce strict # MIME checking for ES module scripts (" html = html_bytes.decode("utf-8") html = html.replace("", f"{tag}", 1) return html.encode("utf-8") def setup_frontend(app: FastAPI, build_path: Path): """Mount frontend static files (optional)""" if not build_path.exists(): return False # Mount assets assets_dir = build_path / "assets" if assets_dir.exists(): app.mount("/assets", StaticFiles(directory = assets_dir), name = "assets") @app.get("/") async def serve_root(): content = (build_path / "index.html").read_bytes() content = _strip_crossorigin(content) content = _inject_bootstrap(content, app) return Response( content = content, media_type = "text/html", headers = {"Cache-Control": "no-cache, no-store, must-revalidate"}, ) @app.get("/{full_path:path}") async def serve_frontend(full_path: str): if full_path in {"api", "v1"} or full_path.startswith(("api/", "v1/")): return {"error": "API endpoint not found"} file_path = (build_path / full_path).resolve() # Block path traversal — ensure resolved path stays inside build_path if not file_path.is_relative_to(build_path.resolve()): return Response(status_code = 403) if file_path.is_file(): return FileResponse(file_path) # Serve index.html as bytes — avoids Content-Length mismatch content = (build_path / "index.html").read_bytes() content = _strip_crossorigin(content) content = _inject_bootstrap(content, app) return Response( content = content, media_type = "text/html", headers = {"Cache-Control": "no-cache, no-store, must-revalidate"}, ) return True