elmadany commited on
Commit
d55213e
Β·
verified Β·
1 Parent(s): 4a7e467

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +148 -91
public/index.html CHANGED
@@ -10,6 +10,8 @@
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
 
13
 
14
  <style>
15
  :root {
@@ -42,10 +44,7 @@
42
  .nav-text { display: flex; flex-direction: column; line-height: 1.1; }
43
  .nav-text span:first-child { font-size: 14px; color: var(--text-yellow); }
44
  .nav-links { display: flex; gap: 25px; align-items: center; }
45
- .nav-link { color: #e2e8f0; font-size: 15px; font-weight: 500; }
46
- .nav-link:hover { color: var(--text-yellow); }
47
  .btn-submit { background: var(--grad-blue); color: white !important; padding: 10px 24px; border-radius: 30px; font-weight: 600; font-size: 14px; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); }
48
- .btn-login { background: var(--text-yellow); color: var(--bg-deep) !important; padding: 10px 24px; border-radius: 30px; font-weight: 700; font-size: 14px; box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4); }
49
 
50
  /* --- HERO --- */
51
  .hero {
@@ -69,11 +68,7 @@
69
  .hero-actions { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; }
70
  .action-btn { display: flex; align-items: center; gap: 8px; padding: 12px 24px; border-radius: 30px; color: white; font-weight: 600; font-size: 15px; transition: transform 0.2s; }
71
  .action-btn:hover { transform: translateY(-3px); color: white; }
72
- .bg-red { background: var(--grad-red); }
73
- .bg-purple { background: var(--grad-purple); }
74
- .bg-orange { background: var(--grad-orange); }
75
- .bg-blue { background: var(--grad-blue); }
76
- .bg-green { background: var(--grad-green); }
77
 
78
  /* Wave Animation */
79
  .wave-bg {
@@ -81,15 +76,9 @@
81
  background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 70%);
82
  z-index: 0; pointer-events: none;
83
  }
84
-
85
  .audio-wave {
86
- height: 150px;
87
- display: flex;
88
- align-items: center;
89
- justify-content: center;
90
- gap: 6px;
91
- margin-top: 60px; /* Space above wave */
92
- mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
93
  }
94
  .bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
95
  .bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
@@ -126,6 +115,23 @@
126
 
127
  h4 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 18px; margin: 0 0 15px 0; border-bottom: 1px solid #eee; padding-bottom: 10px; }
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  /* Citations */
130
  .citation-section { background: #0f172a; border: 1px solid #334155; border-radius: 16px; padding: 40px; box-sizing: border-box; box-shadow: 0 20px 40px rgba(0,0,0,0.3); position: relative; }
131
  .citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
@@ -148,6 +154,7 @@
148
  <div class="nav-text"><span>Voice of a</span> Continent</div>
149
  </a>
150
  <div class="nav-links">
 
151
  <a href="https://africa.dlnlp.ai/simba/" target="_blank" class="btn-submit">Submit New Results</a>
152
  </div>
153
  </nav>
@@ -160,22 +167,14 @@
160
  <h2>Mapping Africa’s Speech Technology</h2>
161
  <p>SimbaBench bridges the digital divide with a unified suite for African AI: the largest open-source speech benchmark covering 61 languages.</p>
162
  <div class="hero-actions">
163
- <a href="https://aclanthology.org/2025.emnlp-main.559" target="_blank" class="action-btn bg-red">
164
- <i class="fa-regular fa-file-pdf"></i> Read Paper
165
- </a>
166
- <a href="https://github.com/UBC-NLP/simba" target="_blank" class="action-btn bg-purple">
167
- <i class="fa-solid fa-code-branch"></i> Pipeline
168
- </a>
169
- <a href="https://huggingface.co/datasets/UBC-NLP/simba" target="_blank" class="action-btn bg-orange">
170
- <i class="fa-solid fa-database"></i> Dataset
171
- </a>
172
- <a href="https://africa.dlnlp.ai/simba/#models" target="_blank" class="action-btn bg-blue">
173
- <i class="fa-solid fa-bolt"></i> Models
174
- </a>
175
  </div>
176
  </div>
177
- <div class="audio-wave">
178
- <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
 
 
179
  </div>
180
  </div>
181
 
@@ -211,7 +210,6 @@
211
  <div id="asr-rank-info" class="rank-info">Ranking: WER (↓) then CER (↓)</div>
212
  </div>
213
  <div id="asr-desc"></div>
214
-
215
  <div id="asr-table" class="table-wrap"></div>
216
  </div>
217
 
@@ -239,14 +237,25 @@
239
  <select id="lang-select" onchange="renderLanguageView()"></select>
240
  </div>
241
 
242
- <h4>Automatic Speech Recognition (ASR) Results <span class="rank-info" style="font-weight:normal;">(Ranked by WER ↓, then CER ↓)</span></h4>
243
- <div id="lang-asr-table" class="table-wrap"></div>
 
 
 
 
 
244
 
245
- <h4>Text-to-Speech (TTS) Results</h4>
246
- <div id="lang-tts-table" class="table-wrap"></div>
 
 
 
247
 
248
- <h4>Spoken Language Identification (SLID) Results <span class="rank-info" style="font-weight:normal;">(Ranked by Score ↑)</span></h4>
249
- <div id="lang-slid-table" class="table-wrap"></div>
 
 
 
250
  </div>
251
  </div>
252
  </div>
@@ -282,6 +291,7 @@
282
  const NAME_TO_ISO = {'Afrikaans':'afr', 'Akuapim-twi':'ak', 'Amharic':'amh', 'Asante-twi':'tw', 'Bemba':'bem', 'Hausa':'hau', 'Igbo':'ibo', 'Yoruba':'yor', 'Zulu':'zul', 'Swahili':'swa', 'Somali':'som', 'Wolof':'wol', 'Xhosa':'xho'};
283
  let DATA = null;
284
  let asrMode = 'overview';
 
285
 
286
  fetch('/api/data').then(r=>r.json()).then(d=>{
287
  DATA = d;
@@ -336,7 +346,7 @@
336
  if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
337
  });
338
 
339
- modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
340
 
341
  html = `<table><thead><tr><th>Model</th><th>Simba ASR Score (WER/CER) <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
342
 
@@ -344,11 +354,10 @@
344
  modelStats.forEach((m, i) => {
345
  if (i > 0) {
346
  const prev = modelStats[i-1];
347
- if (Math.abs(m.wer - prev.wer) > 0.001) currentRank = i + 1;
348
- } else {
349
- currentRank = 1;
350
- }
351
  m.rank = currentRank;
 
352
  let icon = '';
353
  if(m.rank === 1) icon = 'πŸ₯‡ '; else if(m.rank === 2) icon = 'πŸ₯ˆ '; else if(m.rank === 3) icon = 'πŸ₯‰ ';
354
 
@@ -364,6 +373,7 @@
364
 
365
  const d = DATA.asr.by_family[val];
366
  if(!d) return;
 
367
  d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
368
 
369
  html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
@@ -375,10 +385,9 @@
375
  if(idx > 0) {
376
  const prev = d.data[idx-1];
377
  if (Math.abs(r.Avg_WER - prev.Avg_WER) > 0.001) currentRank = idx + 1;
378
- } else {
379
- currentRank = 1;
380
- }
381
  r.rank = currentRank;
 
382
  let icon = '';
383
  if(r.rank === 1) icon = 'πŸ₯‡ '; else if(r.rank === 2) icon = 'πŸ₯ˆ '; else if(r.rank === 3) icon = 'πŸ₯‰ ';
384
 
@@ -451,7 +460,7 @@
451
  function renderLanguageView() {
452
  const lang = document.getElementById('lang-select').value;
453
 
454
- // 1. ASR
455
  let asrRows = [];
456
  Object.keys(DATA.asr.by_model).forEach(modName => {
457
  const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
@@ -459,83 +468,131 @@
459
  });
460
  asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  let h1 = `<table><thead><tr><th>Model</th><th>WER <i class="fa-solid fa-arrow-down arrow-down"></i></th><th>CER <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
463
  if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
464
  else {
465
  let currentRank = 1;
466
  asrRows.forEach((r, i) => {
467
- if (i > 0) {
468
- const prev = asrRows[i-1];
469
- if (Math.abs(r.wer - prev.wer) > 0.001) currentRank = i + 1;
470
- } else {
471
- currentRank = 1;
472
- }
473
- r.rank = currentRank;
474
-
475
- let icon = '';
476
- if(r.rank === 1) icon = 'πŸ₯‡ '; else if(r.rank === 2) icon = 'πŸ₯ˆ '; else if(r.rank === 3) icon = 'πŸ₯‰ ';
477
  h1 += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.model}</td><td>${fmt(r.wer)}</td><td>${fmt(r.cer)}</td></tr>`;
478
  });
479
  }
480
  h1 += `</tbody></table>`;
481
  document.getElementById('lang-asr-table').innerHTML = h1;
482
 
483
- // 2. TTS
484
  let h2 = `<table><thead><tr><th>Model</th>
485
  <th>WER(↓)</th><th>MCD(↓)</th><th>LogF0RMSE(↓)</th><th>SpeechTokenDistance(↓)</th>
486
  <th>PESQ(↑)</th><th>UTMOS(↑)</th><th>SpeechBLEU(↑)</th><th>SpeechBERTScore(↑)</th>
487
  </tr></thead><tbody>`;
488
-
489
- let ttsFound = false;
490
- Object.keys(DATA.tts).forEach(mod => {
491
- const row = DATA.tts[mod].find(r => r.language === lang);
492
- if(row) {
493
- ttsFound = true;
494
  h2 += `<tr>
495
- <td style="font-weight:600;">${mod}</td>
496
  <td>${fmt(row.wer)}</td><td>${fmt(row.mcd)}</td>
497
  <td>${fmt(row.logf0rmse)}</td><td>${fmt(row.speech_token_distance)}</td>
498
  <td>${fmt(row.pesq)}</td><td>${fmt(row.utmos)}</td>
499
  <td>${fmt(row.speechbleu)}</td><td>${fmt(row.speechbert_score)}</td>
500
  </tr>`;
501
- }
502
- });
503
- if(!ttsFound) h2 += `<tr><td colspan="9" class="empty-msg">No TTS results available for ${lang}.</td></tr>`;
504
  h2 += `</tbody></table>`;
505
  document.getElementById('lang-tts-table').innerHTML = h2;
506
 
507
- // 3. SLID
508
  let h3 = `<table><thead><tr><th>Model</th><th>F1-Score <i class="fa-solid fa-arrow-up arrow-up"></i></th></tr></thead><tbody>`;
509
- const slidRow = DATA.slid.find(r => r.Language === lang) ||
510
- DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
511
- DATA.slid.find(r => r.Language.includes(lang));
512
-
513
- if(slidRow) {
514
- const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
515
- const scores = models.map(m => ({n: m, v: slidRow[m]})).sort((a,b) => b.v - a.v);
516
-
517
  let currentRank = 1;
518
- scores.forEach((sc, i) => {
519
- if(i > 0) {
520
- const prev = scores[i-1];
521
- if (Math.abs(sc.v - prev.v) > 0.001) currentRank = i + 1;
522
- } else {
523
- currentRank = 1;
524
- }
525
- sc.rank = currentRank;
526
-
527
- let icon = '';
528
- if(sc.rank === 1) icon = 'πŸ₯‡ '; else if(sc.rank === 2) icon = 'πŸ₯ˆ '; else if(sc.rank === 3) icon = 'πŸ₯‰ ';
529
-
530
- h3 += `<tr><td style="font-weight:600;">${icon}${sc.n}</td><td>${fmt(sc.v)}</td></tr>`;
531
  });
532
- } else {
533
- h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
534
  }
535
  h3 += `</tbody></table>`;
536
  document.getElementById('lang-slid-table').innerHTML = h3;
537
  }
538
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  // --- UI ---
540
  window.setTab = (id, btn) => {
541
  document.querySelectorAll('.view').forEach(e => e.classList.remove('active'));
 
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
+
14
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
15
 
16
  <style>
17
  :root {
 
44
  .nav-text { display: flex; flex-direction: column; line-height: 1.1; }
45
  .nav-text span:first-child { font-size: 14px; color: var(--text-yellow); }
46
  .nav-links { display: flex; gap: 25px; align-items: center; }
 
 
47
  .btn-submit { background: var(--grad-blue); color: white !important; padding: 10px 24px; border-radius: 30px; font-weight: 600; font-size: 14px; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4); }
 
48
 
49
  /* --- HERO --- */
50
  .hero {
 
68
  .hero-actions { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; }
69
  .action-btn { display: flex; align-items: center; gap: 8px; padding: 12px 24px; border-radius: 30px; color: white; font-weight: 600; font-size: 15px; transition: transform 0.2s; }
70
  .action-btn:hover { transform: translateY(-3px); color: white; }
71
+ .bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); }
 
 
 
 
72
 
73
  /* Wave Animation */
74
  .wave-bg {
 
76
  background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 70%);
77
  z-index: 0; pointer-events: none;
78
  }
 
79
  .audio-wave {
80
+ height: 150px; display: flex; align-items: center; justify-content: center; gap: 6px;
81
+ margin-top: 60px; mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
 
 
 
 
 
82
  }
83
  .bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
84
  .bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
 
115
 
116
  h4 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 18px; margin: 0 0 15px 0; border-bottom: 1px solid #eee; padding-bottom: 10px; }
117
 
118
+ /* --- SUMMARY CARDS (Language Analysis) --- */
119
+ .summary-grid {
120
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px;
121
+ }
122
+ .stat-card {
123
+ background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px;
124
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center; transition: 0.3s;
125
+ }
126
+ .stat-card:hover { transform: translateY(-3px); border-color: var(--text-yellow); }
127
+ .stat-title { font-size: 12px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
128
+ .stat-model { font-size: 18px; font-weight: 700; color: #0f172a; margin-bottom: 5px; }
129
+ .stat-val { font-size: 14px; color: var(--text-yellow); font-weight: 600; }
130
+
131
+ /* --- CHART CONTAINERS --- */
132
+ .chart-section { margin-bottom: 50px; }
133
+ .chart-box { height: 300px; width: 100%; margin-bottom: 20px; }
134
+
135
  /* Citations */
136
  .citation-section { background: #0f172a; border: 1px solid #334155; border-radius: 16px; padding: 40px; box-sizing: border-box; box-shadow: 0 20px 40px rgba(0,0,0,0.3); position: relative; }
137
  .citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
 
154
  <div class="nav-text"><span>Voice of a</span> Continent</div>
155
  </a>
156
  <div class="nav-links">
157
+ <a href="https://aclanthology.org/2025.emnlp-main.559" target="_blank" class="nav-link">Paper</a>
158
  <a href="https://africa.dlnlp.ai/simba/" target="_blank" class="btn-submit">Submit New Results</a>
159
  </div>
160
  </nav>
 
167
  <h2>Mapping Africa’s Speech Technology</h2>
168
  <p>SimbaBench bridges the digital divide with a unified suite for African AI: the largest open-source speech benchmark covering 61 languages.</p>
169
  <div class="hero-actions">
170
+ <a href="https://aclanthology.org/2025.emnlp-main.559" target="_blank" class="action-btn bg-red"><i class="fa-regular fa-file-pdf"></i> Read Paper</a>
171
+ <a href="https://africa.dlnlp.ai/simba" target="_blank" class="action-btn bg-purple"><i class="fa-solid fa-code-branch"></i> Official Website</a>
 
 
 
 
 
 
 
 
 
 
172
  </div>
173
  </div>
174
+ <div class="hero-visual">
175
+ <div class="audio-wave">
176
+ <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
177
+ </div>
178
  </div>
179
  </div>
180
 
 
210
  <div id="asr-rank-info" class="rank-info">Ranking: WER (↓) then CER (↓)</div>
211
  </div>
212
  <div id="asr-desc"></div>
 
213
  <div id="asr-table" class="table-wrap"></div>
214
  </div>
215
 
 
237
  <select id="lang-select" onchange="renderLanguageView()"></select>
238
  </div>
239
 
240
+ <div id="lang-summary" class="summary-grid"></div>
241
+
242
+ <div class="chart-section">
243
+ <h4>Automatic Speech Recognition (ASR) <span class="rank-info" style="font-weight:normal;">(WER ↓)</span></h4>
244
+ <div class="chart-box"><canvas id="chart-asr"></canvas></div>
245
+ <div id="lang-asr-table" class="table-wrap"></div>
246
+ </div>
247
 
248
+ <div class="chart-section">
249
+ <h4>Text-to-Speech (TTS)</h4>
250
+ <div class="chart-box"><canvas id="chart-tts"></canvas></div>
251
+ <div id="lang-tts-table" class="table-wrap"></div>
252
+ </div>
253
 
254
+ <div class="chart-section">
255
+ <h4>Spoken Language Identification (SLID) <span class="rank-info" style="font-weight:normal;">(F1 ↑)</span></h4>
256
+ <div class="chart-box"><canvas id="chart-slid"></canvas></div>
257
+ <div id="lang-slid-table" class="table-wrap"></div>
258
+ </div>
259
  </div>
260
  </div>
261
  </div>
 
291
  const NAME_TO_ISO = {'Afrikaans':'afr', 'Akuapim-twi':'ak', 'Amharic':'amh', 'Asante-twi':'tw', 'Bemba':'bem', 'Hausa':'hau', 'Igbo':'ibo', 'Yoruba':'yor', 'Zulu':'zul', 'Swahili':'swa', 'Somali':'som', 'Wolof':'wol', 'Xhosa':'xho'};
292
  let DATA = null;
293
  let asrMode = 'overview';
294
+ let charts = { asr:null, tts:null, slid:null }; // Stores chart instances
295
 
296
  fetch('/api/data').then(r=>r.json()).then(d=>{
297
  DATA = d;
 
346
  if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
347
  });
348
 
349
+ modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer)); // Sorting by WER then CER
350
 
351
  html = `<table><thead><tr><th>Model</th><th>Simba ASR Score (WER/CER) <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
352
 
 
354
  modelStats.forEach((m, i) => {
355
  if (i > 0) {
356
  const prev = modelStats[i-1];
357
+ if (Math.abs(m.wer - prev.wer) > 0.001) { currentRank = i + 1; }
358
+ } else { currentRank = 1; }
 
 
359
  m.rank = currentRank;
360
+
361
  let icon = '';
362
  if(m.rank === 1) icon = 'πŸ₯‡ '; else if(m.rank === 2) icon = 'πŸ₯ˆ '; else if(m.rank === 3) icon = 'πŸ₯‰ ';
363
 
 
373
 
374
  const d = DATA.asr.by_family[val];
375
  if(!d) return;
376
+
377
  d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
378
 
379
  html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
 
385
  if(idx > 0) {
386
  const prev = d.data[idx-1];
387
  if (Math.abs(r.Avg_WER - prev.Avg_WER) > 0.001) currentRank = idx + 1;
388
+ } else { currentRank = 1; }
 
 
389
  r.rank = currentRank;
390
+
391
  let icon = '';
392
  if(r.rank === 1) icon = 'πŸ₯‡ '; else if(r.rank === 2) icon = 'πŸ₯ˆ '; else if(r.rank === 3) icon = 'πŸ₯‰ ';
393
 
 
460
  function renderLanguageView() {
461
  const lang = document.getElementById('lang-select').value;
462
 
463
+ // 1. ASR Data Preparation
464
  let asrRows = [];
465
  Object.keys(DATA.asr.by_model).forEach(modName => {
466
  const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
 
468
  });
469
  asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
470
 
471
+ // 2. TTS Data Preparation
472
+ let ttsRows = [];
473
+ Object.keys(DATA.tts).forEach(mod => {
474
+ const row = DATA.tts[mod].find(r => r.language === lang);
475
+ if(row) ttsRows.push({model: mod, ...row});
476
+ });
477
+
478
+ // 3. SLID Data Preparation
479
+ let slidRows = [];
480
+ const slidRow = DATA.slid.find(r => r.Language === lang) ||
481
+ DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
482
+ DATA.slid.find(r => r.Language.includes(lang));
483
+ if(slidRow) {
484
+ const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
485
+ models.forEach(m => slidRows.push({model: m, val: slidRow[m]}));
486
+ slidRows.sort((a,b) => b.val - a.val); // Descending
487
+ }
488
+
489
+ // --- RENDER SUMMARY CARDS ---
490
+ let summaryHTML = "";
491
+
492
+ // ASR Winner
493
+ if(asrRows.length > 0) {
494
+ summaryHTML += `<div class="stat-card"><div class="stat-title">Best ASR Model</div><div class="stat-model">${asrRows[0].model}</div><div class="stat-val">WER: ${fmt(asrRows[0].wer)}%</div></div>`;
495
+ } else summaryHTML += `<div class="stat-card"><div class="stat-title">ASR</div><div class="stat-val">No Data</div></div>`;
496
+
497
+ // TTS Winner (Lowest WER for Intelligibility)
498
+ if(ttsRows.length > 0) {
499
+ // Sort by WER ascending
500
+ const bestTTS = [...ttsRows].sort((a,b) => a.wer - b.wer)[0];
501
+ summaryHTML += `<div class="stat-card"><div class="stat-title">Best TTS (Intelligibility)</div><div class="stat-model">${bestTTS.model}</div><div class="stat-val">WER: ${fmt(bestTTS.wer)}%</div></div>`;
502
+ } else summaryHTML += `<div class="stat-card"><div class="stat-title">TTS</div><div class="stat-val">No Data</div></div>`;
503
+
504
+ // SLID Winner
505
+ if(slidRows.length > 0) {
506
+ summaryHTML += `<div class="stat-card"><div class="stat-title">Best SLID Model</div><div class="stat-model">${slidRows[0].model}</div><div class="stat-val">F1: ${fmt(slidRows[0].val)}%</div></div>`;
507
+ } else summaryHTML += `<div class="stat-card"><div class="stat-title">SLID</div><div class="stat-val">No Data</div></div>`;
508
+
509
+ document.getElementById('lang-summary').innerHTML = summaryHTML;
510
+
511
+ // --- RENDER CHARTS ---
512
+ updateChart('chart-asr', 'ASR (WER Lower is Better)', asrRows.map(r=>r.model), asrRows.map(r=>r.wer), '#3b82f6');
513
+ updateChart('chart-tts', 'TTS (WER Lower is Better)', ttsRows.map(r=>r.model), ttsRows.map(r=>r.wer), '#f97316');
514
+ updateChart('chart-slid', 'SLID (F1 Higher is Better)', slidRows.map(r=>r.model), slidRows.map(r=>r.val), '#22c55e');
515
+
516
+ // --- RENDER TABLES ---
517
+
518
+ // ASR Table
519
  let h1 = `<table><thead><tr><th>Model</th><th>WER <i class="fa-solid fa-arrow-down arrow-down"></i></th><th>CER <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
520
  if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
521
  else {
522
  let currentRank = 1;
523
  asrRows.forEach((r, i) => {
524
+ if (i > 0) { if (Math.abs(r.wer - asrRows[i-1].wer) > 0.001) currentRank = i + 1; } else currentRank = 1;
525
+ let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
 
 
 
 
 
 
 
 
526
  h1 += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.model}</td><td>${fmt(r.wer)}</td><td>${fmt(r.cer)}</td></tr>`;
527
  });
528
  }
529
  h1 += `</tbody></table>`;
530
  document.getElementById('lang-asr-table').innerHTML = h1;
531
 
532
+ // TTS Table
533
  let h2 = `<table><thead><tr><th>Model</th>
534
  <th>WER(↓)</th><th>MCD(↓)</th><th>LogF0RMSE(↓)</th><th>SpeechTokenDistance(↓)</th>
535
  <th>PESQ(↑)</th><th>UTMOS(↑)</th><th>SpeechBLEU(↑)</th><th>SpeechBERTScore(↑)</th>
536
  </tr></thead><tbody>`;
537
+ if(ttsRows.length === 0) h2 += `<tr><td colspan="9" class="empty-msg">No TTS results available for ${lang}.</td></tr>`;
538
+ else {
539
+ ttsRows.forEach(row => {
 
 
 
540
  h2 += `<tr>
541
+ <td style="font-weight:600;">${row.model}</td>
542
  <td>${fmt(row.wer)}</td><td>${fmt(row.mcd)}</td>
543
  <td>${fmt(row.logf0rmse)}</td><td>${fmt(row.speech_token_distance)}</td>
544
  <td>${fmt(row.pesq)}</td><td>${fmt(row.utmos)}</td>
545
  <td>${fmt(row.speechbleu)}</td><td>${fmt(row.speechbert_score)}</td>
546
  </tr>`;
547
+ });
548
+ }
 
549
  h2 += `</tbody></table>`;
550
  document.getElementById('lang-tts-table').innerHTML = h2;
551
 
552
+ // SLID Table
553
  let h3 = `<table><thead><tr><th>Model</th><th>F1-Score <i class="fa-solid fa-arrow-up arrow-up"></i></th></tr></thead><tbody>`;
554
+ if(slidRows.length === 0) h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
555
+ else {
 
 
 
 
 
 
556
  let currentRank = 1;
557
+ slidRows.forEach((sc, i) => {
558
+ if (i > 0) { if (Math.abs(sc.val - slidRows[i-1].val) > 0.001) currentRank = i + 1; } else currentRank = 1;
559
+ let icon = (currentRank===1)?'πŸ₯‡ ':(currentRank===2)?'πŸ₯ˆ ':(currentRank===3)?'πŸ₯‰ ':'';
560
+ h3 += `<tr><td style="font-weight:600;">${icon}${sc.model}</td><td>${fmt(sc.val)}</td></tr>`;
 
 
 
 
 
 
 
 
 
561
  });
 
 
562
  }
563
  h3 += `</tbody></table>`;
564
  document.getElementById('lang-slid-table').innerHTML = h3;
565
  }
566
 
567
+ // --- CHART LOGIC ---
568
+ function updateChart(canvasId, label, labels, data, color) {
569
+ const ctx = document.getElementById(canvasId).getContext('2d');
570
+
571
+ // Destroy existing chart if it exists
572
+ if(charts[canvasId]) charts[canvasId].destroy();
573
+
574
+ if(labels.length === 0) return; // Don't draw empty chart
575
+
576
+ charts[canvasId] = new Chart(ctx, {
577
+ type: 'bar',
578
+ data: {
579
+ labels: labels,
580
+ datasets: [{
581
+ label: label,
582
+ data: data,
583
+ backgroundColor: color,
584
+ borderRadius: 4
585
+ }]
586
+ },
587
+ options: {
588
+ responsive: true,
589
+ maintainAspectRatio: false,
590
+ plugins: { legend: { display: true } },
591
+ scales: { y: { beginAtZero: true } }
592
+ }
593
+ });
594
+ }
595
+
596
  // --- UI ---
597
  window.setTab = (id, btn) => {
598
  document.querySelectorAll('.view').forEach(e => e.classList.remove('active'));