From 6ad69f7d4eb40090e33942c9424792c3c1e5a052 Mon Sep 17 00:00:00 2001 From: Juan Treminio Date: Sat, 2 May 2026 13:05:00 -0500 Subject: [PATCH 1/2] Allow saving generated ControlNet previews --- src/Text2Image/T2IEngine.cs | 12 +++ src/wwwroot/js/genpage/gentab/params.js | 108 +++++++++++++++++++++--- 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/src/Text2Image/T2IEngine.cs b/src/Text2Image/T2IEngine.cs index c0fc4d84b..75bc1c7d0 100644 --- a/src/Text2Image/T2IEngine.cs +++ b/src/Text2Image/T2IEngine.cs @@ -289,6 +289,18 @@ string format(long t) backend?.Dispose(); return; } + if (user_input.Get(T2IParamTypes.ControlNetPreviewOnly, false)) + { + string controlNetInputFolder = $"inputs/_comfy{backend.Data.ID}/"; + if (!UserImageHistoryHelper.SharedSpecialFolders.ContainsKey(controlNetInputFolder) && backend.Data.Parent is not null) + { + controlNetInputFolder = $"inputs/_comfy{backend.Data.Parent.ID}/"; + } + if (UserImageHistoryHelper.SharedSpecialFolders.ContainsKey(controlNetInputFolder)) + { + user_input.ExtraMeta["controlnet_preview_input_folder"] = controlNetInputFolder; + } + } try { claim.Extend(liveGens: 1); diff --git a/src/wwwroot/js/genpage/gentab/params.js b/src/wwwroot/js/genpage/gentab/params.js index 80aa5341d..236915c81 100644 --- a/src/wwwroot/js/genpage/gentab/params.js +++ b/src/wwwroot/js/genpage/gentab/params.js @@ -814,7 +814,7 @@ function genInputs(delay_final = false) { let controlnetGroup = document.getElementById('input_group_content_controlnet'); if (controlnetGroup) { let firstGroup = controlnetGroup.querySelector('.input-group'); - let buttonDiv = createDiv(`controlnet_button_preview`, null, ``); + let buttonDiv = createDiv(`controlnet_button_preview`, null, ` `); if (firstGroup) { controlnetGroup.insertBefore(buttonDiv, firstGroup); } @@ -1451,8 +1451,17 @@ function debugShowHiddenParams() { } } +/** Clears any shown or stored ControlNet preview. */ +function controlnetClearPreview(previewArea) { + for (let result of previewArea.querySelectorAll('.controlnet-preview-result, .controlnet-save-result')) { + result.remove(); + } + delete previewArea.dataset.controlnetPreviewImage; + delete previewArea.dataset.controlnetPreviewMetadata; +} + /** Loads and shows a preview of ControlNet preprocessing to the user. */ -function controlnetShowPreview() { +function controlnetShowPreview(callback) { let toggler = getRequiredElementById('input_group_content_controlnet_toggle'); if (!toggler.checked) { toggler.checked = true; @@ -1464,18 +1473,12 @@ function controlnetShowPreview() { return; } let previewArea = getRequiredElementById('controlnet_button_preview'); - let clearPreview = () => { - let lastResult = previewArea.querySelector('.controlnet-preview-result'); - if (lastResult) { - lastResult.remove(); - } - }; - clearPreview(); + controlnetClearPreview(previewArea); let imgInput = getRequiredElementById('input_controlnetimageinput'); if (!imgInput || !imgInput.dataset.filedata) { let secondaryImageOption = getRequiredElementById('input_initimage'); if (!secondaryImageOption || !secondaryImageOption.dataset.filedata) { - clearPreview(); + controlnetClearPreview(previewArea); previewArea.append(createDiv(null, 'controlnet-preview-result', 'Must select an image.')); return; } @@ -1509,12 +1512,95 @@ function controlnetShowPreview() { imgElem.src = data.image; resultBox.append(imgElem); } - clearPreview(); + controlnetClearPreview(previewArea); + previewArea.dataset.controlnetPreviewImage = data.image; + previewArea.dataset.controlnetPreviewMetadata = data.metadata ?? ''; previewArea.append(resultBox); + if (callback) { + callback(data); + } }); }); } +/** Gets the target input folder for saved ControlNet previews. */ +function controlnetGetPreviewInputFolder(metadata) { + if (metadata) { + try { + let parsed = JSON.parse(metadata); + let extraData = parsed.sui_extra_data; + if (extraData && extraData.controlnet_preview_input_folder) { + return `${extraData.controlnet_preview_input_folder}`.replace(/\/$/, ''); + } + } + catch (ex) { + } + } + let exactBackendInput = document.getElementById('input_exactbackendid'); + let exactBackendToggle = document.getElementById('input_exactbackendid_toggle'); + if (exactBackendInput && (!exactBackendToggle || exactBackendToggle.checked)) { + let parsedId = parseInt(exactBackendInput.value); + if (!Number.isNaN(parsedId)) { + return `inputs/_comfy${parsedId}`; + } + } + return 'inputs'; +} + +/** Gets a timestamped base file name for a saved ControlNet preview. */ +function controlnetGetPreviewSaveName() { + let now = new Date(); + let pad = (val) => `${val}`.padStart(2, '0'); + let millis = `${now.getMilliseconds()}`.padStart(3, '0'); + return `controlnet-preview-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}-${millis}`; +} + +/** Saves a ControlNet preview data URL through the existing history save route. */ +function controlnetSavePreviewDataToServer(image, metadata) { + let targetFolder = `${controlnetGetPreviewInputFolder(metadata)}/controlnet`; + let data = { + image: image, + ['Override Outpath Format']: `${targetFolder}/${controlnetGetPreviewSaveName()}`.replaceAll('[', '') + }; + let imageFormatsByMimeType = { 'image/png': 'PNG', 'image/jpeg': 'JPG', 'image/webp': 'WEBP' }; + let mimeType = guessMimeTypeForExtension(image); + if (mimeType in imageFormatsByMimeType) { + data['Image Format'] = imageFormatsByMimeType[mimeType]; + } + genericRequest('AddImageToHistory', data, res => { + if (inputBrowserHelper.inputImageBrowser) { + inputBrowserHelper.inputImageBrowser.lightRefresh(); + } + let previewArea = getRequiredElementById('controlnet_button_preview'); + let oldSaveResult = previewArea.querySelector('.controlnet-save-result'); + if (oldSaveResult) { + oldSaveResult.remove(); + } + let saveResult = createDiv(null, 'controlnet-save-result modal_success_bottom', 'Saved ControlNet preview.'); + previewArea.append(saveResult); + setTimeout(() => { + saveResult.remove(); + }, 5000); + }); +} + +/** Saves the current ControlNet preview, generating it first if needed. */ +function controlnetSavePreviewToServer() { + let previewArea = getRequiredElementById('controlnet_button_preview'); + let image = previewArea.dataset.controlnetPreviewImage; + let metadata = previewArea.dataset.controlnetPreviewMetadata ?? ''; + if (image) { + controlnetSavePreviewDataToServer(image, metadata); + return; + } + controlnetShowPreview(data => { + if (!data || !data.image) { + return; + } + controlnetSavePreviewDataToServer(data.image, data.metadata ?? ''); + }); +} + /** Gets the parameter with a given ID, from either the current param set, or the raw set from server. If unavailable, returns null. */ function getParamById(id) { if (!gen_param_types) { From 1a2b5b31dbd22f5e7e4369b818c10bf1e29ca481 Mon Sep 17 00:00:00 2001 From: Juan Treminio Date: Wed, 6 May 2026 12:05:19 -0500 Subject: [PATCH 2/2] Save to root inputs/controlnet --- src/Text2Image/T2IEngine.cs | 12 ----- src/wwwroot/js/genpage/gentab/params.js | 72 ++++++------------------- 2 files changed, 15 insertions(+), 69 deletions(-) diff --git a/src/Text2Image/T2IEngine.cs b/src/Text2Image/T2IEngine.cs index 75bc1c7d0..c0fc4d84b 100644 --- a/src/Text2Image/T2IEngine.cs +++ b/src/Text2Image/T2IEngine.cs @@ -289,18 +289,6 @@ string format(long t) backend?.Dispose(); return; } - if (user_input.Get(T2IParamTypes.ControlNetPreviewOnly, false)) - { - string controlNetInputFolder = $"inputs/_comfy{backend.Data.ID}/"; - if (!UserImageHistoryHelper.SharedSpecialFolders.ContainsKey(controlNetInputFolder) && backend.Data.Parent is not null) - { - controlNetInputFolder = $"inputs/_comfy{backend.Data.Parent.ID}/"; - } - if (UserImageHistoryHelper.SharedSpecialFolders.ContainsKey(controlNetInputFolder)) - { - user_input.ExtraMeta["controlnet_preview_input_folder"] = controlNetInputFolder; - } - } try { claim.Extend(liveGens: 1); diff --git a/src/wwwroot/js/genpage/gentab/params.js b/src/wwwroot/js/genpage/gentab/params.js index 236915c81..ea22472e4 100644 --- a/src/wwwroot/js/genpage/gentab/params.js +++ b/src/wwwroot/js/genpage/gentab/params.js @@ -1451,15 +1451,6 @@ function debugShowHiddenParams() { } } -/** Clears any shown or stored ControlNet preview. */ -function controlnetClearPreview(previewArea) { - for (let result of previewArea.querySelectorAll('.controlnet-preview-result, .controlnet-save-result')) { - result.remove(); - } - delete previewArea.dataset.controlnetPreviewImage; - delete previewArea.dataset.controlnetPreviewMetadata; -} - /** Loads and shows a preview of ControlNet preprocessing to the user. */ function controlnetShowPreview(callback) { let toggler = getRequiredElementById('input_group_content_controlnet_toggle'); @@ -1473,12 +1464,18 @@ function controlnetShowPreview(callback) { return; } let previewArea = getRequiredElementById('controlnet_button_preview'); - controlnetClearPreview(previewArea); + let clearPreview = () => { + for (let result of previewArea.querySelectorAll('.controlnet-preview-result, .controlnet-save-result')) { + result.remove(); + } + delete previewArea.dataset.controlnetPreviewImage; + }; + clearPreview(); let imgInput = getRequiredElementById('input_controlnetimageinput'); if (!imgInput || !imgInput.dataset.filedata) { let secondaryImageOption = getRequiredElementById('input_initimage'); if (!secondaryImageOption || !secondaryImageOption.dataset.filedata) { - controlnetClearPreview(previewArea); + clearPreview(); previewArea.append(createDiv(null, 'controlnet-preview-result', 'Must select an image.')); return; } @@ -1512,9 +1509,8 @@ function controlnetShowPreview(callback) { imgElem.src = data.image; resultBox.append(imgElem); } - controlnetClearPreview(previewArea); + clearPreview(); previewArea.dataset.controlnetPreviewImage = data.image; - previewArea.dataset.controlnetPreviewMetadata = data.metadata ?? ''; previewArea.append(resultBox); if (callback) { callback(data); @@ -1523,54 +1519,17 @@ function controlnetShowPreview(callback) { }); } -/** Gets the target input folder for saved ControlNet previews. */ -function controlnetGetPreviewInputFolder(metadata) { - if (metadata) { - try { - let parsed = JSON.parse(metadata); - let extraData = parsed.sui_extra_data; - if (extraData && extraData.controlnet_preview_input_folder) { - return `${extraData.controlnet_preview_input_folder}`.replace(/\/$/, ''); - } - } - catch (ex) { - } - } - let exactBackendInput = document.getElementById('input_exactbackendid'); - let exactBackendToggle = document.getElementById('input_exactbackendid_toggle'); - if (exactBackendInput && (!exactBackendToggle || exactBackendToggle.checked)) { - let parsedId = parseInt(exactBackendInput.value); - if (!Number.isNaN(parsedId)) { - return `inputs/_comfy${parsedId}`; - } - } - return 'inputs'; -} - -/** Gets a timestamped base file name for a saved ControlNet preview. */ -function controlnetGetPreviewSaveName() { +/** Saves a ControlNet preview data URL through the existing history save route. */ +function controlnetSavePreviewDataToServer(image) { let now = new Date(); let pad = (val) => `${val}`.padStart(2, '0'); let millis = `${now.getMilliseconds()}`.padStart(3, '0'); - return `controlnet-preview-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}-${millis}`; -} - -/** Saves a ControlNet preview data URL through the existing history save route. */ -function controlnetSavePreviewDataToServer(image, metadata) { - let targetFolder = `${controlnetGetPreviewInputFolder(metadata)}/controlnet`; + let name = `controlnet-preview-${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}-${millis}`; let data = { image: image, - ['Override Outpath Format']: `${targetFolder}/${controlnetGetPreviewSaveName()}`.replaceAll('[', '') + ['Override Outpath Format']: `inputs/controlnet/${name}` }; - let imageFormatsByMimeType = { 'image/png': 'PNG', 'image/jpeg': 'JPG', 'image/webp': 'WEBP' }; - let mimeType = guessMimeTypeForExtension(image); - if (mimeType in imageFormatsByMimeType) { - data['Image Format'] = imageFormatsByMimeType[mimeType]; - } genericRequest('AddImageToHistory', data, res => { - if (inputBrowserHelper.inputImageBrowser) { - inputBrowserHelper.inputImageBrowser.lightRefresh(); - } let previewArea = getRequiredElementById('controlnet_button_preview'); let oldSaveResult = previewArea.querySelector('.controlnet-save-result'); if (oldSaveResult) { @@ -1588,16 +1547,15 @@ function controlnetSavePreviewDataToServer(image, metadata) { function controlnetSavePreviewToServer() { let previewArea = getRequiredElementById('controlnet_button_preview'); let image = previewArea.dataset.controlnetPreviewImage; - let metadata = previewArea.dataset.controlnetPreviewMetadata ?? ''; if (image) { - controlnetSavePreviewDataToServer(image, metadata); + controlnetSavePreviewDataToServer(image); return; } controlnetShowPreview(data => { if (!data || !data.image) { return; } - controlnetSavePreviewDataToServer(data.image, data.metadata ?? ''); + controlnetSavePreviewDataToServer(data.image); }); }