|
|
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. 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 |