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.
|
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
|
- **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.
|
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
|
- **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.
|
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
|
- **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.
|
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
|
- [ ] **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.
|
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
|
- [ ] **M — Improve large-table performance.** Character productions and the flat voice library now paginate at 50
|
||||||
tables, update only affected rows, and disable preloading on low-memory devices.
|
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;
|
- [ ] **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.
|
add equivalent LRU cleanup for audio/image entries and object URLs.
|
||||||
- [ ] **M — Add constrained-device validation.** Test with Chrome CPU/memory throttling,
|
- [ ] **M — Add constrained-device validation.** Test with Chrome CPU/memory throttling,
|
||||||
|
|||||||
@ -1816,6 +1816,22 @@ function renderVoiceList() {
|
|||||||
return 0;
|
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();
|
updateLibraryInsights();
|
||||||
if (_libraryIssueFilter) {
|
if (_libraryIssueFilter) {
|
||||||
const note = document.createElement('div');
|
const note = document.createElement('div');
|
||||||
@ -1955,8 +1971,20 @@ function renderVoiceList() {
|
|||||||
if (window._voiceGroupByTag) renderVoicesByTag(list, filtered);
|
if (window._voiceGroupByTag) renderVoicesByTag(list, filtered);
|
||||||
else {
|
else {
|
||||||
const frag = document.createDocumentFragment();
|
const frag = document.createDocumentFragment();
|
||||||
filtered.forEach(v => frag.appendChild(makeVoiceRow(v)));
|
pageItems.forEach(v => frag.appendChild(makeVoiceRow(v)));
|
||||||
list.appendChild(frag);
|
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) {
|
if (_pendingSelectId) {
|
||||||
const wrap = list.querySelector(`[data-id="${CSS.escape(_pendingSelectId)}"]`);
|
const wrap = list.querySelector(`[data-id="${CSS.escape(_pendingSelectId)}"]`);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user