// ── Library Characters / Cast workspace ───────────────────────────────────── // Loaded after library.js; uses its shared production helpers and exports // libraryRenderCharacters for the Library section router. // ── Characters / Cast ───────────────────────────────────────────────────────── async function libraryRenderCharacters() { const container = document.getElementById('lib-chars-list'); if (!container) return; // Fetch BEFORE touching the DOM: a transient failure here (e.g. the // server briefly unresponsive while a TTS container restart is in // flight) must never blank out an already-populated list — confirmed // live as "No characters yet" flashing up over a real, populated cast // while a bulk voice-design run elsewhere was mid-restart. Leave whatever // is already on screen alone and let the next real refresh fix it. let all = []; try { all = (typeof clGetAll === 'function') ? await clGetAll() : []; } catch (e) { console.warn('[characters] load failed', e); if (typeof toast === 'function') toast('Failed to load characters — keeping the current view', 'error'); return; } // Every action on this page (remove voice, auto-design, delete...) re-runs // this full rebuild via `refresh()`. Collapsing the container down to a // one-line loading placeholder mid-rebuild shrinks #main-content below the // user's current scroll position, which the browser clamps back to fit — // confirmed live as "the screen jumps to the top" after every single // action. Restore it once the real content is back, unless a caller // explicitly wants to jump to a specific book (see _libCharsScrollToBook // below), which takes priority over just staying put. const mainEl = document.getElementById('main-content'); const savedScrollTop = (!window._libCharsScrollToBook && mainEl) ? mainEl.scrollTop : null; container.innerHTML = '
No characters yet.
' + 'Open a book in Read Aloud, cast it as an audiobook, then click Cast Characters to generate character sheets — or import an existing cast from SillyTavern.
' + '| ' + th('alpha', 'Name') + th('gender', 'Geschlecht') + th('age', 'Alter', 'Estimated age') + th('lines', 'Zeilen', 'Anzahl Zeilen') + th('language', 'Sprache') + th('align', 'Gut/Böse', 'Moralische Gesinnung') + th('voice', 'Stimme') + ' | Tags | Book / Script | ' + ' |
|---|
Used in every voice design (and image) prompt for this book, so a fantasy story doesn\'t end up with 1920s-general portraits or English voices in a German book just because one character\'s own sheet was too sparse to tell.
' + '"' + escHtml(reuse.voiceId) + '" is already used for ' + escHtml(rec.name) + ' in "' + escHtml(reuse.book) + '". Reuse it for series consistency, or design a brand-new voice just for this book?
' + 'Edit the description, then generate a new voice from it. This replaces ' + (rec.voice ? 'the current voice' : 'this character’s voice') + '.
' + '' + '