perf: paginate large voice libraries
This commit is contained in:
parent
8235b36afd
commit
e52d462dac
@ -43,6 +43,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
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.
|
||||
- **Voice Table Pagination.** The voice library now renders up to 50 sorted rows initially and adds a Show more
|
||||
control for larger result sets, while retaining the full dataset for filtering and sorting.
|
||||
- **Upload Filename Boundaries.** Audio, STT, route-sound and picture uploads now reject path-like or control-character
|
||||
filenames before deriving extensions; voice ZIP imports also reject absolute, traversal and malformed archive paths.
|
||||
- **Security Regression Coverage.** Added a Python test gate for character metadata validation, private-download URL
|
||||
|
||||
4
TODO.md
4
TODO.md
@ -53,8 +53,8 @@ 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.** Character productions now paginate at 50 visible rows; paginate voice
|
||||
tables, update only affected rows, and disable preloading on low-memory devices.
|
||||
- [ ] **M — Improve large-table performance.** Character productions and the flat voice library now paginate at 50
|
||||
visible rows; updating only affected rows and disabling preloading on low-memory devices remain.
|
||||
- [ ] **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.
|
||||
- [ ] **M — Add constrained-device validation.** Test with Chrome CPU/memory throttling,
|
||||
|
||||
@ -1816,6 +1816,22 @@ function renderVoiceList() {
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Keep the complete filtered/sorted list in memory, but avoid creating a
|
||||
// large DOM (and hundreds of audio/image nodes) for big libraries.
|
||||
const pageSignature = JSON.stringify([
|
||||
cat, showDisabled, window._voiceGroupFilter || '', window._voiceTagFilter || '',
|
||||
_libraryIssueFilter || '', _sortField, _sortDir, !!window._voiceTableView, _libraryFilters,
|
||||
]);
|
||||
if (window._voicePageSignature !== pageSignature) {
|
||||
window._voicePageSignature = pageSignature;
|
||||
window._voicePage = 1;
|
||||
}
|
||||
const totalFiltered = filtered.length;
|
||||
const pageSize = 50;
|
||||
const page = Math.max(1, Number(window._voicePage) || 1);
|
||||
const pageEnd = Math.min(totalFiltered, page * pageSize);
|
||||
const pageItems = filtered.slice(0, pageEnd);
|
||||
|
||||
updateLibraryInsights();
|
||||
if (_libraryIssueFilter) {
|
||||
const note = document.createElement('div');
|
||||
@ -1955,8 +1971,20 @@ function renderVoiceList() {
|
||||
if (window._voiceGroupByTag) renderVoicesByTag(list, filtered);
|
||||
else {
|
||||
const frag = document.createDocumentFragment();
|
||||
filtered.forEach(v => frag.appendChild(makeVoiceRow(v)));
|
||||
pageItems.forEach(v => frag.appendChild(makeVoiceRow(v)));
|
||||
list.appendChild(frag);
|
||||
if (pageEnd < totalFiltered) {
|
||||
const more = document.createElement('button');
|
||||
more.type = 'button';
|
||||
more.className = 'btn-secondary vl-load-more';
|
||||
more.style.margin = '12px 0';
|
||||
more.textContent = `Show more voices (${pageEnd} / ${totalFiltered})`;
|
||||
more.addEventListener('click', () => {
|
||||
window._voicePage = page + 1;
|
||||
renderVoiceList();
|
||||
});
|
||||
list.appendChild(more);
|
||||
}
|
||||
}
|
||||
if (_pendingSelectId) {
|
||||
const wrap = list.querySelector(`[data-id="${CSS.escape(_pendingSelectId)}"]`);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user