Spaces:
Sleeping
Sleeping
| const rawData = window.SERVER_DATA || []; | |
| // --- State Management --- | |
| let state = { | |
| championTrack: "Standard", | |
| sortKey: "overall_em", | |
| sortDir: "desc", | |
| filters: { | |
| track: "all", | |
| agent: "", | |
| backbone: "", | |
| retriever: "" // New retriever filter | |
| } | |
| }; | |
| // List of score column keys (used to determine which columns need highlighting) | |
| const SCORE_KEYS = ["overall_em", "overall_f1", "intra_em", "intra_f1", "inter_em", "inter_f1"]; | |
| // --- Main Tab Switching --- | |
| 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(); | |
| } | |
| // --- Update active-sort state for all table headers --- | |
| function updateSortHeaders() { | |
| document.querySelectorAll('.sortable').forEach(th => { | |
| const key = th.dataset.key; | |
| // Remove arrows from text | |
| 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; | |
| }); | |
| } | |
| // --- Logic: Champion Table --- | |
| function renderChampionTable() { | |
| const tbody = document.querySelector('#champion-table tbody'); | |
| let data = rawData.filter(d => d.track === state.championTrack); | |
| // For each method name, keep only the one with the highest overall_em | |
| 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); | |
| } | |
| }); | |
| // Convert to array | |
| data = Array.from(methodBestMap.values()); | |
| data = sortData(data); | |
| tbody.innerHTML = data.map((row, idx) => { | |
| return buildRowHTML(row, idx + 1, false); | |
| }).join(''); | |
| updateSortHeaders(); | |
| } | |
| // --- Logic: Full Table --- | |
| 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(); | |
| } | |
| // --- Helper: Sort --- | |
| 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; | |
| }); | |
| } | |
| // --- Helper: Row HTML Builder --- | |
| 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>` | |
| : ''; | |
| // Generate score cell, add active-col class to current sort column | |
| 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> | |
| `; | |
| } | |
| // --- Event Listeners --- | |
| // 1. Champion Sub-Tabs (Standard/Open) | |
| 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(); | |
| }); | |
| }); | |
| // 2. Sorting | |
| 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'; | |
| } | |
| // Re-render the currently visible table | |
| if (document.getElementById('view-leaderboard').style.display !== 'none') { | |
| renderChampionTable(); | |
| } else { | |
| renderFullTable(); | |
| } | |
| }); | |
| }); | |
| // 3. Full Metrics Filters | |
| 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(); | |
| }); | |
| // 4. Submit Form (AJAX, for displaying PR link feedback) | |
| 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; | |
| // Disable button, show loading | |
| 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'; | |
| } | |
| }); | |
| // --- Init --- | |
| renderChampionTable(); |