"""Search engines and link previews. Every page of the app has its own path, and the server answers each with the app plus, in its head, the page's own title, description, canonical address, Open Graph and Twitter card, and schema.org data (a Dataset for an environment, a SoftwareApplication for a Space, a task as part of its environment, breadcrumbs); in its body, the page's content as plain HTML, for crawlers that don't run JavaScript (the app replaces it as it starts). Plus robots.txt, a sitemap of every environment and every indexed task, and a preview image per page. Catalog pages use public indexes. Row and Space task pages may make bounded anonymous reads of the same withheld task views shown in the UI. Crawlers never start indexing, wake a Space, run an episode or receive a visitor's credentials. """ from __future__ import annotations import html import io import json import re import threading import time from functools import lru_cache from typing import Any from urllib.parse import quote, urlencode from fastapi import Request from fastapi.responses import HTMLResponse, PlainTextResponse, Response from . import catalog, config, seo_tasks, snapshot, space_checks, spaces_live SITE = "HF RL Explorer" TAGLINE = "RL environments on the Hugging Face Hub" DESCRIPTION = ("Explore reinforcement learning environments and tasks on the Hugging Face Hub. Browse OpenEnv, Harbor, " "MiMo, NeMo Gym and Verifiers, inspect rewards, and run supported agent rollouts.") KIND = {"harbor": "Harbor dataset", "verifiers": "Verifiers environment", "nemo-gym": "NeMo Gym dataset", "rows": "RL dataset", "mimo": "MiMo RL release", "openenv": "OpenEnv Space", "space": "environment Space"} MIMO = "XiaomiMiMo/MiMo-V2.6-RL-oss" NOINDEX = re.compile(r"^/(run/|runs$|compare/)") SITEMAP_CHUNK = 10_000 # ── where we are ───────────────────────────────────────────────────────────── def base_url(request: Request) -> str: """The site's own origin, for canonical addresses: the Space's public URL, else what the request came to.""" if config.PUBLIC_URL: return config.PUBLIC_URL proto = request.headers.get("x-forwarded-proto") or request.url.scheme host = request.headers.get("x-forwarded-host") or request.headers.get("host") or request.url.netloc return f"{proto}://{host}" def enc(spec: str) -> str: return "/".join(quote(p, safe="") for p in spec.split("/")) def esc(s: Any) -> str: return html.escape(str(s if s is not None else ""), quote=True) def clip(text: str, n: int) -> str: text = re.sub(r"\s+", " ", re.sub(r"[#*_`>|\[\]]+", " ", str(text or ""))).strip() return text if len(text) <= n else text[: n - 1].rsplit(" ", 1)[0].rstrip(",.;:") + "…" @lru_cache(maxsize=4) def _template(mtime: float) -> str: return (config.WEB_DIR / "index.html").read_text() def template() -> str: p = config.WEB_DIR / "index.html" return _template(p.stat().st_mtime) # ── what we know, cheaply ──────────────────────────────────────────────────── _listing: dict[str, Any] = {"at": 0.0, "by": {}, "rows": []} _lock = threading.Lock() def listing() -> tuple[dict[str, dict[str, Any]], list[dict[str, Any]]]: """The catalog's listing of public environments, by key (`org/name`, `space:org/name`), refreshed every 10 min.""" with _lock: if time.time() - _listing["at"] < 600 and _listing["rows"]: return _listing["by"], _listing["rows"] try: from . import search_api rows = search_api.environments() # the catalog snapshot (public rows only), else the live catalog except Exception: # noqa: BLE001 - the Hub is unreachable: pages still render, with less in them rows = [] by = {r["key"]: r for r in rows} with _lock: _listing.update(at=time.time(), by=by, rows=rows) return by, rows def public_rows(rows): hidden = set(catalog.hidden()) return [r for r in rows if r["key"] not in hidden and not any(r.get(k) for k in ("private", "gated", "restricted"))] def discoverable(r, records=None): return r.get("kind") == "dataset" or space_checks.browseable((space_checks.inventory() if records is None else records).get(r["id"])) def index_rows(spec: str) -> list[dict[str, Any]]: """An environment's tasks, if they are already known here (never built for a crawler): [{path, title, brief, category}].""" if spec == MIMO: return _mimo_rows() try: p = catalog._index_path(spec) mtime = p.stat().st_mtime except (OSError, ValueError): try: return _snapshot_rows(spec, snapshot.get().name) except snapshot.SnapshotError: return [] return _harbor_rows(spec, mtime) @lru_cache(maxsize=1) def _mimo_rows() -> list[dict[str, Any]]: from .mimo import catalog as mcat return [{"path": e["id"], "title": e["t"], "brief": e["s"], "category": e["d"]} for e in mcat.index()["envs"]] @lru_cache(maxsize=32) def _harbor_rows(spec: str, mtime: float) -> list[dict[str, Any]]: idx = catalog._read_index(spec) if not idx or (idx.get("info") or {}).get("restricted"): return [] return [{"path": t["path"], "title": t.get("title"), "brief": t.get("brief"), "category": t.get("category")} for t in (idx or {}).get("tasks") or []] @lru_cache(maxsize=32) def _snapshot_rows(spec: str, revision: str) -> list[dict[str, Any]]: with snapshot.use() as (_, conn): return [dict(r) for r in conn.execute("SELECT ref AS path, title, brief, category FROM tasks WHERE env = ? ORDER BY ref", (spec,))] def display_name(row: dict[str, Any] | None, spec: str) -> str: """What to call an environment: its card's heading when that names it, else the repository name with the heading.""" repo = spec.split("/")[-1] heading = (row or {}).get("heading") or "" norm = lambda x: re.sub(r"[^a-z0-9]", "", x.lower()) # noqa: E731 if not heading or norm(heading) == norm(repo): return repo return heading if norm(repo) in norm(heading) else f"{repo}: {heading}" def kind_of(row: dict[str, Any] | None, spec: str = "") -> str: if spec == MIMO: return KIND["mimo"] if not row: return "RL environment" if row.get("kind") == "space": if row.get("fw") == "unverified-space": return "Unverified Space" return {"openenv": KIND["openenv"], "ors": "ORS Space"}.get(row.get("framework") or ("openenv" if row.get("openenv") else ""), KIND["space"]) return KIND.get(row.get("framework") or "", "RL dataset") # ── a page ─────────────────────────────────────────────────────────────────── def page(request: Request, *, title: str, description: str, path: str, body: str = "", jsonld: list[dict[str, Any]] | None = None, image: str | None = None, noindex: bool = False, og_type: str = "website", status: int = 200) -> HTMLResponse: base = base_url(request) url = base + path full = title if SITE in title else f"{title} · {SITE}" img = base + (image or "/social/rl-explorer.png") head = "\n".join([ f'', f'', '' if noindex else '', f'', f'', f'', f'', f'', f'', '', '', '', '', f'', '', f'', f'', f'', f'', *[f'' for x in jsonld or []], ]) doc = template() doc = re.sub(r".*?", f"{esc(full)}", doc, count=1, flags=re.S) doc = re.sub(r'', f'', doc, count=1) doc = doc.replace("", f"{head}\n", 1) if body: # the page as plain HTML until the app starts (crawlers that don't run JavaScript read this) doc = re.sub(r'
(.*?)
', lambda m: f'
{m.group(1)}
{body}
', doc, count=1, flags=re.S) return HTMLResponse(doc, status_code=status, headers={"Cache-Control": "no-cache", **({"Retry-After": "60"} if status == 503 else {})}) def _ld(x: dict[str, Any]) -> str: # inside a