| | const rawData = window.SERVER_DATA || []; |
| |
|
| | |
| | let state = { |
| | championTrack: "Standard", |
| | sortKey: "overall_em", |
| | sortDir: "desc", |
| | filters: { |
| | track: "all", |
| | agent: "", |
| | backbone: "", |
| | retriever: "" |
| | } |
| | }; |
| |
|
| | |
| | const SCORE_KEYS = ["overall_em", "overall_f1", "intra_em", "intra_f1", "inter_em", "inter_f1"]; |
| |
|
| | |
| | function switchMainTab(tabName) { |
| | document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active')); |
| | const map = {'leaderboard': 0, 'full-metrics': 1, 'submit': 2}; |
| | document.querySelectorAll('.nav-btn')[map[tabName]].classList.add('active'); |
| |
|
| | document.querySelectorAll('.view-section').forEach(el => el.style.display = 'none'); |
| | document.getElementById(`view-${tabName}`).style.display = 'block'; |
| |
|
| | if (tabName === 'leaderboard') renderChampionTable(); |
| | if (tabName === 'full-metrics') renderFullTable(); |
| | } |
| |
|
| | |
| | function updateSortHeaders() { |
| | document.querySelectorAll('.sortable').forEach(th => { |
| | const key = th.dataset.key; |
| | |
| | let text = th.textContent.replace(/ β/g, '').replace(/ β/g, '').trim(); |
| | |
| | if (key === state.sortKey) { |
| | th.classList.add('active-sort'); |
| | text += state.sortDir === 'desc' ? ' β' : ' β'; |
| | } else { |
| | th.classList.remove('active-sort'); |
| | } |
| | th.textContent = text; |
| | }); |
| | } |
| |
|
| | |
| | function renderChampionTable() { |
| | const tbody = document.querySelector('#champion-table tbody'); |
| | let data = rawData.filter(d => d.track === state.championTrack); |
| | |
| | |
| | const methodBestMap = new Map(); |
| | data.forEach(row => { |
| | const method = row.method; |
| | if (!methodBestMap.has(method) || row.overall_em > methodBestMap.get(method).overall_em) { |
| | methodBestMap.set(method, row); |
| | } |
| | }); |
| | |
| | |
| | data = Array.from(methodBestMap.values()); |
| | data = sortData(data); |
| |
|
| | tbody.innerHTML = data.map((row, idx) => { |
| | return buildRowHTML(row, idx + 1, false); |
| | }).join(''); |
| |
|
| | updateSortHeaders(); |
| | } |
| |
|
| | |
| | function renderFullTable() { |
| | const tbody = document.querySelector('#full-table tbody'); |
| |
|
| | let data = rawData.filter(d => { |
| | const f = state.filters; |
| | if (f.track !== 'all' && d.track !== f.track) return false; |
| | if (f.agent && !d.agent.toLowerCase().includes(f.agent.toLowerCase())) return false; |
| | if (f.backbone && !d.backbone.toLowerCase().includes(f.backbone.toLowerCase())) return false; |
| | if (f.retriever && !d.retriever.toLowerCase().includes(f.retriever.toLowerCase())) return false; |
| | return true; |
| | }); |
| |
|
| | data = sortData(data); |
| |
|
| | if (data.length === 0) { |
| | tbody.innerHTML = `<tr><td colspan="12" style="text-align:center; padding:20px; color:#888;">No matching results.</td></tr>`; |
| | return; |
| | } |
| |
|
| | tbody.innerHTML = data.map((row, idx) => { |
| | return buildRowHTML(row, idx + 1, true); |
| | }).join(''); |
| |
|
| | updateSortHeaders(); |
| | } |
| |
|
| | |
| | function sortData(data) { |
| | return data.sort((a, b) => { |
| | let valA = a[state.sortKey]; |
| | let valB = b[state.sortKey]; |
| | if (typeof valA === 'number') { |
| | return state.sortDir === 'desc' ? valB - valA : valA - valB; |
| | } |
| | return 0; |
| | }); |
| | } |
| |
|
| | |
| | function buildRowHTML(row, rank, showTrack) { |
| | const medal = rank === 1 ? 'π₯' : rank === 2 ? 'π₯' : rank === 3 ? 'π₯' : rank; |
| |
|
| | const trackTd = showTrack |
| | ? `<td><span class="track-tag ${row.track}">${row.track}</span></td>` |
| | : ''; |
| |
|
| | |
| | function scoreCell(key, value) { |
| | const isActive = (key === state.sortKey) ? ' active-col' : ''; |
| | return `<td class="score-cell${isActive}">${value.toFixed(1)}</td>`; |
| | } |
| |
|
| | return ` |
| | <tr> |
| | <td class="rank-col">${medal}</td> |
| | <td class="method-col align-left"> |
| | <a href="${row.url}" target="_blank" class="method-name">${row.method}</a> |
| | <div class="org-name">${row.date}</div> |
| | </td> |
| | ${trackTd} |
| | <td>${row.agent}</td> |
| | <td>${row.backbone}</td> |
| | <td>${row.retriever}</td> |
| | ${scoreCell('overall_em', row.overall_em)} |
| | ${scoreCell('overall_f1', row.overall_f1)} |
| | ${scoreCell('intra_em', row.intra_em)} |
| | ${scoreCell('intra_f1', row.intra_f1)} |
| | ${scoreCell('inter_em', row.inter_em)} |
| | ${scoreCell('inter_f1', row.inter_f1)} |
| | </tr> |
| | `; |
| | } |
| |
|
| | |
| |
|
| | |
| | document.querySelectorAll('.sub-tab-btn').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | document.querySelectorAll('.sub-tab-btn').forEach(b => b.classList.remove('active')); |
| | e.target.classList.add('active'); |
| | state.championTrack = e.target.dataset.track; |
| | renderChampionTable(); |
| | }); |
| | }); |
| |
|
| | |
| | document.querySelectorAll('.sortable').forEach(th => { |
| | th.addEventListener('click', (e) => { |
| | const key = e.currentTarget.dataset.key; |
| | if (state.sortKey === key) { |
| | state.sortDir = state.sortDir === 'desc' ? 'asc' : 'desc'; |
| | } else { |
| | state.sortKey = key; |
| | state.sortDir = 'desc'; |
| | } |
| |
|
| | |
| | if (document.getElementById('view-leaderboard').style.display !== 'none') { |
| | renderChampionTable(); |
| | } else { |
| | renderFullTable(); |
| | } |
| | }); |
| | }); |
| |
|
| | |
| | document.getElementById('filter-track').addEventListener('change', (e) => { |
| | state.filters.track = e.target.value; |
| | renderFullTable(); |
| | }); |
| | document.getElementById('filter-agent').addEventListener('input', (e) => { |
| | state.filters.agent = e.target.value; |
| | renderFullTable(); |
| | }); |
| | document.getElementById('filter-backbone').addEventListener('input', (e) => { |
| | state.filters.backbone = e.target.value; |
| | renderFullTable(); |
| | }); |
| | document.getElementById('filter-retriever').addEventListener('input', (e) => { |
| | state.filters.retriever = e.target.value; |
| | renderFullTable(); |
| | }); |
| |
|
| | |
| | document.getElementById('submit-form').addEventListener('submit', async (e) => { |
| | e.preventDefault(); |
| |
|
| | const fileInput = document.getElementById('submit-file'); |
| | const submitBtn = document.getElementById('submit-btn'); |
| | const resultDiv = document.getElementById('submit-result'); |
| |
|
| | if (!fileInput.files.length) return; |
| |
|
| | |
| | submitBtn.disabled = true; |
| | submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Submitting...'; |
| | resultDiv.style.display = 'none'; |
| |
|
| | const formData = new FormData(); |
| | formData.append('file', fileInput.files[0]); |
| |
|
| | try { |
| | const response = await fetch('/upload', { |
| | method: 'POST', |
| | body: formData, |
| | }); |
| |
|
| | const data = await response.json(); |
| |
|
| | resultDiv.style.display = 'block'; |
| |
|
| | if (data.success) { |
| | let html = `<strong>β
${data.message}</strong>`; |
| | if (data.pr_url) { |
| | html += `<br><br>π <a href="${data.pr_url}" target="_blank">View your Pull Request β</a>`; |
| | } |
| | html += `<br><br><small>After maintainers review and merge your PR, scores will be computed and published on the leaderboard.</small>`; |
| | resultDiv.className = 'success'; |
| | resultDiv.innerHTML = html; |
| | } else { |
| | let html = `<strong>β ${data.error || 'Submission failed.'}</strong>`; |
| | if (data.details) { |
| | html += '<ul>' + data.details.map(d => `<li>${d}</li>`).join('') + '</ul>'; |
| | } |
| | resultDiv.className = 'error'; |
| | resultDiv.innerHTML = html; |
| | } |
| | } catch (err) { |
| | resultDiv.style.display = 'block'; |
| | resultDiv.className = 'error'; |
| | resultDiv.innerHTML = `<strong>β Network error:</strong> ${err.message}`; |
| | } finally { |
| | submitBtn.disabled = false; |
| | submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Submit & Create PR'; |
| | } |
| | }); |
| |
|
| | |
| | renderChampionTable(); |