From a344a13863507dd803a0f7c05c820dfa05add987 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:31:37 -0800 Subject: [PATCH 01/10] Move AbortController reset to function --- javascript/gallery.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index fb81bb58a..aa1471c98 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -24,6 +24,13 @@ const el = { const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; +function resetController(reason) { + maintenanceController.abort(reason); + const controller = new AbortController(); + maintenanceController = controller; + return controller; +} + function getVisibleGalleryFiles() { if (!el.files) return []; return Array.from(el.files.children).filter((node) => node.name && node.offsetParent); @@ -1049,9 +1056,8 @@ async function fetchFilesHT(evt, controller) { async function fetchFilesWS(evt) { // fetch file-by-file list over websockets if (!url) return; - const controller = new AbortController(); // Only called here because fetchFilesHT isn't called directly - maintenanceController.abort('Gallery update'); // Abort previous controller - maintenanceController = controller; // Point to new controller for next time + // Abort previous controller and point to new controller for next time + const controller = resetController('Gallery update'); // Called here because fetchFilesHT isn't called directly galleryHashes.clear(); // Must happen AFTER the AbortController steps galleryProgressBar.clear(); resetGallerySelection(); From 2fae55a7f99161e9a93ef4ada6da23e829051f62 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:38:24 -0800 Subject: [PATCH 02/10] Initial thumbnail cache clearing setup --- javascript/gallery.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index aa1471c98..73aea7dd2 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -2,6 +2,7 @@ let ws; let url; let currentImage = null; +let currentGalleryFolder = null; let pruneImagesTimer; let outstanding = 0; let lastSort = 0; @@ -959,9 +960,10 @@ const maintenanceQueue = new SimpleFunctionQueue('Maintenance'); * @param {string} folder - Folder to clean * @param {number} imgCount - Expected number of images in gallery * @param {AbortController} controller - AbortController that's handling this task + * @param {boolean} force - Force full cleanup of the folder */ -async function thumbCacheCleanup(folder, imgCount, controller) { - if (!opts.browser_cache) return; +async function thumbCacheCleanup(folder, imgCount, controller, force = false) { + if (!opts.browser_cache && !force) return; try { if (typeof folder !== 'string' || typeof imgCount !== 'number') { throw new Error('Function called with invalid arguments'); @@ -978,14 +980,14 @@ async function thumbCacheCleanup(folder, imgCount, controller) { callback: async () => { log(`Thumbnail DB cleanup: Checking if "${folder}" needs cleaning`); const t0 = performance.now(); - const staticGalleryHashes = new Set(galleryHashes); // External context should be safe since this function run is guarded by AbortController/AbortSignal in the SimpleFunctionQueue + const staticGalleryHashes = new Set(galleryHashes.values()); // External context should be safe since this function run is guarded by AbortController/AbortSignal in the SimpleFunctionQueue const cachedHashesCount = await idbCount(folder) .catch((e) => { error(`Thumbnail DB cleanup: Error when getting entry count for "${folder}".`, e); return Infinity; // Forces next check to fail if something went wrong }); const cleanupCount = cachedHashesCount - staticGalleryHashes.size; - if (cleanupCount < 500 || !Number.isFinite(cleanupCount)) { + if (!force && (cleanupCount < 500 || !Number.isFinite(cleanupCount))) { // Don't run when there aren't many excess entries return; } @@ -1019,6 +1021,20 @@ async function thumbCacheCleanup(folder, imgCount, controller) { }); } +async function addCacheClearButton() { + const btn = document.createElement('button'); + btn.innerText = 'Clear Folder Thumbnails (double click)'; + btn.addEventListener('dblclick', () => { + if (!currentGalleryFolder) return; + const controller = resetController('Clearing thumbnails'); + galleryHashes.clear(); + galleryProgressBar.clear(); + resetGallerySelection(); + thumbCacheCleanup(currentGalleryFolder, 0, controller, true); + }); + el.files.insertAdjacentElement('afterend', btn); +} + async function fetchFilesHT(evt, controller) { const t0 = performance.now(); const fragment = document.createDocumentFragment(); @@ -1074,6 +1090,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets return; } log(`gallery: connected=${wsConnected} state=${ws?.readyState} url=${ws?.url}`); + currentGalleryFolder = evt.target.name; if (!wsConnected) { await fetchFilesHT(evt, controller); // fallback to http return; @@ -1184,6 +1201,7 @@ async function initGallery() { // triggered on gradio change to monitor when ui updateGalleryStyles(); injectGalleryStatusCSS(); setOverlayAnimation(); + addCacheClearButton(); const progress = gradioApp().getElementById('tab-gallery-progress'); if (progress) { galleryProgressBar.attachTo(progress); From cb0aa2fb97e2f27435b54bbeece42a798f874a27 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:45:04 -0800 Subject: [PATCH 03/10] Update layout --- javascript/gallery.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 73aea7dd2..004c3208b 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -1022,7 +1022,10 @@ async function thumbCacheCleanup(folder, imgCount, controller, force = false) { } async function addCacheClearButton() { + const div = document.createElement('div'); + div.style.marginBlock = '0.5rem'; const btn = document.createElement('button'); + btn.style.cssText = 'margin: auto; display: block;'; btn.innerText = 'Clear Folder Thumbnails (double click)'; btn.addEventListener('dblclick', () => { if (!currentGalleryFolder) return; @@ -1032,7 +1035,8 @@ async function addCacheClearButton() { resetGallerySelection(); thumbCacheCleanup(currentGalleryFolder, 0, controller, true); }); - el.files.insertAdjacentElement('afterend', btn); + div.append(btn); + el.files.insertAdjacentElement('afterend', div); } async function fetchFilesHT(evt, controller) { From 849f04530154125d1db3db4b747fb69dda6e9717 Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Wed, 21 Jan 2026 05:45:43 -0800 Subject: [PATCH 04/10] Clear gallery image list when running --- javascript/gallery.js | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/gallery.js b/javascript/gallery.js index 004c3208b..1621ef10f 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -1033,6 +1033,7 @@ async function addCacheClearButton() { galleryHashes.clear(); galleryProgressBar.clear(); resetGallerySelection(); + el.files.innerHTML = ''; thumbCacheCleanup(currentGalleryFolder, 0, controller, true); }); div.append(btn); From 603560c079ea68cf9f78e04f3c63e0cea900537b Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 24 Jan 2026 13:35:04 -0800 Subject: [PATCH 05/10] Move setup, update style --- javascript/gallery.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 1621ef10f..ceef2a570 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -21,6 +21,7 @@ const el = { search: undefined, status: undefined, btnSend: undefined, + clearCache: undefined, }; const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr']; @@ -1023,7 +1024,7 @@ async function thumbCacheCleanup(folder, imgCount, controller, force = false) { async function addCacheClearButton() { const div = document.createElement('div'); - div.style.marginBlock = '0.5rem'; + div.style.cssText = 'margin-top: 1.5rem; margin-bottom: 0.5rem;'; const btn = document.createElement('button'); btn.style.cssText = 'margin: auto; display: block;'; btn.innerText = 'Clear Folder Thumbnails (double click)'; @@ -1038,6 +1039,7 @@ async function addCacheClearButton() { }); div.append(btn); el.files.insertAdjacentElement('afterend', div); + el.clearCache = div; } async function fetchFilesHT(evt, controller) { @@ -1096,6 +1098,9 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets } log(`gallery: connected=${wsConnected} state=${ws?.readyState} url=${ws?.url}`); currentGalleryFolder = evt.target.name; + if (!el.clearCache) { + addCacheClearButton(); + } if (!wsConnected) { await fetchFilesHT(evt, controller); // fallback to http return; @@ -1206,7 +1211,6 @@ async function initGallery() { // triggered on gradio change to monitor when ui updateGalleryStyles(); injectGalleryStatusCSS(); setOverlayAnimation(); - addCacheClearButton(); const progress = gradioApp().getElementById('tab-gallery-progress'); if (progress) { galleryProgressBar.attachTo(progress); From 5e8ea5217728b129e329716bc5d115045f12ecbe Mon Sep 17 00:00:00 2001 From: awsr <43862868+awsr@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:10:26 -0800 Subject: [PATCH 06/10] Toggle visibility from settings --- javascript/gallery.js | 9 ++++++++- modules/shared.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index ceef2a570..266ce8f0d 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -1022,7 +1022,7 @@ async function thumbCacheCleanup(folder, imgCount, controller, force = false) { }); } -async function addCacheClearButton() { +function addCacheClearButton() { // Don't use async const div = document.createElement('div'); div.style.cssText = 'margin-top: 1.5rem; margin-bottom: 0.5rem;'; const btn = document.createElement('button'); @@ -1042,6 +1042,12 @@ async function addCacheClearButton() { el.clearCache = div; } +async function updateGalleryAdvDisplay(newval, oldval = undefined) { + if (el.clearCache) { + el.clearCache.style.display = newval ? 'block' : 'none'; + } +} + async function fetchFilesHT(evt, controller) { const t0 = performance.now(); const fragment = document.createDocumentFragment(); @@ -1101,6 +1107,7 @@ async function fetchFilesWS(evt) { // fetch file-by-file list over websockets if (!el.clearCache) { addCacheClearButton(); } + updateGalleryAdvDisplay(opts.browser_gallery_advanced); if (!wsConnected) { await fetchFilesHT(evt, controller); // fallback to http return; diff --git a/modules/shared.py b/modules/shared.py index 0ac6511a1..c851f5b7c 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -533,6 +533,7 @@ options_templates.update(options_section(('saving-images', "Image Options"), { "image_sep_browser": OptionInfo("