fix: voice inspector TDZ crash + batch ops respect selection (v1.9.7)
- voice-inspector: move `curGender` const before its first use — it was declared at line 86 but referenced at line 61, causing a ReferenceError (temporal dead zone) that silently aborted selectVoice() on every click - Calc dB, Precompute, Batch Seeds now filter to checked voices when a bulk selection is active, matching Benchmark's existing behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6b52a346d5
commit
217d92b91a
@ -9,6 +9,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.9.7] — 2026-06-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Voice inspector broken**: `curGender` was referenced before its `const` declaration inside `selectVoice()`, causing a temporal dead zone ReferenceError that silently prevented the inspector from opening. Clicking a voice now correctly opens the detail panel again.
|
||||||
|
- **Batch operations ignore selection**: Calc dB, Precompute, and Batch Seeds now operate only on the checked (selected) voices when a selection is active, matching the existing behaviour of Benchmark. Previously all three always ran on all active / visible voices regardless of selection.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.9.6] — 2026-06-26
|
## [1.9.6] — 2026-06-26
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
||||||
<link rel="stylesheet" href="/static/style.css?v=1.9.6">
|
<link rel="stylesheet" href="/static/style.css?v=1.9.7">
|
||||||
|
|
||||||
|
|
||||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||||
@ -308,7 +308,7 @@
|
|||||||
<script src="/static/vendor/wavesurfer-regions.min.js"></script>
|
<script src="/static/vendor/wavesurfer-regions.min.js"></script>
|
||||||
|
|
||||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||||
<script src="/static/loader.js?v=1.9.6"></script>
|
<script src="/static/loader.js?v=1.9.7"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -431,10 +431,13 @@ function attachSeedFinder(voiceId, body) {
|
|||||||
// writes to the same cache the per-voice Seed Finder reads, so opening any voice
|
// writes to the same cache the per-voice Seed Finder reads, so opening any voice
|
||||||
// later shows its samples instantly.
|
// later shows its samples instantly.
|
||||||
function seedFinderBatchAll() {
|
function seedFinderBatchAll() {
|
||||||
const ids = (typeof activeVoiceIds === 'function')
|
const _sfUseSelected = typeof _bulkSelected !== 'undefined' && _bulkSelected.size > 0;
|
||||||
|
const ids = _sfUseSelected
|
||||||
|
? [..._bulkSelected]
|
||||||
|
: (typeof activeVoiceIds === 'function')
|
||||||
? activeVoiceIds()
|
? activeVoiceIds()
|
||||||
: (window._voices || []).filter(v => v.enabled !== false).map(v => v.id);
|
: (window._voices || []).filter(v => v.enabled !== false).map(v => v.id);
|
||||||
if (!ids.length) { if (typeof toast === 'function') toast('No active voices', 'error'); return; }
|
if (!ids.length) { if (typeof toast === 'function') toast('No voices selected', 'error'); return; }
|
||||||
|
|
||||||
// In-app config dialog (no browser prompts): pick the seed range, then Start.
|
// In-app config dialog (no browser prompts): pick the seed range, then Start.
|
||||||
document.getElementById('seedbatch-overlay')?.remove();
|
document.getElementById('seedbatch-overlay')?.remove();
|
||||||
@ -442,7 +445,7 @@ function seedFinderBatchAll() {
|
|||||||
ov.className = 'audiobook-overlay'; ov.id = 'seedbatch-overlay';
|
ov.className = 'audiobook-overlay'; ov.id = 'seedbatch-overlay';
|
||||||
ov.innerHTML = `<div class="audiobook-box">
|
ov.innerHTML = `<div class="audiobook-box">
|
||||||
<div class="audiobook-title"><span class="mdi mdi-dice-multiple-outline"></span> Batch seed generation</div>
|
<div class="audiobook-title"><span class="mdi mdi-dice-multiple-outline"></span> Batch seed generation</div>
|
||||||
<div class="audiobook-msg">Generate & cache Seed Finder samples for all <b>${ids.length}</b> active voices. Already-cached seeds are skipped, so it's resumable; you can cancel anytime.</div>
|
<div class="audiobook-msg">Generate & cache Seed Finder samples for <b>${ids.length}</b> ${_sfUseSelected ? 'selected' : 'active'} voice${ids.length !== 1 ? 's' : ''}. Already-cached seeds are skipped, so it's resumable; you can cancel anytime.</div>
|
||||||
<div class="seed-finder-row seed-finder-params" style="margin:12px 0">
|
<div class="seed-finder-row seed-finder-params" style="margin:12px 0">
|
||||||
<div class="seed-finder-field">
|
<div class="seed-finder-field">
|
||||||
<label>Seeds: from</label>
|
<label>Seeds: from</label>
|
||||||
|
|||||||
@ -45,6 +45,7 @@ function selectVoice(wrap) {
|
|||||||
const langCode = v.lang || wrap.dataset.lang || '';
|
const langCode = v.lang || wrap.dataset.lang || '';
|
||||||
const flagCc = v.flag || wrap.dataset.flagCc || langCode;
|
const flagCc = v.flag || wrap.dataset.flagCc || langCode;
|
||||||
const rating = v.rating || 0;
|
const rating = v.rating || 0;
|
||||||
|
const curGender = v.gender || 'N';
|
||||||
|
|
||||||
const nameParts = voiceId.split('_');
|
const nameParts = voiceId.split('_');
|
||||||
// Prefer an explicit display name (e.g. the character name for Rehearser voices);
|
// Prefer an explicit display name (e.g. the character name for Rehearser voices);
|
||||||
@ -83,7 +84,6 @@ function selectVoice(wrap) {
|
|||||||
const _gM = {F:'♀', M:'♂', N:'⚥', '':'?'};
|
const _gM = {F:'♀', M:'♂', N:'⚥', '':'?'};
|
||||||
const _gL = {F:'Female', M:'Male', N:'Diverse', '':'—'};
|
const _gL = {F:'Female', M:'Male', N:'Diverse', '':'—'};
|
||||||
const _gC = {F:'g-f', M:'g-m', N:'g-n', '':'g-n'};
|
const _gC = {F:'g-f', M:'g-m', N:'g-n', '':'g-n'};
|
||||||
const curGender = v.gender || 'N';
|
|
||||||
const genderLabelHtml = `${_gM[curGender]||'?'} ${_gL[curGender]||'—'}`;
|
const genderLabelHtml = `${_gM[curGender]||'?'} ${_gL[curGender]||'—'}`;
|
||||||
|
|
||||||
inspector.innerHTML = `
|
inspector.innerHTML = `
|
||||||
|
|||||||
@ -417,7 +417,9 @@ async function clientVoiceLoudness(v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function clientCalculateVoiceDb() {
|
async function clientCalculateVoiceDb() {
|
||||||
const voices = visibleLibraryVoices();
|
const voices = (_bulkSelected && _bulkSelected.size > 0)
|
||||||
|
? (_voices || []).filter(v => _bulkSelected.has(v.id))
|
||||||
|
: visibleLibraryVoices();
|
||||||
const errors = [];
|
const errors = [];
|
||||||
let calculated = 0;
|
let calculated = 0;
|
||||||
const stats = {startedAt: Date.now(), ok: 0, slow: 0, errors: 0, middleLabel: 'Skipped'};
|
const stats = {startedAt: Date.now(), ok: 0, slow: 0, errors: 0, middleLabel: 'Skipped'};
|
||||||
@ -622,7 +624,8 @@ function libraryTargetDb() {
|
|||||||
|
|
||||||
$('calculate-db-btn').addEventListener('click', async () => {
|
$('calculate-db-btn').addEventListener('click', async () => {
|
||||||
$('calculate-db-btn').disabled = true;
|
$('calculate-db-btn').disabled = true;
|
||||||
status('Calculating voice loudness…');
|
const _calcTarget = (_bulkSelected && _bulkSelected.size > 0) ? `${_bulkSelected.size} selected` : 'visible';
|
||||||
|
status(`Calculating voice loudness (${_calcTarget})…`);
|
||||||
try {
|
try {
|
||||||
const d = await clientCalculateVoiceDb();
|
const d = await clientCalculateVoiceDb();
|
||||||
renderVoiceList();
|
renderVoiceList();
|
||||||
@ -907,9 +910,11 @@ $('copy-active-voices-btn').addEventListener('click', async () => {
|
|||||||
// (re)builds it from the wav when missing — so this is purely a warm-up.
|
// (re)builds it from the wav when missing — so this is purely a warm-up.
|
||||||
$('precompute-embeddings-btn')?.addEventListener('click', async () => {
|
$('precompute-embeddings-btn')?.addEventListener('click', async () => {
|
||||||
const backend = libraryTtsBackend();
|
const backend = libraryTtsBackend();
|
||||||
const ids = activeVoiceIds();
|
const _useSelected = _bulkSelected && _bulkSelected.size > 0;
|
||||||
if (!ids.length) { toast('No active voices to precompute', 'error'); return; }
|
const ids = _useSelected ? [..._bulkSelected] : activeVoiceIds();
|
||||||
if (!confirm(`Precompute speaker embeddings for ${ids.length} active voice(s) via “${backend}”?\n\nThis warms each voice so the engine caches its .pt and first playback is instant.`)) return;
|
const _scopeLabel = _useSelected ? `${ids.length} selected` : `${ids.length} active`;
|
||||||
|
if (!ids.length) { toast('No voices to precompute', 'error'); return; }
|
||||||
|
if (!confirm(`Precompute speaker embeddings for ${_scopeLabel} voice(s) via “${backend}”?\n\nThis warms each voice so the engine caches its .pt and first playback is instant.`)) return;
|
||||||
|
|
||||||
const btn = $('precompute-embeddings-btn'); if (btn) btn.disabled = true;
|
const btn = $('precompute-embeddings-btn'); if (btn) btn.disabled = true;
|
||||||
const ov = document.createElement('div');
|
const ov = document.createElement('div');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user