Spaces:
Runtime error
Runtime error
Upload index.html
Browse files- index.html +281 -3
index.html
CHANGED
|
@@ -448,6 +448,85 @@
|
|
| 448 |
color:var(--ink);
|
| 449 |
border:1px solid var(--line);
|
| 450 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
</style>
|
| 452 |
</head>
|
| 453 |
<body>
|
|
@@ -523,7 +602,7 @@
|
|
| 523 |
<div class="output" id="output">
|
| 524 |
<audio id="player" controls></audio>
|
| 525 |
<div class="actions">
|
| 526 |
-
<a class="btn-
|
| 527 |
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 528 |
<path d="M12 3v12m0 0l-4.5-4.5M12 15l4.5-4.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
| 529 |
<path d="M5 19.5h14" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
|
@@ -556,6 +635,14 @@
|
|
| 556 |
</div>
|
| 557 |
</div>
|
| 558 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
<div class="modal-section">
|
| 560 |
<div class="modal-row">
|
| 561 |
<span class="modal-row-label" data-i18n="developer">ဖန်တီးသူ</span>
|
|
@@ -614,7 +701,11 @@
|
|
| 614 |
statusRewardDownload: 'ဒေါင်းလုဒ် အခမဲ့ ရယူရန် ကြော်ငြာ ပြသပါမည်...',
|
| 615 |
errSkippedDownload: 'ဒေါင်းလုဒ်ရန် ကြော်ငြာကို အပြည့်အစုံ ကြည့်ပေးပါ။',
|
| 616 |
statusSaved: 'Downloads ဖိုလ်ဒါသို့ သိမ်းပြီးပါပြီ။',
|
| 617 |
-
errSaveFailed: 'ဒေါင်းလုဒ် မအောင်မြင်ပါ။'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
},
|
| 619 |
en: {
|
| 620 |
eyebrow: 'Voice Studio · Free Text-to-Speech',
|
|
@@ -648,7 +739,11 @@
|
|
| 648 |
statusRewardDownload: 'Showing a quick ad to unlock the free download...',
|
| 649 |
errSkippedDownload: 'Please watch the full ad to download for free.',
|
| 650 |
statusSaved: 'Saved to your Downloads folder.',
|
| 651 |
-
errSaveFailed: 'Download failed.'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 652 |
}
|
| 653 |
};
|
| 654 |
|
|
@@ -675,6 +770,7 @@
|
|
| 675 |
uiLang = lang;
|
| 676 |
localStorage.setItem('vs_ui_lang', lang);
|
| 677 |
applyTranslations();
|
|
|
|
| 678 |
}
|
| 679 |
|
| 680 |
applyTranslations();
|
|
@@ -691,6 +787,167 @@
|
|
| 691 |
btn.addEventListener('click', () => setUiLang(btn.dataset.lang));
|
| 692 |
});
|
| 693 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 694 |
// ---- Build signature waveform ----
|
| 695 |
const wave = document.getElementById('wave');
|
| 696 |
const BAR_COUNT = 20;
|
|
@@ -926,6 +1183,27 @@
|
|
| 926 |
status.textContent = t('statusDone');
|
| 927 |
player.play().catch(() => {});
|
| 928 |
notifyInterstitialCheckpoint();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 929 |
} catch (e) {
|
| 930 |
status.textContent = e.message;
|
| 931 |
status.classList.add('error');
|
|
|
|
| 448 |
color:var(--ink);
|
| 449 |
border:1px solid var(--line);
|
| 450 |
}
|
| 451 |
+
|
| 452 |
+
/* ---------- Voice history (inside menu) ---------- */
|
| 453 |
+
.history-list{
|
| 454 |
+
display:flex;
|
| 455 |
+
flex-direction:column;
|
| 456 |
+
gap:8px;
|
| 457 |
+
margin-top:10px;
|
| 458 |
+
max-height:220px;
|
| 459 |
+
overflow-y:auto;
|
| 460 |
+
}
|
| 461 |
+
.history-item{
|
| 462 |
+
display:flex;
|
| 463 |
+
align-items:center;
|
| 464 |
+
gap:10px;
|
| 465 |
+
padding:8px;
|
| 466 |
+
border:1px solid var(--line);
|
| 467 |
+
border-radius:10px;
|
| 468 |
+
background:var(--surface-2);
|
| 469 |
+
}
|
| 470 |
+
.history-play{
|
| 471 |
+
width:30px;
|
| 472 |
+
height:30px;
|
| 473 |
+
border-radius:50%;
|
| 474 |
+
background:var(--accent);
|
| 475 |
+
border:none;
|
| 476 |
+
color:#fff;
|
| 477 |
+
display:flex;
|
| 478 |
+
align-items:center;
|
| 479 |
+
justify-content:center;
|
| 480 |
+
flex-shrink:0;
|
| 481 |
+
cursor:pointer;
|
| 482 |
+
padding:0;
|
| 483 |
+
}
|
| 484 |
+
.history-play svg{ width:13px; height:13px; }
|
| 485 |
+
.history-play[disabled]{ background:#C7CCD6; cursor:not-allowed; }
|
| 486 |
+
.history-meta{ flex:1; min-width:0; }
|
| 487 |
+
.history-text{
|
| 488 |
+
font-family:var(--mm);
|
| 489 |
+
font-size:13px;
|
| 490 |
+
color:var(--ink);
|
| 491 |
+
white-space:nowrap;
|
| 492 |
+
overflow:hidden;
|
| 493 |
+
text-overflow:ellipsis;
|
| 494 |
+
}
|
| 495 |
+
.history-sub{
|
| 496 |
+
font-family:var(--sans);
|
| 497 |
+
font-size:11px;
|
| 498 |
+
color:var(--ink-soft);
|
| 499 |
+
margin-top:2px;
|
| 500 |
+
}
|
| 501 |
+
.history-delete{
|
| 502 |
+
width:26px;
|
| 503 |
+
height:26px;
|
| 504 |
+
border-radius:50%;
|
| 505 |
+
border:none;
|
| 506 |
+
background:transparent;
|
| 507 |
+
color:var(--ink-soft);
|
| 508 |
+
font-size:17px;
|
| 509 |
+
line-height:1;
|
| 510 |
+
cursor:pointer;
|
| 511 |
+
flex-shrink:0;
|
| 512 |
+
}
|
| 513 |
+
.history-empty{
|
| 514 |
+
font-family:var(--sans);
|
| 515 |
+
font-size:13px;
|
| 516 |
+
color:var(--ink-soft);
|
| 517 |
+
padding:8px 0 0;
|
| 518 |
+
}
|
| 519 |
+
.history-clear{
|
| 520 |
+
margin-top:10px;
|
| 521 |
+
font-family:var(--sans);
|
| 522 |
+
font-size:12px;
|
| 523 |
+
font-weight:600;
|
| 524 |
+
color:#D14343;
|
| 525 |
+
background:none;
|
| 526 |
+
border:none;
|
| 527 |
+
cursor:pointer;
|
| 528 |
+
padding:0;
|
| 529 |
+
}
|
| 530 |
</style>
|
| 531 |
</head>
|
| 532 |
<body>
|
|
|
|
| 602 |
<div class="output" id="output">
|
| 603 |
<audio id="player" controls></audio>
|
| 604 |
<div class="actions">
|
| 605 |
+
<a class="btn-primary btn-download" id="downloadLink" download="voice.mp3">
|
| 606 |
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 607 |
<path d="M12 3v12m0 0l-4.5-4.5M12 15l4.5-4.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
| 608 |
<path d="M5 19.5h14" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
|
|
|
| 635 |
</div>
|
| 636 |
</div>
|
| 637 |
|
| 638 |
+
<div class="modal-section">
|
| 639 |
+
<div class="modal-row-label" data-i18n="historyLabel">Voice History</div>
|
| 640 |
+
<div id="historyList" class="history-list"></div>
|
| 641 |
+
<div id="historyEmpty" class="history-empty" data-i18n="historyEmpty">မှတ်တမ်း မရှိသေးပါ။</div>
|
| 642 |
+
<button id="clearHistoryBtn" class="history-clear" data-i18n="clearHistory">မှတ်တမ်း ရှင်းမည်</button>
|
| 643 |
+
<audio id="historyAudio" style="display:none"></audio>
|
| 644 |
+
</div>
|
| 645 |
+
|
| 646 |
<div class="modal-section">
|
| 647 |
<div class="modal-row">
|
| 648 |
<span class="modal-row-label" data-i18n="developer">ဖန်တီးသူ</span>
|
|
|
|
| 701 |
statusRewardDownload: 'ဒေါင်းလုဒ် အခမဲ့ ရယူရန် ကြော်ငြာ ပြသပါမည်...',
|
| 702 |
errSkippedDownload: 'ဒေါင်းလုဒ်ရန် ကြော်ငြာကို အပြည့်အစုံ ကြည့်ပေးပါ။',
|
| 703 |
statusSaved: 'Downloads ဖိုလ်ဒါသို့ သိမ်းပြီးပါပြီ။',
|
| 704 |
+
errSaveFailed: 'ဒေါင်းလုဒ် မအောင်မြင်ပါ။',
|
| 705 |
+
historyLabel: 'အသံ မှတ်တမ်း',
|
| 706 |
+
historyEmpty: 'မှတ်တမ်း မရှိသေးပါ။',
|
| 707 |
+
clearHistory: 'မှတ်တမ်း ရှင်းမည်',
|
| 708 |
+
historyAudioUnavailable: 'ဖိုင်ကြီးလွန်းသောကြောင့် ပြန်နားထောင်ရန် မသိမ်းထားပါ'
|
| 709 |
},
|
| 710 |
en: {
|
| 711 |
eyebrow: 'Voice Studio · Free Text-to-Speech',
|
|
|
|
| 739 |
statusRewardDownload: 'Showing a quick ad to unlock the free download...',
|
| 740 |
errSkippedDownload: 'Please watch the full ad to download for free.',
|
| 741 |
statusSaved: 'Saved to your Downloads folder.',
|
| 742 |
+
errSaveFailed: 'Download failed.',
|
| 743 |
+
historyLabel: 'Voice History',
|
| 744 |
+
historyEmpty: 'No history yet.',
|
| 745 |
+
clearHistory: 'Clear history',
|
| 746 |
+
historyAudioUnavailable: 'Too large to keep for replay'
|
| 747 |
}
|
| 748 |
};
|
| 749 |
|
|
|
|
| 770 |
uiLang = lang;
|
| 771 |
localStorage.setItem('vs_ui_lang', lang);
|
| 772 |
applyTranslations();
|
| 773 |
+
renderHistory();
|
| 774 |
}
|
| 775 |
|
| 776 |
applyTranslations();
|
|
|
|
| 787 |
btn.addEventListener('click', () => setUiLang(btn.dataset.lang));
|
| 788 |
});
|
| 789 |
|
| 790 |
+
// ---- Voice history (stored in localStorage, played back from the menu) ----
|
| 791 |
+
const HISTORY_KEY = 'vs_history_v1';
|
| 792 |
+
const MAX_HISTORY = 8;
|
| 793 |
+
const MAX_HISTORY_AUDIO_BYTES = 1.5 * 1024 * 1024; // ~1.5MB cap per stored clip
|
| 794 |
+
|
| 795 |
+
const TIME_STR = {
|
| 796 |
+
my: {
|
| 797 |
+
justNow: () => 'ခုလေးတင်',
|
| 798 |
+
minAgo: (n) => `${n} မိနစ်အကြာ`,
|
| 799 |
+
hourAgo: (n) => `${n} နာရီအကြာ`,
|
| 800 |
+
dayAgo: (n) => `${n} ရက်အကြာ`
|
| 801 |
+
},
|
| 802 |
+
en: {
|
| 803 |
+
justNow: () => 'just now',
|
| 804 |
+
minAgo: (n) => `${n}m ago`,
|
| 805 |
+
hourAgo: (n) => `${n}h ago`,
|
| 806 |
+
dayAgo: (n) => `${n}d ago`
|
| 807 |
+
}
|
| 808 |
+
};
|
| 809 |
+
|
| 810 |
+
function relativeTime(ts) {
|
| 811 |
+
const diffSec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
| 812 |
+
const dict = TIME_STR[uiLang] || TIME_STR.my;
|
| 813 |
+
if (diffSec < 60) return dict.justNow();
|
| 814 |
+
const mins = Math.floor(diffSec / 60);
|
| 815 |
+
if (mins < 60) return dict.minAgo(mins);
|
| 816 |
+
const hours = Math.floor(mins / 60);
|
| 817 |
+
if (hours < 24) return dict.hourAgo(hours);
|
| 818 |
+
return dict.dayAgo(Math.floor(hours / 24));
|
| 819 |
+
}
|
| 820 |
+
|
| 821 |
+
function loadHistory() {
|
| 822 |
+
try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); }
|
| 823 |
+
catch (e) { return []; }
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
function saveHistory(list) {
|
| 827 |
+
try {
|
| 828 |
+
localStorage.setItem(HISTORY_KEY, JSON.stringify(list));
|
| 829 |
+
} catch (e) {
|
| 830 |
+
// Quota exceeded (likely too many large audio clips) — drop the oldest
|
| 831 |
+
// half and retry once before giving up silently.
|
| 832 |
+
try {
|
| 833 |
+
const trimmed = list.slice(0, Math.ceil(list.length / 2));
|
| 834 |
+
localStorage.setItem(HISTORY_KEY, JSON.stringify(trimmed));
|
| 835 |
+
} catch (e2) { /* give up quietly, history just won't persist */ }
|
| 836 |
+
}
|
| 837 |
+
}
|
| 838 |
+
|
| 839 |
+
function addHistoryEntry(entry) {
|
| 840 |
+
const list = loadHistory();
|
| 841 |
+
list.unshift(entry);
|
| 842 |
+
while (list.length > MAX_HISTORY) list.pop();
|
| 843 |
+
saveHistory(list);
|
| 844 |
+
renderHistory();
|
| 845 |
+
}
|
| 846 |
+
|
| 847 |
+
function deleteHistoryEntry(id) {
|
| 848 |
+
const list = loadHistory().filter(e => e.id !== id);
|
| 849 |
+
saveHistory(list);
|
| 850 |
+
renderHistory();
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
function clearHistory() {
|
| 854 |
+
saveHistory([]);
|
| 855 |
+
renderHistory();
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
const historyListEl = document.getElementById('historyList');
|
| 859 |
+
const historyEmptyEl = document.getElementById('historyEmpty');
|
| 860 |
+
const historyAudio = document.getElementById('historyAudio');
|
| 861 |
+
let activeHistoryPlayBtn = null;
|
| 862 |
+
|
| 863 |
+
function playIconSvg() {
|
| 864 |
+
return '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 5.5v13l11-6.5-11-6.5z"/></svg>';
|
| 865 |
+
}
|
| 866 |
+
function pauseIconSvg() {
|
| 867 |
+
return '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M7 5h3.5v14H7V5zm6.5 0H17v14h-3.5V5z"/></svg>';
|
| 868 |
+
}
|
| 869 |
+
|
| 870 |
+
function resetActivePlayButton() {
|
| 871 |
+
if (activeHistoryPlayBtn) {
|
| 872 |
+
activeHistoryPlayBtn.innerHTML = playIconSvg();
|
| 873 |
+
activeHistoryPlayBtn = null;
|
| 874 |
+
}
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
historyAudio.addEventListener('ended', resetActivePlayButton);
|
| 878 |
+
historyAudio.addEventListener('pause', () => {
|
| 879 |
+
if (activeHistoryPlayBtn) activeHistoryPlayBtn.innerHTML = playIconSvg();
|
| 880 |
+
});
|
| 881 |
+
|
| 882 |
+
function renderHistory() {
|
| 883 |
+
const list = loadHistory();
|
| 884 |
+
historyListEl.innerHTML = '';
|
| 885 |
+
activeHistoryPlayBtn = null;
|
| 886 |
+
|
| 887 |
+
if (!list.length) {
|
| 888 |
+
historyEmptyEl.style.display = 'block';
|
| 889 |
+
return;
|
| 890 |
+
}
|
| 891 |
+
historyEmptyEl.style.display = 'none';
|
| 892 |
+
|
| 893 |
+
list.forEach(entry => {
|
| 894 |
+
const row = document.createElement('div');
|
| 895 |
+
row.className = 'history-item';
|
| 896 |
+
|
| 897 |
+
const playBtn = document.createElement('button');
|
| 898 |
+
playBtn.className = 'history-play';
|
| 899 |
+
playBtn.innerHTML = playIconSvg();
|
| 900 |
+
if (!entry.audio) {
|
| 901 |
+
playBtn.disabled = true;
|
| 902 |
+
playBtn.title = t('historyAudioUnavailable');
|
| 903 |
+
} else {
|
| 904 |
+
playBtn.addEventListener('click', () => {
|
| 905 |
+
const isThisPlaying = activeHistoryPlayBtn === playBtn && !historyAudio.paused;
|
| 906 |
+
resetActivePlayButton();
|
| 907 |
+
if (isThisPlaying) return; // was playing -> just stop
|
| 908 |
+
historyAudio.src = entry.audio;
|
| 909 |
+
historyAudio.play().catch(() => {});
|
| 910 |
+
playBtn.innerHTML = pauseIconSvg();
|
| 911 |
+
activeHistoryPlayBtn = playBtn;
|
| 912 |
+
});
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
const meta = document.createElement('div');
|
| 916 |
+
meta.className = 'history-meta';
|
| 917 |
+
const textEl = document.createElement('div');
|
| 918 |
+
textEl.className = 'history-text';
|
| 919 |
+
textEl.textContent = entry.textPreview;
|
| 920 |
+
const subEl = document.createElement('div');
|
| 921 |
+
subEl.className = 'history-sub';
|
| 922 |
+
subEl.textContent = `${entry.langLabel || ''} · ${entry.voiceLabel || ''} · ${relativeTime(entry.ts)}`;
|
| 923 |
+
meta.appendChild(textEl);
|
| 924 |
+
meta.appendChild(subEl);
|
| 925 |
+
|
| 926 |
+
const delBtn = document.createElement('button');
|
| 927 |
+
delBtn.className = 'history-delete';
|
| 928 |
+
delBtn.textContent = '\u00d7';
|
| 929 |
+
delBtn.addEventListener('click', () => {
|
| 930 |
+
if (activeHistoryPlayBtn === playBtn) {
|
| 931 |
+
historyAudio.pause();
|
| 932 |
+
}
|
| 933 |
+
deleteHistoryEntry(entry.id);
|
| 934 |
+
});
|
| 935 |
+
|
| 936 |
+
row.appendChild(playBtn);
|
| 937 |
+
row.appendChild(meta);
|
| 938 |
+
row.appendChild(delBtn);
|
| 939 |
+
historyListEl.appendChild(row);
|
| 940 |
+
});
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
document.getElementById('clearHistoryBtn').addEventListener('click', () => {
|
| 944 |
+
historyAudio.pause();
|
| 945 |
+
clearHistory();
|
| 946 |
+
});
|
| 947 |
+
|
| 948 |
+
renderHistory();
|
| 949 |
+
|
| 950 |
+
|
| 951 |
// ---- Build signature waveform ----
|
| 952 |
const wave = document.getElementById('wave');
|
| 953 |
const BAR_COUNT = 20;
|
|
|
|
| 1183 |
status.textContent = t('statusDone');
|
| 1184 |
player.play().catch(() => {});
|
| 1185 |
notifyInterstitialCheckpoint();
|
| 1186 |
+
|
| 1187 |
+
// Save to Voice History (skip storing audio itself if the clip is too
|
| 1188 |
+
// large, to keep localStorage usage reasonable).
|
| 1189 |
+
(async () => {
|
| 1190 |
+
let audioDataUrl = null;
|
| 1191 |
+
if (blob.size <= MAX_HISTORY_AUDIO_BYTES) {
|
| 1192 |
+
try { audioDataUrl = await blobToBase64(blob); } catch (e) { audioDataUrl = null; }
|
| 1193 |
+
}
|
| 1194 |
+
addHistoryEntry({
|
| 1195 |
+
id: Date.now() + '_' + Math.random().toString(36).slice(2, 8),
|
| 1196 |
+
textPreview: value.length > 80 ? value.slice(0, 80) + '…' : value,
|
| 1197 |
+
voiceLabel: voiceSelect.options[voiceSelect.selectedIndex]
|
| 1198 |
+
? voiceSelect.options[voiceSelect.selectedIndex].textContent.trim()
|
| 1199 |
+
: voice,
|
| 1200 |
+
langLabel: langSelect.options[langSelect.selectedIndex]
|
| 1201 |
+
? langSelect.options[langSelect.selectedIndex].textContent.trim()
|
| 1202 |
+
: '',
|
| 1203 |
+
ts: Date.now(),
|
| 1204 |
+
audio: audioDataUrl
|
| 1205 |
+
});
|
| 1206 |
+
})();
|
| 1207 |
} catch (e) {
|
| 1208 |
status.textContent = e.message;
|
| 1209 |
status.classList.add('error');
|