From f5ff9147653699c423402ff47fde08ba8aa57c69 Mon Sep 17 00:00:00 2001 From: Chris Dumas Date: Wed, 5 Aug 2026 11:47:14 +0000 Subject: [PATCH] Fix save image external folder feedback --- dumas_image_nodes.py | 8 ++++++ js/save_image/index.js | 45 +++++++++++++++++++-------------- tests/test_dumas_image_nodes.py | 18 +++++++++++++ 3 files changed, 52 insertions(+), 19 deletions(-) diff --git a/dumas_image_nodes.py b/dumas_image_nodes.py index cf94380..fa096d3 100644 --- a/dumas_image_nodes.py +++ b/dumas_image_nodes.py @@ -365,6 +365,14 @@ class DumasSaveImageNode: "type": "output", } ) + else: + ui_images.append( + { + "filename": filename, + "subfolder": frame_dir.replace("\\", "/"), + "type": "external", + } + ) return {"ui": {"images": ui_images}} diff --git a/js/save_image/index.js b/js/save_image/index.js index 3811426..48291d3 100644 --- a/js/save_image/index.js +++ b/js/save_image/index.js @@ -135,10 +135,16 @@ function imageUrl(meta) { return `/view?filename=${encodeURIComponent(meta.filename)}&type=${encodeURIComponent(meta.type)}&subfolder=${encodeURIComponent(meta.subfolder || "")}&t=${Date.now()}`; } +function canPreview(meta) { + return meta?.type === "output"; +} + function updateActionState(node, ui) { const hasImage = !!(node._dsiImages && node._dsiImages.length); - ui.openButton.disabled = !hasImage; - ui.copyButton.disabled = !hasImage; + const current = hasImage ? node._dsiImages[node._dsiImageIndex || 0] : null; + const previewable = canPreview(current); + ui.openButton.disabled = !previewable; + ui.copyButton.disabled = !previewable; ui.prevButton.classList.toggle("show", !!(hasImage && node._dsiImages.length > 1)); ui.nextButton.classList.toggle("show", !!(hasImage && node._dsiImages.length > 1)); ui.count.style.display = hasImage && node._dsiImages.length > 1 ? "block" : "none"; @@ -176,14 +182,24 @@ function renderPreview(node, ui) { } const current = images[currentIndex]; - ui.view.classList.add("has"); - ui.placeholder.style.display = "none"; - ui.img.style.display = "block"; - ui.img.src = imageUrl(current); ui.count.textContent = `${currentIndex + 1} / ${images.length}`; - ui.info.textContent = images.length > 1 - ? `Showing saved image ${currentIndex + 1} of ${images.length}` - : "Showing latest saved image"; + if (canPreview(current)) { + ui.view.classList.add("has"); + ui.placeholder.style.display = "none"; + ui.img.style.display = "block"; + ui.img.src = imageUrl(current); + ui.info.textContent = images.length > 1 + ? `Showing saved image ${currentIndex + 1} of ${images.length}` + : "Showing latest saved image"; + } else { + ui.view.classList.remove("has"); + ui.img.style.display = "none"; + ui.placeholder.style.display = "flex"; + ui.placeholder.textContent = "Saved outside ComfyUI output. Preview is unavailable here."; + ui.info.textContent = current?.subfolder + ? `Saved to ${current.subfolder}/${current.filename}` + : `Saved to ${current.filename}`; + } updateActionState(node, ui); } @@ -324,16 +340,7 @@ function createRoot(node) { renderPreview(node, ui); }); browseButton.addEventListener("click", async () => { - if (typeof window.showDirectoryPicker !== "function") return; - try { - const handle = await window.showDirectoryPicker(); - if (!handle) return; - folderInput.value = handle.name || folderInput.value; - setWidgetValue(node, "folder", folderInput.value); - renderPreview(node, ui); - } catch (_error) { - // Ignore picker cancel/failure. - } + ui.info.textContent = "Browser folder picking cannot provide a real filesystem path here. Paste the full path into the folder field."; }); fmtPng.addEventListener("click", () => { ui.format = "png"; diff --git a/tests/test_dumas_image_nodes.py b/tests/test_dumas_image_nodes.py index 070e5ea..fc2d9ce 100644 --- a/tests/test_dumas_image_nodes.py +++ b/tests/test_dumas_image_nodes.py @@ -167,6 +167,24 @@ class DumasImageNodeTests(unittest.TestCase): self.assertEqual(result["ui"]["images"][0]["type"], "output") self.assertTrue(result["ui"]["images"][0]["filename"].endswith(".jpg")) + def test_save_image_returns_ui_entries_for_external_folder(self): + node = self.image_nodes.DumasSaveImageNode() + image = FakeTensorBatch() + external_dir = tempfile.mkdtemp(prefix="dumas-image-node-external-") + + result = node.save_images( + images=image, + folder=external_dir, + pattern="external_%counter%", + format="png", + quality=100, + embed_workflow=False, + save_on_run=True, + ) + + self.assertEqual(result["ui"]["images"][0]["type"], "external") + self.assertEqual(result["ui"]["images"][0]["subfolder"], external_dir.replace("\\", "/")) + def test_save_image_skips_when_save_is_disabled(self): node = self.image_nodes.DumasSaveImageNode() image = FakeTensorBatch()