// Where the knowledge base lives and how to read it. ?data= points the page at another copy of the // dataset (e.g. a local working copy); every link the page makes keeps it. export const PARAMS = new URLSearchParams(location.search); export const DATA = (PARAMS.get("data") || "https://huggingface.co/datasets/abuzreq/model-bending-knowledge-base/resolve/main").replace(/\/$/, ""); export const DATASET = "abuzreq/model-bending-knowledge-base"; export const DATASET_URL = `https://huggingface.co/datasets/${DATASET}`; export const NAVIGATOR = "https://abuzreq-model-bending-navigator.static.hf.space"; export const SKILL_URL = "https://github.com/abuzreq/comfyui-model-bending-agent"; export const NODES_URL = "https://github.com/abuzreq/ComfyUI-Model-Bending"; export const PAPER_URL = "https://arxiv.org/abs/2607.22428"; export const file = (p) => `${DATA}/${p}`; export const baselineImg = (id) => file(`baselines/${id}.webp`); export const recordImg = (dir) => file(`${dir}/output.webp`); // Links inside the navigator: ?record=, ?cell=, ?base=, ?page=, always keeping ?data=. export function linkTo(q = {}, hash = "") { const p = new URLSearchParams(); if (PARAMS.get("data")) p.set("data", PARAMS.get("data")); for (const [k, v] of Object.entries(q)) if (v !== undefined && v !== null && v !== "") p.set(k, v); const s = p.toString(); return `${location.pathname}${s ? "?" + s : ""}${hash}`; } const cache = new Map(); function fetchText(p) { if (!cache.has(p)) { cache.set(p, fetch(file(p)).then((r) => { if (!r.ok) throw Object.assign(new Error(`${p}: HTTP ${r.status}`), { status: r.status }); return r.text(); })); cache.get(p).catch(() => cache.delete(p)); } return cache.get(p); } export const getJSON = (p) => fetchText(p).then((t) => JSON.parse(t)); export const getLines = (p) => fetchText(p).then((t) => t.split("\n").filter(Boolean).map((l) => JSON.parse(l))); let core; // Cells, findings, the effect vocabulary and the index's counts: what every page needs. export function loadCore() { core ||= Promise.all([getLines("index/cells.jsonl"), getLines("index/findings.jsonl"), getJSON("vocab/effects.json"), getJSON("index/meta.json").catch(() => null)]) .then(([cells, findings, vocab, meta]) => ({ cells, findings, vocab: vocab.tags || {}, meta })); return core; } // The unbent bases (index/navigator/, built by kb_navigator_index.py). Null when this copy has none yet. // Like counts, published by the likes server as community/likes.jsonl (counts only). Empty when none yet. let likes; export const loadLikes = () => (likes ||= getLines("community/likes.jsonl") .then((rows) => new Map(rows.map((r) => [r.record, r.likes]))).catch(() => new Map())); export const loadBases = () => getJSON("index/navigator/bases.json").then((d) => d.bases).catch(() => null); export async function loadBase(base) { const d = await getJSON(base.file); return d.rows.map((row) => Object.fromEntries(d.cols.map((c, i) => [c, row[i]]))); } // Old record and baseline ids (before an id migration) map to the current ones, possibly through several steps. let aliases; export async function resolveId(id, kind = "records") { aliases ||= getJSON("index/id_aliases.json").catch(() => ({})); const table = (await aliases)[kind] || {}; for (let i = 0; i < 10 && table[id]; i++) id = table[id]; return id; }