elmadany commited on
Commit
eb2b89b
·
verified ·
1 Parent(s): b7d2379

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +189 -190
public/index.html CHANGED
@@ -10,138 +10,74 @@
10
  --primary-navy: #282351;
11
  --primary-orange: #D97706;
12
  --secondary-navy: #2f3b7d;
13
- --bg-body: #282351; /* Deep Navy Background */
14
- --bg-card: #ffffff; /* White Card Background */
15
  --border-gold: #dca02a;
16
  --text-dark: #333;
17
  }
18
 
19
- body {
20
- background-color: var(--bg-body);
21
- font-family: 'Inter', sans-serif;
22
- color: var(--text-dark);
23
- margin: 0;
24
- padding: 20px;
25
- }
26
-
27
  .container { max-width: 1400px; margin: 0 auto; }
28
 
29
- /* Header Image */
30
  .header { text-align: center; margin-bottom: 30px; }
31
- .header img {
32
- max-width: 60%;
33
- height: auto;
34
- border-radius: 12px; /* Optional: Softens the image against the dark bg */
35
- }
36
 
37
- /* Content Card */
38
- .card {
39
- background: var(--bg-card);
40
- border-radius: 12px;
41
- padding: 40px;
42
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
43
- margin-bottom: 30px;
44
- }
45
 
46
  h2 { font-family: "Rubik", sans-serif; color: var(--secondary-navy); font-size: 28px; margin-top: 0; }
47
-
 
48
  /* Tabs */
49
  .tabs { display: flex; border-bottom: 2px solid var(--primary-navy); margin-bottom: 20px; }
50
  .tab-btn {
51
  background: none; border: none; padding: 12px 20px;
52
  font-size: 16px; font-weight: 600; color: var(--secondary-navy);
53
- cursor: pointer; border-radius: 8px 8px 0 0;
54
- transition: 0.3s;
55
  }
56
  .tab-btn.active { background: var(--primary-navy); color: white; }
57
  .view { display: none; animation: fadeIn 0.4s; }
58
  .view.active { display: block; }
59
 
60
  /* Controls */
61
- .controls { display: flex; gap: 10px; margin-bottom: 20px; align-items: center; flex-wrap: wrap; }
 
 
 
62
  .btn { padding: 8px 16px; border-radius: 6px; cursor: pointer; border: none; font-weight: bold; }
63
  .btn-primary { background: var(--primary-navy); color: white; }
64
  .btn-secondary { background: #eee; color: var(--primary-navy); }
65
  select { padding: 8px; border-radius: 6px; border: 1px solid #ccc; min-width: 200px; }
66
-
67
- /* --- TABLE STYLING --- */
68
- .table-wrap { overflow-x: auto; padding-bottom: 50px; /* Space for rotated headers */ }
69
-
70
- table {
71
- width: 100%;
72
- border-collapse: collapse;
73
- font-size: 14px;
74
- border-top: 2px solid var(--border-gold);
75
- /* We remove the bottom border from table to let rows define it, or keep it */
76
- border-bottom: 2px solid var(--border-gold);
77
- }
78
-
79
- th, td {
80
- padding: 12px 10px;
81
- text-align: center;
82
- vertical-align: middle;
83
- border-bottom: 1px solid #eee;
84
- }
85
 
86
- /* Header Row Styling */
87
- thead tr {
88
- /* Make header bottom border match the top border color */
89
- border-bottom: 2px solid var(--border-gold);
90
- }
91
-
92
- th {
93
- color: #7d3561;
94
- font-weight: bold;
95
- vertical-align: bottom; /* Align text to bottom for rotated headers */
96
  }
 
 
97
 
98
- /* Left Align First Column (Languages/Models) */
99
- th:first-child, td:first-child {
100
- text-align: left;
101
- font-weight: bold;
102
- color: var(--primary-navy);
103
- min-width: 180px;
104
- }
105
 
106
- /* Left Align Second Column (Often Family) */
107
- th:nth-child(2), td:nth-child(2) {
108
- text-align: left;
109
- }
110
-
111
- /* 45 Degree Rotated Headers */
112
- .rotate-wrapper {
113
- height: 120px;
114
- white-space: nowrap;
115
- position: relative;
116
- }
117
 
118
- .rotate-wrapper > div {
119
- transform: translate(0px, 40px) rotate(-45deg);
120
- width: 30px;
121
- position: absolute;
122
- bottom: 10px;
123
- left: 50%;
124
- text-align: left;
125
- transform-origin: bottom left;
126
- }
127
- .rotate-wrapper span {
128
- border-bottom: 1px solid #ccc;
129
- padding-bottom: 2px;
130
- }
131
- .sub-header {
132
- font-size: 10px;
133
- color: #666;
134
- font-weight: normal;
135
- display: block;
136
- margin-top: 2px;
137
- }
138
 
139
- /* Value Cells */
140
  .val { white-space: nowrap; }
141
-
142
- /* Metric Arrow Colors */
143
  .arrow-up { color: green; font-size: 0.9em; }
144
- .arrow-down { color: #D97706; font-size: 0.9em; } /* Orange for lower-is-better */
145
 
146
  #loader { text-align: center; padding: 40px; font-size: 18px; color: var(--secondary-navy); }
147
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -150,36 +86,51 @@
150
  <body>
151
 
152
  <div class="container">
153
- <div class="header">
154
- <img src="https://africa.dlnlp.ai/simba/images/simbabench_header.png" alt="Simba">
155
- </div>
156
 
157
  <div class="card">
158
  <div id="loader">🦁 Loading Data...</div>
159
 
160
  <div id="content" style="display:none;">
161
  <div class="tabs">
162
- <button class="tab-btn active" onclick="setTab('asr', this)">Automatic Speech Recognition</button>
163
- <button class="tab-btn" onclick="setTab('tts', this)">Text-to-Speech</button>
164
- <button class="tab-btn" onclick="setTab('slid', this)">Language ID</button>
165
  </div>
166
 
167
  <div id="asr" class="view active">
168
  <div class="controls">
169
  <button id="btn-fam" class="btn btn-primary" onclick="setASRMode('family')">By Family</button>
170
  <button id="btn-mod" class="btn btn-secondary" onclick="setASRMode('model')">By Model</button>
 
171
  <div style="flex-grow:1; text-align:right;">
172
- <select id="asr-select" onchange="renderASR()"></select>
 
 
 
 
 
 
 
173
  </div>
174
  </div>
 
 
 
 
175
  <div id="asr-table" class="table-wrap"></div>
176
  </div>
177
 
178
  <div id="tts" class="view">
179
  <div class="controls">
180
- <label>Select Model: </label>
181
- <select id="tts-select" onchange="renderTTS()"></select>
 
 
182
  </div>
 
 
 
183
  <div id="tts-table" class="table-wrap"></div>
184
  </div>
185
 
@@ -197,22 +148,37 @@
197
  </div>
198
 
199
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  let DATA = null;
201
  let asrMode = 'family';
202
 
203
- // Fetch Data
204
  fetch('/api/data').then(r=>r.json()).then(d=>{
205
  DATA = d;
206
  document.getElementById('loader').style.display='none';
207
  document.getElementById('content').style.display='block';
208
  init();
209
- }).catch(e => {
210
- document.getElementById('loader').innerHTML = "Error loading data.";
211
- console.error(e);
212
  });
213
 
214
  function init() {
215
- populate('asr-select', DATA.metadata.families);
 
216
  populate('tts-select', DATA.metadata.tts_models);
217
  renderASR(); renderTTS(); renderSLID();
218
  }
@@ -221,120 +187,153 @@
221
  list.forEach(i=>{ let o=document.createElement('option'); o.value=i; o.text=i; s.add(o); });
222
  }
223
  function fmt(n) { return (n==null)?'N/A':n.toFixed(2); }
 
224
 
225
- // --- Render ASR ---
226
  function renderASR() {
227
- const val = document.getElementById('asr-select').value;
228
  const div = document.getElementById('asr-table');
229
- let html='';
 
 
230
 
231
- if(asrMode==='family'){
232
- const d=DATA.asr.by_family[val];
233
- if(!d) return;
234
 
235
- // Rotated Headers for Languages
236
- html=`<table><thead><tr>
237
- <th>Model</th>
238
- <th>Avg WER/CER <span class="arrow-down">↓</span></th>`;
239
- d.languages.forEach(l=>html+=`<th><div class="rotate-wrapper"><div>${l}<br><span class="sub-header">WER/CER</span></div></div></th>`);
240
- html+=`</tr></thead><tbody>`;
241
 
242
- d.data.forEach(r=>{
243
- html+=`<tr>
 
 
 
 
 
 
 
 
 
 
 
244
  <td>${r.Model}</td>
245
  <td class="val"><b>${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</b></td>`;
246
- d.languages.forEach(l=>{
247
- let w=r[`WER_${l}`], c=r[`CER_${l}`];
248
- html+=`<td class="val">${(w!=null)?fmt(w)+'/'+fmt(c):'-'}</td>`;
249
  });
250
- html+=`</tr>`;
251
  });
252
  } else {
253
- const rows=DATA.asr.by_model[val];
254
- html=`<table><thead><tr>
 
 
 
255
  <th>Language</th>
256
  <th>Family</th>
257
  <th>WER (%) <span class="arrow-down">↓</span></th>
258
  <th>CER (%) <span class="arrow-down">↓</span></th>
259
  </tr></thead><tbody>`;
260
- rows.forEach(r=>html+=`<tr><td>${r.Language}</td><td>${r.Family}</td><td class="val">${fmt(r.WER)}</td><td class="val">${fmt(r.CER)}</td></tr>`);
 
 
 
 
261
  }
262
- html+=`</tbody></table>`;
263
- div.innerHTML=html;
264
  }
265
 
266
- // --- Render TTS (All Metrics) ---
267
  function renderTTS() {
268
- const rows=DATA.tts[document.getElementById('tts-select').value];
269
- if(!rows) return;
270
 
271
- // Calculate Average for Summary
272
- let w=0, m=0, c=0;
273
- rows.forEach(r=>{w+=r.wer;m+=r.mcd;c++;});
274
 
275
- let html=`<p><b>Average Metrics:</b> WER: ${fmt(w/c)} | MCD: ${fmt(m/c)}</p>
276
- <table><thead><tr>
277
- <th>Language</th>
278
- <th>WER <span class="arrow-down">↓</span></th>
279
- <th>MCD <span class="arrow-down">↓</span></th>
280
- <th>LogF0 <span class="arrow-down">↓</span></th>
281
- <th>SpTokDist <span class="arrow-down">↓</span></th>
282
- <th>PESQ <span class="arrow-up">↑</span></th>
283
- <th>UTMOS <span class="arrow-up">↑</span></th>
284
- <th>SpBLEU <span class="arrow-up">↑</span></th>
285
- <th>SpBERT <span class="arrow-up">↑</span></th>
286
- </tr></thead><tbody>`;
 
 
287
 
288
- rows.forEach(r=>{
289
- html+=`<tr>
290
- <td>${r.language}</td>
291
- <td class="val">${fmt(r.wer)}</td>
292
- <td class="val">${fmt(r.mcd)}</td>
293
- <td class="val">${fmt(r.logf0rmse)}</td>
294
- <td class="val">${fmt(r.speech_token_distance)}</td>
295
- <td class="val">${fmt(r.pesq)}</td>
296
- <td class="val">${fmt(r.utmos)}</td>
297
- <td class="val">${fmt(r.speechbleu)}</td>
298
- <td class="val">${fmt(r.speechbert_score)}</td>
299
- </tr>`;
300
  });
301
- html+=`</tbody></table>`;
302
- document.getElementById('tts-table').innerHTML=html;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
 
305
- // --- Render SLID ---
306
  function renderSLID() {
307
- let html=`<table><thead><tr>
308
  <th>Language</th>
309
  <th>MMS-LID-1024 <span class="arrow-up">↑</span></th>
310
  <th>Simba-SLID <span class="arrow-up">↑</span></th>
311
  </tr></thead><tbody>`;
312
 
313
- DATA.slid.forEach(r=>{
314
- let m=r['MMS-LID-1024'], s=r['Simba-SLID'];
315
- // Highlight highest score
316
  let mS = fmt(m), sS = fmt(s);
317
- if(m > s) mS = `<b>${mS}</b>`;
318
- else if(s > m) sS = `<b>${sS}</b>`;
319
-
320
- html+=`<tr><td>${r.Language}</td><td class="val">${mS}</td><td class="val">${sS}</td></tr>`;
321
  });
322
- html+=`</tbody></table>`;
323
- document.getElementById('slid-table').innerHTML=html;
324
  }
325
 
326
- // Interaction
327
- window.setTab=(id,btn)=>{
328
- document.querySelectorAll('.view').forEach(e=>e.classList.remove('active'));
329
- document.querySelectorAll('.tab-btn').forEach(e=>e.classList.remove('active'));
330
- document.getElementById(id).classList.add('active'); btn.classList.add('active');
 
331
  };
332
- window.setASRMode=(m)=>{
333
- asrMode=m;
334
- document.getElementById('btn-fam').className=m==='family'?'btn btn-primary':'btn btn-secondary';
335
- document.getElementById('btn-mod').className=m==='model'?'btn btn-primary':'btn btn-secondary';
336
- document.getElementById('asr-select').style.display = m==='family' ? 'inline-block' : 'inline-block'; // Actually toggle dropdown contents via populate
337
- populate('asr-select', m==='family'?DATA.metadata.families:DATA.metadata.models);
 
 
 
338
  renderASR();
339
  }
340
  </script>
 
10
  --primary-navy: #282351;
11
  --primary-orange: #D97706;
12
  --secondary-navy: #2f3b7d;
13
+ --bg-body: #282351;
14
+ --bg-card: #ffffff;
15
  --border-gold: #dca02a;
16
  --text-dark: #333;
17
  }
18
 
19
+ body { background-color: var(--bg-body); font-family: 'Inter', sans-serif; color: var(--text-dark); margin: 0; padding: 20px; }
 
 
 
 
 
 
 
20
  .container { max-width: 1400px; margin: 0 auto; }
21
 
 
22
  .header { text-align: center; margin-bottom: 30px; }
23
+ .header img { max-width: 60%; height: auto; border-radius: 12px; }
 
 
 
 
24
 
25
+ .card { background: var(--bg-card); border-radius: 12px; padding: 40px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); margin-bottom: 30px; }
 
 
 
 
 
 
 
26
 
27
  h2 { font-family: "Rubik", sans-serif; color: var(--secondary-navy); font-size: 28px; margin-top: 0; }
28
+ h3.section-title { font-family: "Rubik", sans-serif; color: var(--primary-orange); font-size: 20px; margin-bottom: 15px; margin-top: 20px;}
29
+
30
  /* Tabs */
31
  .tabs { display: flex; border-bottom: 2px solid var(--primary-navy); margin-bottom: 20px; }
32
  .tab-btn {
33
  background: none; border: none; padding: 12px 20px;
34
  font-size: 16px; font-weight: 600; color: var(--secondary-navy);
35
+ cursor: pointer; border-radius: 8px 8px 0 0; transition: 0.3s;
 
36
  }
37
  .tab-btn.active { background: var(--primary-navy); color: white; }
38
  .view { display: none; animation: fadeIn 0.4s; }
39
  .view.active { display: block; }
40
 
41
  /* Controls */
42
+ .controls { display: flex; gap: 15px; margin-bottom: 20px; align-items: center; flex-wrap: wrap; background: #f9f9f9; padding: 15px; border-radius: 8px;}
43
+ .control-group { display: flex; align-items: center; gap: 10px; }
44
+ .control-group label { font-weight: bold; color: var(--primary-navy); }
45
+
46
  .btn { padding: 8px 16px; border-radius: 6px; cursor: pointer; border: none; font-weight: bold; }
47
  .btn-primary { background: var(--primary-navy); color: white; }
48
  .btn-secondary { background: #eee; color: var(--primary-navy); }
49
  select { padding: 8px; border-radius: 6px; border: 1px solid #ccc; min-width: 200px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
+ /* Legend */
52
+ .legend-box {
53
+ font-size: 13px;
54
+ background: #fffbf5;
55
+ border: 1px solid #eee;
56
+ padding: 10px;
57
+ margin-bottom: 15px;
58
+ border-radius: 6px;
59
+ color: #555;
60
+ line-height: 1.6;
61
  }
62
+ .legend-item { display: inline-block; margin-right: 15px; }
63
+ .legend-item b { color: var(--primary-navy); }
64
 
65
+ /* Table */
66
+ .table-wrap { overflow-x: auto; }
67
+ table { width: 100%; border-collapse: collapse; font-size: 14px; border-top: 2px solid var(--border-gold); border-bottom: 2px solid var(--border-gold); }
68
+ th, td { padding: 12px 10px; text-align: center; vertical-align: middle; border-bottom: 1px solid #eee; }
 
 
 
69
 
70
+ /* Header Styling */
71
+ thead tr { border-bottom: 2px solid var(--border-gold); }
72
+ th { color: #7d3561; font-weight: bold; vertical-align: bottom; }
 
 
 
 
 
 
 
 
73
 
74
+ /* Left Alignments */
75
+ th:first-child, td:first-child { text-align: left; font-weight: bold; color: var(--primary-navy); min-width: 180px; }
76
+ th:nth-child(2), td:nth-child(2) { text-align: left; } /* For Family Column or WER/CER combo */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
 
78
  .val { white-space: nowrap; }
 
 
79
  .arrow-up { color: green; font-size: 0.9em; }
80
+ .arrow-down { color: #D97706; font-size: 0.9em; }
81
 
82
  #loader { text-align: center; padding: 40px; font-size: 18px; color: var(--secondary-navy); }
83
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
 
86
  <body>
87
 
88
  <div class="container">
89
+ <div class="header"><img src="https://africa.dlnlp.ai/simba/images/simbabench_header.png" alt="Simba"></div>
 
 
90
 
91
  <div class="card">
92
  <div id="loader">🦁 Loading Data...</div>
93
 
94
  <div id="content" style="display:none;">
95
  <div class="tabs">
96
+ <button class="tab-btn active" onclick="setTab('asr', this)">Automatic Speech Recognition (ASR)</button>
97
+ <button class="tab-btn" onclick="setTab('tts', this)">Text-to-Speech (TTS)</button>
98
+ <button class="tab-btn" onclick="setTab('slid', this)">Spoken Language Identification (SLID)</button>
99
  </div>
100
 
101
  <div id="asr" class="view active">
102
  <div class="controls">
103
  <button id="btn-fam" class="btn btn-primary" onclick="setASRMode('family')">By Family</button>
104
  <button id="btn-mod" class="btn btn-secondary" onclick="setASRMode('model')">By Model</button>
105
+
106
  <div style="flex-grow:1; text-align:right;">
107
+ <div class="control-group" id="grp-fam">
108
+ <label for="asr-select-fam">Select Family:</label>
109
+ <select id="asr-select-fam" onchange="renderASR()"></select>
110
+ </div>
111
+ <div class="control-group" id="grp-mod" style="display:none;">
112
+ <label for="asr-select-mod">Select Model:</label>
113
+ <select id="asr-select-mod" onchange="renderASR()"></select>
114
+ </div>
115
  </div>
116
  </div>
117
+
118
+ <h3 id="asr-title" class="section-title"></h3>
119
+ <div id="asr-legend" class="legend-box" style="display:none;"></div>
120
+
121
  <div id="asr-table" class="table-wrap"></div>
122
  </div>
123
 
124
  <div id="tts" class="view">
125
  <div class="controls">
126
+ <div class="control-group">
127
+ <label for="tts-select">Select Model:</label>
128
+ <select id="tts-select" onchange="renderTTS()"></select>
129
+ </div>
130
  </div>
131
+
132
+ <h3 id="tts-title" class="section-title"></h3>
133
+ <div id="tts-summary" class="legend-box"></div>
134
  <div id="tts-table" class="table-wrap"></div>
135
  </div>
136
 
 
148
  </div>
149
 
150
  <script>
151
+ // --- CONSTANTS ---
152
+ // Static mapping based on dataset metadata to ensure correct ISO codes
153
+ const NAME_TO_ISO = {
154
+ 'Afrikaans': 'afr', 'Akuapim-twi': 'Akuapim-twi', 'Amharic': 'amh', 'Asante-twi': 'Asante-twi',
155
+ 'Basaa': 'bas', 'Bemba': 'bem', 'Central Atlas Tamazight': 'tzm', 'Dyula': 'dyu', 'Edo': 'bin',
156
+ 'Ewe': 'ewe', 'Fanti': 'fat', 'Fon': 'fon', 'Ga': 'gaa', 'Ganda': 'lug', 'Guerze/Kpelle': 'kpe',
157
+ 'Hausa': 'hau', 'Ibibio': 'ibb', 'Igbo': 'ibo', 'Kabyle': 'kab', 'Kalenjin': 'kln',
158
+ 'Kinyarwanda': 'kin', 'Kisi': 'kss', 'Konyanka Maninka': 'mku', 'Lingala': 'lin', 'Lozi': 'loz',
159
+ 'Lunda': 'lun', 'Luo (Kenya and Tanzania)': 'luo', 'Malagasy': 'mlg', 'Mandinka': 'mnk',
160
+ 'Nigerian Pidgin': 'pcm', 'North Ndebele': 'nde', 'Northern Sotho': 'nso', 'Nyanja': 'nya',
161
+ 'Oromo': 'orm', 'Pulaar': 'fuc', 'Pular': 'fuf', 'Serer': 'srr', 'Shona': 'sna', 'Somali': 'som',
162
+ 'South Ndebele': 'nbl', 'Southern Sotho': 'sot', 'Standard Moroccan Tamazight': 'zgh', 'Susu': 'sus',
163
+ 'Susuami': 'ssu', 'Swahili': 'swa', 'Swati': 'ssw', 'Taita': 'dav', 'Tem': 'kdh', 'Tigre': 'tig',
164
+ 'Tigrinya': 'tir', 'Tiv': 'tiv', 'Tonga (Zambia)': 'toi', 'Tsonga': 'tso', 'Tswana': 'tsn',
165
+ 'Twi': 'twi', 'Venda': 'ven', 'Western Maninkakan': 'mlq', 'Wolof': 'wol', 'Xhosa': 'xho',
166
+ 'Yoruba': 'yor', 'Zulu': 'zul'
167
+ };
168
+
169
  let DATA = null;
170
  let asrMode = 'family';
171
 
 
172
  fetch('/api/data').then(r=>r.json()).then(d=>{
173
  DATA = d;
174
  document.getElementById('loader').style.display='none';
175
  document.getElementById('content').style.display='block';
176
  init();
 
 
 
177
  });
178
 
179
  function init() {
180
+ populate('asr-select-fam', DATA.metadata.families);
181
+ populate('asr-select-mod', DATA.metadata.models);
182
  populate('tts-select', DATA.metadata.tts_models);
183
  renderASR(); renderTTS(); renderSLID();
184
  }
 
187
  list.forEach(i=>{ let o=document.createElement('option'); o.value=i; o.text=i; s.add(o); });
188
  }
189
  function fmt(n) { return (n==null)?'N/A':n.toFixed(2); }
190
+ function getIso(name) { return NAME_TO_ISO[name] || name.substring(0,3); }
191
 
192
+ // --- RENDER ASR ---
193
  function renderASR() {
 
194
  const div = document.getElementById('asr-table');
195
+ const title = document.getElementById('asr-title');
196
+ const legend = document.getElementById('asr-legend');
197
+ let html = '';
198
 
199
+ if(asrMode === 'family'){
200
+ const val = document.getElementById('asr-select-fam').value;
201
+ title.innerText = `Results based on Family: ${val}`;
202
 
203
+ const d = DATA.asr.by_family[val];
204
+ if(!d) return;
205
+
206
+ // Build Legend & Headers
207
+ let legendHtml = "<b>Legend:</b> ";
208
+ let headerHtml = `<th>Model</th><th>Avg WER/CER <span class="arrow-down">↓</span></th>`;
209
 
210
+ d.languages.forEach(l => {
211
+ const iso = getIso(l);
212
+ legendHtml += `<span class="legend-item"><b>${iso}</b>: ${l}</span>`;
213
+ // Normal direction (not 45 degrees)
214
+ headerHtml += `<th>${iso}</th>`;
215
+ });
216
+
217
+ legend.style.display = 'block';
218
+ legend.innerHTML = legendHtml;
219
+
220
+ html = `<table><thead><tr>${headerHtml}</tr></thead><tbody>`;
221
+ d.data.forEach(r => {
222
+ html += `<tr>
223
  <td>${r.Model}</td>
224
  <td class="val"><b>${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</b></td>`;
225
+ d.languages.forEach(l => {
226
+ let w = r[`WER_${l}`], c = r[`CER_${l}`];
227
+ html += `<td class="val">${(w!=null)?fmt(w)+'/'+fmt(c):'-'}</td>`;
228
  });
229
+ html += `</tr>`;
230
  });
231
  } else {
232
+ const val = document.getElementById('asr-select-mod').value;
233
+ title.innerText = `Results based on Model: ${val}`;
234
+ legend.style.display = 'none';
235
+
236
+ html = `<table><thead><tr>
237
  <th>Language</th>
238
  <th>Family</th>
239
  <th>WER (%) <span class="arrow-down">↓</span></th>
240
  <th>CER (%) <span class="arrow-down">↓</span></th>
241
  </tr></thead><tbody>`;
242
+
243
+ const rows = DATA.asr.by_model[val];
244
+ rows.forEach(r => {
245
+ html += `<tr><td>${r.Language}</td><td>${r.Family}</td><td class="val">${fmt(r.WER)}</td><td class="val">${fmt(r.CER)}</td></tr>`;
246
+ });
247
  }
248
+ html += `</tbody></table>`;
249
+ div.innerHTML = html;
250
  }
251
 
252
+ // --- RENDER TTS ---
253
  function renderTTS() {
254
+ const val = document.getElementById('tts-select').value;
255
+ const rows = DATA.tts[val];
256
 
257
+ document.getElementById('tts-title').innerText = `Results for Model: ${val}`;
 
 
258
 
259
+ if(!rows) return;
260
+
261
+ // Metrics configuration
262
+ // key: json key, label: col header, higherBetter: bool
263
+ const metrics = [
264
+ {k:'wer', l:'WER', h:false}, {k:'mcd', l:'MCD', h:false},
265
+ {k:'logf0rmse', l:'LogF0', h:false}, {k:'speech_token_distance', l:'SpTokDist', h:false},
266
+ {k:'pesq', l:'PESQ', h:true}, {k:'utmos', l:'UTMOS', h:true},
267
+ {k:'speechbleu', l:'SpBLEU', h:true}, {k:'speechbert_score', l:'SpBERT', h:true}
268
+ ];
269
+
270
+ // Compute Averages
271
+ let sums = {}, count = 0;
272
+ metrics.forEach(m => sums[m.k] = 0);
273
 
274
+ rows.forEach(r => {
275
+ metrics.forEach(m => sums[m.k] += (r[m.k] || 0));
276
+ count++;
277
+ });
278
+
279
+ let summaryHtml = "<b>Average Metrics: </b>";
280
+ metrics.forEach((m, idx) => {
281
+ if(idx > 0) summaryHtml += " | ";
282
+ summaryHtml += `${m.l}: ${fmt(sums[m.k]/count)}`;
 
 
 
283
  });
284
+ document.getElementById('tts-summary').innerHTML = summaryHtml;
285
+
286
+ // Build Table
287
+ let html = `<table><thead><tr><th>Language</th>`;
288
+ metrics.forEach(m => {
289
+ const arrow = m.h ? '<span class="arrow-up">↑</span>' : '<span class="arrow-down">↓</span>';
290
+ html += `<th>${m.l} ${arrow}</th>`;
291
+ });
292
+ html += `</tr></thead><tbody>`;
293
+
294
+ rows.forEach(r => {
295
+ html += `<tr><td>${r.language}</td>`;
296
+ metrics.forEach(m => html += `<td class="val">${fmt(r[m.k])}</td>`);
297
+ html += `</tr>`;
298
+ });
299
+ html += `</tbody></table>`;
300
+ document.getElementById('tts-table').innerHTML = html;
301
  }
302
 
303
+ // --- RENDER SLID ---
304
  function renderSLID() {
305
+ let html = `<table><thead><tr>
306
  <th>Language</th>
307
  <th>MMS-LID-1024 <span class="arrow-up">↑</span></th>
308
  <th>Simba-SLID <span class="arrow-up">↑</span></th>
309
  </tr></thead><tbody>`;
310
 
311
+ DATA.slid.forEach(r => {
312
+ let m = r['MMS-LID-1024'], s = r['Simba-SLID'];
 
313
  let mS = fmt(m), sS = fmt(s);
314
+ if(m > s) mS = `<b>${mS}</b>`; else if(s > m) sS = `<b>${sS}</b>`;
315
+ html += `<tr><td>${r.Language}</td><td class="val">${mS}</td><td class="val">${sS}</td></tr>`;
 
 
316
  });
317
+ html += `</tbody></table>`;
318
+ document.getElementById('slid-table').innerHTML = html;
319
  }
320
 
321
+ // --- INTERACTIONS ---
322
+ window.setTab = (id, btn) => {
323
+ document.querySelectorAll('.view').forEach(e => e.classList.remove('active'));
324
+ document.querySelectorAll('.tab-btn').forEach(e => e.classList.remove('active'));
325
+ document.getElementById(id).classList.add('active');
326
+ btn.classList.add('active');
327
  };
328
+
329
+ window.setASRMode = (m) => {
330
+ asrMode = m;
331
+ // Toggle Buttons
332
+ document.getElementById('btn-fam').className = m==='family'?'btn btn-primary':'btn btn-secondary';
333
+ document.getElementById('btn-mod').className = m==='model'?'btn btn-primary':'btn btn-secondary';
334
+ // Toggle Dropdown Groups
335
+ document.getElementById('grp-fam').style.display = m==='family'?'flex':'none';
336
+ document.getElementById('grp-mod').style.display = m==='model'?'flex':'none';
337
  renderASR();
338
  }
339
  </script>