Spaces:
Running
Running
| const headerRowTop = document.getElementById('header-row-top'); | |
| const headerRowSub = document.getElementById('header-row-sub'); | |
| const bodyRow = document.getElementById('body-row'); | |
| const searchInput = document.getElementById('search'); | |
| const typeFilters = Array.from(document.querySelectorAll('.type-filter')); | |
| const copyCitationBtn = document.getElementById('copy-citation-btn'); | |
| const citationText = document.getElementById('citation-text'); | |
| const GROUPS = [ | |
| { id: 'avg', label: 'Avg. (5)', prefix: 'avg' }, | |
| { id: 'lc', label: 'LangChain', prefix: 'lc' }, | |
| { id: 'yolo', label: 'Yolo v7 & v8', prefix: 'yolo' }, | |
| { id: 'laravel', label: 'Laravel 10 & 11', prefix: 'laravel' }, | |
| { id: 'angular', label: 'Angular 16, 17 & 18', prefix: 'angular' }, | |
| { id: 'godot', label: 'Godot4', prefix: 'godot' } | |
| ]; | |
| const METRICS = [ | |
| { id: 'a10', label: 'α@10' }, | |
| { id: 'c20', label: 'C@20' }, | |
| { id: 'r50', label: 'R@50' } | |
| ]; | |
| let rows = []; | |
| let sortKey = 'avg_r50'; | |
| let sortAsc = false; | |
| const PLOT_METRICS = [ | |
| { id: 'alpha_ndcg_10', key: 'avg_a10', plotId: 'plot-avg-alpha10', title: 'alpha-nDCG@10', yLabel: 'α@10 (Avg. 5)', yMin: 0.1, yMax: 0.541 }, | |
| { id: 'coverage_20', key: 'avg_c20', plotId: 'plot-avg-c20', title: 'Coverage@20', yLabel: 'C@20 (Avg. 5)', yMin: 0.25, yMax: 0.868 }, | |
| { id: 'recall_50', key: 'avg_r50', plotId: 'plot-avg-r50', title: 'Recall@50', yLabel: 'R@50 (Avg. 5)', yMin: 0.15, yMax: 0.755 } | |
| ]; | |
| const DATE_PLOT_METRICS = [ | |
| { id: 'alpha_ndcg_10', key: 'avg_a10', plotId: 'plot-date-avg-alpha10', title: 'alpha-nDCG@10', yLabel: 'α@10 (Avg. 5)', yMin: 0.1, yMax: 0.541 }, | |
| { id: 'coverage_20', key: 'avg_c20', plotId: 'plot-date-avg-c20', title: 'Coverage@20', yLabel: 'C@20 (Avg. 5)', yMin: 0.25, yMax: 0.868 }, | |
| { id: 'recall_50', key: 'avg_r50', plotId: 'plot-date-avg-r50', title: 'Recall@50', yLabel: 'R@50 (Avg. 5)', yMin: 0.15, yMax: 0.755 } | |
| ]; | |
| function num(v) { | |
| return typeof v === 'number' ? v.toFixed(3) : '-'; | |
| } | |
| function typeBadge(type) { | |
| const labels = { | |
| open_source: 'Open Source', | |
| proprietary: 'Proprietary', | |
| upper_baseline: 'Oracle' | |
| }; | |
| return `<span class="type-pill type-${type}">${labels[type] || type}</span>`; | |
| } | |
| function isNewModel(dateStr) { | |
| if (!dateStr) return false; | |
| const modelDate = new Date(dateStr); | |
| if (Number.isNaN(modelDate.getTime())) return false; | |
| const now = new Date(); | |
| const daysDiff = (now - modelDate) / (1000 * 60 * 60 * 24); | |
| return daysDiff >= 0 && daysDiff <= 90; | |
| } | |
| function parseSizeToBillions(sizeStr) { | |
| if (!sizeStr || sizeStr === '-') return null; | |
| const m = String(sizeStr).trim().match(/^([\d.]+)\s*([BMK])$/i); | |
| if (!m) return null; | |
| const numValue = parseFloat(m[1]); | |
| const unit = m[2].toUpperCase(); | |
| if (Number.isNaN(numValue)) return null; | |
| if (unit === 'B') return numValue; | |
| if (unit === 'M') return numValue / 1000; | |
| if (unit === 'K') return numValue / 1e6; | |
| return null; | |
| } | |
| function formatParameterSize(sizeInBillions) { | |
| if (sizeInBillions === undefined || sizeInBillions === null || Number.isNaN(sizeInBillions)) return '-'; | |
| if (sizeInBillions < 1) { | |
| const inMillions = sizeInBillions * 1000; | |
| return `${parseFloat(inMillions.toFixed(2))}M`; | |
| } | |
| return `${parseFloat(sizeInBillions.toFixed(3))}B`; | |
| } | |
| function inferFamily(name) { | |
| const n = String(name || '').toLowerCase(); | |
| if (n.includes('stella') || n.includes('jasper')) return 'Stella'; | |
| if (n.includes('harrier')) return 'Harrier OSS'; | |
| if (n.includes('voyage')) return 'Voyage'; | |
| if (n.includes('jina')) return 'Jina'; | |
| if (n.includes('qwen3')) return 'Qwen3'; | |
| if (n.includes('granite')) return 'IBM Granite'; | |
| if (n.includes('arctic embed')) return 'Arctic Embed'; | |
| if (n.includes('perplexity embed')) return 'Perplexity Embed'; | |
| if (n.includes('nomic embed') || n.includes('coderankembed')) return 'Nomic Embed'; | |
| if (n.includes('bge')) return 'BGE'; | |
| if (n.includes('e5')) return 'E5'; | |
| if (n.includes('gte')) return 'GTE'; | |
| if (n.includes('bm25')) return 'BM25'; | |
| if (n.includes('fusion')) return 'Fusion'; | |
| return 'Other'; | |
| } | |
| function normalizeModelName(rawName) { | |
| return String(rawName || '').toLowerCase().replace(/^oracle:\s*/i, '').trim(); | |
| } | |
| function isReferenceBaselineModel(rawName) { | |
| const name = normalizeModelName(rawName); | |
| return name === 'bm25' || name === 'fusion (bm25, bge, e5, voyage)'; | |
| } | |
| function parseReleaseDate(dateStr) { | |
| if (!dateStr) return null; | |
| const d = new Date(dateStr); | |
| return Number.isNaN(d.getTime()) ? null : d; | |
| } | |
| const FAMILY_COLORS = { | |
| 'Stella': '#1f77b4', 'Harrier OSS': '#ff7f0e', 'Voyage': '#009688', 'Jina': '#d62728', | |
| 'Qwen3': '#9467bd', 'IBM Granite': '#8c564b', 'Arctic Embed': '#e377c2', 'Perplexity Embed': '#17becf', | |
| 'Nomic Embed': '#6a1b9a', 'BGE': '#7f7f7f', 'E5': '#393b79', 'GTE': '#bcbd22', | |
| 'BM25': '#969696', 'Fusion': '#e6550d', 'Other': '#9e9e9e' | |
| }; | |
| function familyMarkerHtml(name, type) { | |
| const family = inferFamily(name); | |
| const color = FAMILY_COLORS[family] || '#9e9e9e'; | |
| // Shape by model type; color by model family | |
| const symbol = type === 'proprietary' ? '◆' : '●'; | |
| return `<span class="family-marker" title="${family}" aria-label="${family}" style="color:${color}">${symbol}</span>`; | |
| } | |
| function mapRow(item) { | |
| return { | |
| name: item.info.name, | |
| size: item.info.size, | |
| date: item.info.date, | |
| type: item.info.type, | |
| link: item.info.link || '', | |
| avg_a10: item.datasets.average.alpha_ndcg_10, | |
| avg_c20: item.datasets.average.coverage_20, | |
| avg_r50: item.datasets.average.recall_50, | |
| lc_a10: item.datasets.langchain.alpha_ndcg_10, | |
| lc_c20: item.datasets.langchain.coverage_20, | |
| lc_r50: item.datasets.langchain.recall_50, | |
| yolo_a10: item.datasets.yolo.alpha_ndcg_10, | |
| yolo_c20: item.datasets.yolo.coverage_20, | |
| yolo_r50: item.datasets.yolo.recall_50, | |
| laravel_a10: item.datasets.laravel.alpha_ndcg_10, | |
| laravel_c20: item.datasets.laravel.coverage_20, | |
| laravel_r50: item.datasets.laravel.recall_50, | |
| angular_a10: item.datasets.angular.alpha_ndcg_10, | |
| angular_c20: item.datasets.angular.coverage_20, | |
| angular_r50: item.datasets.angular.recall_50, | |
| godot_a10: item.datasets.godot.alpha_ndcg_10, | |
| godot_c20: item.datasets.godot.coverage_20, | |
| godot_r50: item.datasets.godot.recall_50 | |
| }; | |
| } | |
| function renderHeaders() { | |
| headerRowTop.innerHTML = ` | |
| <th rowspan="2" data-key="rank">#</th> | |
| <th rowspan="2" data-key="name">Retriever</th> | |
| <th rowspan="2" data-key="type">Type</th> | |
| <th rowspan="2" data-key="size">Params</th> | |
| <th rowspan="2" data-key="date">Date</th> | |
| ${GROUPS.map(g => `<th colspan="3" class="group-name">${g.label}</th>`).join('')} | |
| `; | |
| headerRowSub.innerHTML = GROUPS.map(g => | |
| METRICS.map(m => { | |
| const key = `${g.prefix}_${m.id}`; | |
| const arrow = sortKey === key ? (sortAsc ? ' ↑' : ' ↓') : ''; | |
| return `<th data-key="${key}" class="metric-header">${m.label}${arrow}</th>`; | |
| }).join('') | |
| ).join(''); | |
| document.querySelectorAll('th[data-key]').forEach(th => { | |
| th.addEventListener('click', () => { | |
| const key = th.dataset.key; | |
| if (!key || key === 'rank') return; | |
| if (sortKey === key) sortAsc = !sortAsc; | |
| else { sortKey = key; sortAsc = false; } | |
| renderHeaders(); | |
| renderBody(); | |
| }); | |
| }); | |
| } | |
| function activeTypes() { | |
| return typeFilters.filter(cb => cb.checked).map(cb => cb.value); | |
| } | |
| function renderBody() { | |
| const q = searchInput.value.trim().toLowerCase(); | |
| const types = activeTypes(); | |
| let filtered = rows.filter(r => | |
| types.includes(r.type) && (`${r.name} ${r.size} ${r.date} ${r.type}`).toLowerCase().includes(q) | |
| ); | |
| filtered.sort((a, b) => { | |
| const av = a[sortKey]; | |
| const bv = b[sortKey]; | |
| if (typeof av === 'number' && typeof bv === 'number') return sortAsc ? av - bv : bv - av; | |
| return sortAsc | |
| ? String(av).localeCompare(String(bv)) | |
| : String(bv).localeCompare(String(av)); | |
| }); | |
| bodyRow.innerHTML = filtered.map((r, idx) => ` | |
| <tr> | |
| <td>${idx + 1}</td> | |
| <td class="model-cell"> | |
| ${familyMarkerHtml(r.name, r.type)} | |
| ${r.link ? `<a href="${r.link}" target="_blank">${r.name}</a>` : r.name} | |
| ${isNewModel(r.date) ? '<span class="new-badge">🆕</span>' : ''} | |
| </td> | |
| <td>${typeBadge(r.type)}</td> | |
| <td>${r.size || '-'}</td> | |
| <td>${r.date || '-'}</td> | |
| <td class="avg-score">${num(r.avg_a10)}</td><td class="avg-score">${num(r.avg_c20)}</td><td class="avg-score">${num(r.avg_r50)}</td> | |
| <td>${num(r.lc_a10)}</td><td>${num(r.lc_c20)}</td><td>${num(r.lc_r50)}</td> | |
| <td>${num(r.yolo_a10)}</td><td>${num(r.yolo_c20)}</td><td>${num(r.yolo_r50)}</td> | |
| <td>${num(r.laravel_a10)}</td><td>${num(r.laravel_c20)}</td><td>${num(r.laravel_r50)}</td> | |
| <td>${num(r.angular_a10)}</td><td>${num(r.angular_c20)}</td><td>${num(r.angular_r50)}</td> | |
| <td>${num(r.godot_a10)}</td><td>${num(r.godot_c20)}</td><td>${num(r.godot_r50)}</td> | |
| </tr> | |
| `).join(''); | |
| } | |
| function renderPlots() { | |
| if (typeof Plotly === 'undefined') return; | |
| const active = activeTypes(); | |
| const filtered = rows.filter(r => active.includes(r.type)); | |
| const filteredNoBaselines = filtered.filter(r => !isReferenceBaselineModel(r.name)); | |
| PLOT_METRICS.forEach(metric => { | |
| const grouped = {}; | |
| filteredNoBaselines.forEach(r => { | |
| const x = parseSizeToBillions(r.size); | |
| const y = r[metric.key]; | |
| if (x === null || typeof y !== 'number') return; | |
| const fam = inferFamily(r.name); | |
| if (!grouped[fam]) grouped[fam] = { x: [], y: [], text: [] }; | |
| grouped[fam].x.push(x); | |
| grouped[fam].y.push(y); | |
| grouped[fam].text.push(r.name); | |
| }); | |
| const traces = Object.keys(grouped).sort().map(fam => ({ | |
| type: 'scatter', | |
| mode: 'markers', | |
| name: fam, | |
| x: grouped[fam].x, | |
| y: grouped[fam].y, | |
| text: grouped[fam].text, | |
| customdata: grouped[fam].x.map(v => formatParameterSize(v)), | |
| marker: { color: FAMILY_COLORS[fam] || '#9e9e9e', size: 11, line: { width: 1, color: '#fff' } }, | |
| hovertemplate: '<b>%{text}</b><br>Params: %{customdata}<br>Score: %{y:.3f}<extra></extra>' | |
| })); | |
| const bm25 = filtered.find(r => String(r.name).toLowerCase() === 'bm25'); | |
| const fusion = filtered.find(r => String(r.name).toLowerCase() === 'fusion (bm25, bge, e5, voyage)'); | |
| const xs = traces.flatMap(t => t.x || []); | |
| if (xs.length) { | |
| const xmin = Math.min(...xs); | |
| const xmax = Math.max(...xs); | |
| if (bm25 && typeof bm25[metric.key] === 'number') { | |
| traces.push({ type: 'scatter', mode: 'lines', name: 'BM25', x: [xmin, xmax], y: [bm25[metric.key], bm25[metric.key]], line: { color: 'rgba(97,97,97,0.55)', width: 1.1, dash: 'dash' } }); | |
| } | |
| if (fusion && typeof fusion[metric.key] === 'number') { | |
| traces.push({ type: 'scatter', mode: 'lines', name: 'Fusion', x: [xmin, xmax], y: [fusion[metric.key], fusion[metric.key]], line: { color: 'rgba(106,27,154,0.55)', width: 1.1, dash: 'dot' } }); | |
| } | |
| } | |
| Plotly.newPlot(metric.plotId, traces, { | |
| title: { text: metric.title, x: 0.01, xanchor: 'left', font: { size: 16 } }, | |
| height: 460, | |
| margin: { t: 46, r: 12, b: 178, l: 56 }, | |
| xaxis: { title: { text: 'Model Parameters (Billions)', standoff: 26 }, type: 'log', automargin: true, showgrid: true }, | |
| yaxis: { title: metric.yLabel, range: [metric.yMin, metric.yMax], tickformat: '.2f', automargin: true, showgrid: true }, | |
| legend: { orientation: 'h', y: -0.46, x: 0.5, xanchor: 'center' }, | |
| hovermode: 'closest' | |
| }, { responsive: true, displaylogo: false }); | |
| }); | |
| DATE_PLOT_METRICS.forEach(metric => { | |
| const grouped = {}; | |
| filteredNoBaselines.forEach(r => { | |
| const x = parseReleaseDate(r.date); | |
| const y = r[metric.key]; | |
| if (x === null || typeof y !== 'number') return; | |
| const fam = inferFamily(r.name); | |
| if (!grouped[fam]) grouped[fam] = { x: [], y: [], text: [] }; | |
| grouped[fam].x.push(x); | |
| grouped[fam].y.push(y); | |
| grouped[fam].text.push(r.name); | |
| }); | |
| const traces = Object.keys(grouped).sort().map(fam => ({ | |
| type: 'scatter', | |
| mode: 'markers', | |
| name: fam, | |
| x: grouped[fam].x, | |
| y: grouped[fam].y, | |
| text: grouped[fam].text, | |
| marker: { color: FAMILY_COLORS[fam] || '#9e9e9e', size: 11, line: { width: 1, color: '#fff' } }, | |
| hovertemplate: '<b>%{text}</b><br>Release date: %{x|%Y-%m-%d}<br>Score: %{y:.3f}<extra></extra>' | |
| })); | |
| const bm25 = filtered.find(r => normalizeModelName(r.name) === 'bm25'); | |
| const fusion = filtered.find(r => normalizeModelName(r.name) === 'fusion (bm25, bge, e5, voyage)'); | |
| const xs = traces.flatMap(t => t.x || []); | |
| const xMin = xs.length ? new Date(Math.min(...xs.map(d => d.getTime()))) : null; | |
| const xMax = xs.length ? new Date(Math.max(...xs.map(d => d.getTime()))) : null; | |
| const xMinMonthStart = xMin ? new Date(xMin.getFullYear(), xMin.getMonth(), 1) : null; | |
| if (xMin && xMax) { | |
| if (bm25 && typeof bm25[metric.key] === 'number') { | |
| traces.push({ type: 'scatter', mode: 'lines', name: 'BM25', x: [xMin, xMax], y: [bm25[metric.key], bm25[metric.key]], line: { color: 'rgba(97,97,97,0.55)', width: 1.1, dash: 'dash' } }); | |
| } | |
| if (fusion && typeof fusion[metric.key] === 'number') { | |
| traces.push({ type: 'scatter', mode: 'lines', name: 'Fusion', x: [xMin, xMax], y: [fusion[metric.key], fusion[metric.key]], line: { color: 'rgba(106,27,154,0.55)', width: 1.1, dash: 'dot' } }); | |
| } | |
| } | |
| Plotly.newPlot(metric.plotId, traces, { | |
| title: { text: metric.title, x: 0.01, xanchor: 'left', font: { size: 16 } }, | |
| height: 460, | |
| margin: { t: 46, r: 12, b: 172, l: 56 }, | |
| xaxis: { | |
| title: { text: 'Model Release Date', standoff: 26 }, | |
| type: 'date', | |
| tickmode: 'linear', | |
| tick0: xMinMonthStart ? xMinMonthStart.toISOString().slice(0, 10) : undefined, | |
| dtick: 'M1', | |
| tickformat: '%b %Y', | |
| tickangle: -45, | |
| automargin: true, | |
| showgrid: true | |
| }, | |
| yaxis: { title: metric.yLabel, range: [metric.yMin, metric.yMax], tickformat: '.2f', automargin: true, showgrid: true }, | |
| legend: { | |
| orientation: 'h', | |
| y: -0.45, | |
| x: 0.5, | |
| xanchor: 'center', | |
| entrywidthmode: 'pixels', | |
| entrywidth: 125, | |
| itemsizing: 'constant' | |
| }, | |
| hovermode: 'closest' | |
| }, { responsive: true, displaylogo: false }); | |
| }); | |
| } | |
| async function init() { | |
| const resp = await fetch('./leaderboard_data.json'); | |
| const data = await resp.json(); | |
| rows = data.leaderboardData.map(mapRow); | |
| renderHeaders(); | |
| renderBody(); | |
| renderPlots(); | |
| } | |
| searchInput.addEventListener('input', renderBody); | |
| typeFilters.forEach(cb => cb.addEventListener('change', () => { renderBody(); renderPlots(); })); | |
| document.getElementById('toggle-submit').addEventListener('click', () => { | |
| document.getElementById('submit-panel').classList.toggle('hidden'); | |
| }); | |
| document.getElementById('toggle-metrics').addEventListener('click', () => { | |
| document.getElementById('metrics-panel').classList.toggle('hidden'); | |
| }); | |
| copyCitationBtn.addEventListener('click', async () => { | |
| try { | |
| await navigator.clipboard.writeText(citationText.textContent); | |
| copyCitationBtn.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; | |
| setTimeout(() => { | |
| copyCitationBtn.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; | |
| }, 1400); | |
| } catch (_) {} | |
| }); | |
| init().catch(() => { | |
| bodyRow.innerHTML = '<tr><td colspan="23">Failed to load leaderboard data.</td></tr>'; | |
| }); | |