|
<!DOCTYPE html> |
|
|
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Spoken Language Model Leaderboard</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; |
|
background: #f5f5f5; |
|
padding: 20px; |
|
} |
|
|
|
.container { |
|
max-width: 1400px; |
|
margin: 0 auto; |
|
background: white; |
|
border-radius: 8px; |
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|
overflow: hidden; |
|
} |
|
|
|
.header { |
|
background: linear-gradient(135deg, #4f46e5, #7c3aed); |
|
color: white; |
|
padding: 30px; |
|
text-align: center; |
|
} |
|
|
|
.header h1 { |
|
font-size: 2em; |
|
margin-bottom: 10px; |
|
} |
|
|
|
.header p { |
|
opacity: 0.9; |
|
} |
|
|
|
.controls { |
|
padding: 20px 30px; |
|
border-bottom: 1px solid #e5e5e5; |
|
display: flex; |
|
gap: 20px; |
|
flex-wrap: wrap; |
|
} |
|
|
|
.search-container { |
|
flex: 1; |
|
min-width: 200px; |
|
} |
|
|
|
.search-container input { |
|
width: 100%; |
|
padding: 10px; |
|
border: 1px solid #ddd; |
|
border-radius: 4px; |
|
font-size: 14px; |
|
} |
|
|
|
.info { |
|
padding: 10px 30px; |
|
background: #f9f9f9; |
|
font-size: 14px; |
|
color: #666; |
|
} |
|
|
|
.table-container { |
|
overflow-x: auto; |
|
max-height: 600px; |
|
overflow-y: auto; |
|
} |
|
|
|
table { |
|
width: 100%; |
|
border-collapse: collapse; |
|
} |
|
|
|
thead { |
|
background: #f9f9f9; |
|
position: sticky; |
|
top: 0; |
|
z-index: 10; |
|
} |
|
|
|
th { |
|
padding: 12px; |
|
text-align: left; |
|
font-weight: 600; |
|
font-size: 14px; |
|
border-bottom: 2px solid #e5e5e5; |
|
cursor: pointer; |
|
white-space: nowrap; |
|
background: #f9f9f9; |
|
} |
|
|
|
th:hover { |
|
background: #f0f0f0; |
|
} |
|
|
|
th.sort-asc::after { |
|
content: ' ↑'; |
|
color: #4f46e5; |
|
} |
|
|
|
th.sort-desc::after { |
|
content: ' ↓'; |
|
color: #4f46e5; |
|
} |
|
|
|
tbody tr { |
|
border-bottom: 1px solid #f0f0f0; |
|
} |
|
|
|
tbody tr:hover { |
|
background: #f9f9f9; |
|
} |
|
|
|
td { |
|
padding: 10px 12px; |
|
font-size: 14px; |
|
white-space: nowrap; |
|
} |
|
|
|
.rank { |
|
font-weight: bold; |
|
color: #4f46e5; |
|
} |
|
|
|
.model-name { |
|
font-weight: 500; |
|
} |
|
|
|
.empty { |
|
color: #ccc; |
|
font-style: italic; |
|
} |
|
|
|
.loading { |
|
padding: 50px; |
|
text-align: center; |
|
color: #666; |
|
} |
|
|
|
.error { |
|
padding: 50px; |
|
text-align: center; |
|
color: #dc2626; |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.controls { |
|
flex-direction: column; |
|
} |
|
} |
|
</style> |
|
|
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="header"> |
|
<h1>Spoken Language Model Leaderboard</h1> |
|
<p></p> |
|
</div> |
|
|
|
<div class="controls"> |
|
<div class="search-container"> |
|
<input type="text" id="searchInput" placeholder="Search models..."> |
|
</div> |
|
<div class="search-container"> |
|
<input type="text" id="columnSearchInput" placeholder="Search benchmarks..."> |
|
</div> |
|
</div> |
|
|
|
<div class="info" id="info"> |
|
Loading data... |
|
</div> |
|
|
|
<div class="table-container"> |
|
<div id="loading" class="loading">Loading leaderboard data...</div> |
|
<div id="error" class="error" style="display: none;"></div> |
|
<table id="leaderboard" style="display: none;"> |
|
<thead id="tableHead"></thead> |
|
<tbody id="tableBody"></tbody> |
|
</table> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
let data = []; |
|
let filteredData = []; |
|
let columns = []; |
|
let selectedColumns = []; |
|
let filteredColumns = []; |
|
let sortColumn = ''; |
|
let sortOrder = 'desc'; |
|
|
|
|
|
function loadCSV() { |
|
fetch('data.csv') |
|
.then(response => { |
|
if (!response.ok) { |
|
throw new Error('Could not load data.csv'); |
|
} |
|
return response.text(); |
|
}) |
|
.then(csvText => { |
|
parseCSV(csvText); |
|
}) |
|
.catch(error => { |
|
showError('Error loading data.csv: ' + error.message + |
|
'<br>Please ensure data.csv is in the same directory as this HTML file.' + |
|
'<br>Note: This page needs to be served via a web server (not file://)'); |
|
}); |
|
} |
|
|
|
|
|
function parseCSV(csvText) { |
|
const result = Papa.parse(csvText, { |
|
header: true, |
|
dynamicTyping: true, |
|
skipEmptyLines: true |
|
}); |
|
|
|
if (result.errors.length > 0 && result.errors[0].code !== 'TooFewFields') { |
|
console.warn('CSV parsing warnings:', result.errors); |
|
} |
|
|
|
data = result.data; |
|
filteredData = [...data]; |
|
columns = result.meta.fields || []; |
|
|
|
|
|
columns = columns.map(col => col.trim()); |
|
|
|
|
|
selectedColumns = columns; |
|
filteredColumns = columns; |
|
|
|
initializeControls(); |
|
updateTable(); |
|
hideLoading(); |
|
} |
|
|
|
|
|
function initializeControls() { |
|
|
|
document.getElementById('searchInput').addEventListener('input', function(e) { |
|
const searchTerm = e.target.value.toLowerCase(); |
|
if (searchTerm === '') { |
|
filteredData = [...data]; |
|
} else { |
|
filteredData = data.filter(row => { |
|
return Object.values(row).some(val => |
|
val && val.toString().toLowerCase().includes(searchTerm) |
|
); |
|
}); |
|
} |
|
updateTable(); |
|
}); |
|
|
|
|
|
document.getElementById('columnSearchInput').addEventListener('input', function(e) { |
|
const searchTerm = e.target.value.toLowerCase(); |
|
if (searchTerm === '') { |
|
filteredColumns = [...columns]; |
|
} else { |
|
filteredColumns = columns.filter(col => |
|
col.toLowerCase().includes(searchTerm) |
|
); |
|
|
|
if (!filteredColumns.includes(columns[0])) { |
|
filteredColumns.unshift(columns[0]); |
|
} |
|
} |
|
updateTable(); |
|
}); |
|
} |
|
|
|
|
|
function sortData(column) { |
|
if (sortColumn === column) { |
|
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; |
|
} else { |
|
sortColumn = column; |
|
sortOrder = 'desc'; |
|
} |
|
|
|
filteredData.sort((a, b) => { |
|
let aVal = a[column]; |
|
let bVal = b[column]; |
|
|
|
|
|
if (aVal === null || aVal === undefined || aVal === '') return 1; |
|
if (bVal === null || bVal === undefined || bVal === '') return -1; |
|
|
|
|
|
if (typeof aVal === 'number' && typeof bVal === 'number') { |
|
return sortOrder === 'asc' ? aVal - bVal : bVal - aVal; |
|
} |
|
|
|
|
|
const result = String(aVal).localeCompare(String(bVal)); |
|
return sortOrder === 'asc' ? result : -result; |
|
}); |
|
|
|
updateTable(); |
|
} |
|
|
|
|
|
function updateTable() { |
|
const thead = document.getElementById('tableHead'); |
|
const tbody = document.getElementById('tableBody'); |
|
|
|
|
|
document.getElementById('info').textContent = |
|
`Showing ${filteredData.length} of ${data.length} models | ${filteredColumns.length} of ${columns.length} columns`; |
|
|
|
|
|
thead.innerHTML = ''; |
|
const headerRow = document.createElement('tr'); |
|
|
|
|
|
const rankTh = document.createElement('th'); |
|
rankTh.textContent = ''; |
|
headerRow.appendChild(rankTh); |
|
|
|
|
|
filteredColumns.forEach(col => { |
|
const th = document.createElement('th'); |
|
th.textContent = col; |
|
th.onclick = () => sortData(col); |
|
|
|
if (col === sortColumn) { |
|
th.className = sortOrder === 'asc' ? 'sort-asc' : 'sort-desc'; |
|
} |
|
|
|
headerRow.appendChild(th); |
|
}); |
|
|
|
thead.appendChild(headerRow); |
|
|
|
|
|
tbody.innerHTML = ''; |
|
filteredData.forEach((row, index) => { |
|
const tr = document.createElement('tr'); |
|
|
|
|
|
const rankTd = document.createElement('td'); |
|
rankTd.className = 'rank'; |
|
rankTd.textContent = index + 1; |
|
tr.appendChild(rankTd); |
|
|
|
|
|
filteredColumns.forEach((col, colIndex) => { |
|
const td = document.createElement('td'); |
|
const value = row[col]; |
|
|
|
if (value === null || value === undefined || value === '') { |
|
td.innerHTML = '<span class="empty">—</span>'; |
|
} else if (colIndex === 0) { |
|
td.className = 'model-name'; |
|
td.textContent = value; |
|
} else if (typeof value === 'number') { |
|
td.textContent = Number.isInteger(value) ? value : value.toFixed(4); |
|
} else { |
|
td.textContent = value; |
|
} |
|
|
|
tr.appendChild(td); |
|
}); |
|
|
|
tbody.appendChild(tr); |
|
}); |
|
|
|
document.getElementById('leaderboard').style.display = 'table'; |
|
} |
|
|
|
|
|
function hideLoading() { |
|
document.getElementById('loading').style.display = 'none'; |
|
} |
|
|
|
|
|
function showError(message) { |
|
document.getElementById('loading').style.display = 'none'; |
|
document.getElementById('error').innerHTML = message; |
|
document.getElementById('error').style.display = 'block'; |
|
} |
|
|
|
|
|
window.addEventListener('DOMContentLoaded', loadCSV); |
|
</script> |
|
|
|
</body> |
|
</html> |
|
|