| """Central registry for image assets and Gradio-version-safe asset URLs. |
| |
| Gradio 5+ serves user files under ``/gradio_api/file=``; Gradio 4 used |
| ``/file=``. We build the prefix dynamically so the same code works on the |
| local install (6.x) and on older Hugging Face Space runtimes. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from pathlib import Path |
|
|
| import gradio as gr |
|
|
| ASSET_DIR = Path(__file__).resolve().parent.parent / "assets" |
|
|
| _GRADIO_MAJOR = int(gr.__version__.split(".")[0]) |
| _FILE_PREFIX = "/gradio_api/file=" if _GRADIO_MAJOR >= 5 else "/file=" |
|
|
|
|
| def file_url(relative_path: str) -> str: |
| """Return a servable URL for a file inside ``assets/``.""" |
| return f"{_FILE_PREFIX}assets/{relative_path}" |
|
|
|
|
| def file_url_from_path(absolute_path: str) -> str: |
| """Convert an absolute file path to a Gradio-servable URL.""" |
| if not absolute_path: |
| return "" |
| return f"{_FILE_PREFIX}{absolute_path.lstrip('/')}" |
|
|
|
|
| def allowed_paths() -> list[str]: |
| return [str(ASSET_DIR), str(ASSET_DIR / "generated")] |
|
|
|
|
| |
| |
| |
| LOCATION_IMAGES = { |
| "library": "loc_library.jpg", |
| "sea": "loc_sea.jpg", |
| "clock-forest": "loc_clock.jpg", |
| "moon-market": "loc_market.jpg", |
| "valley": "loc_valley.jpg", |
| "crossroads": "loc_crossroads.jpg", |
| "mirror-bogs": "loc_bogs.jpg", |
| "hollow-mountain": "loc_mountain.jpg", |
| } |
|
|
| BANNER = "banner.png" |
|
|
|
|
| def location_image_url(slug: str | None) -> str | None: |
| if not slug: |
| return None |
| name = LOCATION_IMAGES.get(slug) |
| return file_url(name) if name else None |
|
|
|
|
| def banner_url() -> str: |
| return file_url(BANNER) |
|
|
|
|
| |
| |
| |
| |
| |
| NAMED_SPRITES = { |
| "Crystal Tree": "char_crystal_tree.png", |
| "Joke Dragon": "char_joke_dragon.png", |
| "Blind Cartographer": "char_blind_cartographer.png", |
| "Fog Merchant": "char_fog_merchant.png", |
| "Lantern Fox": "char_lantern_fox.png", |
| "Thirteen Sighs": "char_thirteen_sighs.png", |
| "Silent Gear": "char_silent_gear.png", |
| "Disagreeing Reflection": "char_disagreeing_reflection.png", |
| "Echo Counter": "char_echo_counter.png", |
| "Unfed Bonfire": "char_unfed_bonfire.png", |
| } |
|
|
| |
| |
| TYPE_SPRITES: dict[str, list[str]] = { |
| "character": [ |
| "char_blind_cartographer.png", |
| "char_echo_counter.png", |
| "char_fog_merchant.png", |
| "char_disagreeing_reflection.png", |
| ], |
| "creature": [ |
| "char_lantern_fox.png", |
| "char_joke_dragon.png", |
| "char_crystal_tree.png", |
| ], |
| "object": [ |
| "char_silent_gear.png", |
| "char_thirteen_sighs.png", |
| ], |
| "place": [ |
| "char_unfed_bonfire.png", |
| "char_silent_gear.png", |
| ], |
| } |
|
|
| _FALLBACK_SPRITE = "char_blind_cartographer.png" |
|
|
|
|
| def sprite_filename(entity: dict) -> str: |
| """Stable sprite filename for an entity (named art, else type pool).""" |
| name = (entity.get("display_name") or "").strip() |
| if name in NAMED_SPRITES and (ASSET_DIR / NAMED_SPRITES[name]).exists(): |
| return NAMED_SPRITES[name] |
|
|
| etype = entity.get("type", "character") |
| pool = TYPE_SPRITES.get(etype, TYPE_SPRITES["character"]) |
| uid = entity.get("id") or name or "x" |
| chosen = pool[sum(ord(c) for c in uid) % len(pool)] |
| if (ASSET_DIR / chosen).exists(): |
| return chosen |
| return _FALLBACK_SPRITE |
|
|
|
|
| def sprite_url(entity: dict) -> str: |
| """Served URL for a diorama billboard sprite (HTML <img>).""" |
| return file_url(sprite_filename(entity)) |
|
|
|
|
| def sprite_path(entity: dict) -> str: |
| """Filesystem path for a sprite (gr.Gallery needs paths, not URLs).""" |
| return str(ASSET_DIR / sprite_filename(entity)) |
|
|