Spaces:
Running
Running
| // Dataset browser (adapted from anon-submission-2026/docs/viewer.js) | |
| // Wrapped in lazy-init so it only loads when the Dataset Browser tab is opened. | |
| let _viewerInitialized = false; | |
| // Base URL for images hosted in the HF dataset repo | |
| const IMAGE_BASE_URL = "https://huggingface.co/datasets/jpeper/LudoBench_test/resolve/main/images"; | |
| // ----------------------- | |
| // Manifest loader | |
| // ----------------------- | |
| async function loadManifest() { | |
| const res = await fetch("manifest.json"); | |
| if (!res.ok) throw new Error("Failed to load manifest: " + res.status); | |
| const manifest = await res.json(); | |
| return manifest.files; | |
| } | |
| // ----------------------- | |
| // Folder handling | |
| // ----------------------- | |
| function buildFolderIndex(files) { | |
| const set = new Set(); | |
| for (const f of files) set.add(f.folder); | |
| return Array.from(set).sort(); | |
| } | |
| function populateFolderSelect(folders) { | |
| const sel = document.getElementById("folderSelect"); | |
| sel.innerHTML = ""; | |
| for (const f of folders) { | |
| const opt = document.createElement("option"); | |
| opt.textContent = f; | |
| opt.value = f; | |
| sel.appendChild(opt); | |
| } | |
| } | |
| function filterFiles(files, folder) { | |
| return files.filter(f => f.folder === folder); | |
| } | |
| function populateFileSelect(files) { | |
| const sel = document.getElementById("fileSelect"); | |
| sel.innerHTML = ""; | |
| for (const f of files) { | |
| const opt = document.createElement("option"); | |
| opt.value = f.json_path; | |
| opt.textContent = f.name; | |
| sel.appendChild(opt); | |
| } | |
| } | |
| // ----------------------- | |
| // Load one JSON annotation | |
| // ----------------------- | |
| async function loadJson(path) { | |
| const res = await fetch(path); | |
| if (!res.ok) throw new Error("Error loading " + path); | |
| return await res.json(); | |
| } | |
| // ----------------------- | |
| // Rendering question + answer | |
| // ----------------------- | |
| function normalizedAnswers(raw) { | |
| return new Set( | |
| String(raw || "") | |
| .split(/,|\/|\bor\b/i) | |
| .map(s => s.trim().toLowerCase()) | |
| .filter(Boolean) | |
| ); | |
| } | |
| function isNumber(s) { | |
| return s !== "" && !Number.isNaN(Number(s)); | |
| } | |
| function renderQuestion(data) { | |
| const card = document.getElementById("questionCard"); | |
| card.innerHTML = ` | |
| <h2>${data.Game} <span class="id-tag">(ID ${data.ID})</span></h2> | |
| <p class="question-label">Question:</p> | |
| <p class="question-text">${data.Question}</p> | |
| `; | |
| const info = document.getElementById("answerInfo"); | |
| info.textContent = ""; | |
| info.className = "answer-info"; | |
| } | |
| function attachAnswerLogic(data) { | |
| const raw = String(data.Answer || "").trim(); | |
| const accepted = normalizedAnswers(raw); | |
| const input = document.getElementById("answerInput"); | |
| const button = document.getElementById("checkButton"); | |
| const info = document.getElementById("answerInfo"); | |
| const sol = document.getElementById("solutionText"); | |
| sol.innerHTML = `<strong>Expected:</strong> ${raw || "\u2014"}`; | |
| button.onclick = () => { | |
| const user = input.value.trim().toLowerCase(); | |
| let ok = false; | |
| if (accepted.has(user)) ok = true; | |
| else if (isNumber(user)) { | |
| for (const a of accepted) | |
| if (isNumber(a) && Math.abs(Number(a) - Number(user)) < 1e-9) | |
| ok = true; | |
| } | |
| info.textContent = ok ? "Correct!" : "Not quite. Try again."; | |
| info.className = "answer-info " + (ok ? "correct" : "wrong"); | |
| }; | |
| } | |
| // ----------------------- | |
| // Render images (local paths) | |
| // ----------------------- | |
| function renderImage(data) { | |
| const container = document.getElementById("imageContainer"); | |
| const multi = document.getElementById("multiImages"); | |
| multi.innerHTML = ""; | |
| container.classList.add("hidden"); | |
| let urls = data.game_state_url; | |
| if (!urls) return; | |
| if (!Array.isArray(urls)) urls = [urls]; | |
| const folder = data.Game.toLowerCase().replace(/\s+/g, "_"); | |
| urls.forEach(url => { | |
| const file = url.split("/").pop(); | |
| const localPath = `${IMAGE_BASE_URL}/${folder}/${file}`; | |
| const block = document.createElement("div"); | |
| block.className = "multi-img-block"; | |
| const spinner = document.createElement("div"); | |
| spinner.className = "spinner"; | |
| block.appendChild(spinner); | |
| const img = document.createElement("img"); | |
| img.style.display = "none"; | |
| img.src = localPath; | |
| img.onload = () => { | |
| spinner.style.display = "none"; | |
| img.style.display = "block"; | |
| }; | |
| img.onerror = () => { | |
| spinner.style.display = "none"; | |
| const err = document.createElement("div"); | |
| err.textContent = "Failed to load " + localPath; | |
| err.style.color = "#d44"; | |
| block.appendChild(err); | |
| }; | |
| const link = document.createElement("a"); | |
| link.href = localPath; | |
| link.target = "_blank"; | |
| link.rel = "noopener noreferrer"; | |
| link.appendChild(img); | |
| block.appendChild(link); | |
| const caption = document.createElement("div"); | |
| caption.className = "multi-img-caption"; | |
| caption.textContent = file; | |
| block.appendChild(caption); | |
| const full = document.createElement("a"); | |
| full.href = localPath; | |
| full.target = "_blank"; | |
| full.rel = "noopener noreferrer"; | |
| full.className = "full-img-link"; | |
| full.textContent = "View full image"; | |
| block.appendChild(full); | |
| multi.appendChild(block); | |
| }); | |
| container.classList.remove("hidden"); | |
| } | |
| // ----------------------- | |
| // Navigation (prev / next) | |
| // ----------------------- | |
| let GLOBAL_FILES = []; | |
| let GLOBAL_CURRENT_FOLDER = ""; | |
| let loadAndRenderRef = null; | |
| function goRelative(offset) { | |
| const fileSelect = document.getElementById("fileSelect"); | |
| const options = Array.from(fileSelect.options); | |
| if (options.length === 0) return; | |
| const values = options.map(o => o.value); | |
| const current = fileSelect.value; | |
| let idx = values.indexOf(current); | |
| if (idx === -1) return; | |
| let next = idx + offset; | |
| if (next < 0) next = values.length - 1; | |
| if (next >= values.length) next = 0; | |
| fileSelect.value = values[next]; | |
| if (loadAndRenderRef) loadAndRenderRef(values[next]); | |
| } | |
| // ----------------------- | |
| // Lazy initialization | |
| // ----------------------- | |
| async function initViewer() { | |
| if (_viewerInitialized) return; | |
| _viewerInitialized = true; | |
| document.getElementById("prevBtn").onclick = () => goRelative(-1); | |
| document.getElementById("nextBtn").onclick = () => goRelative(1); | |
| const questionCard = document.getElementById("questionCard"); | |
| try { | |
| const files = await loadManifest(); | |
| GLOBAL_FILES = files; | |
| const folders = buildFolderIndex(files); | |
| populateFolderSelect(folders); | |
| const folderSel = document.getElementById("folderSelect"); | |
| const fileSel = document.getElementById("fileSelect"); | |
| async function loadAndRender(path) { | |
| const data = await loadJson(path); | |
| renderQuestion(data); | |
| attachAnswerLogic(data); | |
| renderImage(data); | |
| } | |
| loadAndRenderRef = loadAndRender; | |
| function refresh() { | |
| GLOBAL_CURRENT_FOLDER = folderSel.value; | |
| const filtered = filterFiles(files, GLOBAL_CURRENT_FOLDER); | |
| populateFileSelect(filtered); | |
| if (filtered.length > 0) | |
| loadAndRender(filtered[0].json_path); | |
| } | |
| folderSel.onchange = refresh; | |
| fileSel.onchange = () => loadAndRender(fileSel.value); | |
| folderSel.value = "kingdomino_tier1"; | |
| refresh(); | |
| } catch (err) { | |
| console.error(err); | |
| questionCard.innerHTML = `<p style="color:#f55;">Init error: ${err}</p>`; | |
| } | |
| } | |