tts-voice-creator-clone-and.../static/loader.js
mARTin-B78 b33829ded5 Add skeleton loading, self-host assets, fix LLM empty response
Perceived startup speed:
- index.html: animated shimmer skeleton (header + toolbar + 8 voice cards)
  visible immediately; fades out when loader.js finishes
- WaveSurfer (57KB) and MDI icon font (394KB woff2) now served from
  static/vendor/ — removes 3 render-blocking external requests from <head>
- Flag-icons CSS loaded async (rel=preload onload trick) — non-blocking

loader.js:
- Fade out skeleton + remove from DOM (300ms transition)
- Reveal page-sections after JS finishes loading

Conversation playground:
- Qwen3 thinking mode fix: fall back to delta.reasoning_content when
  delta.content is empty so think-only LLM turns produce visible output
- Better error message with /no-think hint when LLM returns empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:28:42 +02:00

107 lines
4.1 KiB
JavaScript

(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',
];
// ── Fetch app version once for JS cache-busting ──────────────────────────
// JS files are served with ?v=<version> so the browser can cache them
// aggressively. Sections use Date.now() to always get fresh HTML.
var _appVersion = 'dev';
try {
var _vr = await fetch('/api/version');
var _vd = await _vr.json();
_appVersion = _vd.version || _appVersion;
} catch (_) {}
// ── 1. Fetch all section HTML partials in parallel ────────────────────────
var _ts = Date.now(); // sections always fresh during a session
await Promise.all(SECTIONS.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);
var el = document.getElementById(id);
if (el) el.innerHTML = await res.text();
} catch (e) {
console.error('[loader] section', id, 'failed:', e.message);
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>';
}
}));
// ── 2. 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.
//
// Batch order:
// A) utils.js — defines $, toast, escHtml; needed by everything
// B) settings.js — declares _appSettings; needed before features read it
// C) Feature modules — parallel fetch + ordered execute
// D) init.js — calls loadSettings() + initBenchmarkSampleControls()
// E) Post-init modules — parallel fetch + ordered execute
// F) nav.js — navigation overrides; must be last
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) {
// Append all <script async=false> at once → browser fetches all in parallel
// and executes them in the order they were appended.
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');
// 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 = '';
});
})();