Fix Shuffle 404 with embedded gallery manifest
Browse files- .gitignore +2 -0
- Dockerfile +1 -1
- download_demo_gallery.py +10 -0
- landing/index.html +6 -2
- validate_space.py +15 -0
.gitignore
CHANGED
|
@@ -1,3 +1,5 @@
|
|
| 1 |
__pycache__/
|
| 2 |
*.py[cod]
|
| 3 |
.cache/
|
|
|
|
|
|
|
|
|
| 1 |
__pycache__/
|
| 2 |
*.py[cod]
|
| 3 |
.cache/
|
| 4 |
+
landing/gallery/
|
| 5 |
+
landing/gallery-manifest.js
|
Dockerfile
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
FROM python:3.11-slim
|
| 2 |
|
| 3 |
-
LABEL org.opencontainers.image.revision="
|
| 4 |
|
| 5 |
ENV DEBIAN_FRONTEND=noninteractive \
|
| 6 |
PIP_NO_CACHE_DIR=1 \
|
|
|
|
| 1 |
FROM python:3.11-slim
|
| 2 |
|
| 3 |
+
LABEL org.opencontainers.image.revision="embedded-gallery-manifest-v3"
|
| 4 |
|
| 5 |
ENV DEBIAN_FRONTEND=noninteractive \
|
| 6 |
PIP_NO_CACHE_DIR=1 \
|
download_demo_gallery.py
CHANGED
|
@@ -12,6 +12,7 @@ from huggingface_hub import snapshot_download
|
|
| 12 |
|
| 13 |
ROOT = Path(__file__).resolve().parent
|
| 14 |
GALLERY = ROOT / "landing" / "gallery"
|
|
|
|
| 15 |
REPO_ID = os.environ.get(
|
| 16 |
"TRHASH_DEMO_IMAGE_REPO",
|
| 17 |
"Pacific-i64/TR-HASH-Vision-v8-Demo-Images",
|
|
@@ -48,6 +49,15 @@ def main() -> None:
|
|
| 48 |
raise RuntimeError(f"Invalid demo image payload: {filename}")
|
| 49 |
shutil.copyfile(source, GALLERY / filename)
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
print(f"Downloaded {len(images)} demo images from {REPO_ID}")
|
| 52 |
|
| 53 |
|
|
|
|
| 12 |
|
| 13 |
ROOT = Path(__file__).resolve().parent
|
| 14 |
GALLERY = ROOT / "landing" / "gallery"
|
| 15 |
+
GALLERY_SCRIPT = ROOT / "landing" / "gallery-manifest.js"
|
| 16 |
REPO_ID = os.environ.get(
|
| 17 |
"TRHASH_DEMO_IMAGE_REPO",
|
| 18 |
"Pacific-i64/TR-HASH-Vision-v8-Demo-Images",
|
|
|
|
| 49 |
raise RuntimeError(f"Invalid demo image payload: {filename}")
|
| 50 |
shutil.copyfile(source, GALLERY / filename)
|
| 51 |
|
| 52 |
+
# Embed the manifest in the landing page assets. Shuffle must remain usable
|
| 53 |
+
# even when a proxy/cache layer does not expose the JSON route yet.
|
| 54 |
+
GALLERY_SCRIPT.write_text(
|
| 55 |
+
"window.__TR_HASH_DEMO_GALLERY__ = "
|
| 56 |
+
+ json.dumps(manifest, ensure_ascii=False, separators=(",", ":"))
|
| 57 |
+
+ ";\n",
|
| 58 |
+
encoding="utf-8",
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
print(f"Downloaded {len(images)} demo images from {REPO_ID}")
|
| 62 |
|
| 63 |
|
landing/index.html
CHANGED
|
@@ -359,6 +359,7 @@
|
|
| 359 |
<p class="note">Final checkpoint from the first 50-epoch pretraining phase. The public Space runs on CPU; the first request after wake-up also loads the checkpoint and can take longer.</p>
|
| 360 |
</div>
|
| 361 |
|
|
|
|
| 362 |
<script>
|
| 363 |
const elements = {
|
| 364 |
input: document.querySelector("#image-input"),
|
|
@@ -435,7 +436,7 @@
|
|
| 435 |
}
|
| 436 |
elements.galleryAttribution.append(`COCO image ${image.image_id} · `);
|
| 437 |
const source = document.createElement("a");
|
| 438 |
-
source.href = image.
|
| 439 |
source.target = "_blank";
|
| 440 |
source.rel = "noopener";
|
| 441 |
source.textContent = "source";
|
|
@@ -504,7 +505,10 @@
|
|
| 504 |
|
| 505 |
async function loadGallery() {
|
| 506 |
if (galleryLoaded) return;
|
| 507 |
-
const manifest =
|
|
|
|
|
|
|
|
|
|
| 508 |
galleryImages = manifest.images || [];
|
| 509 |
galleryLoaded = true;
|
| 510 |
refillGalleryDeck();
|
|
|
|
| 359 |
<p class="note">Final checkpoint from the first 50-epoch pretraining phase. The public Space runs on CPU; the first request after wake-up also loads the checkpoint and can take longer.</p>
|
| 360 |
</div>
|
| 361 |
|
| 362 |
+
<script src="/gallery-manifest.js?v=20260816-5"></script>
|
| 363 |
<script>
|
| 364 |
const elements = {
|
| 365 |
input: document.querySelector("#image-input"),
|
|
|
|
| 436 |
}
|
| 437 |
elements.galleryAttribution.append(`COCO image ${image.image_id} · `);
|
| 438 |
const source = document.createElement("a");
|
| 439 |
+
source.href = `https://huggingface.co/datasets/Pacific-i64/TR-HASH-Vision-v8-Demo-Images/blob/main/images/${encodeURIComponent(image.file)}`;
|
| 440 |
source.target = "_blank";
|
| 441 |
source.rel = "noopener";
|
| 442 |
source.textContent = "source";
|
|
|
|
| 505 |
|
| 506 |
async function loadGallery() {
|
| 507 |
if (galleryLoaded) return;
|
| 508 |
+
const manifest = window.__TR_HASH_DEMO_GALLERY__;
|
| 509 |
+
if (!manifest || !Array.isArray(manifest.images)) {
|
| 510 |
+
throw new Error("The embedded demo image bank is unavailable");
|
| 511 |
+
}
|
| 512 |
galleryImages = manifest.images || [];
|
| 513 |
galleryLoaded = true;
|
| 514 |
refillGalleryDeck();
|
validate_space.py
CHANGED
|
@@ -11,7 +11,9 @@ from urllib.request import urlopen
|
|
| 11 |
ROOT = Path(__file__).resolve().parent
|
| 12 |
WORKFLOW_PATH = ROOT / "workflows" / "TR-HASH-Vision-v8-COCO-api.json"
|
| 13 |
GALLERY_PATH = ROOT / "landing" / "gallery"
|
|
|
|
| 14 |
ENTRYPOINT_PATH = ROOT / "entrypoint.sh"
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
def validate_entrypoint_contract() -> None:
|
|
@@ -39,6 +41,14 @@ def load_json(path: Path) -> dict:
|
|
| 39 |
|
| 40 |
def validate_static() -> set[str]:
|
| 41 |
validate_entrypoint_contract()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
workflow = load_json(WORKFLOW_PATH)
|
| 43 |
node_types = {node["class_type"] for node in workflow.values()}
|
| 44 |
required = {"LoadImage", "TRHashLoadModel", "TRHashDetect", "TRHashOutput"}
|
|
@@ -64,6 +74,11 @@ def validate_static() -> set[str]:
|
|
| 64 |
invalid.append(f"{name} (not JPEG)")
|
| 65 |
if invalid:
|
| 66 |
raise RuntimeError(f"Gallery contains invalid image payloads: {invalid[:5]}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
return node_types
|
| 68 |
|
| 69 |
|
|
|
|
| 11 |
ROOT = Path(__file__).resolve().parent
|
| 12 |
WORKFLOW_PATH = ROOT / "workflows" / "TR-HASH-Vision-v8-COCO-api.json"
|
| 13 |
GALLERY_PATH = ROOT / "landing" / "gallery"
|
| 14 |
+
GALLERY_SCRIPT_PATH = ROOT / "landing" / "gallery-manifest.js"
|
| 15 |
ENTRYPOINT_PATH = ROOT / "entrypoint.sh"
|
| 16 |
+
LANDING_PATH = ROOT / "landing" / "index.html"
|
| 17 |
|
| 18 |
|
| 19 |
def validate_entrypoint_contract() -> None:
|
|
|
|
| 41 |
|
| 42 |
def validate_static() -> set[str]:
|
| 43 |
validate_entrypoint_contract()
|
| 44 |
+
landing = LANDING_PATH.read_text(encoding="utf-8")
|
| 45 |
+
manifest_tag = '<script src="/gallery-manifest.js?v=20260816-5"></script>'
|
| 46 |
+
if manifest_tag not in landing:
|
| 47 |
+
raise RuntimeError("The landing page must load the embedded gallery manifest")
|
| 48 |
+
if landing.index(manifest_tag) > landing.index("window.__TR_HASH_DEMO_GALLERY__"):
|
| 49 |
+
raise RuntimeError("The gallery manifest must load before Shuffle reads it")
|
| 50 |
+
if 'requestJson("/gallery/manifest.json"' in landing:
|
| 51 |
+
raise RuntimeError("Shuffle must not depend on a runtime manifest request")
|
| 52 |
workflow = load_json(WORKFLOW_PATH)
|
| 53 |
node_types = {node["class_type"] for node in workflow.values()}
|
| 54 |
required = {"LoadImage", "TRHashLoadModel", "TRHashDetect", "TRHashOutput"}
|
|
|
|
| 74 |
invalid.append(f"{name} (not JPEG)")
|
| 75 |
if invalid:
|
| 76 |
raise RuntimeError(f"Gallery contains invalid image payloads: {invalid[:5]}")
|
| 77 |
+
embedded_manifest = GALLERY_SCRIPT_PATH.read_text(encoding="utf-8")
|
| 78 |
+
if not embedded_manifest.startswith("window.__TR_HASH_DEMO_GALLERY__ = "):
|
| 79 |
+
raise RuntimeError("The landing page is missing its embedded gallery manifest")
|
| 80 |
+
if embedded_manifest.count('"file":') != 300:
|
| 81 |
+
raise RuntimeError("The embedded gallery manifest must contain 300 images")
|
| 82 |
return node_types
|
| 83 |
|
| 84 |
|