(async function () { 'use strict'; const SECTIONS = [ 's-voices', 's-clone', 's-design', 's-studio', 's-tryout', 's-performance', 's-routing', 's-connect', 's-settings', 's-llms', 's-conversation', ]; // Determine which section to show first (same logic as nav.js) var _savedSection = localStorage.getItem('ttsvc_section'); var _activeSection = (_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 = '

Failed to load section ' + _activeSection + ': ' + e.message + '

'; } // ── 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 = '

Failed to load section ' + id + ': ' + e.message + '

'; } }) ); // ── 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/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 (9 files fetched in parallel, executed in order) await _loadBatch([ '/static/js/voice-inspector.js', '/static/js/voice-sources.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/benchmark.js', '/static/js/stt.js', ]); // D — init (needs everything above to be defined) await _load('/static/js/init.js'); // E — post-init modules (4 files fetched in parallel, executed in order) await _loadBatch([ '/static/js/engines.js', '/static/js/ai-backends.js', '/static/js/generation.js', '/static/js/conversation.js', ]); // F — navigation await _load('/static/nav.js'); // ── Remove skeleton, reveal sections ──────────────────────────────────── 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 = ''; }); })();