|
import { useState, useEffect } from 'react' |
|
import { PrimeReactProvider } from 'primereact/api' |
|
import 'primereact/resources/themes/lara-light-cyan/theme.css' |
|
import ModelTable from './components/ModelTable' |
|
import LanguageTable from './components/LanguageTable' |
|
import DatasetTable from './components/DatasetTable' |
|
import WorldMap from './components/WorldMap' |
|
import AutoComplete from './components/AutoComplete' |
|
import LanguagePlot from './components/LanguagePlot' |
|
import SpeakerPlot from './components/SpeakerPlot' |
|
import HistoryPlot from './components/HistoryPlot' |
|
import CostPlot from './components/CostPlot' |
|
import { Carousel } from 'primereact/carousel' |
|
import { Dialog } from 'primereact/dialog' |
|
import { Button } from 'primereact/button' |
|
|
|
function App () { |
|
const [data, setData] = useState(null) |
|
const [loading, setLoading] = useState(true) |
|
const [error, setError] = useState(null) |
|
const [selectedLanguages, setSelectedLanguages] = useState([]) |
|
const [machineTranslatedMetrics, setMachineTranslatedMetrics] = useState([]) |
|
const [dialogVisible, setDialogVisible] = useState(false) |
|
const [aboutVisible, setAboutVisible] = useState(false) |
|
const [contributeVisible, setContributeVisible] = useState(false) |
|
|
|
|
|
const [carouselItems, setCarouselItems] = useState([]) |
|
const [fullScreenCarouselItems, setFullScreenCarouselItems] = useState([]) |
|
|
|
useEffect(() => { |
|
fetch('/api/data', { |
|
method: 'POST', |
|
body: JSON.stringify({ selectedLanguages }) |
|
}) |
|
.then(response => { |
|
if (!response.ok) { |
|
throw new Error('Network response was not ok') |
|
} |
|
return response.json() |
|
}) |
|
.then(jsonData => { |
|
setData(jsonData) |
|
setMachineTranslatedMetrics(jsonData.machine_translated_metrics || []) |
|
setLoading(false) |
|
}) |
|
.catch(err => { |
|
setError(err.message) |
|
setLoading(false) |
|
}) |
|
}, [selectedLanguages]) |
|
|
|
|
|
useEffect(() => { |
|
if (data) { |
|
|
|
const timer = setTimeout(() => { |
|
setCarouselItems([ |
|
<WorldMap key="worldmap-0" data={data.countries} allLanguages={data.language_table} width={750} height={500} />, |
|
<LanguagePlot key="langplot-1" data={data} width={750} height={500} />, |
|
<SpeakerPlot key="speakerplot-2" data={data} width={750} height={500} />, |
|
<HistoryPlot key="histplot-3" data={data} width={750} height={500} />, |
|
<CostPlot key="costplot-4" data={data} width={750} height={500} /> |
|
]); |
|
}, 100); |
|
|
|
return () => clearTimeout(timer); |
|
} |
|
}, [data]) |
|
|
|
const [windowWidth, setWindowWidth] = useState(window.innerWidth) |
|
const [windowHeight, setWindowHeight] = useState(window.innerHeight) |
|
|
|
useEffect(() => { |
|
const handleResize = () => { |
|
setWindowWidth(window.innerWidth) |
|
setWindowHeight(window.innerHeight) |
|
} |
|
window.addEventListener('resize', handleResize) |
|
return () => window.removeEventListener('resize', handleResize) |
|
}, []) |
|
|
|
|
|
useEffect(() => { |
|
if (data) { |
|
const timer = setTimeout(() => { |
|
setFullScreenCarouselItems([ |
|
<WorldMap |
|
key="fs-worldmap-0" |
|
data={data.countries} |
|
allLanguages={data.language_table} |
|
width={windowWidth * 0.7} |
|
height={windowHeight * 0.6} |
|
/>, |
|
<LanguagePlot |
|
key="fs-langplot-1" |
|
data={data} |
|
width={windowWidth * 0.7} |
|
height={windowHeight * 0.6} |
|
/>, |
|
<SpeakerPlot |
|
key="fs-speakerplot-2" |
|
data={data} |
|
width={windowWidth * 0.7} |
|
height={windowHeight * 0.6} |
|
/>, |
|
<HistoryPlot |
|
key="fs-histplot-3" |
|
data={data} |
|
width={windowWidth * 0.7} |
|
height={windowHeight * 0.6} |
|
/>, |
|
<CostPlot key="fs-costplot-4" data={data} width={windowWidth * 0.7} height={windowHeight * 0.6} /> |
|
]); |
|
}, 100); |
|
|
|
return () => clearTimeout(timer); |
|
} |
|
}, [data, windowWidth, windowHeight]) |
|
|
|
return ( |
|
<PrimeReactProvider> |
|
<div |
|
style={{ |
|
minHeight: '100vh', |
|
display: 'flex', |
|
flexDirection: 'column', |
|
width: '100vw' |
|
}} |
|
> |
|
<div |
|
style={{ |
|
backgroundColor: '#fff3cd', |
|
color: '#856404', |
|
padding: '1rem 1.5rem', |
|
marginBottom: '1rem', |
|
border: '1px solid #ffeeba', |
|
borderRadius: '0.25rem', |
|
textAlign: 'center', |
|
lineHeight: '1.5', |
|
position: 'relative' |
|
}} |
|
> |
|
<strong>Work in Progress:</strong> This dashboard is currently under |
|
active development. Evaluation results are not yet final. Note that the visualised results currently stem from sampling 20 instances per combination of model, task, and language. We have evaluated 139 languages across 41 models and 7 tasks, totaling over 300,000 individual evaluations. Only the top 150 languages by speaker count are included in the current evaluation scope. More extensive evaluation runs will be released later this year. |
|
</div> |
|
<div |
|
style={{ |
|
display: 'flex', |
|
justifyContent: 'flex-end', |
|
padding: '0 1.5rem', |
|
marginBottom: '1rem' |
|
}} |
|
> |
|
<a |
|
href='https://github.com/datenlabor-bmz/ai-language-monitor' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
style={{ |
|
textDecoration: 'none', |
|
color: '#6c757d', |
|
fontSize: '1rem', |
|
fontWeight: '500', |
|
padding: '0.5rem 1rem', |
|
borderRadius: '0.375rem', |
|
backgroundColor: '#f8f9fa', |
|
border: '1px solid #e9ecef', |
|
display: 'flex', |
|
alignItems: 'center', |
|
gap: '0.5rem', |
|
transition: 'all 0.2s ease', |
|
':hover': { |
|
backgroundColor: '#e9ecef', |
|
color: '#495057' |
|
} |
|
}} |
|
> |
|
<i className='pi pi-github' title='View on GitHub' /> |
|
GitHub |
|
</a> |
|
</div> |
|
<header |
|
style={{ |
|
display: 'flex', |
|
flexDirection: 'column', |
|
alignItems: 'center', |
|
justifyContent: 'center', |
|
padding: '5vh 5vw', |
|
width: '100%', |
|
maxWidth: '1400px', |
|
margin: '0 auto' |
|
}} |
|
> |
|
<div> |
|
<span |
|
role='img' |
|
aria-label='Globe Emoji' |
|
style={{ fontSize: '40px' }} |
|
> |
|
π |
|
</span> |
|
</div> |
|
<h1 |
|
style={{ |
|
fontSize: '2.5rem', |
|
fontWeight: '600', |
|
margin: '1rem 0 0.5rem 0', |
|
color: '#333', |
|
letterSpacing: '-0.01em' |
|
}} |
|
> |
|
AI Language Proficiency Monitor |
|
</h1> |
|
<p |
|
style={{ |
|
fontSize: '1.1rem', |
|
color: '#666', |
|
margin: '0 0 2.5rem 0', |
|
fontWeight: '400', |
|
maxWidth: '700px', |
|
lineHeight: '1.5' |
|
}} |
|
> |
|
Comprehensive multilingual evaluation results for AI language models |
|
</p> |
|
|
|
<div |
|
style={{ |
|
display: 'flex', |
|
gap: '0.75rem', |
|
marginBottom: '2rem', |
|
flexWrap: 'wrap', |
|
justifyContent: 'center' |
|
}} |
|
> |
|
<button |
|
onClick={() => setAboutVisible(true)} |
|
style={{ |
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', |
|
color: 'white', |
|
border: 'none', |
|
padding: '0.75rem 1.5rem', |
|
borderRadius: '12px', |
|
fontSize: '0.95rem', |
|
fontWeight: '500', |
|
cursor: 'pointer', |
|
display: 'flex', |
|
alignItems: 'center', |
|
gap: '0.5rem', |
|
boxShadow: '0 4px 15px rgba(102, 126, 234, 0.25)', |
|
transition: 'all 0.3s ease', |
|
':hover': { |
|
transform: 'translateY(-2px)', |
|
boxShadow: '0 8px 25px rgba(102, 126, 234, 0.35)' |
|
} |
|
}} |
|
onMouseEnter={(e) => { |
|
e.target.style.transform = 'translateY(-2px)'; |
|
e.target.style.boxShadow = '0 8px 25px rgba(102, 126, 234, 0.35)'; |
|
}} |
|
onMouseLeave={(e) => { |
|
e.target.style.transform = 'translateY(0)'; |
|
e.target.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.25)'; |
|
}} |
|
> |
|
<span style={{ fontSize: '1.1rem' }}>π</span> |
|
About this tool |
|
</button> |
|
|
|
<button |
|
onClick={() => setContributeVisible(true)} |
|
title='This feature is on our roadmap and will be available soon.' |
|
style={{ |
|
background: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fecfef 100%)', |
|
color: '#6b46c1', |
|
border: 'none', |
|
padding: '0.75rem 1.5rem', |
|
borderRadius: '12px', |
|
fontSize: '0.95rem', |
|
fontWeight: '500', |
|
cursor: 'pointer', |
|
display: 'flex', |
|
alignItems: 'center', |
|
gap: '0.5rem', |
|
boxShadow: '0 4px 15px rgba(255, 154, 158, 0.25)', |
|
transition: 'all 0.3s ease', |
|
position: 'relative', |
|
overflow: 'hidden' |
|
}} |
|
onMouseEnter={(e) => { |
|
e.target.style.transform = 'translateY(-2px)'; |
|
e.target.style.boxShadow = '0 8px 25px rgba(255, 154, 158, 0.35)'; |
|
}} |
|
onMouseLeave={(e) => { |
|
e.target.style.transform = 'translateY(0)'; |
|
e.target.style.boxShadow = '0 4px 15px rgba(255, 154, 158, 0.25)'; |
|
}} |
|
> |
|
<span style={{ fontSize: '1.1rem' }}>π</span> |
|
Add your model |
|
<span style={{ |
|
fontSize: '0.75rem', |
|
backgroundColor: 'rgba(107, 70, 193, 0.15)', |
|
padding: '0.2rem 0.5rem', |
|
borderRadius: '6px', |
|
marginLeft: '0.5rem', |
|
fontWeight: '600' |
|
}}> |
|
soon |
|
</span> |
|
</button> |
|
</div> |
|
|
|
{data && ( |
|
<AutoComplete |
|
languages={data?.language_table} |
|
onComplete={items => setSelectedLanguages(items)} |
|
/> |
|
)} |
|
</header> |
|
<main |
|
style={{ |
|
display: 'flex', |
|
flexDirection: 'column', |
|
gap: '3rem', |
|
width: '100%', |
|
paddingBottom: '5vh', |
|
padding: '1rem 15vw 5vh 15vw' |
|
}} |
|
> |
|
{loading && ( |
|
<div style={{ width: '100%', textAlign: 'center' }}> |
|
<i |
|
className='pi pi-spinner pi-spin' |
|
style={{ fontSize: '4rem' }} |
|
/> |
|
</div> |
|
)} |
|
{error && ( |
|
<div style={{ width: '100%', textAlign: 'center' }}> |
|
<p>Error: {error}</p> |
|
</div> |
|
)} |
|
{data && ( |
|
<> |
|
<ModelTable |
|
data={data.model_table} |
|
selectedLanguages={selectedLanguages} |
|
allLanguages={data.language_table || []} |
|
machineTranslatedMetrics={machineTranslatedMetrics} |
|
/> |
|
<LanguageTable |
|
data={data.language_table} |
|
selectedLanguages={selectedLanguages} |
|
setSelectedLanguages={setSelectedLanguages} |
|
totalModels={data.model_table?.length || 0} |
|
/> |
|
<DatasetTable data={data} /> |
|
<div |
|
id='figure' |
|
style={{ |
|
width: '100%', |
|
position: 'relative' |
|
}} |
|
> |
|
<Button |
|
icon='pi pi-external-link' |
|
className='p-button-text p-button-plain' |
|
onClick={() => setDialogVisible(true)} |
|
tooltip='Open in larger view' |
|
style={{ |
|
position: 'absolute', |
|
top: '10px', |
|
right: '10px', |
|
zIndex: 1, |
|
color: '#666' |
|
}} |
|
/> |
|
{carouselItems.length > 0 && ( |
|
<Carousel |
|
key={`main-carousel-${carouselItems.length}-${Date.now()}`} |
|
value={carouselItems} |
|
numScroll={1} |
|
numVisible={1} |
|
itemTemplate={item => item} |
|
circular={false} |
|
activeIndex={0} |
|
style={{ width: '100%', minHeight: '650px' }} |
|
/> |
|
)} |
|
</div> |
|
</> |
|
)} |
|
</main> |
|
|
|
{/* About Dialog */} |
|
<Dialog |
|
visible={aboutVisible} |
|
onHide={() => setAboutVisible(false)} |
|
style={{ width: '600px' }} |
|
modal |
|
header='About this tool' |
|
> |
|
<div> |
|
<p> |
|
The <i>AI Language Proficiency Monitor</i> presents comprehensive |
|
multilingual evaluation results of AI language models. |
|
</p> |
|
<h4>Who is this for?</h4> |
|
<ul> |
|
<li> |
|
<b>Practitioners</b> can pick the best model for a given |
|
language. |
|
</li> |
|
<li> |
|
<b>Policymakers and funders</b> can identify and prioritize |
|
neglected languages. |
|
</li> |
|
<li> |
|
<b>Model developers</b> can compete on our{' '} |
|
<i>AI Language Proficiency</i> metric. |
|
</li> |
|
</ul> |
|
<h4>β‘ Live Updates</h4> |
|
<p> |
|
Benchmark results automatically refresh every night and include |
|
the most popular models from{' '} |
|
<a |
|
href='https://openrouter.ai' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
> |
|
OpenRouter |
|
</a> |
|
, plus community-submitted models. |
|
</p> |
|
<h4>Authors</h4> |
|
<p> |
|
The AI Language Proficiency Monitor is a collaboration between |
|
BMZ's{' '} |
|
<a |
|
href='https://www.bmz-digital.global/en/overview-of-initiatives/the-bmz-data-lab/' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
> |
|
Data Lab |
|
</a> |
|
, the BMZ-Initiative{' '} |
|
<a |
|
href='https://www.bmz-digital.global/en/overview-of-initiatives/fair-forward/' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
> |
|
Fair Forward |
|
</a>{' '} |
|
(implemented by GIZ), and the{' '} |
|
<a |
|
href='https://www.dfki.de/en/web/research/research-departments/multilinguality-and-language-technology/ee-team' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
> |
|
E&E group |
|
</a>{' '} |
|
of DFKI's Multilinguality and Language Technology Lab. |
|
</p> |
|
<h4>π Links</h4> |
|
<p> |
|
<a |
|
href='https://github.com/datenlabor-bmz/ai-language-monitor' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
style={{ |
|
color: '#666', |
|
textDecoration: 'none', |
|
display: 'inline-flex', |
|
alignItems: 'center', |
|
gap: '0.5rem' |
|
}} |
|
> |
|
<i className='pi pi-github' style={{ fontSize: '1.2rem' }} /> |
|
View source code on GitHub |
|
</a> |
|
</p> |
|
</div> |
|
</Dialog> |
|
|
|
{/* Contribute Dialog */} |
|
<Dialog |
|
visible={contributeVisible} |
|
onHide={() => setContributeVisible(false)} |
|
style={{ width: '600px' }} |
|
modal |
|
header='Add your model & Contribute' |
|
> |
|
<div> |
|
<h4>π Submit Your Model</h4> |
|
<p> |
|
Have a custom fine-tuned model you'd like to see on the |
|
leaderboard? |
|
</p> |
|
<p> |
|
<a |
|
href='https://forms.gle/ckvY9pS7XLcHYnaV8' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
style={{ color: '#28a745', fontWeight: 'bold' }} |
|
> |
|
β Submit your model here |
|
</a> |
|
</p> |
|
|
|
<h4>π§ Contribute to Development</h4> |
|
<p> |
|
Help us expand language coverage and add new evaluation tasks: |
|
</p> |
|
<p> |
|
<a |
|
href='https://github.com/datenlabor-bmz/ai-language-monitor/blob/main/CONTRIBUTING.md' |
|
target='_blank' |
|
rel='noopener noreferrer' |
|
style={{ color: '#007bff', fontWeight: 'bold' }} |
|
> |
|
β Contribution guidelines |
|
</a> |
|
</p> |
|
</div> |
|
</Dialog> |
|
|
|
{/* Full-screen Dialog for Charts */} |
|
<Dialog |
|
visible={dialogVisible} |
|
onHide={() => setDialogVisible(false)} |
|
style={{ width: '90vw', height: '90vh' }} |
|
maximizable |
|
modal |
|
header={null} |
|
> |
|
{fullScreenCarouselItems.length > 0 && ( |
|
<div style={{ width: '100%', height: '100%' }}> |
|
<Carousel |
|
key={`fs-carousel-${fullScreenCarouselItems.length}-${Date.now()}`} |
|
value={fullScreenCarouselItems} |
|
numScroll={1} |
|
numVisible={1} |
|
itemTemplate={item => item} |
|
circular={false} |
|
activeIndex={0} |
|
style={{ width: '100%', height: 'calc(90vh - 120px)' }} |
|
/> |
|
</div> |
|
)} |
|
</Dialog> |
|
</div> |
|
</PrimeReactProvider> |
|
) |
|
} |
|
|
|
export default App |