Jonna Marie Matthiesen
Extract embed SVG generator into embed.js and add version tracking
5969585
// ─── Embed SVG Generator ─────────────────────────────────────────────────────
//
// Extracted module. Call initEmbed(deps) from the main app once globals are
// ready. Returns { showEmbedModal }.
// eslint-disable-next-line no-unused-vars
function initEmbed(deps) {
const {
config, filters, activeFamilyKey, getActiveModelSet,
getData, MODEL_COL, FAMILY_COL, GROUP_BY, CHART_CFG,
MODEL_COLORS, MODEL_SHORT, isOOMRow, isExternalModel,
sortModels, parseModelSize,
} = deps;
let embedModal = null;
let cachedCommitHash = null;
const HF_DOCS_REPO = "embedl/documentation-images";
const HF_SVG_FOLDER = "Edge-Inference-Benchmarks";
const HF_SPACES_REPO = "embedl/Edge-Inference-Benchmarks";
const HF_SPACES_URL = "https://huggingface.co/spaces/" + HF_SPACES_REPO;
async function loadCommitHash() {
if (cachedCommitHash) return cachedCommitHash;
try {
const resp = await fetch("https://huggingface.co/api/spaces/" + HF_SPACES_REPO);
if (resp.ok) {
const data = await resp.json();
cachedCommitHash = data.sha ? data.sha.substring(0, 7) : null;
}
} catch {}
return cachedCommitHash;
}
function embedFamilyKey() {
return filters.variant || activeFamilyKey();
}
function embedFileName() {
return embedFamilyKey() + "__" + filters[GROUP_BY] + ".svg";
}
function getEmbedChartData() {
const familyCfg = config.model_families?.[activeFamilyKey()] || {};
const chartCfg = familyCfg.chart || CHART_CFG;
const scenarios = chartCfg.scenarios || [];
const metricCol = filters.metric;
const metricCfg = config.metrics.find(m => m.column === metricCol) || {};
const groupFilterCfg = config.filters.find(f => f.column === GROUP_BY);
const groupVal = filters[GROUP_BY];
if (groupVal === "all") return null;
const groupLabel = groupFilterCfg?.value_labels?.[groupVal] || String(groupVal);
const familyModels = getActiveModelSet();
const filtered = getData().filter(r => {
if (!familyModels.has(r[MODEL_COL])) return false;
for (const f of config.filters) {
const fv = filters[f.column];
if (fv === "all" || fv === "" || fv === undefined) continue;
if (String(r[f.column]) !== String(fv)) return false;
}
return true;
});
const gRows = filtered.filter(r => String(r[GROUP_BY]) === String(groupVal));
if (!gRows.length) return null;
const uniqueModels = new Set(gRows.map(r => r[MODEL_COL]));
if (uniqueModels.size <= 1) return null;
const scenarioList = scenarios.length ? scenarios : [{ label: "", match: {} }];
// Find first scenario that produces data (mirrors buildChart logic)
let scenario = null;
let picked = [];
for (const sc of scenarioList) {
const matchRows = gRows.filter(r =>
Object.entries(sc.match || {}).every(([col, val]) =>
String(r[col]) === String(val)
)
);
const matchedModels = new Set(matchRows.map(r => r[MODEL_COL]));
const oomRows = gRows.filter(r => !matchedModels.has(r[MODEL_COL]) && isOOMRow(r)
&& Object.entries(sc.match || {}).every(([col, val]) =>
r[col] === null || r[col] === "" || r[col] === "OOM" || String(r[col]) === String(val)
)
);
const allRows = matchRows.concat(oomRows);
const models = sortModels([...new Set(allRows.map(r => r[MODEL_COL]))]);
const candidates = models.map(m => allRows.find(r => r[MODEL_COL] === m)).filter(Boolean);
if (candidates.length) {
scenario = sc;
picked = candidates;
break;
}
}
if (!picked.length) return null;
const hib = metricCfg.higher_is_better !== false;
picked.sort((a, b) => {
const sizeA = parseModelSize(a[FAMILY_COL] || a[MODEL_COL]);
const sizeB = parseModelSize(b[FAMILY_COL] || b[MODEL_COL]);
if (sizeA !== sizeB) return sizeA - sizeB;
const extA = isExternalModel(a[MODEL_COL]) ? 0 : 1;
const extB = isExternalModel(b[MODEL_COL]) ? 0 : 1;
if (extA !== extB) return extA - extB;
const va = a[metricCol] ?? 0;
const vb = b[metricCol] ?? 0;
return hib ? va - vb : vb - va;
});
const rawLabels = picked.map(r => MODEL_SHORT[r[MODEL_COL]]);
const families = new Set(picked.map(r => r[FAMILY_COL]));
const needPrefix = families.size > 1;
const labels = rawLabels.map((lbl, i) => {
if (needPrefix) {
const fk = picked[i][FAMILY_COL] || "";
return lbl ? `${fk} ${lbl}` : fk;
}
return lbl;
});
return {
familyKey: embedFamilyKey(),
groupLabel,
scenarioLabel: scenario.label || "",
metricCol,
metricLabel: metricCfg.short || metricCol,
higherIsBetter: hib,
picked,
labels,
};
}
function generateEmbedSVG(version) {
const data = getEmbedChartData();
if (!data) return null;
const { familyKey, groupLabel, scenarioLabel, metricCol, metricLabel, higherIsBetter, picked, labels } = data;
const width = 800;
const barHeight = 32;
const barGap = 10;
const topPad = 76;
const bottomPad = 44;
const leftPad = 260;
const rightPad = 90;
const barAreaWidth = width - leftPad - rightPad;
const contentHeight = picked.length * (barHeight + barGap) - barGap;
const height = topPad + contentHeight + bottomPad;
const maxVal = Math.max(...picked.map(r => r[metricCol] ?? 0), 1);
function esc(s) {
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
let bars = "";
picked.forEach((r, i) => {
const val = r[metricCol] ?? 0;
const barW = maxVal > 0 ? Math.max((val / maxVal) * barAreaWidth, 0) : 0;
const y = topPad + i * (barHeight + barGap);
const cy = y + barHeight / 2 + 5;
const color = MODEL_COLORS[r[MODEL_COL]]?.border || "#58b1c3";
const label = labels[i];
const oom = val === 0 && isOOMRow(r);
const valText = oom ? "OOM" : val.toFixed(1);
const valColor = oom ? "#ff4d6d" : "#e8e8e8";
bars += ` <text x="${leftPad - 14}" y="${cy}" text-anchor="end" fill="${color}" font-size="13" font-weight="500" font-family="system-ui,-apple-system,sans-serif">${esc(label)}</text>\n`;
if (!oom && barW > 0) {
bars += ` <rect x="${leftPad}" y="${y}" width="${barW.toFixed(1)}" height="${barHeight}" rx="4" fill="${color}" opacity="0.7"/>\n`;
}
bars += ` <text x="${leftPad + (oom ? 0 : barW) + 10}" y="${cy}" fill="${valColor}" font-size="13" font-weight="${oom ? "600" : "400"}" font-family="system-ui,-apple-system,sans-serif">${valText}</text>\n`;
});
const hint = higherIsBetter ? "(higher is better)" : "(lower is better)";
const subtitle = [familyKey, groupLabel, scenarioLabel, metricLabel + " " + hint].filter(Boolean).join(" \u00b7 ");
const commitAttr = version ? ` data-commit="${esc(version)}"` : "";
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}"${commitAttr}>
<defs>
<linearGradient id="topGlow" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#58b1c3" stop-opacity="0"/>
<stop offset="50%" stop-color="#58b1c3" stop-opacity="0.5"/>
<stop offset="100%" stop-color="#58b1c3" stop-opacity="0"/>
</linearGradient>
</defs>
<rect width="${width}" height="${height}" rx="12" fill="#0B1527"/>
<rect x="0" y="0" width="${width}" height="${height}" rx="12" fill="none" stroke="#58b1c3" stroke-opacity="0.15"/>
<rect x="80" y="1" width="${width - 160}" height="2" rx="1" fill="url(#topGlow)"/>
<text x="28" y="32" fill="#e8e8e8" font-size="17" font-weight="700" font-family="system-ui,-apple-system,sans-serif">Edge Inference Benchmarks</text>
<text x="28" y="54" fill="#8899aa" font-size="12" font-family="system-ui,-apple-system,sans-serif">${esc(subtitle)}</text>
${bars} <line x1="28" y1="${height - 36}" x2="${width - 28}" y2="${height - 36}" stroke="#58b1c3" stroke-opacity="0.15"/>
<text x="28" y="${height - 14}" fill="#5a6a7a" font-size="11" font-family="system-ui,-apple-system,sans-serif">embedl.com</text>
<text x="${width - 28}" y="${height - 14}" text-anchor="end" fill="#58b1c3" font-size="11" font-family="system-ui,-apple-system,sans-serif">View Benchmarks \u2192</text>
</svg>`;
}
function createEmbedModal() {
const overlay = document.createElement("div");
overlay.className = "embed-overlay";
overlay.innerHTML = `
<div class="embed-modal">
<div class="embed-modal-header">
<h3>Embed in Model Card</h3>
<button class="embed-close" aria-label="Close">&times;</button>
</div>
<div class="embed-preview" id="embed-preview"></div>
<div class="embed-status-bar">
<span class="embed-status" id="embed-status"></span>
</div>
<div class="embed-actions">
<button class="btn embed-action-btn" id="embed-copy">Copy Embed Code</button>
<button class="btn embed-action-btn embed-secondary" id="embed-upload-manual">Download SVG & Open HF</button>
</div>
<div class="embed-code-section">
<label>Paste this into your HuggingFace model card README.md:</label>
<textarea class="embed-code" id="embed-code" readonly rows="6"></textarea>
</div>
</div>
`;
overlay.querySelector(".embed-close").addEventListener("click", () => overlay.classList.remove("visible"));
overlay.addEventListener("click", e => { if (e.target === overlay) overlay.classList.remove("visible"); });
overlay.querySelector("#embed-upload-manual").addEventListener("click", () => {
const svg = generateEmbedSVG(cachedCommitHash);
if (!svg) return;
const fileName = embedFileName();
const blob = new Blob([svg], { type: "image/svg+xml" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
const commitMsg = "Update " + fileName + " (" + (cachedCommitHash || "latest") + ")";
window.open("https://huggingface.co/datasets/" + HF_DOCS_REPO + "/upload/main/" + HF_SVG_FOLDER + "?commit_message=" + encodeURIComponent(commitMsg), "_blank");
});
overlay.querySelector("#embed-copy").addEventListener("click", () => {
const textarea = overlay.querySelector("#embed-code");
navigator.clipboard.writeText(textarea.value);
const btn = overlay.querySelector("#embed-copy");
const orig = btn.textContent;
btn.textContent = "Copied!";
setTimeout(() => { btn.textContent = orig; }, 2000);
});
document.body.appendChild(overlay);
return overlay;
}
function setEmbedStatus(cls, html) {
const el = embedModal.querySelector("#embed-status");
el.className = "embed-status " + cls;
el.innerHTML = html;
}
function showUploadUI(show) {
embedModal.querySelector("#embed-upload-manual").style.display = show ? "" : "none";
}
async function runEmbedUpload() {
const version = cachedCommitHash;
const fileName = embedFileName();
const svgPath = HF_SVG_FOLDER + "/" + fileName;
const svgUrl = "https://huggingface.co/datasets/" + HF_DOCS_REPO + "/resolve/main/" + svgPath;
setEmbedStatus("checking", "Checking\u2026");
showUploadUI(false);
let remoteCommit = null;
let exists = false;
try {
const resp = await fetch(svgUrl, { cache: "no-store" });
if (resp.ok) {
exists = true;
const text = await resp.text();
const m = text.match(/data-commit="([^"]+)"/);
if (m) remoteCommit = m[1];
}
} catch {}
if (exists && version && remoteCommit === version) {
setEmbedStatus("up-to-date", "");
return;
}
if (exists) {
setEmbedStatus("needs-token", "SVG outdated \u2014 use Download SVG &amp; Open HF below to update");
} else {
setEmbedStatus("needs-token", "SVG not yet uploaded \u2014 use Download SVG &amp; Open HF below to upload manually");
}
showUploadUI(true);
}
async function showEmbedModal() {
if (!embedModal) embedModal = createEmbedModal();
const version = await loadCommitHash();
const svg = generateEmbedSVG(version);
if (!svg) return;
embedModal.querySelector("#embed-preview").innerHTML = svg;
const famKey = embedFamilyKey();
const fileName = embedFileName();
const svgUrl = "https://huggingface.co/datasets/" + HF_DOCS_REPO + "/resolve/main/" + HF_SVG_FOLDER + "/" + fileName;
const embedCode = '<a href="' + HF_SPACES_URL + '" target="_blank" rel="noopener">\n <img\n src="' + svgUrl + '"\n alt="Edge Inference Benchmarks for ' + famKey + '"\n width="100%"\n />\n</a>';
embedModal.querySelector("#embed-code").value = embedCode;
embedModal.classList.add("visible");
runEmbedUpload();
}
return { showEmbedModal };
} // end initEmbed