perf: paginate large character productions
This commit is contained in:
parent
760a3640ba
commit
b5d2e773d8
@ -41,6 +41,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
and older unmarked image pairs are visibly flagged for regeneration.
|
||||
- **Visual Identity Fingerprints.** Consistent pairs now record a compact fingerprint of canonical appearance fields;
|
||||
changing those fields causes the existing pair to be flagged stale.
|
||||
- **Character Table Pagination.** Productions now render 50 character cards/rows at a time with an explicit Show more
|
||||
control, keeping sorting and record lookups intact while reducing DOM and image work for large casts.
|
||||
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||
rejection and oversized JSON request rejection.
|
||||
- **Navigation Reliability.** Rehearser phase requests now survive the feature-module initialization boundary.
|
||||
|
||||
2
TODO.md
2
TODO.md
@ -53,7 +53,7 @@ the first block before investing in architecture or polish.
|
||||
cross-record validation so mismatched profile/reference files are detected even when both are current-version.
|
||||
- [ ] **M — Add bounded duplicate-generation protection.** Consistent portrait/reference jobs now deduplicate
|
||||
repeated requests with `Idempotency-Key`; extend the same bounded contract to voice and casting jobs.
|
||||
- [ ] **M — Improve large-table performance.** Virtualize/paginate character and voice
|
||||
- [ ] **M — Improve large-table performance.** Character productions now paginate at 50 visible rows; paginate voice
|
||||
tables, update only affected rows, and disable preloading on low-memory devices.
|
||||
- [ ] **M — Add explicit cache invalidation and eviction.** Fish.audio search responses now have bounded eviction;
|
||||
add equivalent LRU cleanup for audio/image entries and object URLs.
|
||||
|
||||
@ -182,6 +182,15 @@ async function libraryRenderCharacters() {
|
||||
const narrIdx = chars.findIndex(function (r) { return String(r.name || '').trim().toLowerCase() === 'narrator'; });
|
||||
if (narrIdx > 0) chars.unshift(chars.splice(narrIdx, 1)[0]);
|
||||
|
||||
// Keep large productions responsive: the complete record set remains in
|
||||
// memory for sorting, relationship lookups and bulk operations, while
|
||||
// only one 50-row page is put into the DOM at a time.
|
||||
const pageKey = 'ttsvc_libchars_page::' + book;
|
||||
const pageSize = 50;
|
||||
const requestedPage = Math.max(1, parseInt(localStorage.getItem(pageKey) || '1', 10) || 1);
|
||||
const visibleCount = Math.min(chars.length, requestedPage * pageSize);
|
||||
const visibleChars = chars.slice(0, visibleCount);
|
||||
|
||||
const cov = libBookCover(book);
|
||||
const prod = document.createElement('div');
|
||||
prod.className = 'lib-chars-production';
|
||||
@ -206,7 +215,7 @@ async function libraryRenderCharacters() {
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-reh-btn" data-book="' + escHtml(book) + '" title="Open in Script Rehearsal"><span class="mdi mdi-theater"></span> Rehearse</button>'
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-read-btn" data-book="' + escHtml(book) + '" title="Open in Read Aloud"><span class="mdi mdi-book-open-page-variant-outline"></span> Read Aloud</button>'
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-imp-btn" data-book="' + escHtml(book) + '" title="Import SillyTavern cards into this production"><span class="mdi mdi-import"></span></button>'
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-select-all-btn" data-book="' + escHtml(book) + '" title="Select or deselect every character in this production"><span class="mdi mdi-checkbox-multiple-marked-outline"></span> Select all</button>'
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-select-all-btn" data-book="' + escHtml(book) + '" title="Select or deselect every visible character in this production"><span class="mdi mdi-checkbox-multiple-marked-outline"></span> Select visible</button>'
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-bulk-voice-btn" data-book="' + escHtml(book) + '" title="Auto-assign an existing library voice to every checked character" disabled><span class="mdi mdi-account-voice"></span> Auto-assign selected (<span class="lib-chars-bulk-count">0</span>)</button>'
|
||||
+ '<button class="btn-secondary btn-sm lib-chars-bulk-design-btn" data-book="' + escHtml(book) + '" title="Design and save a brand-new voice for every checked character, from their Voice Design Prompt" disabled><span class="mdi mdi-creation"></span> Auto-design voices (<span class="lib-chars-bulk-count-design">0</span>)</button>'
|
||||
+ '<select class="lib-chars-image-provider" title="Image generation engine for this run — overrides the Active Provider in Settings just for this run">'
|
||||
@ -226,12 +235,20 @@ async function libraryRenderCharacters() {
|
||||
+ '</div></div>'
|
||||
+ '<div class="lib-chars-prod-body">'
|
||||
+ (viewMode === 'table'
|
||||
? _charsTableHtml(chars, sortMode, sortDir)
|
||||
? _charsTableHtml(visibleChars, sortMode, sortDir)
|
||||
: '<div class="lib-chars-grid" id="lib-chars-grid-' + encodeURIComponent(book).replace(/%/g,'_') + '">'
|
||||
+ chars.map(function (rec) { return _charCardHtml(rec, chars); }).join('')
|
||||
+ visibleChars.map(function (rec) { return _charCardHtml(rec, chars); }).join('')
|
||||
+ '</div>')
|
||||
+ (visibleCount < chars.length
|
||||
? '<button type="button" class="btn-secondary btn-sm lib-chars-load-more" style="margin:12px 0">Show more characters (' + visibleCount + ' / ' + chars.length + ')</button>'
|
||||
: '')
|
||||
+ '</div>';
|
||||
|
||||
prod.querySelector('.lib-chars-load-more')?.addEventListener('click', function () {
|
||||
localStorage.setItem(pageKey, String(requestedPage + 1));
|
||||
libraryRenderCharacters();
|
||||
});
|
||||
|
||||
prod.querySelector('.lib-chars-prod-collapse-btn').addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
const collapsed = prod.classList.toggle('lib-chars-production-collapsed');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user