| 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(); |