75 lines
3.6 KiB
JavaScript
75 lines
3.6 KiB
JavaScript
// ── 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 = '<span class="reh-imsdb-spinner"></span> 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 => `<option value="${escHtml(b.id)}">${escHtml(b.label)}</option>`).join('')
|
|
: '<option value="">No backend available</option>';
|
|
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 => `<option value="${escHtml(b.id)}">${escHtml(b.label)}</option>`).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);
|