// ── Rehearser voice design controls ───────────────────────────────────────── // Character development and backend selection resolve the shared Rehearser // state at interaction time, so they can be loaded as a leaf feature module. (function (root) { async function _castDevelop(sp, btn) { const script = $('reh-script-text')?.value.trim() || linesToScriptText(); if (!script) { toast('Load a script first', 'error'); return; } const c = rehState.cast[sp]; const orig = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' Developing…'; try { const r = await fetch('/api/analyze-characters', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ script, names: [sp], llm_url: $('reh-llm-url')?.value.trim() || rehDefaultLlmUrl(), model: $('reh-llm-model')?.value || '', language: c.lang || $('reh-design-lang')?.value || 'English', }), }); if (!r.ok) { const e = await r.json().catch(()=>({})); throw new Error(e.detail || r.statusText); } const info = (await r.json()).characters?.[0] || {}; if (info.gender) c.gender = String(info.gender).toUpperCase().charAt(0).replace(/[^MFN]/, 'N'); if (info.description) { c.soul = info.description; c.instruct = c.instruct || info.description; } renderCastList(); toast(`Developed ${sp}`, 'success'); } catch (e) { btn.disabled = false; btn.innerHTML = orig; toast('Develop failed: ' + e.message, 'error'); } } async function refreshRehBackends() { rehInitLlmField(); const sel = $('reh-backend-select'); if (!sel) return; let backends = typeof availableTtsBackends === 'function' ? availableTtsBackends() : []; if (!backends.length && typeof refreshTtsBackendAvailability === 'function') { await refreshTtsBackendAvailability().catch(() => {}); backends = typeof availableTtsBackends === 'function' ? availableTtsBackends() : []; } if (!backends.length && typeof _ttsBackends !== 'undefined' && Array.isArray(_ttsBackends)) backends = _ttsBackends; const prev = sel.value || rehState.backend; sel.innerHTML = backends.length ? backends.map(b => ``).join('') : ''; if (prev && [...sel.options].some(o => o.value === prev)) sel.value = prev; else { const preferred = ['fishspeech', 'voice_clone', 'customvoice', 'voice_design']; const pick = preferred.find(id => [...sel.options].some(o => o.value === id)); if (pick) sel.value = pick; } rehState.backend = sel.value || ''; _checkToneStyleSupport(); } window._ttsRefreshHooks = window._ttsRefreshHooks || []; window._ttsRefreshHooks.push(() => { const sel = $('reh-backend-select'); if (!sel) return; const backends = typeof availableTtsBackends === 'function' ? availableTtsBackends() : []; if (!backends.length) return; const prev = sel.value || rehState.backend; sel.innerHTML = backends.map(b => ``).join(''); if (prev && [...sel.options].some(o => o.value === prev)) sel.value = prev; else if (sel.options.length) sel.value = sel.options[0].value; rehState.backend = sel.value || ''; }); Object.assign(root, { _castDevelop, refreshRehBackends }); root.TTVC = root.TTVC || {}; root.TTVC.rehearser = root.TTVC.rehearser || {}; root.TTVC.rehearser.voiceDesign = { develop: _castDevelop, refreshBackends: refreshRehBackends }; })(window);