// ─── 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, "&").replace(//g, ">").replace(/"/g, """);
}
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 += `