tts-voice-creator-clone-and.../static/loader.js
mARTin-B78 fff6e7e096 feat: add Seed Finder panel to voice inspector
Adds a collapsible "🎲 Seed Finder" panel to the voice inspector pane.
Select a voice, expand the panel, set a seed range (default 1–15), and
click Run to generate audio samples with different seeds.

Each result row has Play and "Use this seed" buttons. "Use this seed"
saves the chosen seed to the faster-qwen3-tts voices.json via the new
/api/tts-voice-seed proxy endpoint (which calls /voice-seed on the TTS
server). The saved row gets a ✓ marker.

Test sentence auto-selects DE/EN mixed or EN-only based on voice name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 14:53:51 +02:00

167 lines
6.8 KiB
JavaScript

(async function () {
'use strict';
const SECTIONS = [
's-voices', 's-clone', 's-design', 's-studio', 's-tryout', 's-rehearser',
's-reader', 's-performance', 's-routing', 's-connect', 's-settings',
's-llms', 's-conversation',
];
function _storedSection() {
try { return localStorage.getItem('ttsvc_section') || ''; } catch (_) { return ''; }
}
function _hashSection() {
var raw = String(location.hash || '').replace(/^#/, '').trim();
if (!raw) return '';
if (raw.indexOf('section=') === 0) raw = raw.slice(8);
raw = decodeURIComponent(raw).replace(/^\/?/, '');
return SECTIONS.indexOf(raw) !== -1 ? raw : '';
}
// Determine which section to show first (same logic as nav.js): hash > stored > My Voices.
var _savedSection = _storedSection();
var _activeSection = _hashSection() || ((_savedSection && SECTIONS.indexOf(_savedSection) !== -1) ? _savedSection : 's-voices');
// ── Fetch app version once for JS cache-busting ──────────────────────────
var _appVersion = 'dev';
try {
var _vr = await fetch('/api/version');
var _vd = await _vr.json();
_appVersion = _vd.version || _appVersion;
} catch (_) {}
var _ts = Date.now(); // sections always fresh during a session
// Append session timestamp so that a version bump + container restart
// always invalidates previously cached JS, even on the same version string.
_appVersion = _appVersion + '-' + _ts;
function _inject(id, html) {
var el = document.getElementById(id);
if (el) el.innerHTML = html;
}
// ── 1. Load ONLY the active section before anything else ─────────────────
// Feature modules set up DOM event listeners — the active section's elements
// must exist when JS runs. Other sections are loaded in step 2.
try {
var _ar = await fetch('/static/sections/' + _activeSection + '.html?v=' + _ts);
if (!_ar.ok) throw new Error(_ar.status + ' ' + _ar.statusText);
_inject(_activeSection, await _ar.text());
} catch (e) {
var _ael = document.getElementById(_activeSection);
if (_ael) _ael.innerHTML =
'<p style="color:var(--red);padding:24px">Failed to load section <b>' +
_activeSection + '</b>: ' + e.message + '</p>';
}
// ── 2. Fetch remaining sections in background (parallel with JS loading) ──
// These run concurrently with utils + settings below. Because section HTML
// files are much smaller than JS, they finish before the feature-module batch
// needs them.
var _bgSections = Promise.all(
SECTIONS.filter(function (id) { return id !== _activeSection; })
.map(async function (id) {
try {
var res = await fetch('/static/sections/' + id + '.html?v=' + _ts);
if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
_inject(id, await res.text());
} catch (e) {
var el = document.getElementById(id);
if (el) el.innerHTML =
'<p style="color:var(--red);padding:24px">Failed to load section <b>' +
id + '</b>: ' + e.message + '</p>';
}
})
);
// ── 3. Load JS in parallel batches ────────────────────────────────────────
// Scripts with async=false are FETCHED in parallel but EXECUTED in DOM
// insertion order — safe for modules that share a global scope.
function _load(src) {
return new Promise(function (resolve, reject) {
var s = document.createElement('script');
s.src = src + '?v=' + _appVersion;
s.async = false; // fetch in parallel, execute in DOM order
s.onload = resolve;
s.onerror = function () { reject(new Error('Failed to load ' + src)); };
document.body.appendChild(s);
});
}
function _loadBatch(srcs) {
return Promise.all(srcs.map(_load));
}
// A — foundation (sequential: each must finish before the next batch starts)
await _load('/static/js/utils.js');
await _load('/static/js/i18n.js'); // defines window.t / applyI18n before features run
await _load('/static/js/settings.js');
// Wait for background sections BEFORE feature modules run.
// Feature modules set up event listeners on DOM elements; all section HTML
// must already be injected. On a local server the sections finish well before
// settings.js completes, so this await is typically instant.
await _bgSections;
// C — feature modules (executed in order; share a global scope).
// Opt-in single bundle (npm run minify) keeps these as one minified request;
// default loads them individually so editing static/js/* is live without a build.
var _featureFiles = [
'/static/js/voice-picker.js',
'/static/js/benchmark-voice-picker.js',
'/static/js/voice-inspector.js',
'/static/js/seed-finder.js',
'/static/js/voice-sources.js',
'/static/js/fishaudio-browser.js',
'/static/js/integrations.js',
'/static/js/routing.js',
'/static/js/voice-clone.js',
'/static/js/voice-library.js',
'/static/js/tts-preview.js',
'/static/js/generation.js',
'/static/js/benchmark.js',
'/static/js/stt.js',
'/static/js/rehearser-parse.js',
'/static/js/rehearser.js',
'/static/js/reader.js',
'/static/js/audiobook.js',
'/static/js/character-sheets.js',
];
var _useBundle = window.APP_USE_BUNDLE === true || location.search.indexOf('bundle=1') !== -1;
var _bundleOk = false;
if (_useBundle) {
try { await _load('/static/dist/main.min.js'); _bundleOk = true; }
catch (_) { _bundleOk = false; } // not built / missing → fall back to per-file
}
if (!_bundleOk) await _loadBatch(_featureFiles);
// D — init (needs everything above to be defined)
await _load('/static/js/init.js');
// F — navigation (must run before revealing UI so the correct section shows)
await _load('/static/nav.js');
// ── Remove skeleton, reveal sections — as soon as nav is ready ──────────
var sk = document.getElementById('app-skeleton');
if (sk) {
sk.classList.add('sk-hidden');
setTimeout(function () { if (sk.parentNode) sk.parentNode.removeChild(sk); }, 300);
}
document.querySelectorAll('.page-section').forEach(function (s) {
s.style.display = '';
});
// ── i18n: add the language picker and translate the (now fully-rendered) UI ──
if (window.initLangPicker) window.initLangPicker();
if (window.applyI18n) window.applyI18n(document);
if (window.initAccessibilityEnhancements) window.initAccessibilityEnhancements();
// E — post-init modules load in background AFTER the UI is already visible.
// Engines, AI backends, and conversation load while the user browses other tabs.
_loadBatch([
'/static/js/engines.js',
'/static/js/ai-backends.js',
'/static/js/conversation.js',
]);
})();