GitHub Actions commited on
Commit
afeed38
·
1 Parent(s): d730de6

Sync from GitHub repo

Browse files
Files changed (1) hide show
  1. templates/arena.html +185 -134
templates/arena.html CHANGED
@@ -28,10 +28,6 @@
28
  <button type="submit" class="mobile-synth-btn">Synthesize</button>
29
  </form>
30
 
31
- <div class="keyboard-hint" id="tts-input-hint">
32
- Press <kbd>R</kbd> for random text, <kbd>Enter</kbd> to synthesize, <kbd>N</kbd> for random + synthesize
33
- </div>
34
-
35
  <div class="loading-container" style="display: none;">
36
  <div class="loader-wrapper">
37
  <div class="loader-animation">
@@ -77,7 +73,7 @@
77
  </div>
78
 
79
  <div class="keyboard-hint">
80
- Press <kbd>Space</kbd> to play/pause audio, <kbd>A</kbd> or <kbd>B</kbd> to vote after listening, <kbd>R</kbd> for random text, <kbd>Enter</kbd> to synthesize, <kbd>N</kbd> for new random + synthesize
81
  </div>
82
  </div>
83
 
@@ -119,11 +115,11 @@
119
  <!-- Script lines will be added here -->
120
  </div>
121
 
122
- <div class="keyboard-hint podcast-keyboard-hint" id="podcast-input-hint">
123
- Press <kbd>Ctrl</kbd>+<kbd>Enter</kbd> or <kbd>Alt</kbd>+<kbd>Enter</kbd> to add a new line, <kbd>R</kbd> for random script, <kbd>Enter</kbd> to generate, <kbd>N</kbd> for random + generate
124
- </div>
125
-
126
  <button type="button" class="add-line-btn">+ Add Line</button>
 
 
 
 
127
  </div>
128
 
129
  <div class="podcast-loading-container" style="display: none;">
@@ -171,7 +167,7 @@
171
  </div>
172
 
173
  <div class="keyboard-hint">
174
- Press <kbd>Space</kbd> to play/pause audio, <kbd>A</kbd> or <kbd>B</kbd> to vote after listening, <kbd>R</kbd> for random script, <kbd>Enter</kbd> to generate, <kbd>N</kbd> for new random + generate
175
  </div>
176
 
177
  <div class="podcast-vote-results vote-results" style="display: none;">
@@ -1011,24 +1007,28 @@
1011
  const rejectedModelNameElement = document.querySelector('.rejected-model-name');
1012
  const modelNameDisplays = document.querySelectorAll('.model-name-display');
1013
  const wavePlayerContainers = document.querySelectorAll('.wave-player-container');
1014
- const ttsInputHint = document.getElementById('tts-input-hint');
1015
 
1016
  let bothSamplesPlayed = false;
1017
  let currentSessionId = null;
1018
  let modelNames = { a: '', b: '' };
1019
  let wavePlayers = { a: null, b: null };
1020
- let cachedSentences = [];
1021
 
 
1022
  wavePlayerContainers.forEach(container => {
1023
  const model = container.dataset.model;
1024
  wavePlayers[model] = new WavePlayer(container, {
 
1025
  backend: 'MediaElement',
1026
- mediaControls: false
1027
  });
1028
  });
1029
 
 
 
1030
  const fallbackRandomTexts = JSON.parse({{ harvard_sentences | tojson | safe }});
1031
 
 
1032
  function fetchCachedSentences() {
1033
  fetch('/api/tts/cached-sentences')
1034
  .then(response => response.ok ? response.json() : Promise.reject('Failed to fetch cached sentences'))
@@ -1038,18 +1038,22 @@
1038
  })
1039
  .catch(error => {
1040
  console.error('Error fetching cached sentences:', error);
 
1041
  });
1042
  }
1043
 
 
1044
  function checkHashAndSetTab() {
1045
  const hash = window.location.hash.toLowerCase();
1046
  if (hash === '#conversational') {
 
1047
  tabs.forEach(t => t.classList.remove('active'));
1048
  tabContents.forEach(c => c.classList.remove('active'));
1049
 
1050
  document.querySelector('.tab[data-tab="conversational"]').classList.add('active');
1051
  document.getElementById('conversational-tab').classList.add('active');
1052
  } else if (hash === '#tts') {
 
1053
  tabs.forEach(t => t.classList.remove('active'));
1054
  tabContents.forEach(c => c.classList.remove('active'));
1055
 
@@ -1058,22 +1062,29 @@
1058
  }
1059
  }
1060
 
 
1061
  checkHashAndSetTab();
1062
 
 
1063
  window.addEventListener('hashchange', checkHashAndSetTab);
1064
 
 
1065
  tabs.forEach(tab => {
1066
  tab.addEventListener('click', function() {
1067
  const tabId = this.dataset.tab;
1068
 
 
1069
  history.replaceState(null, null, `#${tabId}`);
1070
 
 
1071
  tabs.forEach(t => t.classList.remove('active'));
1072
  tabContents.forEach(c => c.classList.remove('active'));
1073
 
 
1074
  this.classList.add('active');
1075
  document.getElementById(`${tabId}-tab`).classList.add('active');
1076
 
 
1077
  if (tabId !== 'tts') {
1078
  resetToInitialState();
1079
  }
@@ -1098,25 +1109,28 @@
1098
 
1099
  textInput.blur();
1100
 
1101
- if (ttsInputHint) ttsInputHint.style.display = 'none';
1102
-
1103
  loadingContainer.style.display = 'flex';
1104
  playersContainer.style.display = 'none';
1105
  voteResultsContainer.style.display = 'none';
1106
  nextRoundContainer.style.display = 'none';
1107
 
 
1108
  voteButtons.forEach(btn => {
1109
  btn.disabled = true;
1110
  btn.classList.remove('selected');
1111
  btn.querySelector('.vote-loader').style.display = 'none';
1112
  });
1113
 
 
1114
  modelNameDisplays.forEach(display => {
1115
  display.textContent = '';
1116
  });
1117
 
 
1118
  bothSamplesPlayed = false;
1119
 
 
1120
  fetch('/api/tts/generate', {
1121
  method: 'POST',
1122
  headers: {
@@ -1135,19 +1149,25 @@
1135
  .then(data => {
1136
  currentSessionId = data.session_id;
1137
 
 
1138
  wavePlayers.a.loadAudio(data.audio_a);
1139
  wavePlayers.b.loadAudio(data.audio_b);
1140
 
 
1141
  loadingContainer.style.display = 'none';
1142
  playersContainer.style.display = 'flex';
1143
 
 
1144
  wavePlayers.a.wavesurfer.once('ready', function() {
1145
  wavePlayers.a.play();
1146
 
 
1147
  wavePlayers.a.wavesurfer.once('finish', function() {
 
1148
  setTimeout(() => {
1149
  wavePlayers.b.play();
1150
 
 
1151
  wavePlayers.b.wavesurfer.once('finish', function() {
1152
  bothSamplesPlayed = true;
1153
  voteButtons.forEach(btn => {
@@ -1158,6 +1178,7 @@
1158
  });
1159
  });
1160
 
 
1161
  fetchCachedSentences();
1162
  })
1163
  .catch(error => {
@@ -1165,11 +1186,10 @@
1165
  openToast(error.message, "error");
1166
  console.error('Error:', error);
1167
  });
1168
-
1169
- if (ttsInputHint) ttsInputHint.style.display = 'block';
1170
  }
1171
 
1172
  function handleVote(model) {
 
1173
  voteButtons.forEach(btn => {
1174
  btn.disabled = true;
1175
  if (btn.dataset.model === model) {
@@ -1177,6 +1197,7 @@
1177
  }
1178
  });
1179
 
 
1180
  fetch('/api/tts/vote', {
1181
  method: 'POST',
1182
  headers: {
@@ -1196,31 +1217,40 @@
1196
  return response.json();
1197
  })
1198
  .then(data => {
 
1199
  voteButtons.forEach(btn => {
1200
  btn.querySelector('.vote-loader').style.display = 'none';
1201
 
 
1202
  if (btn.dataset.model === model) {
1203
  btn.classList.add('selected');
1204
  }
1205
  });
1206
 
 
 
1207
  if (data.chosen_model && data.chosen_model.name) {
1208
  modelNames.a = data.names.a;
1209
  modelNames.b = data.names.b;
1210
  }
1211
 
 
1212
  modelNameDisplays[0].textContent = modelNames.a ? `(${modelNames.a})` : '';
1213
  modelNameDisplays[1].textContent = modelNames.b ? `(${modelNames.b})` : '';
1214
 
 
1215
  chosenModelNameElement.textContent = data.chosen_model.name;
1216
  rejectedModelNameElement.textContent = data.rejected_model.name;
1217
  voteResultsContainer.style.display = 'block';
1218
 
 
1219
  nextRoundContainer.style.display = 'block';
1220
 
 
1221
  openToast("Vote recorded successfully!", "success");
1222
  })
1223
  .catch(error => {
 
1224
  voteButtons.forEach(btn => {
1225
  btn.disabled = false;
1226
  btn.querySelector('.vote-loader').style.display = 'none';
@@ -1232,45 +1262,56 @@
1232
  }
1233
 
1234
  function resetToInitialState() {
 
1235
  playersContainer.style.display = 'none';
1236
  voteResultsContainer.style.display = 'none';
1237
  nextRoundContainer.style.display = 'none';
1238
 
 
1239
  voteButtons.forEach(btn => {
1240
  btn.disabled = true;
1241
  btn.classList.remove('selected');
1242
  btn.querySelector('.vote-loader').style.display = 'none';
1243
  });
1244
 
 
1245
  modelNameDisplays.forEach(display => {
1246
  display.textContent = '';
1247
  });
1248
 
 
1249
  modelNames = { a: '', b: '' };
1250
 
 
1251
  textInput.value = '';
1252
 
 
1253
  for (const model in wavePlayers) {
1254
  if (wavePlayers[model]) {
1255
  wavePlayers[model].stop();
1256
  }
1257
  }
1258
 
 
1259
  currentSessionId = null;
1260
 
 
1261
  bothSamplesPlayed = false;
1262
  }
1263
 
1264
  function handleRandom() {
1265
  let selectedText = '';
1266
  if (cachedSentences && cachedSentences.length > 0) {
 
1267
  selectedText = cachedSentences[Math.floor(Math.random() * cachedSentences.length)];
1268
  console.log("Using random sentence from cache.");
1269
  } else {
 
1270
  console.log("Cache empty or unavailable, using random sentence from fallback list.");
1271
  if (fallbackRandomTexts && fallbackRandomTexts.length > 0) {
1272
  selectedText = fallbackRandomTexts[Math.floor(Math.random() * fallbackRandomTexts.length)];
1273
  } else {
 
1274
  console.error("Both cached sentences and fallback sentences are unavailable.");
1275
  return;
1276
  }
@@ -1283,8 +1324,10 @@
1283
  openToast("Please listen to both audio samples before voting", "info");
1284
  }
1285
 
 
1286
  synthForm.addEventListener('submit', handleSynthesize);
1287
 
 
1288
  voteButtons.forEach(btn => {
1289
  btn.addEventListener('click', function() {
1290
  if (bothSamplesPlayed) {
@@ -1296,89 +1339,72 @@
1296
  });
1297
  });
1298
 
 
1299
  document.addEventListener('keydown', function(e) {
 
1300
  const ttsTab = document.getElementById('tts-tab');
1301
  if (!ttsTab.classList.contains('active')) return;
1302
 
1303
- const isInputVisible = playersContainer.style.display === 'none' && loadingContainer.style.display === 'none';
1304
- const isPlaybackVisible = playersContainer.style.display !== 'none';
1305
- const isNextRoundVisible = nextRoundContainer.style.display === 'block';
1306
-
1307
  if (document.activeElement === textInput) {
1308
- if (e.key === 'Enter' && isInputVisible) {
1309
- e.preventDefault();
1310
- handleSynthesize();
1311
- }
1312
  return;
1313
  }
1314
-
1315
- if (isInputVisible) {
1316
- if (e.key.toLowerCase() === 'r') {
 
 
 
 
 
 
 
 
 
 
 
 
1317
  if (!e.ctrlKey && !e.metaKey) {
1318
  e.preventDefault();
1319
- handleRandom();
1320
  }
1321
- } else if (e.key === 'Enter') {
 
 
 
 
1322
  e.preventDefault();
1323
- handleSynthesize();
1324
- } else if (e.key.toLowerCase() === 'n') {
1325
- if (!e.ctrlKey && !e.metaKey) {
1326
- e.preventDefault();
1327
- handleRandom();
1328
- setTimeout(() => {
1329
- handleSynthesize();
1330
- }, 50);
1331
- }
1332
  }
1333
- }
1334
-
1335
- else if (isPlaybackVisible) {
1336
- if (e.key === ' ') {
1337
  e.preventDefault();
 
1338
  if (wavePlayers.a.isPlaying) {
1339
  wavePlayers.a.togglePlayPause();
1340
  } else if (wavePlayers.b.isPlaying) {
1341
  wavePlayers.b.togglePlayPause();
1342
- } else if (wavePlayers.a) {
1343
- if (wavePlayers.a.wavesurfer.getCurrentTime() < wavePlayers.a.wavesurfer.getDuration()) {
1344
- wavePlayers.a.play();
1345
- } else if (wavePlayers.b) {
1346
- wavePlayers.b.play();
1347
- }
1348
- }
1349
- } else if (e.key.toLowerCase() === 'a') {
1350
- if (bothSamplesPlayed && !voteButtons[0].disabled) {
1351
- handleVote('a');
1352
- } else if (!bothSamplesPlayed) {
1353
- showListenToastMessage();
1354
  }
1355
- } else if (e.key.toLowerCase() === 'b') {
1356
- if (bothSamplesPlayed && !voteButtons[1].disabled) {
1357
- handleVote('b');
1358
- } else if (!bothSamplesPlayed) {
1359
- showListenToastMessage();
1360
- }
1361
- }
1362
- }
1363
-
1364
- else if (isNextRoundVisible && e.key.toLowerCase() === 'n') {
1365
- if (!e.ctrlKey && !e.metaKey) {
1366
- e.preventDefault();
1367
  }
1368
- resetToInitialState();
1369
  }
1370
  });
1371
 
 
1372
  randomBtn.addEventListener('click', handleRandom);
1373
 
 
1374
  nextRoundBtn.addEventListener('click', resetToInitialState);
1375
 
 
1376
  fetchCachedSentences();
1377
  });
1378
  </script>
1379
 
1380
  <script>
1381
  document.addEventListener('DOMContentLoaded', function() {
 
1382
  const podcastContainer = document.querySelector('.podcast-container');
1383
  const podcastLinesContainer = document.querySelector('.podcast-lines');
1384
  const addLineBtn = document.querySelector('.add-line-btn');
@@ -1394,7 +1420,6 @@
1394
  const podcastNextRoundBtn = podcastPlayerContainer.querySelector('.next-round-btn');
1395
  const chosenModelNameElement = podcastVoteResults.querySelector('.chosen-model-name');
1396
  const rejectedModelNameElement = podcastVoteResults.querySelector('.rejected-model-name');
1397
- const podcastInputHint = document.getElementById('podcast-input-hint');
1398
 
1399
  let podcastWavePlayers = { a: null, b: null };
1400
  let bothPodcastSamplesPlayed = false;
@@ -1496,9 +1521,11 @@
1496
  addPodcastLine(2);
1497
  }
1498
 
 
1499
  function addPodcastLine(speakerNum = null) {
1500
  const lineCount = podcastLinesContainer.querySelectorAll('.podcast-line').length;
1501
 
 
1502
  if (speakerNum === null) {
1503
  speakerNum = (lineCount % 2) + 1;
1504
  }
@@ -1520,8 +1547,10 @@
1520
 
1521
  podcastLinesContainer.appendChild(lineElement);
1522
 
 
1523
  const removeBtn = lineElement.querySelector('.remove-line-btn');
1524
  removeBtn.addEventListener('click', function() {
 
1525
  if (podcastLinesContainer.querySelectorAll('.podcast-line').length > 2) {
1526
  lineElement.remove();
1527
  } else {
@@ -1529,12 +1558,15 @@
1529
  }
1530
  });
1531
 
 
1532
  const inputField = lineElement.querySelector('.line-input');
1533
  inputField.addEventListener('keydown', function(e) {
 
1534
  if (e.key === 'Enter' && (e.altKey || e.ctrlKey)) {
1535
  e.preventDefault();
1536
  addPodcastLine();
1537
 
 
1538
  setTimeout(() => {
1539
  const inputs = podcastLinesContainer.querySelectorAll('.line-input');
1540
  inputs[inputs.length - 1].focus();
@@ -1545,18 +1577,24 @@
1545
  return lineElement;
1546
  }
1547
 
 
1548
  function loadRandomScript() {
 
1549
  podcastLinesContainer.innerHTML = '';
1550
 
 
1551
  const randomScript = randomScripts[Math.floor(Math.random() * randomScripts.length)];
1552
 
 
1553
  randomScript.forEach(line => {
1554
  const lineElement = addPodcastLine(line.speaker);
1555
  lineElement.querySelector('.line-input').value = line.text;
1556
  });
1557
  }
1558
 
 
1559
  function generatePodcast() {
 
1560
  const lines = [];
1561
  podcastLinesContainer.querySelectorAll('.podcast-line').forEach(line => {
1562
  const speaker_id = line.querySelector('.speaker-label').textContent.includes('1') ? 0 : 1;
@@ -1567,17 +1605,20 @@
1567
  }
1568
  });
1569
 
 
1570
  if (lines.length < 2) {
1571
  openToast("Please enter at least 2 lines of dialog", "warning");
1572
  return;
1573
  }
1574
 
 
1575
  podcastVoteButtons.forEach(btn => {
1576
  btn.disabled = true;
1577
  btn.classList.remove('selected');
1578
  btn.querySelector('.vote-loader').style.display = 'none';
1579
  });
1580
 
 
1581
  const modelNameDisplays = podcastPlayerContainer.querySelectorAll('.model-name-display');
1582
  modelNameDisplays.forEach(display => {
1583
  display.textContent = '';
@@ -1586,13 +1627,14 @@
1586
  podcastVoteResults.style.display = 'none';
1587
  podcastNextRoundContainer.style.display = 'none';
1588
 
 
1589
  bothPodcastSamplesPlayed = false;
1590
-
1591
- if (podcastInputHint) podcastInputHint.style.display = 'none';
1592
-
1593
  podcastLoadingContainer.style.display = 'flex';
1594
  podcastPlayerContainer.style.display = 'none';
1595
 
 
1596
  fetch('/api/conversational/generate', {
1597
  method: 'POST',
1598
  headers: {
@@ -1611,23 +1653,30 @@
1611
  .then(data => {
1612
  currentPodcastSessionId = data.session_id;
1613
 
 
1614
  podcastLoadingContainer.style.display = 'none';
1615
 
 
1616
  podcastPlayerContainer.style.display = 'block';
1617
 
 
1618
  if (!podcastWavePlayers.a) {
1619
  podcastWavePlayers.a = new WavePlayer(podcastWavePlayerA, {
 
1620
  backend: 'MediaElement',
1621
- mediaControls: false
1622
  });
1623
  podcastWavePlayers.b = new WavePlayer(podcastWavePlayerB, {
 
1624
  backend: 'MediaElement',
1625
- mediaControls: false
1626
  });
1627
 
 
1628
  podcastWavePlayers.a.loadAudio(data.audio_a);
1629
  podcastWavePlayers.b.loadAudio(data.audio_b);
1630
 
 
1631
  setTimeout(() => {
1632
  if (podcastWavePlayers.a && podcastWavePlayers.a.hideLoading) {
1633
  podcastWavePlayers.a.hideLoading();
@@ -1638,16 +1687,19 @@
1638
  console.log('Forced hiding of podcast loading indicators (safety timeout - existing players)');
1639
  }, 5000);
1640
  } else {
 
1641
  try {
1642
  podcastWavePlayers.a.wavesurfer.empty();
1643
  podcastWavePlayers.b.wavesurfer.empty();
1644
 
 
1645
  podcastWavePlayers.a.hideLoading();
1646
  podcastWavePlayers.b.hideLoading();
1647
 
1648
  podcastWavePlayers.a.loadAudio(data.audio_a);
1649
  podcastWavePlayers.b.loadAudio(data.audio_b);
1650
 
 
1651
  setTimeout(() => {
1652
  if (podcastWavePlayers.a && podcastWavePlayers.a.hideLoading) {
1653
  podcastWavePlayers.a.hideLoading();
@@ -1660,6 +1712,7 @@
1660
  } catch (err) {
1661
  console.error('Error resetting podcast waveplayers:', err);
1662
 
 
1663
  podcastWavePlayers.a = new WavePlayer(podcastWavePlayerA, {
1664
  backend: 'MediaElement',
1665
  mediaControls: false
@@ -1672,6 +1725,7 @@
1672
  podcastWavePlayers.a.loadAudio(data.audio_a);
1673
  podcastWavePlayers.b.loadAudio(data.audio_b);
1674
 
 
1675
  setTimeout(() => {
1676
  if (podcastWavePlayers.a && podcastWavePlayers.a.hideLoading) {
1677
  podcastWavePlayers.a.hideLoading();
@@ -1684,13 +1738,17 @@
1684
  }
1685
  }
1686
 
 
1687
  podcastWavePlayers.a.wavesurfer.once('ready', function() {
1688
  podcastWavePlayers.a.play();
1689
 
 
1690
  podcastWavePlayers.a.wavesurfer.once('finish', function() {
 
1691
  setTimeout(() => {
1692
  podcastWavePlayers.b.play();
1693
 
 
1694
  podcastWavePlayers.b.wavesurfer.once('finish', function() {
1695
  bothPodcastSamplesPlayed = true;
1696
  podcastVoteButtons.forEach(btn => {
@@ -1706,11 +1764,11 @@
1706
  openToast(error.message, "error");
1707
  console.error('Error:', error);
1708
  });
1709
-
1710
- if (podcastInputHint) podcastInputHint.style.display = 'block';
1711
  }
1712
 
 
1713
  function handlePodcastVote(model) {
 
1714
  podcastVoteButtons.forEach(btn => {
1715
  btn.disabled = true;
1716
  if (btn.dataset.model === model) {
@@ -1718,6 +1776,7 @@
1718
  }
1719
  });
1720
 
 
1721
  fetch('/api/conversational/vote', {
1722
  method: 'POST',
1723
  headers: {
@@ -1737,30 +1796,38 @@
1737
  return response.json();
1738
  })
1739
  .then(data => {
 
1740
  podcastVoteButtons.forEach(btn => {
1741
  btn.querySelector('.vote-loader').style.display = 'none';
1742
 
 
1743
  if (btn.dataset.model === model) {
1744
  btn.classList.add('selected');
1745
  }
1746
  });
1747
 
 
1748
  podcastModelNames.a = data.names.a;
1749
  podcastModelNames.b = data.names.b;
1750
 
1751
- modelNameDisplays = podcastPlayerContainer.querySelectorAll('.model-name-display');
 
1752
  modelNameDisplays[0].textContent = data.names.a ? `(${data.names.a})` : '';
1753
  modelNameDisplays[1].textContent = data.names.b ? `(${data.names.b})` : '';
1754
 
 
1755
  chosenModelNameElement.textContent = data.chosen_model.name;
1756
  rejectedModelNameElement.textContent = data.rejected_model.name;
1757
  podcastVoteResults.style.display = 'block';
1758
 
 
1759
  podcastNextRoundContainer.style.display = 'block';
1760
 
 
1761
  openToast("Vote recorded successfully!", "success");
1762
  })
1763
  .catch(error => {
 
1764
  podcastVoteButtons.forEach(btn => {
1765
  btn.disabled = false;
1766
  btn.querySelector('.vote-loader').style.display = 'none';
@@ -1771,104 +1838,85 @@
1771
  });
1772
  }
1773
 
 
1774
  function resetPodcastState() {
 
1775
  podcastPlayerContainer.style.display = 'none';
1776
  podcastVoteResults.style.display = 'none';
1777
  podcastNextRoundContainer.style.display = 'none';
1778
 
 
1779
  podcastVoteButtons.forEach(btn => {
1780
  btn.disabled = true;
1781
  btn.classList.remove('selected');
1782
  btn.querySelector('.vote-loader').style.display = 'none';
1783
  });
1784
 
1785
- modelNameDisplays = podcastPlayerContainer.querySelectorAll('.model-name-display');
 
1786
  modelNameDisplays.forEach(display => {
1787
  display.textContent = '';
1788
  });
1789
 
 
1790
  if (podcastWavePlayers.a) podcastWavePlayers.a.stop();
1791
  if (podcastWavePlayers.b) podcastWavePlayers.b.stop();
1792
 
 
1793
  currentPodcastSessionId = null;
1794
 
 
1795
  bothPodcastSamplesPlayed = false;
1796
  }
1797
 
 
1798
  document.addEventListener('keydown', function(e) {
 
1799
  const podcastTab = document.getElementById('conversational-tab');
1800
  if (!podcastTab.classList.contains('active')) return;
1801
 
1802
- const isScriptEditorVisible = podcastPlayerContainer.style.display === 'none' && podcastLoadingContainer.style.display === 'none';
1803
- const isPodcastPlaybackVisible = podcastPlayerContainer.style.display !== 'none';
1804
- const isPodcastNextRoundVisible = podcastNextRoundContainer.style.display === 'block';
1805
-
1806
- const activeElementTag = document.activeElement.tagName;
1807
- const isInputFocused = activeElementTag === 'INPUT' || activeElementTag === 'TEXTAREA';
1808
-
1809
- if (isInputFocused && e.key === 'Enter' && (e.altKey || e.ctrlKey)) {
1810
- return;
1811
- }
1812
-
1813
- if (isInputFocused) {
1814
- return;
1815
  }
1816
-
1817
- if (e.key.toLowerCase() === 'r' && isScriptEditorVisible) {
1818
- if (!e.ctrlKey && !e.metaKey) {
1819
- e.preventDefault();
1820
- loadRandomScript();
 
1821
  }
1822
- } else if (e.key === 'Enter' && isScriptEditorVisible) {
1823
- e.preventDefault();
1824
- generatePodcast();
1825
- } else if (e.key.toLowerCase() === 'n' && isScriptEditorVisible) {
1826
- if (!e.ctrlKey && !e.metaKey) {
1827
- e.preventDefault();
1828
- loadRandomScript();
1829
- setTimeout(() => {
1830
- generatePodcast();
1831
- }, 50);
1832
  }
1833
- }
1834
-
1835
- else if (isPodcastPlaybackVisible) {
1836
- if (e.key === ' ') {
 
 
 
 
 
 
1837
  e.preventDefault();
 
1838
  if (podcastWavePlayers.a && podcastWavePlayers.a.isPlaying) {
1839
  podcastWavePlayers.a.togglePlayPause();
1840
  } else if (podcastWavePlayers.b && podcastWavePlayers.b.isPlaying) {
1841
  podcastWavePlayers.b.togglePlayPause();
1842
  } else if (podcastWavePlayers.a) {
1843
- if (podcastWavePlayers.a.wavesurfer.getCurrentTime() < podcastWavePlayers.a.wavesurfer.getDuration()) {
1844
- podcastWavePlayers.a.play();
1845
- } else if (podcastWavePlayers.b) {
1846
- podcastWavePlayers.b.play();
1847
- }
1848
- }
1849
- } else if (e.key.toLowerCase() === 'a') {
1850
- if (bothPodcastSamplesPlayed && !podcastVoteButtons[0].disabled) {
1851
- handlePodcastVote('a');
1852
- } else if (!bothPodcastSamplesPlayed) {
1853
- openToast("Please listen to both audio samples before voting", "info");
1854
- }
1855
- } else if (e.key.toLowerCase() === 'b') {
1856
- if (bothPodcastSamplesPlayed && !podcastVoteButtons[1].disabled) {
1857
- handlePodcastVote('b');
1858
- } else if (!bothPodcastSamplesPlayed) {
1859
- openToast("Please listen to both audio samples before voting", "info");
1860
  }
1861
  }
1862
  }
1863
-
1864
- else if (isPodcastNextRoundVisible && e.key.toLowerCase() === 'n') {
1865
- if (!e.ctrlKey && !e.metaKey) {
1866
- e.preventDefault();
1867
- }
1868
- resetPodcastState();
1869
- }
1870
  });
1871
 
 
1872
  addLineBtn.addEventListener('click', function() {
1873
  addPodcastLine();
1874
  });
@@ -1881,6 +1929,7 @@
1881
  generatePodcast();
1882
  });
1883
 
 
1884
  podcastVoteButtons.forEach(btn => {
1885
  btn.addEventListener('click', function() {
1886
  if (bothPodcastSamplesPlayed) {
@@ -1892,8 +1941,10 @@
1892
  });
1893
  });
1894
 
 
1895
  podcastNextRoundBtn.addEventListener('click', resetPodcastState);
1896
 
 
1897
  initializePodcastLines();
1898
  });
1899
  </script>
 
28
  <button type="submit" class="mobile-synth-btn">Synthesize</button>
29
  </form>
30
 
 
 
 
 
31
  <div class="loading-container" style="display: none;">
32
  <div class="loader-wrapper">
33
  <div class="loader-animation">
 
73
  </div>
74
 
75
  <div class="keyboard-hint">
76
+ Press <kbd>Space</kbd> to play/pause audio, <kbd>A</kbd> or <kbd>B</kbd> to vote after listening, <kbd>R</kbd> for random audio, <kbd>Enter</kbd> to generate
77
  </div>
78
  </div>
79
 
 
115
  <!-- Script lines will be added here -->
116
  </div>
117
 
 
 
 
 
118
  <button type="button" class="add-line-btn">+ Add Line</button>
119
+
120
+ <div class="keyboard-hint podcast-keyboard-hint">
121
+ Press <kbd>Ctrl</kbd>+<kbd>Enter</kbd> or <kbd>Alt</kbd>+<kbd>Enter</kbd> to add a new line
122
+ </div>
123
  </div>
124
 
125
  <div class="podcast-loading-container" style="display: none;">
 
167
  </div>
168
 
169
  <div class="keyboard-hint">
170
+ Press <kbd>Space</kbd> to play/pause audio, <kbd>A</kbd> or <kbd>B</kbd> to vote after listening, <kbd>R</kbd> for random audio, <kbd>Enter</kbd> to generate
171
  </div>
172
 
173
  <div class="podcast-vote-results vote-results" style="display: none;">
 
1007
  const rejectedModelNameElement = document.querySelector('.rejected-model-name');
1008
  const modelNameDisplays = document.querySelectorAll('.model-name-display');
1009
  const wavePlayerContainers = document.querySelectorAll('.wave-player-container');
 
1010
 
1011
  let bothSamplesPlayed = false;
1012
  let currentSessionId = null;
1013
  let modelNames = { a: '', b: '' };
1014
  let wavePlayers = { a: null, b: null };
1015
+ let cachedSentences = []; // To store sentences available in cache
1016
 
1017
+ // Initialize WavePlayers with mobile settings
1018
  wavePlayerContainers.forEach(container => {
1019
  const model = container.dataset.model;
1020
  wavePlayers[model] = new WavePlayer(container, {
1021
+ // Add mobile-friendly options but hide native controls
1022
  backend: 'MediaElement',
1023
+ mediaControls: false // Hide native audio controls
1024
  });
1025
  });
1026
 
1027
+ // Load fallback sentences directly from Flask variable (JSON string)
1028
+ // Note: This might cause linter errors, but JSON is JS-compatible.
1029
  const fallbackRandomTexts = JSON.parse({{ harvard_sentences | tojson | safe }});
1030
 
1031
+ // Fetch cached sentences on load
1032
  function fetchCachedSentences() {
1033
  fetch('/api/tts/cached-sentences')
1034
  .then(response => response.ok ? response.json() : Promise.reject('Failed to fetch cached sentences'))
 
1038
  })
1039
  .catch(error => {
1040
  console.error('Error fetching cached sentences:', error);
1041
+ // Keep cachedSentences as empty array, fallback will be used
1042
  });
1043
  }
1044
 
1045
+ // Check URL hash for direct tab access
1046
  function checkHashAndSetTab() {
1047
  const hash = window.location.hash.toLowerCase();
1048
  if (hash === '#conversational') {
1049
+ // Switch to conversational tab
1050
  tabs.forEach(t => t.classList.remove('active'));
1051
  tabContents.forEach(c => c.classList.remove('active'));
1052
 
1053
  document.querySelector('.tab[data-tab="conversational"]').classList.add('active');
1054
  document.getElementById('conversational-tab').classList.add('active');
1055
  } else if (hash === '#tts') {
1056
+ // Switch to TTS tab (explicit)
1057
  tabs.forEach(t => t.classList.remove('active'));
1058
  tabContents.forEach(c => c.classList.remove('active'));
1059
 
 
1062
  }
1063
  }
1064
 
1065
+ // Check hash on page load
1066
  checkHashAndSetTab();
1067
 
1068
+ // Listen for hash changes
1069
  window.addEventListener('hashchange', checkHashAndSetTab);
1070
 
1071
+ // Tab switching functionality
1072
  tabs.forEach(tab => {
1073
  tab.addEventListener('click', function() {
1074
  const tabId = this.dataset.tab;
1075
 
1076
+ // Update URL hash without page reload
1077
  history.replaceState(null, null, `#${tabId}`);
1078
 
1079
+ // Remove active class from all tabs and contents
1080
  tabs.forEach(t => t.classList.remove('active'));
1081
  tabContents.forEach(c => c.classList.remove('active'));
1082
 
1083
+ // Add active class to clicked tab and corresponding content
1084
  this.classList.add('active');
1085
  document.getElementById(`${tabId}-tab`).classList.add('active');
1086
 
1087
+ // Reset TTS tab state if switching away from it
1088
  if (tabId !== 'tts') {
1089
  resetToInitialState();
1090
  }
 
1109
 
1110
  textInput.blur();
1111
 
1112
+ // Show loading animation
 
1113
  loadingContainer.style.display = 'flex';
1114
  playersContainer.style.display = 'none';
1115
  voteResultsContainer.style.display = 'none';
1116
  nextRoundContainer.style.display = 'none';
1117
 
1118
+ // Reset vote buttons
1119
  voteButtons.forEach(btn => {
1120
  btn.disabled = true;
1121
  btn.classList.remove('selected');
1122
  btn.querySelector('.vote-loader').style.display = 'none';
1123
  });
1124
 
1125
+ // Clear model name displays
1126
  modelNameDisplays.forEach(display => {
1127
  display.textContent = '';
1128
  });
1129
 
1130
+ // Reset the flag for both samples played
1131
  bothSamplesPlayed = false;
1132
 
1133
+ // Call the API to generate TTS
1134
  fetch('/api/tts/generate', {
1135
  method: 'POST',
1136
  headers: {
 
1149
  .then(data => {
1150
  currentSessionId = data.session_id;
1151
 
1152
+ // Load audio in waveplayers
1153
  wavePlayers.a.loadAudio(data.audio_a);
1154
  wavePlayers.b.loadAudio(data.audio_b);
1155
 
1156
+ // Show players
1157
  loadingContainer.style.display = 'none';
1158
  playersContainer.style.display = 'flex';
1159
 
1160
+ // Setup automatic sequential playback
1161
  wavePlayers.a.wavesurfer.once('ready', function() {
1162
  wavePlayers.a.play();
1163
 
1164
+ // When audio A ends, play audio B
1165
  wavePlayers.a.wavesurfer.once('finish', function() {
1166
+ // Wait a short moment before playing B
1167
  setTimeout(() => {
1168
  wavePlayers.b.play();
1169
 
1170
+ // When audio B ends, enable voting
1171
  wavePlayers.b.wavesurfer.once('finish', function() {
1172
  bothSamplesPlayed = true;
1173
  voteButtons.forEach(btn => {
 
1178
  });
1179
  });
1180
 
1181
+ // Fetch cached sentences again to update the list
1182
  fetchCachedSentences();
1183
  })
1184
  .catch(error => {
 
1186
  openToast(error.message, "error");
1187
  console.error('Error:', error);
1188
  });
 
 
1189
  }
1190
 
1191
  function handleVote(model) {
1192
+ // Disable both vote buttons
1193
  voteButtons.forEach(btn => {
1194
  btn.disabled = true;
1195
  if (btn.dataset.model === model) {
 
1197
  }
1198
  });
1199
 
1200
+ // Send vote to server
1201
  fetch('/api/tts/vote', {
1202
  method: 'POST',
1203
  headers: {
 
1217
  return response.json();
1218
  })
1219
  .then(data => {
1220
+ // Hide loaders
1221
  voteButtons.forEach(btn => {
1222
  btn.querySelector('.vote-loader').style.display = 'none';
1223
 
1224
+ // Highlight the selected button
1225
  if (btn.dataset.model === model) {
1226
  btn.classList.add('selected');
1227
  }
1228
  });
1229
 
1230
+
1231
+ // Store model names from vote response
1232
  if (data.chosen_model && data.chosen_model.name) {
1233
  modelNames.a = data.names.a;
1234
  modelNames.b = data.names.b;
1235
  }
1236
 
1237
+ // Now display model names after voting
1238
  modelNameDisplays[0].textContent = modelNames.a ? `(${modelNames.a})` : '';
1239
  modelNameDisplays[1].textContent = modelNames.b ? `(${modelNames.b})` : '';
1240
 
1241
+ // Show vote results
1242
  chosenModelNameElement.textContent = data.chosen_model.name;
1243
  rejectedModelNameElement.textContent = data.rejected_model.name;
1244
  voteResultsContainer.style.display = 'block';
1245
 
1246
+ // Show next round button
1247
  nextRoundContainer.style.display = 'block';
1248
 
1249
+ // Show success toast
1250
  openToast("Vote recorded successfully!", "success");
1251
  })
1252
  .catch(error => {
1253
+ // Re-enable vote buttons
1254
  voteButtons.forEach(btn => {
1255
  btn.disabled = false;
1256
  btn.querySelector('.vote-loader').style.display = 'none';
 
1262
  }
1263
 
1264
  function resetToInitialState() {
1265
+ // Hide players, results, and next round button
1266
  playersContainer.style.display = 'none';
1267
  voteResultsContainer.style.display = 'none';
1268
  nextRoundContainer.style.display = 'none';
1269
 
1270
+ // Reset vote buttons
1271
  voteButtons.forEach(btn => {
1272
  btn.disabled = true;
1273
  btn.classList.remove('selected');
1274
  btn.querySelector('.vote-loader').style.display = 'none';
1275
  });
1276
 
1277
+ // Clear model name displays
1278
  modelNameDisplays.forEach(display => {
1279
  display.textContent = '';
1280
  });
1281
 
1282
+ // Reset model names
1283
  modelNames = { a: '', b: '' };
1284
 
1285
+ // Clear text input
1286
  textInput.value = '';
1287
 
1288
+ // Stop any playing audio and destroy wavesurfers
1289
  for (const model in wavePlayers) {
1290
  if (wavePlayers[model]) {
1291
  wavePlayers[model].stop();
1292
  }
1293
  }
1294
 
1295
+ // Reset session
1296
  currentSessionId = null;
1297
 
1298
+ // Reset the flag for both samples played
1299
  bothSamplesPlayed = false;
1300
  }
1301
 
1302
  function handleRandom() {
1303
  let selectedText = '';
1304
  if (cachedSentences && cachedSentences.length > 0) {
1305
+ // Select a random text from the cache
1306
  selectedText = cachedSentences[Math.floor(Math.random() * cachedSentences.length)];
1307
  console.log("Using random sentence from cache.");
1308
  } else {
1309
+ // Fallback to the initial list if cache is empty or failed to load
1310
  console.log("Cache empty or unavailable, using random sentence from fallback list.");
1311
  if (fallbackRandomTexts && fallbackRandomTexts.length > 0) {
1312
  selectedText = fallbackRandomTexts[Math.floor(Math.random() * fallbackRandomTexts.length)];
1313
  } else {
1314
+ // If fallback list is also empty, do nothing. Log an error.
1315
  console.error("Both cached sentences and fallback sentences are unavailable.");
1316
  return;
1317
  }
 
1324
  openToast("Please listen to both audio samples before voting", "info");
1325
  }
1326
 
1327
+ // Add submit event listener to form
1328
  synthForm.addEventListener('submit', handleSynthesize);
1329
 
1330
+ // Add click event listeners to vote buttons
1331
  voteButtons.forEach(btn => {
1332
  btn.addEventListener('click', function() {
1333
  if (bothSamplesPlayed) {
 
1339
  });
1340
  });
1341
 
1342
+ // Add keyboard shortcut listeners
1343
  document.addEventListener('keydown', function(e) {
1344
+ // Check if TTS tab is active
1345
  const ttsTab = document.getElementById('tts-tab');
1346
  if (!ttsTab.classList.contains('active')) return;
1347
 
1348
+ // Only process keyboard shortcuts if text input is not focused
 
 
 
1349
  if (document.activeElement === textInput) {
 
 
 
 
1350
  return;
1351
  }
1352
+
1353
+ if (e.key.toLowerCase() === 'a') {
1354
+ if (bothSamplesPlayed && !voteButtons[0].disabled) {
1355
+ handleVote('a');
1356
+ } else if (playersContainer.style.display !== 'none' && !bothSamplesPlayed) {
1357
+ showListenToastMessage();
1358
+ }
1359
+ } else if (e.key.toLowerCase() === 'b') {
1360
+ if (bothSamplesPlayed && !voteButtons[1].disabled) {
1361
+ handleVote('b');
1362
+ } else if (playersContainer.style.display !== 'none' && !bothSamplesPlayed) {
1363
+ showListenToastMessage();
1364
+ }
1365
+ } else if (e.key.toLowerCase() === 'n') {
1366
+ if (nextRoundContainer.style.display === 'block') {
1367
  if (!e.ctrlKey && !e.metaKey) {
1368
  e.preventDefault();
 
1369
  }
1370
+ resetToInitialState();
1371
+ }
1372
+ } else if (e.key.toLowerCase() === 'r') {
1373
+ // Only trigger random if not trying to reload (Ctrl+R or Cmd+R)
1374
+ if (!e.ctrlKey && !e.metaKey) {
1375
  e.preventDefault();
1376
+ handleRandom();
 
 
 
 
 
 
 
 
1377
  }
1378
+ } else if (e.key === ' ') {
1379
+ // Space to play/pause current audio
1380
+ if (playersContainer.style.display !== 'none') {
 
1381
  e.preventDefault();
1382
+ // If A is playing, toggle A, else if B is playing, toggle B, else play A
1383
  if (wavePlayers.a.isPlaying) {
1384
  wavePlayers.a.togglePlayPause();
1385
  } else if (wavePlayers.b.isPlaying) {
1386
  wavePlayers.b.togglePlayPause();
1387
+ } else {
1388
+ wavePlayers.a.play();
 
 
 
 
 
 
 
 
 
 
1389
  }
 
 
 
 
 
 
 
 
 
 
 
 
1390
  }
 
1391
  }
1392
  });
1393
 
1394
+ // Add event listener for random button
1395
  randomBtn.addEventListener('click', handleRandom);
1396
 
1397
+ // Add event listener for next round button
1398
  nextRoundBtn.addEventListener('click', resetToInitialState);
1399
 
1400
+ // Fetch cached sentences when the DOM is ready
1401
  fetchCachedSentences();
1402
  });
1403
  </script>
1404
 
1405
  <script>
1406
  document.addEventListener('DOMContentLoaded', function() {
1407
+ // Variables for podcast UI
1408
  const podcastContainer = document.querySelector('.podcast-container');
1409
  const podcastLinesContainer = document.querySelector('.podcast-lines');
1410
  const addLineBtn = document.querySelector('.add-line-btn');
 
1420
  const podcastNextRoundBtn = podcastPlayerContainer.querySelector('.next-round-btn');
1421
  const chosenModelNameElement = podcastVoteResults.querySelector('.chosen-model-name');
1422
  const rejectedModelNameElement = podcastVoteResults.querySelector('.rejected-model-name');
 
1423
 
1424
  let podcastWavePlayers = { a: null, b: null };
1425
  let bothPodcastSamplesPlayed = false;
 
1521
  addPodcastLine(2);
1522
  }
1523
 
1524
+ // Add a new podcast line
1525
  function addPodcastLine(speakerNum = null) {
1526
  const lineCount = podcastLinesContainer.querySelectorAll('.podcast-line').length;
1527
 
1528
+ // If speaker number isn't specified, alternate between 1 and 2
1529
  if (speakerNum === null) {
1530
  speakerNum = (lineCount % 2) + 1;
1531
  }
 
1547
 
1548
  podcastLinesContainer.appendChild(lineElement);
1549
 
1550
+ // Add event listener to remove button
1551
  const removeBtn = lineElement.querySelector('.remove-line-btn');
1552
  removeBtn.addEventListener('click', function() {
1553
+ // Don't allow removing if there are only 2 lines
1554
  if (podcastLinesContainer.querySelectorAll('.podcast-line').length > 2) {
1555
  lineElement.remove();
1556
  } else {
 
1558
  }
1559
  });
1560
 
1561
+ // Add event listener for keyboard navigation in the input field
1562
  const inputField = lineElement.querySelector('.line-input');
1563
  inputField.addEventListener('keydown', function(e) {
1564
+ // Alt+Enter or Ctrl+Enter to add new line
1565
  if (e.key === 'Enter' && (e.altKey || e.ctrlKey)) {
1566
  e.preventDefault();
1567
  addPodcastLine();
1568
 
1569
+ // Focus the new line's input field
1570
  setTimeout(() => {
1571
  const inputs = podcastLinesContainer.querySelectorAll('.line-input');
1572
  inputs[inputs.length - 1].focus();
 
1577
  return lineElement;
1578
  }
1579
 
1580
+ // Load a random script
1581
  function loadRandomScript() {
1582
+ // Clear existing lines
1583
  podcastLinesContainer.innerHTML = '';
1584
 
1585
+ // Select a random script
1586
  const randomScript = randomScripts[Math.floor(Math.random() * randomScripts.length)];
1587
 
1588
+ // Add each line from the script
1589
  randomScript.forEach(line => {
1590
  const lineElement = addPodcastLine(line.speaker);
1591
  lineElement.querySelector('.line-input').value = line.text;
1592
  });
1593
  }
1594
 
1595
+ // Generate podcast (mock functionality)
1596
  function generatePodcast() {
1597
+ // Get all lines
1598
  const lines = [];
1599
  podcastLinesContainer.querySelectorAll('.podcast-line').forEach(line => {
1600
  const speaker_id = line.querySelector('.speaker-label').textContent.includes('1') ? 0 : 1;
 
1605
  }
1606
  });
1607
 
1608
+ // Validate that we have at least 2 lines with content
1609
  if (lines.length < 2) {
1610
  openToast("Please enter at least 2 lines of dialog", "warning");
1611
  return;
1612
  }
1613
 
1614
+ // Reset vote buttons and hide results
1615
  podcastVoteButtons.forEach(btn => {
1616
  btn.disabled = true;
1617
  btn.classList.remove('selected');
1618
  btn.querySelector('.vote-loader').style.display = 'none';
1619
  });
1620
 
1621
+ // Clear model name displays
1622
  const modelNameDisplays = podcastPlayerContainer.querySelectorAll('.model-name-display');
1623
  modelNameDisplays.forEach(display => {
1624
  display.textContent = '';
 
1627
  podcastVoteResults.style.display = 'none';
1628
  podcastNextRoundContainer.style.display = 'none';
1629
 
1630
+ // Reset the flag for both samples played
1631
  bothPodcastSamplesPlayed = false;
1632
+
1633
+ // Show loading animation
 
1634
  podcastLoadingContainer.style.display = 'flex';
1635
  podcastPlayerContainer.style.display = 'none';
1636
 
1637
+ // Call API to generate podcast
1638
  fetch('/api/conversational/generate', {
1639
  method: 'POST',
1640
  headers: {
 
1653
  .then(data => {
1654
  currentPodcastSessionId = data.session_id;
1655
 
1656
+ // Hide loading
1657
  podcastLoadingContainer.style.display = 'none';
1658
 
1659
+ // Show player
1660
  podcastPlayerContainer.style.display = 'block';
1661
 
1662
+ // Initialize WavePlayers if not already done
1663
  if (!podcastWavePlayers.a) {
1664
  podcastWavePlayers.a = new WavePlayer(podcastWavePlayerA, {
1665
+ // Add mobile-friendly options but hide native controls
1666
  backend: 'MediaElement',
1667
+ mediaControls: false // Hide native audio controls
1668
  });
1669
  podcastWavePlayers.b = new WavePlayer(podcastWavePlayerB, {
1670
+ // Add mobile-friendly options but hide native controls
1671
  backend: 'MediaElement',
1672
+ mediaControls: false // Hide native audio controls
1673
  });
1674
 
1675
+ // Load audio in waveplayers
1676
  podcastWavePlayers.a.loadAudio(data.audio_a);
1677
  podcastWavePlayers.b.loadAudio(data.audio_b);
1678
 
1679
+ // Force hide loading indicators after 5 seconds as a fallback
1680
  setTimeout(() => {
1681
  if (podcastWavePlayers.a && podcastWavePlayers.a.hideLoading) {
1682
  podcastWavePlayers.a.hideLoading();
 
1687
  console.log('Forced hiding of podcast loading indicators (safety timeout - existing players)');
1688
  }, 5000);
1689
  } else {
1690
+ // Reset and reload for existing players
1691
  try {
1692
  podcastWavePlayers.a.wavesurfer.empty();
1693
  podcastWavePlayers.b.wavesurfer.empty();
1694
 
1695
+ // Make sure loading indicators are reset
1696
  podcastWavePlayers.a.hideLoading();
1697
  podcastWavePlayers.b.hideLoading();
1698
 
1699
  podcastWavePlayers.a.loadAudio(data.audio_a);
1700
  podcastWavePlayers.b.loadAudio(data.audio_b);
1701
 
1702
+ // Force hide loading indicators after 5 seconds as a fallback
1703
  setTimeout(() => {
1704
  if (podcastWavePlayers.a && podcastWavePlayers.a.hideLoading) {
1705
  podcastWavePlayers.a.hideLoading();
 
1712
  } catch (err) {
1713
  console.error('Error resetting podcast waveplayers:', err);
1714
 
1715
+ // Recreate the players if there was an error
1716
  podcastWavePlayers.a = new WavePlayer(podcastWavePlayerA, {
1717
  backend: 'MediaElement',
1718
  mediaControls: false
 
1725
  podcastWavePlayers.a.loadAudio(data.audio_a);
1726
  podcastWavePlayers.b.loadAudio(data.audio_b);
1727
 
1728
+ // Force hide loading indicators after 5 seconds as a fallback
1729
  setTimeout(() => {
1730
  if (podcastWavePlayers.a && podcastWavePlayers.a.hideLoading) {
1731
  podcastWavePlayers.a.hideLoading();
 
1738
  }
1739
  }
1740
 
1741
+ // Setup automatic sequential playback
1742
  podcastWavePlayers.a.wavesurfer.once('ready', function() {
1743
  podcastWavePlayers.a.play();
1744
 
1745
+ // When audio A ends, play audio B
1746
  podcastWavePlayers.a.wavesurfer.once('finish', function() {
1747
+ // Wait a short moment before playing B
1748
  setTimeout(() => {
1749
  podcastWavePlayers.b.play();
1750
 
1751
+ // When audio B ends, enable voting
1752
  podcastWavePlayers.b.wavesurfer.once('finish', function() {
1753
  bothPodcastSamplesPlayed = true;
1754
  podcastVoteButtons.forEach(btn => {
 
1764
  openToast(error.message, "error");
1765
  console.error('Error:', error);
1766
  });
 
 
1767
  }
1768
 
1769
+ // Handle vote for a podcast model
1770
  function handlePodcastVote(model) {
1771
+ // Disable both vote buttons
1772
  podcastVoteButtons.forEach(btn => {
1773
  btn.disabled = true;
1774
  if (btn.dataset.model === model) {
 
1776
  }
1777
  });
1778
 
1779
+ // Send vote to server
1780
  fetch('/api/conversational/vote', {
1781
  method: 'POST',
1782
  headers: {
 
1796
  return response.json();
1797
  })
1798
  .then(data => {
1799
+ // Hide loaders
1800
  podcastVoteButtons.forEach(btn => {
1801
  btn.querySelector('.vote-loader').style.display = 'none';
1802
 
1803
+ // Highlight the selected button
1804
  if (btn.dataset.model === model) {
1805
  btn.classList.add('selected');
1806
  }
1807
  });
1808
 
1809
+ // Store model names from vote response
1810
  podcastModelNames.a = data.names.a;
1811
  podcastModelNames.b = data.names.b;
1812
 
1813
+ // Show model names after voting
1814
+ const modelNameDisplays = podcastPlayerContainer.querySelectorAll('.model-name-display');
1815
  modelNameDisplays[0].textContent = data.names.a ? `(${data.names.a})` : '';
1816
  modelNameDisplays[1].textContent = data.names.b ? `(${data.names.b})` : '';
1817
 
1818
+ // Show vote results
1819
  chosenModelNameElement.textContent = data.chosen_model.name;
1820
  rejectedModelNameElement.textContent = data.rejected_model.name;
1821
  podcastVoteResults.style.display = 'block';
1822
 
1823
+ // Show next round button
1824
  podcastNextRoundContainer.style.display = 'block';
1825
 
1826
+ // Show success toast
1827
  openToast("Vote recorded successfully!", "success");
1828
  })
1829
  .catch(error => {
1830
+ // Re-enable vote buttons
1831
  podcastVoteButtons.forEach(btn => {
1832
  btn.disabled = false;
1833
  btn.querySelector('.vote-loader').style.display = 'none';
 
1838
  });
1839
  }
1840
 
1841
+ // Reset podcast UI to initial state
1842
  function resetPodcastState() {
1843
+ // Hide players, results, and next round button
1844
  podcastPlayerContainer.style.display = 'none';
1845
  podcastVoteResults.style.display = 'none';
1846
  podcastNextRoundContainer.style.display = 'none';
1847
 
1848
+ // Reset vote buttons
1849
  podcastVoteButtons.forEach(btn => {
1850
  btn.disabled = true;
1851
  btn.classList.remove('selected');
1852
  btn.querySelector('.vote-loader').style.display = 'none';
1853
  });
1854
 
1855
+ // Clear model name displays
1856
+ const modelNameDisplays = podcastPlayerContainer.querySelectorAll('.model-name-display');
1857
  modelNameDisplays.forEach(display => {
1858
  display.textContent = '';
1859
  });
1860
 
1861
+ // Stop any playing audio
1862
  if (podcastWavePlayers.a) podcastWavePlayers.a.stop();
1863
  if (podcastWavePlayers.b) podcastWavePlayers.b.stop();
1864
 
1865
+ // Reset session
1866
  currentPodcastSessionId = null;
1867
 
1868
+ // Reset the flag for both samples played
1869
  bothPodcastSamplesPlayed = false;
1870
  }
1871
 
1872
+ // Add keyboard shortcut listeners for podcast voting
1873
  document.addEventListener('keydown', function(e) {
1874
+ // Check if we're in the podcast tab and it's active
1875
  const podcastTab = document.getElementById('conversational-tab');
1876
  if (!podcastTab.classList.contains('active')) return;
1877
 
1878
+ // Only process if input fields are not focused
1879
+ if (document.activeElement.tagName === 'INPUT' ||
1880
+ document.activeElement.tagName === 'TEXTAREA') {
1881
+ return;
 
 
 
 
 
 
 
 
 
1882
  }
1883
+
1884
+ if (e.key.toLowerCase() === 'a') {
1885
+ if (bothPodcastSamplesPlayed && !podcastVoteButtons[0].disabled) {
1886
+ handlePodcastVote('a');
1887
+ } else if (podcastPlayerContainer.style.display !== 'none' && !bothPodcastSamplesPlayed) {
1888
+ openToast("Please listen to both audio samples before voting", "info");
1889
  }
1890
+ } else if (e.key.toLowerCase() === 'b') {
1891
+ if (bothPodcastSamplesPlayed && !podcastVoteButtons[1].disabled) {
1892
+ handlePodcastVote('b');
1893
+ } else if (podcastPlayerContainer.style.display !== 'none' && !bothPodcastSamplesPlayed) {
1894
+ openToast("Please listen to both audio samples before voting", "info");
 
 
 
 
 
1895
  }
1896
+ } else if (e.key.toLowerCase() === 'n') {
1897
+ if (podcastNextRoundContainer.style.display === 'block') {
1898
+ if (!e.ctrlKey && !e.metaKey) {
1899
+ e.preventDefault();
1900
+ }
1901
+ resetPodcastState();
1902
+ }
1903
+ } else if (e.key === ' ') {
1904
+ // Space to play/pause current audio
1905
+ if (podcastPlayerContainer.style.display !== 'none') {
1906
  e.preventDefault();
1907
+ // If A is playing, toggle A, else if B is playing, toggle B, else play A
1908
  if (podcastWavePlayers.a && podcastWavePlayers.a.isPlaying) {
1909
  podcastWavePlayers.a.togglePlayPause();
1910
  } else if (podcastWavePlayers.b && podcastWavePlayers.b.isPlaying) {
1911
  podcastWavePlayers.b.togglePlayPause();
1912
  } else if (podcastWavePlayers.a) {
1913
+ podcastWavePlayers.a.play();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1914
  }
1915
  }
1916
  }
 
 
 
 
 
 
 
1917
  });
1918
 
1919
+ // Event listeners
1920
  addLineBtn.addEventListener('click', function() {
1921
  addPodcastLine();
1922
  });
 
1929
  generatePodcast();
1930
  });
1931
 
1932
+ // Add event listeners to vote buttons
1933
  podcastVoteButtons.forEach(btn => {
1934
  btn.addEventListener('click', function() {
1935
  if (bothPodcastSamplesPlayed) {
 
1941
  });
1942
  });
1943
 
1944
+ // Add event listener for next round button
1945
  podcastNextRoundBtn.addEventListener('click', resetPodcastState);
1946
 
1947
+ // Initialize with 2 empty lines
1948
  initializePodcastLines();
1949
  });
1950
  </script>