Download leaderboard.js from CamoAiLab/InferenceNet-Leaderboard: direct link, hf CLI and curl.
- Browser
- Download file 23.1 kB
-
https://huggingface.co/spaces/CamoAiLab/InferenceNet-Leaderboard/resolve/main/leaderboard.js
- Command line
-
hf download hf://spaces/CamoAiLab/InferenceNet-Leaderboard/leaderboard.js
-
curl -L -o leaderboard.js https://huggingface.co/spaces/CamoAiLab/InferenceNet-Leaderboard/resolve/main/leaderboard.js
23.1 kB
| import { initSite } from './site-shell.js'; | |
| import { mountain } from './mountain.js'; | |
| ; | |
| const $ = id => document.getElementById(id); | |
| const metricKeys = ['perfect', 'partial_replication', 'compilation_success', 'coefficient_direction', 'significance_level']; | |
| const en = Object.fromEntries([...document.querySelectorAll('[data-i18n]')].map(el => [el.dataset.i18n, el.innerHTML])); | |
| Object.assign(en, { | |
| model: 'Model / API alias', rank: '#', score: 'Score', evidenceCol: 'Evidence', pp: 'pp', | |
| passed: 'successes', metricUnknown: 'metric unknown', review: 'Evidence gaps', | |
| config: 'Config', recordsLink: 'Results', allSealed: 'Archived', | |
| taskUnknown: 'task status unknown', invalid: 'invalid evidence', interruptions: 'interruptions', | |
| historicalStatus: 'Historical status; task unknown count not exported', | |
| groupLocal: 'Local paper profile', groupHF: 'HF-style profile · parity unverified', | |
| loading: 'Loading archived results…', searchPlaceholder: 'Search models…', | |
| matched: '{n} of {total} models', localProfile: 'local-paper-v1', hfProfile: 'hf-leaderboard-v1 · parity unverified', | |
| matchedRuns: '{n} of {total} runs', protocol: 'Protocol', independentRun: 'Independent · alias unverified', | |
| published: 'Published {date}', | |
| perfectDesc: 'Coefficient and standard-error relative errors ≤ 1%, and p-value absolute error ≤ 0.01, all at once.', | |
| partial_replicationDesc: 'Coefficient relative error ≤ 5%. No standard-error or p-value condition.', | |
| compilation_successDesc: 'Verified code execution completed successfully; a valid prediction JSON is not required.', | |
| coefficient_directionDesc: 'The coefficient has the same strictly positive or negative sign as the reference.', | |
| significance_levelDesc: 'Matching p-value category: p < .01, p < .05, p < .1, or otherwise. No direction condition.', | |
| }); | |
| const zh = { | |
| dataset: '数据集 ↗', archive: '结果存档 ↗', edition: '研究榜单 · 2026 年 9 月', | |
| title: 'InferenceNet 挑战赛排行榜', | |
| intro: '模型与 Harness 在 1,000 道计量经济学复现任务上的表现。', | |
| models: '种模型别名', groups: '组实验', tasks: '题 / 组', records: '条任务记录', published: '发布于 {date}', | |
| single: '单次 Agent', harness: '全部 Harness 实验', deepagents: 'DeepAgents', | |
| dsh: 'DeepSeek Harness · 独立实验', | |
| dshDesc: '使用官方 DSH SDK,请求 v4-pro API 别名。按固定规则纳入技术补测,底层模型权重未经核实。', | |
| dshNoticeTitle: 'DSH 作为独立实验展示。', | |
| dshNotice: '请求使用 deepseek-v4-pro 别名,底层模型权重未经核实。固定的 1,000 题结果包含 87 次技术补测替换(其中 27 题调整了输入 schema 提示),仍有 30 题状态未知;不纳入 DeepAgents 配对提升计算。', | |
| dshProtocol: '查看运行协议与证据 ↗', | |
| singleDesc: '一次模型调用生成代码,然后执行;不进行多轮 Agent 循环。', | |
| harnessDesc: 'DeepAgents 进行规划、工具调用与代码修订;最多 6 次模型调用,最后执行。', | |
| resultsLabel: '01 / 评测结果', rankings: 'Agent 与 Harness 对照榜单', research: '研究结果 · 阶段版', | |
| scope: '基于已存档结果的本地评分,尚未验证与官方评分器完全一致。两组预算及不同批次协议存在差异,提升值用于描述观察结果,不代表等成本对比。', | |
| compare: 'Baseline / DeepAgents 配对', download: '下载 CSV ↓', metric: '评分指标', | |
| perfect: '完整复现率 · 本地口径', partial_replication: '部分复现率 · HF 口径', | |
| compilation_success: '执行成功率 · HF 口径', coefficient_direction: '系数方向正确率 · HF 口径', | |
| significance_level: '显著性水平正确率 · HF 口径', | |
| sort: '排序方式', harnessScore: 'DeepAgents 组成绩', singleScore: '单次组成绩', lift: '提升', search: '查找模型', | |
| error: '结果加载失败,请重试或打开结果存档。', retry: '重试', empty: '没有匹配的模型。', | |
| denominator: '所有指标固定以 1,000 题为分母,未知和无效记录不计为成功。', | |
| methodLabel: '02 / 如何解读', methodTitle: '每个成绩都有据可查。', historical: '查看历史榜单 ↗', | |
| fixedTasks: '固定的题目集合', fixedDesc: '所有组使用相同的 Selected_1000 题号和数据集版本。成绩 = 成功数 ÷ 1,000,失败和未知记录始终保留在分母中。', | |
| separateMetrics: '两套评分口径', profilesDesc: '完整复现率采用本地论文口径。其余四项 HF 指标由本地实现复现网页定义,尚未验证与官方评分器一致。', | |
| budgetTitle: '结合预算看提升', budgetDesc: '单次组使用 1 次模型调用;DeepAgents 和 DSH 每次尝试最多 6 次模型调用、4 次试运行工具调用。DSH 额外进行了 87 次技术补测,完整环境和推理设定是否一致尚未核实;不计算 DSH 配对提升,也不能据此判断等成本的因果效果。', | |
| evidence: '证据状态与指标定义', | |
| unknownDesc: '“指标未知”表示该指标无法判断,可能源于执行失败或输出缺失;它与“任务最终状态未知”不同。两者均不剔除,也不计为成功。', | |
| evidenceCaption: '各组已接收存档中的状态', model: '模型 / API 别名', protocol: '执行方式', | |
| verification: '四项 HF 指标已与逐题存档标记核对。Sol 和 Opus 的完整复现率来自已接收汇总;其余实验(含 DSH)均可逐题核对本地评分标记。本次网页更新没有重新运行或重新评分实验。', | |
| archiveNote: '各组原始存档及当时的发布元数据保持不变,本页是基于存档的新展示。本版不包含 GPT-5.5 和尚未存档的实验。', | |
| footer: '衡量计量经济学复现能力。', source: '来源快照 ↗', json: '数据 JSON ↗', project: '项目页 ↗', | |
| rank: '#', score: '成绩', evidenceCol: '证据', pp: '百分点', passed: '题成功', metricUnknown: '指标未知', review: '存在证据缺口', | |
| config: '配置', recordsLink: '逐题结果', allSealed: '已存档', taskUnknown: '任务状态未知', invalid: '无效证据', interruptions: '中断记录', | |
| historicalStatus: '历史存档,未导出任务未知总数', groupLocal: '本地论文口径', groupHF: 'HF 网页口径 · 一致性待验证', | |
| loading: '正在加载存档结果…', searchPlaceholder: '搜索模型…', matched: '显示 {n} / {total} 个模型', | |
| matchedRuns: '显示 {n} / {total} 组实验', independentRun: '独立实验 · 别名权重未核实', | |
| localProfile: 'local-paper-v1 · 本地口径', hfProfile: 'hf-leaderboard-v1 · 一致性待验证', | |
| perfectDesc: '系数与标准误的相对误差均 ≤ 1%,同时 p 值绝对误差 ≤ 0.01。', | |
| partial_replicationDesc: '系数相对误差 ≤ 5%,不要求标准误或 p 值达标。', | |
| compilation_successDesc: '有可信证据证明代码完整执行成功,不要求生成有效的预测 JSON。', | |
| coefficient_directionDesc: '预测系数与参考系数具有相同的严格正号或负号。', | |
| significance_levelDesc: 'p 值类别一致:p < .01、p < .05、p < .1 或其他,不要求系数方向一致。', | |
| }; | |
| let language = 'en'; | |
| try { language = localStorage.getItem('inferencenet-language') === 'zh' ? 'zh' : 'en'; } catch (_) {} | |
| let data = null; | |
| let view = 'all'; | |
| let display = 'table'; | |
| let sortBy = 'deepagents'; | |
| const t = key => (language === 'zh' ? zh[key] : en[key]) ?? en[key] ?? key; | |
| const esc = value => String(value).replace(/[&<>"']/g, char => ({'&':'&', '<':'<', '>':'>', '"':'"', "'":'''}[char])); | |
| const sourceURL = (path, tree = false) => `https://huggingface.co/spaces/CamoAiLab/InferenceNet-Leaderboard/${tree ? 'tree' : 'blob'}/${data.source_revision}/${path}`; | |
| const external = (url, label) => `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(label)} ↗</a>`; | |
| Object.assign(en, { | |
| publicationNote: 'Research snapshot · local scoring; official scorer parity unverified. Run budgets differ.', | |
| all: 'All results', website: 'Project website ↗', tableView: 'Table', mountainView: 'Mountain view', | |
| evaluationDetails: 'Evaluation protocol, definitions & evidence', detailsLink: 'Details ↗', | |
| dshShort: 'DSH: model weights unverified; 87 technical replacements, 30 unknown task outcomes. Excluded from paired lift.', | |
| harnessColumn: 'Harness', noHarness: 'None', independentLabel: 'Independent', | |
| light: 'Light', dark: 'Dark', fullShort: 'Full replication', partialShort: 'Partial replication', | |
| executionShort: 'Execution success', directionShort: 'Coefficient direction', significanceShort: 'Significance level', | |
| mountainTitle: 'The climb to full replication', summit: '100% · every task succeeds', | |
| mountainNote: 'Height represents the selected score. Run protocols and budgets differ.', | |
| scoreDetails: 'Successes / tasks; metric unknown', comparisonNote: 'Paired view includes baseline and DeepAgents only. DSH remains a separate result.', | |
| }); | |
| Object.assign(zh, { | |
| publicationNote: '研究快照 · 本地评分,尚未验证与官方评分器一致;各组运行预算存在差异。', | |
| all: '全部结果', website: '项目官网 ↗', tableView: '成绩表', mountainView: '登山图', | |
| evaluationDetails: '评测协议、指标定义与证据', detailsLink: '查看详情 ↗', | |
| dshShort: 'DSH:模型权重未核实,含 87 次技术补测替换、30 题状态未知;不纳入配对提升。', | |
| harnessColumn: 'Harness', noHarness: '无', independentLabel: '独立实验', | |
| light: '浅色', dark: '深色', fullShort: '完整复现率', partialShort: '部分复现率', | |
| executionShort: '执行成功率', directionShort: '系数方向', significanceShort: '显著性水平', | |
| mountainTitle: '向完整复现迈进', summit: '100% · 每道任务均成功', | |
| mountainNote: '高度表示所选指标得分。各组运行协议与预算存在差异。', | |
| scoreDetails: '成功数 / 题数;指标未知', comparisonNote: '配对视图仅比较单次组与 DeepAgents;DSH 单独展示。', | |
| metric: '排名指标', single: '单次生成', harness: '模型 + Harness', compare: '配对提升', | |
| }); | |
| const shortKeys = ['fullShort', 'partialShort', 'executionShort', 'directionShort', 'significanceShort']; | |
| function applyLanguage() { | |
| document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en'; | |
| document.querySelectorAll('[data-i18n]').forEach(el => { el.innerHTML = t(el.dataset.i18n); }); | |
| $('language').textContent = language === 'en' ? '中文' : 'EN'; | |
| $('language').setAttribute('aria-label', language === 'en' ? 'Switch to Chinese' : 'Switch to English'); | |
| $('search').placeholder = t('searchPlaceholder'); | |
| $('load-state').textContent = t('loading'); | |
| document.querySelector('[data-group=local]').label = t('groupLocal'); | |
| document.querySelector('[data-group=hf]').label = t('groupHF'); | |
| $('definitions').innerHTML = metricKeys.map(key => `<li><strong>${esc(t(key))}:</strong> ${esc(t(key + 'Desc'))}</li>`).join(''); | |
| render(); | |
| } | |
| function validate(candidate) { | |
| if (candidate.schema_version !== 1 || candidate.models?.length !== 6 || ![12, 13].includes(candidate.groups) || candidate.records !== candidate.groups * 1000 || candidate.tasks_per_group !== 1000 || candidate.official_parity_verified !== false || !/^[a-f0-9]{40}$/.test(candidate.source_revision) || !/^\d{4}-\d{2}-\d{2}$/.test(candidate.published_date)) throw new Error('Invalid leaderboard snapshot'); | |
| const ids = new Set(); | |
| let groups = 0; | |
| for (const model of candidate.models) { | |
| if (!/^[a-z0-9.-]+$/.test(model.id) || ids.has(model.id) || typeof model.name !== 'string') throw new Error('Invalid model'); | |
| ids.add(model.id); | |
| if (!model.arms?.baseline || !model.arms?.deepagents) throw new Error('Missing paired arm'); | |
| for (const armId of Object.keys(model.arms)) { | |
| if (!['baseline', 'deepagents'].includes(armId) && !(model.id === 'deepseek-v4-pro' && armId === 'dsh')) throw new Error('Unexpected arm'); | |
| const arm = model.arms?.[armId]; | |
| if (arm?.path !== `results/${model.id}/${armId}` || typeof arm.complete !== 'boolean') throw new Error('Invalid arm'); | |
| if (armId === 'dsh' && (arm.paired_comparison !== false || arm.comparison_group !== 'independent' || arm.underlying_model_weights_verified !== false || arm.model_alias !== 'deepseek-v4-pro' || typeof arm.display_name !== 'string' || arm.evidence_class !== 'technical_recovery_view' || arm.protocol_url !== arm.path + '/README.md')) throw new Error('Invalid independent result'); | |
| groups += 1; | |
| for (const key of metricKeys) { | |
| const metric = arm.metrics?.[key]; | |
| if (!metric || metric.denominator !== 1000 || !Number.isInteger(metric.count) || !Number.isInteger(metric.unknown) || metric.count < 0 || metric.unknown < 0 || metric.count + metric.unknown > 1000 || !Number.isFinite(metric.score) || Math.abs(metric.score - metric.count / 10) > 1e-8 || metric.profile !== (key === 'perfect' ? 'local-paper-v1' : 'hf-leaderboard-v1')) throw new Error('Invalid metric'); | |
| } | |
| } | |
| } | |
| if (groups !== candidate.groups) throw new Error('Inconsistent group count'); | |
| return candidate; | |
| } | |
| // Pairing is intentionally limited to the two historical arms. Independent | |
| // harness entries have their own rank and can never enter a paired lift. | |
| function rankedEntries(snapshot, selectedView, key, selectedSort, query) { | |
| const entries = selectedView === 'compare' | |
| ? snapshot.models.map(model => ({model, armId: null, arm: model.arms.baseline, name: model.name, | |
| value: selectedSort === 'gain' ? model.arms.deepagents.metrics[key].count - model.arms.baseline.metrics[key].count : model.arms[selectedSort].metrics[key].count})) | |
| : snapshot.models.flatMap(model => Object.entries(model.arms) | |
| .filter(([id]) => selectedView === 'all' || (selectedView === 'baseline' ? id === 'baseline' : id !== 'baseline')) | |
| .map(([armId, arm]) => ({model, armId, arm, name: armId === 'dsh' ? 'DeepSeek API · v4-pro alias' : model.name, value: arm.metrics[key].count}))); | |
| const search = query.toLowerCase().trim(); | |
| const filtered = entries.filter(entry => `${entry.name} ${entry.model.id} ${entry.armId || ''} ${entry.arm.display_name || ''}`.toLowerCase().includes(search)) | |
| .sort((a, b) => b.value - a.value || a.name.localeCompare(b.name) || String(a.armId).localeCompare(String(b.armId))); | |
| let last = null, rank = 0; | |
| filtered.forEach((entry, i) => { if (entry.value !== last) rank = i + 1; last = entry.value; entry.rank = rank; }); | |
| return {total: entries.length, entries: filtered}; | |
| } | |
| function protocolName(armId) { return armId === 'baseline' ? t('noHarness') : armId === 'dsh' ? 'DSH' : 'DeepAgents'; } | |
| function compactScore(arm, key, active) { | |
| const m = arm.metrics[key]; | |
| const description = `${m.count} / 1,000 ${t('passed')}; ${t('metricUnknown')}: ${m.unknown}`; | |
| return `<td class="numeric ${active ? 'selected-metric' : ''}"><span tabindex="0" class="metric-value" aria-label="${esc(m.score.toFixed(1) + '%; ' + description)}">${m.score.toFixed(1)}<small>%</small><span class="metric-tooltip" role="tooltip">${esc(description)}</span></span></td>`; | |
| } | |
| function renderMountain(entries, key) { | |
| const el = $('ranking-mountain'); | |
| el.hidden = display !== 'mountain' || entries.length === 0 || view === 'compare'; | |
| if (el.hidden) return; | |
| // Mountain markers use unique ordinal IDs, with tied scores at the same altitude. | |
| mountain(el, { | |
| title: t('mountainTitle'), metric: t(shortKeys[metricKeys.indexOf(key)]), unit: '%', max: 100, | |
| peakLabel: t('summit'), caption: t('mountainNote'), | |
| items: entries.map((entry, i) => ({rank: i + 1, label: `${entry.name} · ${protocolName(entry.armId)}${entry.armId === 'dsh' ? ' *' : ''}`, value: entry.arm.metrics[key].score, kind: 'internal'})), | |
| locale: language, | |
| }, {table: false, aspect: 0.44, compactAspect: 0.75}); | |
| } | |
| function scoreCell(arm, key, style) { | |
| const m = arm.metrics[key]; | |
| return `<td class="score-cell ${style}"><span class="score">${m.score.toFixed(1)}<small>%</small></span><div class="track" aria-hidden="true"><div class="fill" style="width:${m.score}%"></div></div><span class="counts">${m.count} / 1,000 ${esc(t('passed'))}</span><span class="unknown">${esc(t('metricUnknown'))}: ${m.unknown}</span></td>`; | |
| } | |
| function evidenceState(arm) { | |
| let status = ''; | |
| if (arm.task_unknown_count !== null) status = `${arm.task_unknown_count} ${t('taskUnknown')} · ${arm.invalid_evidence_count} ${t('invalid')}`; | |
| else status = `${arm.interruption_count ?? '—'} ${t('interruptions')}<span>${esc(t('historicalStatus'))}</span>`; | |
| return `<td>${status}<span>${external(sourceURL(arm.path + '/config.json'), t('config'))} · ${external(sourceURL(arm.path + '/summary.json'), t('recordsLink'))}</span></td>`; | |
| } | |
| function render() { | |
| const key = $('metric').value; | |
| $('metric-description').textContent = t(key + 'Desc'); | |
| if (!data) return; | |
| const {total, entries} = rankedEntries(data, view, key, sortBy, $('search').value); | |
| $('published-date').textContent = t('published').replace('{date}', data.published_date); | |
| $('sort-control').hidden = view !== 'compare'; | |
| $('table-wrap').hidden = entries.length === 0 || (display === 'mountain' && view !== 'compare'); | |
| $('empty').hidden = entries.length !== 0; | |
| $('result-count').textContent = t(view === 'compare' ? 'matched' : 'matchedRuns').replace('{n}', entries.length).replace('{total}', total); | |
| $('caption').textContent = `${t(view === 'compare' ? 'compare' : view === 'all' ? 'all' : view === 'baseline' ? 'single' : 'harness')} — ${t(key)}`; | |
| const th = (text, cls = '') => `<th scope="col" class="${cls}">${esc(text)}</th>`; | |
| $('table-head').innerHTML = '<tr>' + th(t('rank'), 'rank') + th(t('model'), 'model-heading') + (view === 'compare' | |
| ? th(t('single'), 'numeric') + th('DeepAgents', 'numeric') + th(t('lift'), 'numeric') | |
| : th(t('harnessColumn')) + metricKeys.map((metric, i) => `<th scope="col" class="numeric ${metric === key ? 'selected-metric' : ''}" aria-sort="${metric === key ? 'descending' : 'none'}"><button type="button" data-metric="${metric}" title="${esc(t(metric + 'Desc'))}">${esc(t(shortKeys[i]))}${metric === key ? ' ↓' : ''}</button></th>`).join('')) + th(t('evidenceCol'), 'evidence-col') + '</tr>'; | |
| $('table-body').innerHTML = entries.map(entry => { | |
| const {model, arm, armId, name, rank} = entry; | |
| const arms = model.arms; | |
| const identity = `<td class="rank"><span class="rank-number rank-${rank}">${rank}</span></td><th scope="row" class="model-cell">${esc(name)}</th>`; | |
| let scores = '', sources = ''; | |
| if (view === 'compare') { | |
| const gain = (arms.deepagents.metrics[key].count - arms.baseline.metrics[key].count) / 10; | |
| scores = scoreCell(arms.baseline, key, 'baseline') + scoreCell(arms.deepagents, key, 'harness') + `<td class="gain numeric ${gain < 0 ? 'negative' : ''}">${gain > 0 ? '+' : ''}${gain.toFixed(1)}<small>${esc(t('pp'))}</small></td>`; | |
| sources = external(sourceURL(arms.baseline.path, true), 'A') + external(sourceURL(arms.deepagents.path, true), 'B'); | |
| } else { | |
| const badge = armId === 'dsh' ? `<a class="independent-tag" href="#protocol-details">${esc(t('independentLabel'))} ↗</a>` : ''; | |
| scores = `<td class="protocol-cell"><span class="protocol-label ${armId === 'baseline' ? 'no-harness' : ''}">${esc(protocolName(armId))}</span>${badge}</td>` + metricKeys.map(metric => compactScore(arm, metric, metric === key)).join(''); | |
| sources = external(sourceURL(arm.path + '/results.jsonl'), t('recordsLink')) + (!arm.complete ? `<span class="evidence-gap">${esc(t('review'))}</span>` : ''); | |
| } | |
| return `<tr data-model="${esc(model.id)}" data-arm="${esc(armId || 'paired')}">${identity}${scores}<td class="source-cell">${sources}</td></tr>`; | |
| }).join(''); | |
| $('evidence-body').innerHTML = data.models.flatMap(m => Object.entries(m.arms).map(([id, arm]) => `<tr><th scope="row">${esc(arm.display_name || m.name)}</th><td>${esc(id === 'baseline' ? t('single') : id === 'dsh' ? t('dsh') : 'DeepAgents')}</td>${evidenceState(arm)}</tr>`)).join(''); | |
| const dsh = data.models.find(m => m.id === 'deepseek-v4-pro')?.arms.dsh; | |
| $('independent-notice').hidden = !dsh; | |
| $('independent-summary').hidden = !dsh || view === 'baseline' || view === 'compare'; | |
| if (dsh) $('dsh-protocol').href = sourceURL(dsh.protocol_url); | |
| document.querySelector('[data-display="mountain"]').disabled = view === 'compare'; | |
| renderMountain(entries, key); | |
| } | |
| async function loadData() { | |
| data = null; | |
| $('retry').disabled = true; | |
| $('error').hidden = true; | |
| $('load-state').hidden = false; | |
| $('table-wrap').hidden = true; | |
| $('ranking-mountain').hidden = true; | |
| $('independent-summary').hidden = true; | |
| $('empty').hidden = true; | |
| $('result-count').textContent = ''; | |
| $('evidence-body').replaceChildren(); | |
| $('independent-notice').hidden = true; | |
| for (const id of ['model-count', 'group-count', 'task-count', 'record-count']) $(id).textContent = '—'; | |
| try { | |
| const response = await fetch('./leaderboard-data.json', {cache: 'no-cache'}); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}`); | |
| data = validate(await response.json()); | |
| $('model-count').textContent = data.models.length; | |
| $('group-count').textContent = data.groups; | |
| $('task-count').textContent = data.tasks_per_group.toLocaleString('en-US'); | |
| $('record-count').textContent = data.records.toLocaleString('en-US'); | |
| $('revision-link').href = sourceURL('results', true); | |
| render(); | |
| } catch (error) { | |
| console.error('Leaderboard data could not be loaded:', error); | |
| $('error').hidden = false; | |
| } finally { | |
| $('load-state').hidden = true; | |
| $('retry').disabled = false; | |
| } | |
| } | |
| document.querySelectorAll('[data-view]').forEach(button => button.addEventListener('click', () => { | |
| view = button.dataset.view; | |
| if (view === 'compare') setDisplay('table'); | |
| document.querySelectorAll('[data-view]').forEach(el => el.setAttribute('aria-pressed', String(el === button))); | |
| render(); | |
| })); | |
| $('metric').addEventListener('change', render); | |
| $('sort').addEventListener('change', event => { sortBy = event.target.value; render(); }); | |
| $('search').addEventListener('input', render); | |
| $('retry').addEventListener('click', loadData); | |
| function setDisplay(next) { | |
| display = next; | |
| document.querySelectorAll('[data-display]').forEach(el => el.setAttribute('aria-pressed', String(el.dataset.display === next))); | |
| render(); | |
| } | |
| document.querySelectorAll('[data-display]').forEach(button => button.addEventListener('click', () => setDisplay(button.dataset.display))); | |
| $('table-head').addEventListener('click', event => { | |
| const button = event.target.closest('[data-metric]'); | |
| if (button) { $('metric').value = button.dataset.metric; render(); } | |
| }); | |
| document.addEventListener('click', event => { | |
| if (event.target.closest('a[href="#protocol-details"]')) $('protocol-details').open = true; | |
| }); | |
| initSite({ onLanguageChange: next => { language = next; applyLanguage(); } }); | |
| applyLanguage(); | |
| loadData(); | |