LudoBench / js /viewer.js
jpeper's picture
Upload folder using huggingface_hub
194476e verified
// 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/launch/LudoBench/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>`;
}
}