|
<!DOCTYPE html> |
|
<html> |
|
|
|
<head> |
|
<title>Local Language Monitor</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
body { |
|
margin: 0 auto; |
|
padding: 20px; |
|
font-family: sans-serif; |
|
} |
|
|
|
.language-header { |
|
margin-bottom: 10px; |
|
} |
|
|
|
.speaker-count { |
|
font-size: 0.8em; |
|
color: #666; |
|
font-weight: normal; |
|
margin: 0; |
|
} |
|
</style> |
|
<link rel="icon" |
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22 fill=%22black%22>π</text></svg>"> |
|
</head> |
|
|
|
<body> |
|
<nav class="border-b border-gray-200 bg-white"> |
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> |
|
|
|
<div class="sm:hidden absolute left-4 top-4"> |
|
<button onclick="toggleMobileMenu()" class="text-gray-500 hover:text-gray-700 focus:outline-none"> |
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> |
|
</svg> |
|
</button> |
|
</div> |
|
|
|
|
|
<div id="mobileMenu" class="hidden sm:hidden absolute left-0 top-16 w-full bg-white border-b border-gray-200 py-2"> |
|
<div class="flex flex-col space-y-2 px-4"> |
|
<a href="#" onclick="showSection('coverage'); toggleMobileMenu()" class="nav-link block px-3 py-2 text-base font-medium text-gray-700"> |
|
Language Coverage |
|
</a> |
|
<a href="#" onclick="showSection('comparison'); toggleMobileMenu()" class="nav-link block px-3 py-2 text-base font-medium text-gray-700"> |
|
LLM Comparison |
|
</a> |
|
<a href="#" onclick="showSection('results'); toggleMobileMenu()" class="nav-link block px-3 py-2 text-base font-medium text-gray-700"> |
|
Results by Language |
|
</a> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="hidden sm:flex justify-center h-16"> |
|
<div class="flex"> |
|
<div class="flex space-x-8"> |
|
<a href="#" onclick="showSection('coverage')" class="nav-link active inline-flex items-center px-1 pt-1 border-b-2 border-indigo-500 text-sm font-medium text-gray-900"> |
|
Language Coverage |
|
</a> |
|
<a href="#" onclick="showSection('comparison')" class="nav-link inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"> |
|
LLM Comparison |
|
</a> |
|
<a href="#" onclick="showSection('results')" class="nav-link inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700"> |
|
Results by Language |
|
</a> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</nav> |
|
|
|
<div class="p-6"> |
|
<section id="coverage" class="section"> |
|
<div id="summary-chart"></div> |
|
</section> |
|
|
|
<section id="comparison" class="section hidden"> |
|
<p class="text-gray-600">Coming soon...</p> |
|
</section> |
|
|
|
<section id="results" class="section hidden"> |
|
<div id="language-list"></div> |
|
</section> |
|
</div> |
|
|
|
<script type="module"> |
|
|
|
import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm"; |
|
|
|
function showSection(sectionId) { |
|
|
|
document.querySelectorAll('.nav-link').forEach(link => { |
|
link.classList.remove('border-indigo-500', 'text-gray-900'); |
|
link.classList.add('border-transparent', 'text-gray-500'); |
|
}); |
|
const activeLink = document.querySelector(`[onclick="showSection('${sectionId}')"]`); |
|
activeLink.classList.remove('border-transparent', 'text-gray-500'); |
|
activeLink.classList.add('border-indigo-500', 'text-gray-900'); |
|
|
|
|
|
document.querySelectorAll('.section').forEach(section => { |
|
section.classList.add('hidden'); |
|
}); |
|
document.getElementById(sectionId).classList.remove('hidden'); |
|
} |
|
window.showSection = showSection; |
|
|
|
function toggleMobileMenu() { |
|
const mobileMenu = document.getElementById('mobileMenu'); |
|
mobileMenu.classList.toggle('hidden'); |
|
} |
|
window.toggleMobileMenu = toggleMobileMenu; |
|
|
|
async function init() { |
|
const scoreKey = "bleu" |
|
const scoreName = "BLEU Score" |
|
const summaryChartDiv = document.getElementById('summary-chart'); |
|
const languageListDiv = document.getElementById('language-list'); |
|
|
|
const response = await fetch('results.json'); |
|
const data = await response.json(); |
|
|
|
const formatScore = (score) => score > 0 ? score.toFixed(2) : "No benchmark available!" |
|
const formatTitle = d => (d.language_name + "\n" + parseInt(d.speakers / 1_000_00) / 10 + "M speakers\n" + scoreName + ": " + formatScore(d[scoreKey])) |
|
|
|
|
|
const summaryPlot = Plot.plot({ |
|
width: summaryChartDiv.clientWidth, |
|
height: 400, |
|
marginBottom: 100, |
|
x: { label: "Number of speakers", axis: null }, |
|
y: { label: `${scoreName} (average across models)` }, |
|
|
|
marks: [ |
|
Plot.rectY(data, Plot.stackX({ |
|
x: "speakers", |
|
order: scoreKey, |
|
reverse: true, |
|
y2: scoreKey, |
|
title: formatTitle, |
|
tip: true, |
|
fill: d => d[scoreKey] > 0 ? "black" : "pink" |
|
})), |
|
Plot.rectY(data, Plot.pointerX(Plot.stackX({ |
|
x: "speakers", |
|
order: scoreKey, |
|
reverse: true, |
|
y2: scoreKey, |
|
fill: "grey", |
|
}))), |
|
Plot.text(data, Plot.stackX({ |
|
x: "speakers", |
|
y2: scoreKey, |
|
order: scoreKey, |
|
reverse: true, |
|
text: "language_name", |
|
frameAnchor: "bottom", |
|
textAnchor: "end", |
|
dy: 10, |
|
rotate: 270, |
|
opacity: (d) => d.speakers > 50_000_000 ? 1 : 0, |
|
})) |
|
] |
|
}); |
|
|
|
|
|
summaryChartDiv.appendChild(summaryPlot); |
|
|
|
|
|
const languageMap = new Map(); |
|
data.forEach(r => { |
|
if (!languageMap.has(r.language_name)) { |
|
languageMap.set(r.language_name, r.speakers); |
|
} |
|
}); |
|
|
|
|
|
const languages = [...languageMap.entries()] |
|
.sort((a, b) => b[1] - a[1]) |
|
.map(([lang]) => lang); |
|
|
|
|
|
languages.forEach(language => { |
|
const headerDiv = document.createElement('div'); |
|
headerDiv.className = 'language-header'; |
|
|
|
const h2 = document.createElement('h2'); |
|
h2.textContent = language; |
|
h2.style.marginBottom = '5px'; |
|
|
|
const speakerP = document.createElement('p'); |
|
speakerP.className = 'speaker-count'; |
|
const speakerCount = (languageMap.get(language) / 1_000_000).toFixed(1); |
|
speakerP.textContent = `${speakerCount}M speakers`; |
|
|
|
headerDiv.appendChild(h2); |
|
headerDiv.appendChild(speakerP); |
|
languageListDiv.appendChild(headerDiv); |
|
|
|
const languageData = data.filter(r => r.language_name === language)[0]["scores"]; |
|
|
|
const descriptor = code => { |
|
let [org, model] = code.split("/") |
|
return model.split("-")[0] |
|
} |
|
|
|
|
|
if (languageData && languageData.length > 1) { |
|
const plot = Plot.plot({ |
|
width: 400, |
|
height: 200, |
|
margin: 30, |
|
y: { |
|
domain: [0, 1], |
|
label: scoreName |
|
}, |
|
marks: [ |
|
Plot.barY(languageData, { |
|
x: d => descriptor(d.model), |
|
y: scoreKey |
|
}) |
|
] |
|
}); |
|
languageListDiv.appendChild(plot); |
|
} |
|
}); |
|
} |
|
|
|
init(); |
|
</script> |
|
</body> |
|
|
|
</html> |