From defe95c0e80307349d6032fd523e481b7ce1b954 Mon Sep 17 00:00:00 2001 From: pingren <5123601+Pingren@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:40:33 +0800 Subject: [PATCH 01/21] refactor: rename comfyui-share --- js/{comfyui-share.js => comfyui-share-common.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename js/{comfyui-share.js => comfyui-share-common.js} (100%) diff --git a/js/comfyui-share.js b/js/comfyui-share-common.js similarity index 100% rename from js/comfyui-share.js rename to js/comfyui-share-common.js From f07fddcdad06306593ff908fff1402dbddb3b69e Mon Sep 17 00:00:00 2001 From: pingren <5123601+Pingren@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:14:10 +0800 Subject: [PATCH 02/21] feat: add ShareDialogChooser and OpenArtShareDialog basic UI --- js/comfyui-manager.js | 12 ++- js/comfyui-share-common.js | 192 ++++++++++++++++++++++++++++++++++++ js/comfyui-share-openart.js | 124 +++++++++++++++++++++++ 3 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 js/comfyui-share-openart.js diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 914dec96..492fd403 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes } from "./comfyui-share.js"; +import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes, ShareDialogChooser } from "./comfyui-share-common.js"; import { CustomNodesInstaller } from "./custom-nodes-downloader.js"; import { AlternativesInstaller } from "./a1111-alter-downloader.js"; import { SnapshotManager } from "./snapshot.js"; @@ -586,9 +586,13 @@ app.registerExtension({ const shareButton = document.createElement("button"); shareButton.textContent = "Share"; shareButton.onclick = () => { - if (!ShareDialog.instance) { - ShareDialog.instance = new ShareDialog(); + + if(!ShareDialogChooser.instance) { + ShareDialogChooser.instance = new ShareDialogChooser(); } + // TODO: DEV ONLY, remove this line + ShareDialogChooser.instance.show({ potential_outputs : [], potential_output_nodes: [] }); + return app.graphToPrompt().then(prompt => { // console.log({ prompt }) @@ -608,7 +612,7 @@ app.registerExtension({ return; } - ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); + ShareDialogChooser.instance.show({ potential_outputs, potential_output_nodes }); }); } // make the background color a gradient of blue to green diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index 9ea8aef0..48bf794a 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -1,6 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; +import { OpenArtShareDialog } from "./comfyui-share-openart.js"; export const SUPPORTED_OUTPUT_NODE_TYPES = [ "PreviewImage", @@ -149,7 +150,198 @@ export function parseURLPath(urlPath) { // Return the object with the parsed parameters return parsedParams; } +export class ShareDialogChooser extends ComfyDialog { + static instance = null; + constructor() { + super(); + this.element = $el("div.comfy-modal", { + parent: document.body, style: { + 'overflow-y': "auto", + } + }, + [$el("div.comfy-modal-content", + {}, + [...this.createButtons()]), + ]); + + } + createButtons() { + const handleShowOpenArtShareDialog = () => { + if (!OpenArtShareDialog.instance) { + OpenArtShareDialog.instance = new OpenArtShareDialog(); + } + OpenArtShareDialog.instance.show({ potential_outputs: this.potential_output_nodes, potential_output_nodes: this.potential_output_nodes }) + this.close(); + } + + const handleShowShareDialog = () => { + if (!ShareDialog.instance) { + ShareDialog.instance = new ShareDialog(); + } + ShareDialog.instance.show({ potential_outputs: this.potential_output_nodes, potential_output_nodes: this.potential_output_nodes }) + this.close(); + } + const buttons = [ + { + key: "openart", + textContent: "OpenArt AI", + website: "https://openart.ai/workflows/", + description: "Best place to share your workflow and art.", + onclick: handleShowOpenArtShareDialog + }, + { + key: "matrix", + textContent: "Matrix Server", + website: "https://app.element.io/#/room/%23comfyui_space%3Amatrix.org", + description: "Share your art on the official ComfyUI matrix server.", + onclick: handleShowShareDialog + }, + { + key: "comfyworkflows", + textContent: "ComfyWorkflows", + website: "https://comfyworkflows.com", + description: "Share ComfyUI art: Download & drop any image into ComfyUI to load its workflow.", + onclick: handleShowShareDialog + }, + ]; + + function createShareButtonsWithDescriptions() { + // Responsive container + const container = $el("div", { + style: { + display: "flex", + 'flex-wrap': 'wrap', + 'justify-content': 'space-around', + 'padding': '20px', + } + }); + + buttons.forEach(b => { + const button = $el("button", { + type: "button", + textContent: b.textContent, + onclick: b.onclick, + style: { + 'width': '25%', + 'minWidth': '200px', + 'background-color': b.backgroundColor || '', + 'border-radius': '5px', + 'cursor': 'pointer', + 'padding': '5px 5px', + 'margin-bottom': '5px', + 'transition': 'background-color 0.3s', + } + }); + button.addEventListener('mouseover', () => { + button.style.backgroundColor = '#007BFF'; // Change color on hover + }); + button.addEventListener('mouseout', () => { + button.style.backgroundColor = b.backgroundColor || ''; + }); + + const description = $el("p", { + textContent: b.description, + style: { + 'text-align': 'center', + color: 'white', + 'font-style': 'italic', + 'font-size': '14px', + 'margin-bottom': '10px', + }, + }); + + const websiteLink = $el("a", { + textContent: "🌐 Website", + href: b.website, + target: "_blank", + style: { + color: 'white', + 'margin-left': '10px', + 'font-size': '12px', + 'text-decoration': 'none', + 'align-self': 'center', + }, + }); + + // Add highlight to the website link + websiteLink.addEventListener('mouseover', () => { + websiteLink.style.opacity = '0.7'; + }); + + websiteLink.addEventListener('mouseout', () => { + websiteLink.style.opacity = '1'; + }); + + const buttonLinkContainer = $el("div", { + style: { + display: 'flex', + 'align-items': 'center', + 'margin-bottom': '10px', + } + }, [button, websiteLink]); + + const column = $el("div", { + style: { + 'flex-basis': '100%', + 'margin': '10px', + 'padding': '20px', + 'border': '1px solid #ddd', + 'border-radius': '5px', + 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)', + } + }, [buttonLinkContainer, description]); + + container.appendChild(column); + }); + + return container; + } + + + return [ + $el("tr.td", { width: "100%" }, [ + $el("font", { size: 6, color: "white" }, [`Where would you like to share your workflow?`]), + ]), + $el("br", {}, []), + + $el("div.cm-menu-container", { + id: "comfyui-share-container" + }, [ + $el("div.cm-menu-column", [ + $el("p", { + size: 3, color: "white", style: { + color: 'white' + } + }), + createShareButtonsWithDescriptions(), + $el("br", {}, []), + ]), + ]), + $el("div.cm-menu-container", { + id: "comfyui-share-container" + }, [ + $el("button", { + type: "button", + style: { + margin: "0 25px", + width: "100%", + }, + textContent: "Close", + onclick: () => { + this.close() + } + }), + $el("br", {}, []), + ]), + ]; + } + show({ potential_outputs, potential_output_nodes }) { + this.element.style.display = "block"; + this.potential_outputs = potential_outputs; + this.potential_output_nodes = potential_output_nodes; + } +} export class ShareDialog extends ComfyDialog { static instance = null; static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js new file mode 100644 index 00000000..93e3130e --- /dev/null +++ b/js/comfyui-share-openart.js @@ -0,0 +1,124 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js" +import { ComfyDialog, $el } from "../../scripts/ui.js"; + +const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; + +export class OpenArtShareDialog extends ComfyDialog { + static instance = null; + + constructor() { + super(); + this.element = $el("div.comfy-modal", { + parent: document.body, style: { + 'overflow-y': "auto", + } + }, + [$el("div.comfy-modal-content", + {}, + [...this.createButtons()]), + ]); + this.selectedOutputIndex = 0; + } + readKeyFromLocalStorage() { + return localStorage.getItem(LOCAL_STORAGE_KEY) || ''; + } + saveKeyToLocalStorage(value) { + localStorage.setItem(LOCAL_STORAGE_KEY, value); + } + createButtons() { + const sectionStyle = { + marginBottom: '20px', + padding: '15px', + borderRadius: '8px', + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)' + }; + + const inputStyle = { + display: 'block', + minWidth: '500px', + width: '100%', + padding: '10px', + margin: '10px 0', + borderRadius: '4px', + border: '1px solid #ddd', + boxSizing: 'border-box' + }; + + const headerLabelStyle = { + color: "#f8f8f8", + display: 'block', + marginBottom: '15px', + fontWeight: 'bold', + textDecoration: 'none', + fontSize: '20px', + }; + + const labelStyle = { + color: "#f8f8f8", + display: 'block', + marginBottom: '5px', + fontWeight: 'bold', + textDecoration: 'none', + }; + + const buttonStyle = { + padding: '10px 80px', + margin: '10px 5px', + borderRadius: '4px', + border: 'none', + cursor: 'pointer', + color: '#fff', + backgroundColor: '#007bff' + }; + + this.keyInput = $el("input", { type: 'text', placeholder: "Share Your key", style: inputStyle }) + // Account Section + const AccountSection = $el("div", { style: sectionStyle }, [ + $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["Check out 1000+ workflows others have uploaded."]), + $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["You can get OpenArt key at https://openart.ai/"]), + $el("label", { style: labelStyle }, ["OpenArt API Key"]), + this.keyInput, + ]); + + // Additional Inputs Section + const additionalInputsSection = $el("div", { style: sectionStyle }, [ + $el("label", { style: labelStyle }, ["Details"]), + $el("input", { type: "text", placeholder: "Title (required)", style: inputStyle }), + $el("textarea", { placeholder: "Description (optional)", style: { + ...inputStyle, + minHeight: '100px', + } }), + ]); + + // Share and Close Buttons + const buttonsSection = $el("div", { style: { textAlign: 'right', marginTop: '20px', display: 'flex', justifyContent: 'space-between' } }, [ + $el("button", { type: "button", textContent: "Close", style: { + ...buttonStyle, + backgroundColor: undefined + }, onclick: () => { this.close(); } }), + $el("button", { type: "submit", textContent: "Share", style: buttonStyle, onclick: () => { this.share(); } }), + ]); + + // Final Message Section + const finalMessage = $el("div", { style: { color: "white", textAlign: "center", padding: "10px" } }, []); + // Composing the full layout + const layout = [ + AccountSection, + additionalInputsSection, + buttonsSection, + finalMessage, + ]; + + return layout; + } + async share() { + this.saveKeyToLocalStorage(this.keyInput.value); + } + show({ potential_outputs, potential_output_nodes }) { + this.element.style.display = "block"; + // read key from local storage and set it to the input + const key = this.readKeyFromLocalStorage(); + this.keyInput.value = key; + } +} \ No newline at end of file From 06307b75b319132d276b14224c4e4513ffd691a7 Mon Sep 17 00:00:00 2001 From: pingren <5123601+Pingren@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:09:01 +0800 Subject: [PATCH 03/21] feat: add upload Image section --- js/comfyui-share-openart.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 93e3130e..fdb0d536 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -28,7 +28,7 @@ export class OpenArtShareDialog extends ComfyDialog { } createButtons() { const sectionStyle = { - marginBottom: '20px', + marginBottom: '10px', padding: '15px', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)' @@ -57,7 +57,7 @@ export class OpenArtShareDialog extends ComfyDialog { const labelStyle = { color: "#f8f8f8", display: 'block', - marginBottom: '5px', + margin: '10px 0', fontWeight: 'bold', textDecoration: 'none', }; @@ -71,19 +71,41 @@ export class OpenArtShareDialog extends ComfyDialog { color: '#fff', backgroundColor: '#007bff' }; + + // upload images input + this.uploadImagesInput = $el("input", { type: 'file', multiple: false, style: inputStyle }) - this.keyInput = $el("input", { type: 'text', placeholder: "Share Your key", style: inputStyle }) + this.uploadImagesInput.addEventListener('change', async (e) => { + const file = e.target.files[0]; + if (!file) { + return; + } + const reader = new FileReader(); + reader.onload = async (e) => { + const imgData = e.target.result; + this.previewImage.src = imgData; + }; + reader.readAsDataURL(file); + }); + + // preview image + this.previewImage = $el("img", { src: "", style: { maxWidth: '100%', maxHeight: '100px' } }); + + this.keyInput = $el("input", { type: 'text', placeholder: "Copy & paste your API key", style: inputStyle }) // Account Section const AccountSection = $el("div", { style: sectionStyle }, [ $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["Check out 1000+ workflows others have uploaded."]), - $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["You can get OpenArt key at https://openart.ai/"]), + $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["You can get API key at https://openart.ai/"]), $el("label", { style: labelStyle }, ["OpenArt API Key"]), this.keyInput, ]); // Additional Inputs Section const additionalInputsSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["Details"]), + $el("label", { style: labelStyle }, ["Image/Thumbnail (Required)"]), + this.uploadImagesInput, + this.previewImage, + $el("label", { style: labelStyle }, ["Workflow Information"]), $el("input", { type: "text", placeholder: "Title (required)", style: inputStyle }), $el("textarea", { placeholder: "Description (optional)", style: { ...inputStyle, From bc6083647aa634b36d76569ce2043885853d6abe Mon Sep 17 00:00:00 2001 From: pingren <5123601+Pingren@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:32:30 +0800 Subject: [PATCH 04/21] feat: updated share UI --- js/comfyui-share-openart.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index fdb0d536..68401ad5 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -3,13 +3,27 @@ import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; +const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true"; + +const style = ` +.openart-share-dialog a { + color: #f8f8f8; +} +.openart-share-dialog a:hover { + color: #007bff; +} +`; export class OpenArtShareDialog extends ComfyDialog { static instance = null; constructor() { super(); - this.element = $el("div.comfy-modal", { + $el("style", { + textContent: style, + parent: document.head, + }); + this.element = $el("div.comfy-modal.openart-share-dialog", { parent: document.body, style: { 'overflow-y': "auto", } @@ -45,12 +59,10 @@ export class OpenArtShareDialog extends ComfyDialog { boxSizing: 'border-box' }; - const headerLabelStyle = { - color: "#f8f8f8", + const hyperLinkStyle = { display: 'block', marginBottom: '15px', fontWeight: 'bold', - textDecoration: 'none', fontSize: '20px', }; @@ -94,8 +106,8 @@ export class OpenArtShareDialog extends ComfyDialog { this.keyInput = $el("input", { type: 'text', placeholder: "Copy & paste your API key", style: inputStyle }) // Account Section const AccountSection = $el("div", { style: sectionStyle }, [ - $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["Check out 1000+ workflows others have uploaded."]), - $el("a", { style: headerLabelStyle, href: "https://openart.ai/workflows" }, ["You can get API key at https://openart.ai/"]), + $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, ["Check out 1000+ workflows others have uploaded."]), + $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, ["You can get API key at here."]), $el("label", { style: labelStyle }, ["OpenArt API Key"]), this.keyInput, ]); @@ -106,7 +118,7 @@ export class OpenArtShareDialog extends ComfyDialog { this.uploadImagesInput, this.previewImage, $el("label", { style: labelStyle }, ["Workflow Information"]), - $el("input", { type: "text", placeholder: "Title (required)", style: inputStyle }), + $el("input", { type: "text", placeholder: "Name (required)", style: inputStyle }), $el("textarea", { placeholder: "Description (optional)", style: { ...inputStyle, minHeight: '100px', From d13a6e4b2a308a6999a2106a75d044478ef242d9 Mon Sep 17 00:00:00 2001 From: pingren <5123601+Pingren@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:56:57 +0800 Subject: [PATCH 05/21] feat(WIP): share workflow --- js/comfyui-share-openart.js | 440 +++++++++++++++++++++++++----------- 1 file changed, 311 insertions(+), 129 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 68401ad5..0aac2000 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -1,10 +1,13 @@ import { app } from "../../scripts/app.js"; -import { api } from "../../scripts/api.js" +import { api } from "../../scripts/api.js"; import { ComfyDialog, $el } from "../../scripts/ui.js"; const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true"; +// const API_ENDPOINT = "https://openart.ai/api"; +const API_ENDPOINT = "http://localhost:8080/api"; + const style = ` .openart-share-dialog a { color: #f8f8f8; @@ -15,144 +18,323 @@ const style = ` `; export class OpenArtShareDialog extends ComfyDialog { - static instance = null; + static instance = null; - constructor() { - super(); - $el("style", { - textContent: style, - parent: document.head, - }); - this.element = $el("div.comfy-modal.openart-share-dialog", { - parent: document.body, style: { - 'overflow-y': "auto", - } - }, - [$el("div.comfy-modal-content", - {}, - [...this.createButtons()]), - ]); - this.selectedOutputIndex = 0; - } - readKeyFromLocalStorage() { - return localStorage.getItem(LOCAL_STORAGE_KEY) || ''; - } - saveKeyToLocalStorage(value) { - localStorage.setItem(LOCAL_STORAGE_KEY, value); - } - createButtons() { - const sectionStyle = { - marginBottom: '10px', - padding: '15px', - borderRadius: '8px', - boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)' - }; + constructor() { + super(); + $el("style", { + textContent: style, + parent: document.head, + }); + this.element = $el( + "div.comfy-modal.openart-share-dialog", + { + parent: document.body, + style: { + "overflow-y": "auto", + }, + }, + [$el("div.comfy-modal-content", {}, [...this.createButtons()])] + ); + this.selectedOutputIndex = 0; + this.uploadedImages = []; + } + readKeyFromLocalStorage() { + return localStorage.getItem(LOCAL_STORAGE_KEY) || ""; + } + saveKeyToLocalStorage(value) { + localStorage.setItem(LOCAL_STORAGE_KEY, value); + } + createButtons() { + const sectionStyle = { + marginBottom: "10px", + padding: "15px", + borderRadius: "8px", + boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)", + }; - const inputStyle = { - display: 'block', - minWidth: '500px', - width: '100%', - padding: '10px', - margin: '10px 0', - borderRadius: '4px', - border: '1px solid #ddd', - boxSizing: 'border-box' - }; + const inputStyle = { + display: "block", + minWidth: "500px", + width: "100%", + padding: "10px", + margin: "10px 0", + borderRadius: "4px", + border: "1px solid #ddd", + boxSizing: "border-box", + }; - const hyperLinkStyle = { - display: 'block', - marginBottom: '15px', - fontWeight: 'bold', - fontSize: '20px', - }; + const hyperLinkStyle = { + display: "block", + marginBottom: "15px", + fontWeight: "bold", + fontSize: "20px", + }; - const labelStyle = { - color: "#f8f8f8", - display: 'block', - margin: '10px 0', - fontWeight: 'bold', - textDecoration: 'none', - }; + const labelStyle = { + color: "#f8f8f8", + display: "block", + margin: "10px 0", + fontWeight: "bold", + textDecoration: "none", + }; - const buttonStyle = { - padding: '10px 80px', - margin: '10px 5px', - borderRadius: '4px', - border: 'none', - cursor: 'pointer', - color: '#fff', - backgroundColor: '#007bff' - }; + const buttonStyle = { + padding: "10px 80px", + margin: "10px 5px", + borderRadius: "4px", + border: "none", + cursor: "pointer", + color: "#fff", + backgroundColor: "#007bff", + }; - // upload images input - this.uploadImagesInput = $el("input", { type: 'file', multiple: false, style: inputStyle }) - - this.uploadImagesInput.addEventListener('change', async (e) => { - const file = e.target.files[0]; - if (!file) { - return; - } - const reader = new FileReader(); - reader.onload = async (e) => { - const imgData = e.target.result; - this.previewImage.src = imgData; - }; - reader.readAsDataURL(file); - }); + // upload images input + this.uploadImagesInput = $el("input", { + type: "file", + multiple: false, + style: inputStyle, + }); - // preview image - this.previewImage = $el("img", { src: "", style: { maxWidth: '100%', maxHeight: '100px' } }); + this.uploadImagesInput.addEventListener("change", async (e) => { + const file = e.target.files[0]; + if (!file) { + return; + } + const reader = new FileReader(); + reader.onload = async (e) => { + const imgData = e.target.result; + this.previewImage.src = imgData; + }; + reader.readAsDataURL(file); + }); - this.keyInput = $el("input", { type: 'text', placeholder: "Copy & paste your API key", style: inputStyle }) - // Account Section - const AccountSection = $el("div", { style: sectionStyle }, [ - $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, ["Check out 1000+ workflows others have uploaded."]), - $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, ["You can get API key at here."]), - $el("label", { style: labelStyle }, ["OpenArt API Key"]), - this.keyInput, - ]); + // preview image + this.previewImage = $el("img", { + src: "", + style: { maxWidth: "100%", maxHeight: "100px" }, + }); - // Additional Inputs Section - const additionalInputsSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["Image/Thumbnail (Required)"]), - this.uploadImagesInput, - this.previewImage, - $el("label", { style: labelStyle }, ["Workflow Information"]), - $el("input", { type: "text", placeholder: "Name (required)", style: inputStyle }), - $el("textarea", { placeholder: "Description (optional)", style: { - ...inputStyle, - minHeight: '100px', - } }), - ]); + this.keyInput = $el("input", { + type: "text", + placeholder: "Copy & paste your API key", + style: inputStyle, + }); + this.NameInput = $el("input", { + type: "text", + placeholder: "Name (required)", + style: inputStyle, + }); + this.descriptionInput = $el("textarea", { + placeholder: "Description (optional)", + style: { + ...inputStyle, + minHeight: "100px", + }, + }); - // Share and Close Buttons - const buttonsSection = $el("div", { style: { textAlign: 'right', marginTop: '20px', display: 'flex', justifyContent: 'space-between' } }, [ - $el("button", { type: "button", textContent: "Close", style: { - ...buttonStyle, - backgroundColor: undefined - }, onclick: () => { this.close(); } }), - $el("button", { type: "submit", textContent: "Share", style: buttonStyle, onclick: () => { this.share(); } }), - ]); + // Account Section + const AccountSection = $el("div", { style: sectionStyle }, [ + $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, [ + "Check out 1000+ workflows others have uploaded.", + ]), + $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, [ + "You can get API key at here.", + ]), + $el("label", { style: labelStyle }, ["OpenArt API Key"]), + this.keyInput, + ]); - // Final Message Section - const finalMessage = $el("div", { style: { color: "white", textAlign: "center", padding: "10px" } }, []); - // Composing the full layout - const layout = [ - AccountSection, - additionalInputsSection, - buttonsSection, - finalMessage, - ]; + // Additional Inputs Section + const additionalInputsSection = $el("div", { style: sectionStyle }, [ + $el("label", { style: labelStyle }, ["Image/Thumbnail (Required)"]), + this.uploadImagesInput, + this.previewImage, + $el("label", { style: labelStyle }, ["Workflow Information"]), + this.NameInput, + this.descriptionInput, + ]); - return layout; + this.shareButton = $el("button", { + type: "submit", + textContent: "Share", + style: buttonStyle, + onclick: () => { + this.handleShareButtonClick(); + }, + }); + + // Share and Close Buttons + const buttonsSection = $el( + "div", + { + style: { + textAlign: "right", + marginTop: "20px", + display: "flex", + justifyContent: "space-between", + }, + }, + [ + $el("button", { + type: "button", + textContent: "Close", + style: { + ...buttonStyle, + backgroundColor: undefined, + }, + onclick: () => { + this.close(); + }, + }), + this.shareButton, + ] + ); + + // Message Section + this.message = $el( + "div", + { + style: { + color: "#ff3d00", + textAlign: "center", + padding: "10px", + fontSize: "20px", + }, + }, + [] + ); + // Composing the full layout + const layout = [ + AccountSection, + additionalInputsSection, + this.message, + buttonsSection, + ]; + + return layout; + } + async fetchApi(path, options, statusText) { + if (statusText) { + this.message.textContent = statusText; } - async share() { - this.saveKeyToLocalStorage(this.keyInput.value); - } - show({ potential_outputs, potential_output_nodes }) { - this.element.style.display = "block"; - // read key from local storage and set it to the input - const key = this.readKeyFromLocalStorage(); - this.keyInput.value = key; - } -} \ No newline at end of file + const addSearchParams = (url, params = {}) => + new URL( + `${url.origin}${url.pathname}?${new URLSearchParams([ + ...Array.from(url.searchParams.entries()), + ...Object.entries(params), + ])}` + ); + + const fullPath = addSearchParams(new URL(API_ENDPOINT + path), { + workflow_api_key: this.keyInput.value, + }); + + const response = await fetch(fullPath, options); + + if (statusText) { + this.message.textContent = ""; + } + + return await response.json(); + } + async uploadThumbnail(uploadFile) { + const form = new FormData(); + form.append("file", uploadFile); + try { + const res = await this.fetchApi( + `/workflows/upload_thumbnail`, + { + method: "POST", + body: form, + }, + "Uploading thumbnail..." + ); + if (res.status === 200 && res.data) { + const { image_url, width, height } = res.data; + this.uploadedImages.push({ + url: image_url, + width, + height, + }); + } + } catch (e) { + if (e?.response?.status === 413) { + throw new Error("File size is too large (max 20MB)"); + } else { + throw new Error("Error uploading thumbnail: " + e.message); + } + } + } + async handleShareButtonClick() { + try { + this.shareButton.disabled = true; + this.shareButton.textContent = "Sharing..."; + await this.share(); + } catch (e) { + alert(e.message); + } + this.shareButton.disabled = false; + this.shareButton.textContent = "Share"; + this.message.textContent = ""; + this.uploadedImages = []; + } + async share() { + this.uploadedImages = []; + const prompt = await app.graphToPrompt(); + const workflowJSON = prompt["workflow"]; + const form_values = { + name: this.NameInput.value, + description: this.descriptionInput.value, + }; + + if (!this.keyInput.value) { + throw new Error("API key is required"); + } + + if (!this.uploadImagesInput.files[0]) { + throw new Error("Thumbnail is required"); + } + + if (!form_values.name) { + throw new Error("Name is required"); + } + + for (const file of this.uploadImagesInput.files) { + await this.uploadThumbnail(file); + } + + if (this.uploadImagesInput.files.length === 0) { + throw new Error("No thumbnail uploaded"); + } + + try { + const response = await this.fetchApi( + "/workflows/publish", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + workflow_json: workflowJSON, + upload_images: this.uploadedImages, + form_values, + }), + }, + "Uploading workflow..." + ); + + if (response.status === 200) { + console.log(response.data); + this.saveKeyToLocalStorage(this.keyInput.value); + } + } catch (e) { + throw new Error("Error sharing workflow: " + e.message); + } + } + show({ potential_outputs, potential_output_nodes }) { + this.element.style.display = "block"; + // read key from local storage and set it to the input + const key = this.readKeyFromLocalStorage(); + this.keyInput.value = key; + } +} From e2e7b748e39615edb6493065e8a897b8a93d9aeb Mon Sep 17 00:00:00 2001 From: Ping <5123601+pingren@users.noreply.github.com> Date: Fri, 17 Nov 2023 20:46:57 +0800 Subject: [PATCH 06/21] feat: share workflow --- js/comfyui-share-openart.js | 83 +++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 0aac2000..4cadd449 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -94,11 +94,13 @@ export class OpenArtShareDialog extends ComfyDialog { type: "file", multiple: false, style: inputStyle, + accept: "image/*", }); this.uploadImagesInput.addEventListener("change", async (e) => { const file = e.target.files[0]; if (!file) { + this.previewImage.src = ""; return; } const reader = new FileReader(); @@ -135,12 +137,16 @@ export class OpenArtShareDialog extends ComfyDialog { // Account Section const AccountSection = $el("div", { style: sectionStyle }, [ - $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, [ - "Check out 1000+ workflows others have uploaded.", - ]), - $el("a", { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL }, [ - "You can get API key at here.", - ]), + $el( + "a", + { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL, target: "_blank" }, + ["Check out 1000+ workflows others have uploaded."] + ), + $el( + "a", + { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL, target: "_blank" }, + ["You can get API key at here."] + ), $el("label", { style: labelStyle }, ["OpenArt API Key"]), this.keyInput, ]); @@ -225,18 +231,27 @@ export class OpenArtShareDialog extends ComfyDialog { ...Object.entries(params), ])}` ); - - const fullPath = addSearchParams(new URL(API_ENDPOINT + path), { - workflow_api_key: this.keyInput.value, - }); + + const fullPath = addSearchParams(new URL(API_ENDPOINT + path), { + workflow_api_key: this.keyInput.value, + }); const response = await fetch(fullPath, options); + if (!response.ok) { + throw new Error(response.statusText); + } + if (statusText) { this.message.textContent = ""; } - - return await response.json(); + const data = await response.json(); + return { + ok: response.ok, + statusText: response.statusText, + status: response.status, + data, + }; } async uploadThumbnail(uploadFile) { const form = new FormData(); @@ -250,7 +265,8 @@ export class OpenArtShareDialog extends ComfyDialog { }, "Uploading thumbnail..." ); - if (res.status === 200 && res.data) { + + if (res.ok && res.data) { const { image_url, width, height } = res.data; this.uploadedImages.push({ url: image_url, @@ -267,6 +283,8 @@ export class OpenArtShareDialog extends ComfyDialog { } } async handleShareButtonClick() { + this.message.textContent = ""; + this.saveKeyToLocalStorage(this.keyInput.value); try { this.shareButton.disabled = true; this.shareButton.textContent = "Sharing..."; @@ -274,13 +292,10 @@ export class OpenArtShareDialog extends ComfyDialog { } catch (e) { alert(e.message); } - this.shareButton.disabled = false; - this.shareButton.textContent = "Share"; - this.message.textContent = ""; - this.uploadedImages = []; + this.shareButton.disabled = false; + this.shareButton.textContent = "Share"; } async share() { - this.uploadedImages = []; const prompt = await app.graphToPrompt(); const workflowJSON = prompt["workflow"]; const form_values = { @@ -300,12 +315,23 @@ export class OpenArtShareDialog extends ComfyDialog { throw new Error("Name is required"); } - for (const file of this.uploadImagesInput.files) { - await this.uploadThumbnail(file); - } + if (!this.uploadedImages.length) { + for (const file of this.uploadImagesInput.files) { + try { + await this.uploadThumbnail(file); + } catch (e) { + this.uploadedImages = []; + throw new Error(e.message); + } + } - if (this.uploadImagesInput.files.length === 0) { - throw new Error("No thumbnail uploaded"); + if (this.uploadImagesInput.files.length === 0) { + throw new Error("No thumbnail uploaded"); + } + + if (this.uploadImagesInput.files.length === 0) { + throw new Error("No thumbnail uploaded"); + } } try { @@ -323,15 +349,18 @@ export class OpenArtShareDialog extends ComfyDialog { "Uploading workflow..." ); - if (response.status === 200) { - console.log(response.data); - this.saveKeyToLocalStorage(this.keyInput.value); + if (response.ok) { + const { workflow_id } = response.data; + if (workflow_id) { + const url = `https://openart.ai/workflows/-/-/${workflow_id}`; + this.message.innerHTML = `Workflow has been shared successfully. Click here to view it.`; + } } } catch (e) { throw new Error("Error sharing workflow: " + e.message); } } - show({ potential_outputs, potential_output_nodes }) { + show({ potential_outputs, potential_output_nodes } = {}) { this.element.style.display = "block"; // read key from local storage and set it to the input const key = this.readKeyFromLocalStorage(); From 407f9b899e3ddf0c00da7053b852a09e473f8f29 Mon Sep 17 00:00:00 2001 From: Ping <5123601+pingren@users.noreply.github.com> Date: Fri, 17 Nov 2023 20:47:46 +0800 Subject: [PATCH 07/21] feat: delay share condition check --- js/comfyui-manager.js | 25 ++----------------------- js/comfyui-share-common.js | 29 +++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 492fd403..aae8b853 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -590,30 +590,9 @@ app.registerExtension({ if(!ShareDialogChooser.instance) { ShareDialogChooser.instance = new ShareDialogChooser(); } - // TODO: DEV ONLY, remove this line - ShareDialogChooser.instance.show({ potential_outputs : [], potential_output_nodes: [] }); + + ShareDialogChooser.instance.show(); return - - app.graphToPrompt().then(prompt => { - // console.log({ prompt }) - return app.graph._nodes; - }).then(nodes => { - // console.log({ nodes }); - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - - if (potential_outputs.length === 0) { - if (potential_output_nodes.length === 0) { - // todo: add support for other output node types (animatediff combine, etc.) - const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); - alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); - } else { - alert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); - } - return; - } - - ShareDialogChooser.instance.show({ potential_outputs, potential_output_nodes }); - }); } // make the background color a gradient of blue to green shareButton.style.background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index 48bf794a..6f99d74d 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -170,7 +170,7 @@ export class ShareDialogChooser extends ComfyDialog { if (!OpenArtShareDialog.instance) { OpenArtShareDialog.instance = new OpenArtShareDialog(); } - OpenArtShareDialog.instance.show({ potential_outputs: this.potential_output_nodes, potential_output_nodes: this.potential_output_nodes }) + OpenArtShareDialog.instance.show() this.close(); } @@ -178,8 +178,27 @@ export class ShareDialogChooser extends ComfyDialog { if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(); } - ShareDialog.instance.show({ potential_outputs: this.potential_output_nodes, potential_output_nodes: this.potential_output_nodes }) - this.close(); + app.graphToPrompt().then(prompt => { + // console.log({ prompt }) + return app.graph._nodes; + }).then(nodes => { + // console.log({ nodes }); + const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); + + if (potential_outputs.length === 0) { + if (potential_output_nodes.length === 0) { + // todo: add support for other output node types (animatediff combine, etc.) + const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); + alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); + } else { + alert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); + } + return; + } + + ShareDialogChooser.instance.show({ potential_outputs, potential_output_nodes }); + this.close(); + }); } const buttons = [ @@ -336,10 +355,8 @@ export class ShareDialogChooser extends ComfyDialog { ]), ]; } - show({ potential_outputs, potential_output_nodes }) { + show() { this.element.style.display = "block"; - this.potential_outputs = potential_outputs; - this.potential_output_nodes = potential_output_nodes; } } export class ShareDialog extends ComfyDialog { From ddf64ba12beac6c9ab5269e3ee45331c901e6984 Mon Sep 17 00:00:00 2001 From: Ping <5123601+pingren@users.noreply.github.com> Date: Sat, 18 Nov 2023 09:03:33 +0800 Subject: [PATCH 08/21] fix: typo in handleShowShareDialog --- js/comfyui-share-common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index 6f99d74d..42b514e6 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -196,7 +196,7 @@ export class ShareDialogChooser extends ComfyDialog { return; } - ShareDialogChooser.instance.show({ potential_outputs, potential_output_nodes }); + ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); this.close(); }); } From 1a5b2dc36ac89784e4c6229119a25c548b6141c0 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 17 Nov 2023 17:58:56 -0700 Subject: [PATCH 09/21] Minor polish --- js/comfyui-share-common.js | 33 +++++++++--------- js/comfyui-share-openart.js | 69 ++++++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 41 deletions(-) diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index 42b514e6..d213c2f6 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -163,7 +163,7 @@ export class ShareDialogChooser extends ComfyDialog { {}, [...this.createButtons()]), ]); - + } createButtons() { const handleShowOpenArtShareDialog = () => { @@ -173,7 +173,7 @@ export class ShareDialogChooser extends ComfyDialog { OpenArtShareDialog.instance.show() this.close(); } - + const handleShowShareDialog = () => { if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(); @@ -206,21 +206,21 @@ export class ShareDialogChooser extends ComfyDialog { key: "openart", textContent: "OpenArt AI", website: "https://openart.ai/workflows/", - description: "Best place to share your workflow and art.", + description: "Share ComfyUI workflows and art on OpenArt.ai", onclick: handleShowOpenArtShareDialog }, { key: "matrix", textContent: "Matrix Server", website: "https://app.element.io/#/room/%23comfyui_space%3Amatrix.org", - description: "Share your art on the official ComfyUI matrix server.", + description: "Share your art on the official ComfyUI matrix server", onclick: handleShowShareDialog }, { key: "comfyworkflows", textContent: "ComfyWorkflows", website: "https://comfyworkflows.com", - description: "Share ComfyUI art: Download & drop any image into ComfyUI to load its workflow.", + description: "Share ComfyUI art on comfyworkflows.com", onclick: handleShowShareDialog }, ]; @@ -235,7 +235,7 @@ export class ShareDialogChooser extends ComfyDialog { 'padding': '20px', } }); - + buttons.forEach(b => { const button = $el("button", { type: "button", @@ -258,18 +258,17 @@ export class ShareDialogChooser extends ComfyDialog { button.addEventListener('mouseout', () => { button.style.backgroundColor = b.backgroundColor || ''; }); - + const description = $el("p", { textContent: b.description, style: { - 'text-align': 'center', + 'text-align': 'left', color: 'white', - 'font-style': 'italic', 'font-size': '14px', 'margin-bottom': '10px', }, }); - + const websiteLink = $el("a", { textContent: "🌐 Website", href: b.website, @@ -287,11 +286,11 @@ export class ShareDialogChooser extends ComfyDialog { websiteLink.addEventListener('mouseover', () => { websiteLink.style.opacity = '0.7'; }); - + websiteLink.addEventListener('mouseout', () => { websiteLink.style.opacity = '1'; }); - + const buttonLinkContainer = $el("div", { style: { display: 'flex', @@ -299,7 +298,7 @@ export class ShareDialogChooser extends ComfyDialog { 'margin-bottom': '10px', } }, [button, websiteLink]); - + const column = $el("div", { style: { 'flex-basis': '100%', @@ -310,13 +309,13 @@ export class ShareDialogChooser extends ComfyDialog { 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.1)', } }, [buttonLinkContainer, description]); - + container.appendChild(column); }); - + return container; } - + return [ $el("tr.td", { width: "100%" }, [ @@ -895,4 +894,4 @@ export class ShareDialog extends ComfyDialog { this.radio_buttons.appendChild(new_radio_buttons); this.element.style.display = "block"; } -} \ No newline at end of file +} diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 4cadd449..f8faac0c 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -68,7 +68,7 @@ export class OpenArtShareDialog extends ComfyDialog { display: "block", marginBottom: "15px", fontWeight: "bold", - fontSize: "20px", + fontSize: "14px", }; const labelStyle = { @@ -135,18 +135,37 @@ export class OpenArtShareDialog extends ComfyDialog { }, }); + // LinkSection + this.communityLink = $el("a", { + style: hyperLinkStyle, + href: DEFAULT_HOMEPAGE_URL, + target: "_blank" + }, ["πŸ‘‰ Check out thousands of workflows shared from the community"]) + this.getAPIKeyLink = $el("a", { + style: { + ...hyperLinkStyle, + color: "#59E8C6" + }, + href: DEFAULT_HOMEPAGE_URL, + target: "_blank" + }, ["πŸ‘‰ Get your API key here"]) + const LinkSection = $el( + "div", + { + style: { + marginTop: "10px", + display: "flex", + flexDirection: "column", + }, + }, + [ + this.communityLink, + this.getAPIKeyLink, + ] + ); + // Account Section const AccountSection = $el("div", { style: sectionStyle }, [ - $el( - "a", - { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL, target: "_blank" }, - ["Check out 1000+ workflows others have uploaded."] - ), - $el( - "a", - { style: hyperLinkStyle, href: DEFAULT_HOMEPAGE_URL, target: "_blank" }, - ["You can get API key at here."] - ), $el("label", { style: labelStyle }, ["OpenArt API Key"]), this.keyInput, ]); @@ -161,6 +180,20 @@ export class OpenArtShareDialog extends ComfyDialog { this.descriptionInput, ]); + // Message Section + this.message = $el( + "div", + { + style: { + color: "#ff3d00", + textAlign: "center", + padding: "10px", + fontSize: "20px", + }, + }, + [] + ); + this.shareButton = $el("button", { type: "submit", textContent: "Share", @@ -197,21 +230,9 @@ export class OpenArtShareDialog extends ComfyDialog { ] ); - // Message Section - this.message = $el( - "div", - { - style: { - color: "#ff3d00", - textAlign: "center", - padding: "10px", - fontSize: "20px", - }, - }, - [] - ); // Composing the full layout const layout = [ + LinkSection, AccountSection, additionalInputsSection, this.message, From 676455b0eb52571e9201609a5d27d84367a787dd Mon Sep 17 00:00:00 2001 From: johnqiao Date: Sat, 18 Nov 2023 01:00:39 -0700 Subject: [PATCH 10/21] Move share setting to ComfyUI Manager dialog and provide share options. --- js/comfyui-manager.js | 74 ++++++++++++++--------- js/comfyui-share-common.js | 115 +++++++++++++++++++----------------- js/comfyui-share-openart.js | 5 +- 3 files changed, 111 insertions(+), 83 deletions(-) diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index aae8b853..5dd9e18e 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; -import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes, ShareDialogChooser } from "./comfyui-share-common.js"; +import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes, ShareDialogChooser, showOpenArtShareDialog, showShareDialog } from "./comfyui-share-common.js"; import { CustomNodesInstaller } from "./custom-nodes-downloader.js"; import { AlternativesInstaller } from "./a1111-alter-downloader.js"; import { SnapshotManager } from "./snapshot.js"; @@ -42,7 +42,7 @@ const style = ` #comfyworkflows-button { position: relative; overflow: hidden; - } + } .pysssss-workflow-arrow-2 { position: absolute; top: 0; @@ -235,16 +235,16 @@ async function updateAll(update_check_checkbox) { function newDOMTokenList(initialTokens) { const tmp = document.createElement(`div`); - + const classList = tmp.classList; if (initialTokens) { initialTokens.forEach(token => { classList.add(token); }); } - + return classList; - } + } // ----------- @@ -401,12 +401,37 @@ class ManagerMenuDialog extends ComfyDialog { } }); + // share + let share_combo = document.createElement("select"); + const share_options = [ + ['none', 'None'], + ['openart', 'OpenArt AI'], + ['matrix', 'Matrix Server'], + ['comfyworkflows', 'ComfyWorkflows'], + ['all', 'All'], + ]; + for (const option of share_options) { + share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, [])); + } + share_combo.addEventListener('change', function (event) { + const value = event.target.value; + localStorage.setItem("share_option", value); + const shareButton = document.getElementById("shareButton"); + if (value === 'none') { + shareButton.style.display = "none"; + } else { + shareButton.style.display = "inline-block"; + } + }); + share_combo.value = localStorage.getItem("share_option") || 'all'; + return [ $el("div", {}, [this.local_mode_checkbox, checkbox_text, this.update_check_checkbox, uc_checkbox_text]), $el("br", {}, []), preview_combo, badge_combo, channel_combo, + share_combo, $el("hr", {}, []), $el("center", {}, ["!! EXPERIMENTAL !!"]), @@ -466,14 +491,14 @@ class ManagerMenuDialog extends ComfyDialog { if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(); } - + app.graphToPrompt().then(prompt => { // console.log({ prompt }) return app.graph._nodes; }).then(nodes => { // console.log({ nodes }); const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - + if (potential_outputs.length === 0) { if (potential_output_nodes.length === 0) { // todo: add support for other output node types (animatediff combine, etc.) @@ -484,7 +509,7 @@ class ManagerMenuDialog extends ComfyDialog { } return; } - + ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); }); }, @@ -582,37 +607,32 @@ app.registerExtension({ } menu.append(managerButton); - const shareButton = document.createElement("button"); + shareButton.id = "shareButton"; shareButton.textContent = "Share"; shareButton.onclick = () => { + const shareOption = localStorage.getItem("share_option") || 'all'; + if (shareOption === 'openart') { + showOpenArtShareDialog(); + return; + } else if (shareOption === 'matrix' || shareOption === 'comfyworkflows') { + showShareDialog(); + return; + } if(!ShareDialogChooser.instance) { ShareDialogChooser.instance = new ShareDialogChooser(); } - ShareDialogChooser.instance.show(); - return } // make the background color a gradient of blue to green shareButton.style.background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; shareButton.style.color = "black"; - app.ui.settings.addSetting({ - id: "ComfyUIManager.ShowShareButtonInMainMenu", - name: "Show 'Share' button in the main menu", - type: "boolean", - defaultValue: true, - onChange: (value) => { - if (value) { - // show the button - shareButton.style.display = "inline-block"; - } else { - // hide the button - shareButton.style.display = "none"; - } - } - }); + // Load share option from local storage to determine whether to show + // the share button. + const shouldShowShareButton = localStorage.getItem("share_option") !== 'none'; + shareButton.style.display = shouldShowShareButton ? "inline-block" : "none"; menu.append(shareButton); }, @@ -712,4 +732,4 @@ app.registerExtension({ }; } } -}); \ No newline at end of file +}); diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index d213c2f6..d871c83a 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -150,6 +150,39 @@ export function parseURLPath(urlPath) { // Return the object with the parsed parameters return parsedParams; } + + +export const showOpenArtShareDialog = () => { + if (!OpenArtShareDialog.instance) { + OpenArtShareDialog.instance = new OpenArtShareDialog(); + } + OpenArtShareDialog.instance.show(); +} + +export const showShareDialog = () => { + if (!ShareDialog.instance) { + ShareDialog.instance = new ShareDialog(); + } + app.graphToPrompt().then(prompt => { + // console.log({ prompt }) + return app.graph._nodes; + }).then(nodes => { + // console.log({ nodes }); + const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); + if (potential_outputs.length === 0) { + if (potential_output_nodes.length === 0) { + // todo: add support for other output node types (animatediff combine, etc.) + const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); + alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); + } else { + alert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); + } + return; + } + ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); + }); +} + export class ShareDialogChooser extends ComfyDialog { static instance = null; constructor() { @@ -166,62 +199,36 @@ export class ShareDialogChooser extends ComfyDialog { } createButtons() { - const handleShowOpenArtShareDialog = () => { - if (!OpenArtShareDialog.instance) { - OpenArtShareDialog.instance = new OpenArtShareDialog(); - } - OpenArtShareDialog.instance.show() - this.close(); - } - - const handleShowShareDialog = () => { - if (!ShareDialog.instance) { - ShareDialog.instance = new ShareDialog(); - } - app.graphToPrompt().then(prompt => { - // console.log({ prompt }) - return app.graph._nodes; - }).then(nodes => { - // console.log({ nodes }); - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - - if (potential_outputs.length === 0) { - if (potential_output_nodes.length === 0) { - // todo: add support for other output node types (animatediff combine, etc.) - const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); - alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); - } else { - alert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); - } - return; - } - - ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); - this.close(); - }); - } - const buttons = [ { key: "openart", textContent: "OpenArt AI", website: "https://openart.ai/workflows/", description: "Share ComfyUI workflows and art on OpenArt.ai", - onclick: handleShowOpenArtShareDialog + onclick: () => { + showOpenArtShareDialog(); + this.close(); + } }, { key: "matrix", textContent: "Matrix Server", website: "https://app.element.io/#/room/%23comfyui_space%3Amatrix.org", description: "Share your art on the official ComfyUI matrix server", - onclick: handleShowShareDialog + onclick: () => { + showShareDialog(); + this.close(); + } }, { key: "comfyworkflows", textContent: "ComfyWorkflows", website: "https://comfyworkflows.com", description: "Share ComfyUI art on comfyworkflows.com", - onclick: handleShowShareDialog + onclick: () => { + showShareDialog(); + this.close(); + } }, ]; @@ -316,22 +323,22 @@ export class ShareDialogChooser extends ComfyDialog { return container; } - return [ - $el("tr.td", { width: "100%" }, [ - $el("font", { size: 6, color: "white" }, [`Where would you like to share your workflow?`]), - ]), - $el("br", {}, []), + $el("p", { + textContent: 'Choose a platform to share your workflow', + style: { + 'text-align': 'center', + 'color': 'white', + 'font-size': '18px', + 'margin-bottom': '10px', + }, + } + ), $el("div.cm-menu-container", { id: "comfyui-share-container" }, [ $el("div.cm-menu-column", [ - $el("p", { - size: 3, color: "white", style: { - color: 'white' - } - }), createShareButtonsWithDescriptions(), $el("br", {}, []), ]), @@ -391,12 +398,12 @@ export class ShareDialog extends ComfyDialog { this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) this.matrix_destination_checkbox.style.color = "var(--fg-color)"; - this.matrix_destination_checkbox.checked = false; //true; + this.matrix_destination_checkbox.checked = localStorage.getItem("share_option") === 'matrix'; //true; this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, []) const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"]) this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)"; - this.comfyworkflows_destination_checkbox.checked = true; + this.comfyworkflows_destination_checkbox.checked = localStorage.getItem("share_option") !== 'matrix'; this.matrix_homeserver_input = $el("input", { type: 'text', id: "matrix_homeserver", placeholder: "matrix.org", value: ShareDialog.matrix_auth.homeserver || 'matrix.org' }, []); this.matrix_username_input = $el("input", { type: 'text', placeholder: "Username", value: ShareDialog.matrix_auth.username || '' }, []); @@ -453,8 +460,8 @@ export class ShareDialog extends ComfyDialog { textContent: "Close", onclick: () => { // Reset state - this.matrix_destination_checkbox.checked = false; - this.comfyworkflows_destination_checkbox.checked = true; + this.matrix_destination_checkbox.checked = localStorage.getItem("share_option") === 'matrix'; + this.comfyworkflows_destination_checkbox.checked = localStorage.getItem("share_option") !== 'matrix'; this.share_button.textContent = "Share"; this.share_button.style.display = "inline-block"; this.final_message.innerHTML = ""; @@ -625,8 +632,8 @@ export class ShareDialog extends ComfyDialog { textContent: "Close", onclick: () => { // Reset state - this.matrix_destination_checkbox.checked = false; - this.comfyworkflows_destination_checkbox.checked = true; + this.matrix_destination_checkbox.checked = localStorage.getItem("share_option") === 'matrix'; + this.comfyworkflows_destination_checkbox.checked = localStorage.getItem("share_option") !== 'matrix'; this.share_button.textContent = "Share"; this.share_button.style.display = "inline-block"; this.final_message.innerHTML = ""; diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index f8faac0c..a52c7678 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -4,9 +4,10 @@ import { ComfyDialog, $el } from "../../scripts/ui.js"; const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true"; +//const DEFAULT_HOMEPAGE_URL = "http://localhost:8080/workflows/dev?developer=true"; -// const API_ENDPOINT = "https://openart.ai/api"; -const API_ENDPOINT = "http://localhost:8080/api"; + const API_ENDPOINT = "https://openart.ai/api"; +//const API_ENDPOINT = "http://localhost:8080/api"; const style = ` .openart-share-dialog a { From 442942aae4c9ce70b3a5b11c77380ba08d46bae0 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Sun, 19 Nov 2023 01:26:38 -0700 Subject: [PATCH 11/21] Store share setting and key in backend config file --- __init__.py | 62 +++++++++++++++++++++++----- js/comfyui-manager.js | 31 ++++++++++---- js/comfyui-share-common.js | 81 ++++++++++++++++++++++--------------- js/comfyui-share-openart.js | 43 ++++++++++++++++---- 4 files changed, 159 insertions(+), 58 deletions(-) diff --git a/__init__.py b/__init__.py index c18b0ab2..7b00495d 100644 --- a/__init__.py +++ b/__init__.py @@ -115,6 +115,7 @@ def write_config(): 'git_exe': get_config()['git_exe'], 'channel_url': get_config()['channel_url'], 'channel_url_list': get_config()['channel_url_list'], + 'share_option': get_config()['share_option'], 'bypass_ssl': get_config()['bypass_ssl'] } with open(config_path, 'w') as configfile: @@ -146,6 +147,7 @@ def read_config(): 'git_exe': default_conf['git_exe'] if 'git_exe' in default_conf else '', 'channel_url': default_conf['channel_url'] if 'channel_url' in default_conf else 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main', 'channel_url_list': ch_url_list, + 'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all', 'bypass_ssl': default_conf['bypass_ssl'] if 'bypass_ssl' in default_conf else False, } @@ -156,6 +158,7 @@ def read_config(): 'git_exe': '', 'channel_url': 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main', 'channel_url_list': '', + 'share_option': 'all', 'bypass_ssl': False } @@ -401,7 +404,7 @@ def git_pull(path): origin = repo.remote(name='origin') origin.pull() repo.git.submodule('update', '--init', '--recursive') - + repo.close() return True @@ -1344,7 +1347,7 @@ async def install_model(request): if json_data['url'].startswith('https://github.com') or json_data['url'].startswith('https://huggingface.co'): model_dir = get_model_dir(json_data) download_url(json_data['url'], model_dir) - + return web.json_response({}, content_type='application/json') else: res = download_url_with_agent(json_data['url'], model_path) @@ -1408,6 +1411,29 @@ async def channel_url_list(request): return web.Response(status=200) + +@server.PromptServer.instance.routes.get("/manager/share_option") +async def channel_url_list(request): + if "value" in request.rel_url.query: + get_config()['share_option'] = request.rel_url.query['value'] + write_config() + else: + return web.Response(text=get_config()['share_option'], status=200) + + return web.Response(status=200) + + +def get_openart_auth(): + if not os.path.exists(os.path.join(folder_paths.base_path, ".openart_key")): + return None + try: + with open(os.path.join(folder_paths.base_path, ".openart_key"), "r") as f: + openart_key = f.read().strip() + return openart_key if openart_key else None + except: + return None + + def get_matrix_auth(): if not os.path.exists(os.path.join(folder_paths.base_path, "matrix_auth")): return None @@ -1437,6 +1463,22 @@ def get_comfyworkflows_auth(): except: return None +@server.PromptServer.instance.routes.get("/manager/get_openart_auth") +async def api_get_openart_auth(request): + # print("Getting stored Matrix credentials...") + openart_key = get_openart_auth() + if not openart_key: + return web.Response(status=404) + return web.json_response({"openart_key": openart_key}) + +@server.PromptServer.instance.routes.post("/manager/set_openart_auth") +async def api_set_openart_auth(request): + json_data = await request.json() + openart_key = json_data['openart_key'] + with open(os.path.join(folder_paths.base_path, ".openart_key"), "w") as f: + f.write(openart_key) + return web.Response(status=200) + @server.PromptServer.instance.routes.get("/manager/get_matrix_auth") async def api_get_matrix_auth(request): # print("Getting stored Matrix credentials...") @@ -1491,13 +1533,13 @@ async def share_art(request): prompt = json_data['prompt'] potential_outputs = json_data['potential_outputs'] selected_output_index = json_data['selected_output_index'] - + try: output_to_share = potential_outputs[int(selected_output_index)] except: # for now, pick the first output output_to_share = potential_outputs[0] - + assert output_to_share['type'] in ('image', 'output') output_dir = folder_paths.get_output_directory() @@ -1522,7 +1564,7 @@ async def share_art(request): if "comfyworkflows" in share_destinations: share_website_host = "https://comfyworkflows.com" share_endpoint = f"{share_website_host}/api" - + # get presigned urls async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: async with session.post( @@ -1530,7 +1572,7 @@ async def share_art(request): json={ "assetFileName": asset_filename, "assetFileType": assetFileType, - "workflowJsonFileName" : 'workflow.json', + "workflowJsonFileName" : 'workflow.json', "workflowJsonFileType" : 'application/json', }, @@ -1541,7 +1583,7 @@ async def share_art(request): assetFileKey = presigned_urls_json["assetFileKey"] workflowJsonFilePresignedUrl = presigned_urls_json["workflowJsonFilePresignedUrl"] workflowJsonFileKey = presigned_urls_json["workflowJsonFileKey"] - + # upload asset async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp: @@ -1592,7 +1634,7 @@ async def share_art(request): homeserver = homeserver.replace("http://", "https://") if not homeserver.startswith("https://"): homeserver = "https://" + homeserver - + client = MatrixClient(homeserver) try: token = client.login(username=matrix_auth['username'], password=matrix_auth['password']) @@ -1604,7 +1646,7 @@ async def share_art(request): matrix = MatrixHttpApi(homeserver, token=token) with open(asset_filepath, 'rb') as f: mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri'] - + workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri'] text_content = "" @@ -1621,7 +1663,7 @@ async def share_art(request): import traceback traceback.print_exc() return web.json_response({"error" : "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500) - + return web.json_response({ "comfyworkflows" : { "url" : None if "comfyworkflows" not in share_destinations else f"{share_website_host}/workflows/{workflowId}", diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 5dd9e18e..d6e9c8e9 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -36,6 +36,7 @@ var update_comfyui_button = null; var fetch_updates_button = null; var update_all_button = null; var badge_mode = "none"; +let share_option = 'all'; // copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts const style = ` @@ -79,7 +80,16 @@ async function init_badge_mode() { .then(data => { badge_mode = data; }) } +async function init_share_option() { + api.fetchApi('/manager/share_option') + .then(response => response.text()) + .then(data => { + share_option = data || 'all'; + }); +} + await init_badge_mode(); +await init_share_option(); async function fetchNicknames() { @@ -413,9 +423,18 @@ class ManagerMenuDialog extends ComfyDialog { for (const option of share_options) { share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, [])); } + + api.fetchApi('/manager/share_option') + .then(response => response.text()) + .then(data => { + share_combo.value = data || 'all'; + share_option = data || 'all'; + }); + share_combo.addEventListener('change', function (event) { const value = event.target.value; - localStorage.setItem("share_option", value); + share_option = value; + api.fetchApi(`/manager/share_option?value=${value}`); const shareButton = document.getElementById("shareButton"); if (value === 'none') { shareButton.style.display = "none"; @@ -423,7 +442,6 @@ class ManagerMenuDialog extends ComfyDialog { shareButton.style.display = "inline-block"; } }); - share_combo.value = localStorage.getItem("share_option") || 'all'; return [ $el("div", {}, [this.local_mode_checkbox, checkbox_text, this.update_check_checkbox, uc_checkbox_text]), @@ -611,12 +629,11 @@ app.registerExtension({ shareButton.id = "shareButton"; shareButton.textContent = "Share"; shareButton.onclick = () => { - const shareOption = localStorage.getItem("share_option") || 'all'; - if (shareOption === 'openart') { + if (share_option === 'openart') { showOpenArtShareDialog(); return; - } else if (shareOption === 'matrix' || shareOption === 'comfyworkflows') { - showShareDialog(); + } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { + showShareDialog(share_option); return; } @@ -631,7 +648,7 @@ app.registerExtension({ // Load share option from local storage to determine whether to show // the share button. - const shouldShowShareButton = localStorage.getItem("share_option") !== 'none'; + const shouldShowShareButton = share_option !== 'none'; shareButton.style.display = shouldShowShareButton ? "inline-block" : "none"; menu.append(shareButton); diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index d871c83a..9f100ab1 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -159,28 +159,31 @@ export const showOpenArtShareDialog = () => { OpenArtShareDialog.instance.show(); } -export const showShareDialog = () => { +export const showShareDialog = async (share_option) => { if (!ShareDialog.instance) { - ShareDialog.instance = new ShareDialog(); + ShareDialog.instance = new ShareDialog(share_option); } - app.graphToPrompt().then(prompt => { - // console.log({ prompt }) - return app.graph._nodes; - }).then(nodes => { - // console.log({ nodes }); - const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - if (potential_outputs.length === 0) { - if (potential_output_nodes.length === 0) { - // todo: add support for other output node types (animatediff combine, etc.) - const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); - alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); - } else { - alert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); - } - return; - } - ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); - }); + return app.graphToPrompt() + .then(prompt => { + // console.log({ prompt }) + return app.graph._nodes; + }) + .then(nodes => { + // console.log({ nodes }); + const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); + if (potential_outputs.length === 0) { + if (potential_output_nodes.length === 0) { + // todo: add support for other output node types (animatediff combine, etc.) + const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); + alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); + } else { + alert("To share this, first run a prompt. Once it's done, click 'Share'.\n\nNOTE: Images of the Share target can only be selected in the PreviewImage, SaveImage, and VHS_VideoCombine nodes. In the case of VHS_VideoCombine, only the image/gif and image/webp formats are supported."); + } + return false; + } + ShareDialog.instance.show({ potential_outputs, potential_output_nodes, share_option }); + return true; + }); } export class ShareDialogChooser extends ComfyDialog { @@ -215,9 +218,10 @@ export class ShareDialogChooser extends ComfyDialog { textContent: "Matrix Server", website: "https://app.element.io/#/room/%23comfyui_space%3Amatrix.org", description: "Share your art on the official ComfyUI matrix server", - onclick: () => { - showShareDialog(); - this.close(); + onclick: async () => { + showShareDialog('matrix').then((suc) => { + suc && this.close(); + }) } }, { @@ -226,8 +230,9 @@ export class ShareDialogChooser extends ComfyDialog { website: "https://comfyworkflows.com", description: "Share ComfyUI art on comfyworkflows.com", onclick: () => { - showShareDialog(); - this.close(); + showShareDialog('comfyworkflows').then((suc) => { + suc && this.close(); + }) } }, ]; @@ -370,8 +375,9 @@ export class ShareDialog extends ComfyDialog { static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; static cw_sharekey = ""; - constructor() { + constructor(share_option) { super(); + this.share_option = share_option; this.element = $el("div.comfy-modal", { parent: document.body, style: { 'overflow-y': "auto", @@ -398,12 +404,12 @@ export class ShareDialog extends ComfyDialog { this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) this.matrix_destination_checkbox.style.color = "var(--fg-color)"; - this.matrix_destination_checkbox.checked = localStorage.getItem("share_option") === 'matrix'; //true; + this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; //true; this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, []) const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"]) this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)"; - this.comfyworkflows_destination_checkbox.checked = localStorage.getItem("share_option") !== 'matrix'; + this.comfyworkflows_destination_checkbox.checked = this.share_option !== 'matrix'; this.matrix_homeserver_input = $el("input", { type: 'text', id: "matrix_homeserver", placeholder: "matrix.org", value: ShareDialog.matrix_auth.homeserver || 'matrix.org' }, []); this.matrix_username_input = $el("input", { type: 'text', placeholder: "Username", value: ShareDialog.matrix_auth.username || '' }, []); @@ -460,8 +466,8 @@ export class ShareDialog extends ComfyDialog { textContent: "Close", onclick: () => { // Reset state - this.matrix_destination_checkbox.checked = localStorage.getItem("share_option") === 'matrix'; - this.comfyworkflows_destination_checkbox.checked = localStorage.getItem("share_option") !== 'matrix'; + this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; + this.comfyworkflows_destination_checkbox.checked = this.share_option !== 'matrix'; this.share_button.textContent = "Share"; this.share_button.style.display = "inline-block"; this.final_message.innerHTML = ""; @@ -632,8 +638,8 @@ export class ShareDialog extends ComfyDialog { textContent: "Close", onclick: () => { // Reset state - this.matrix_destination_checkbox.checked = localStorage.getItem("share_option") === 'matrix'; - this.comfyworkflows_destination_checkbox.checked = localStorage.getItem("share_option") !== 'matrix'; + this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; + this.comfyworkflows_destination_checkbox.checked = this.share_option !== 'matrix'; this.share_button.textContent = "Share"; this.share_button.style.display = "inline-block"; this.final_message.innerHTML = ""; @@ -825,7 +831,7 @@ export class ShareDialog extends ComfyDialog { return res; } - show({ potential_outputs, potential_output_nodes }) { + show({ potential_outputs, potential_output_nodes, share_option }) { // console.log({ potential_outputs, potential_output_nodes }) this.radio_buttons.innerHTML = ""; // clear the radio buttons const new_radio_buttons = $el("div", { @@ -900,5 +906,14 @@ export class ShareDialog extends ComfyDialog { // this.radio_buttons.appendChild(subheader); this.radio_buttons.appendChild(new_radio_buttons); this.element.style.display = "block"; + + share_option = share_option || this.share_option; + if (share_option === 'comfyworkflows') { + this.matrix_destination_checkbox.checked = false; + this.comfyworkflows_destination_checkbox.checked = true; + } else { + this.matrix_destination_checkbox.checked = true; + this.comfyworkflows_destination_checkbox.checked = false; + } } } diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index a52c7678..d0b7337e 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -40,12 +40,35 @@ export class OpenArtShareDialog extends ComfyDialog { this.selectedOutputIndex = 0; this.uploadedImages = []; } - readKeyFromLocalStorage() { - return localStorage.getItem(LOCAL_STORAGE_KEY) || ""; + + async readKey() { + let key = "" + try { + // console.log("Fetching openart key") + key = await api.fetchApi(`/manager/get_openart_auth`) + .then(response => response.json()) + .then(data => { + return data.openart_key; + }) + .catch(error => { + // console.log(error); + }); + } catch (error) { + // console.log(error); + } + return key || ""; } - saveKeyToLocalStorage(value) { - localStorage.setItem(LOCAL_STORAGE_KEY, value); + + async saveKey(value) { + await api.fetchApi(`/manager/set_openart_auth`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + openart_key: value + }) + }); } + createButtons() { const sectionStyle = { marginBottom: "10px", @@ -242,6 +265,7 @@ export class OpenArtShareDialog extends ComfyDialog { return layout; } + async fetchApi(path, options, statusText) { if (statusText) { this.message.textContent = statusText; @@ -275,6 +299,7 @@ export class OpenArtShareDialog extends ComfyDialog { data, }; } + async uploadThumbnail(uploadFile) { const form = new FormData(); form.append("file", uploadFile); @@ -304,9 +329,10 @@ export class OpenArtShareDialog extends ComfyDialog { } } } + async handleShareButtonClick() { this.message.textContent = ""; - this.saveKeyToLocalStorage(this.keyInput.value); + await this.saveKey(this.keyInput.value); try { this.shareButton.disabled = true; this.shareButton.textContent = "Sharing..."; @@ -317,6 +343,7 @@ export class OpenArtShareDialog extends ComfyDialog { this.shareButton.disabled = false; this.shareButton.textContent = "Share"; } + async share() { const prompt = await app.graphToPrompt(); const workflowJSON = prompt["workflow"]; @@ -382,10 +409,10 @@ export class OpenArtShareDialog extends ComfyDialog { throw new Error("Error sharing workflow: " + e.message); } } - show({ potential_outputs, potential_output_nodes } = {}) { + + async show({ potential_outputs, potential_output_nodes } = {}) { this.element.style.display = "block"; - // read key from local storage and set it to the input - const key = this.readKeyFromLocalStorage(); + const key = await this.readKey(); this.keyInput.value = key; } } From 07b85b66ae860cb65050e00edc3e996c1cd4ae8f Mon Sep 17 00:00:00 2001 From: johnqiao Date: Sun, 19 Nov 2023 01:41:45 -0700 Subject: [PATCH 12/21] Fix typo --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 7b00495d..ef37beab 100644 --- a/__init__.py +++ b/__init__.py @@ -1413,7 +1413,7 @@ async def channel_url_list(request): @server.PromptServer.instance.routes.get("/manager/share_option") -async def channel_url_list(request): +async def share_option(request): if "value" in request.rel_url.query: get_config()['share_option'] = request.rel_url.query['value'] write_config() From 20bffcaa405fd858d4fca68d6cf658090bdd9415 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 24 Nov 2023 16:37:19 -0700 Subject: [PATCH 13/21] Right click to share; Support SaveAnimatedWEBP as the output node --- __init__.py | 20 ++- js/comfyui-manager.js | 50 +++++- js/comfyui-share-common.js | 58 ++++++- js/comfyui-share-openart.js | 325 +++++++++++++++++++++++++++++++----- 4 files changed, 404 insertions(+), 49 deletions(-) diff --git a/__init__.py b/__init__.py index 3f2118c1..53a4533c 100644 --- a/__init__.py +++ b/__init__.py @@ -826,14 +826,22 @@ def get_current_snapshot(): def save_snapshot_with_postfix(postfix): - now = datetime.datetime.now() + now = datetime.datetime.now() - date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") - file_name = f"{date_time_format}_{postfix}" + date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S") + file_name = f"{date_time_format}_{postfix}" - path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{file_name}.json") - with open(path, "w") as json_file: - json.dump(get_current_snapshot(), json_file, indent=4) + path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{file_name}.json") + with open(path, "w") as json_file: + json.dump(get_current_snapshot(), json_file, indent=4) + + +@server.PromptServer.instance.routes.get("/snapshot/get_current") +async def get_current_snapshot_api(request): + try: + return web.json_response(get_current_snapshot(), content_type='application/json') + except: + return web.Response(status=400) @server.PromptServer.instance.routes.get("/snapshot/save") diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index d6e9c8e9..d2e64916 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -2,6 +2,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes, ShareDialogChooser, showOpenArtShareDialog, showShareDialog } from "./comfyui-share-common.js"; +import { OpenArtShareDialog } from "./comfyui-share-openart.js"; import { CustomNodesInstaller } from "./custom-nodes-downloader.js"; import { AlternativesInstaller } from "./a1111-alter-downloader.js"; import { SnapshotManager } from "./snapshot.js"; @@ -256,6 +257,18 @@ function newDOMTokenList(initialTokens) { return classList; } +/** + * Check whether the node is a potential output node (img, gif or video output) + */ +const isOutputNode = (node) => { + return [ + "VHS_VideoCombine", + "PreviewImage", + "SaveImage", + "ADE_AnimateDiffCombine", + "SaveAnimatedWEBP", + ].includes(node.type); +} // ----------- class ManagerMenuDialog extends ComfyDialog { @@ -695,10 +708,12 @@ app.registerExtension({ } return r; }; + + this._addExtraNodeContextMenu(nodeType, app); }, async loadedGraphNode(node, app) { - if (node.has_errors) { + if (node.has_errors) { const onDrawForeground = node.onDrawForeground; node.onDrawForeground = function (ctx) { const r = onDrawForeground?.apply?.(this, arguments); @@ -748,5 +763,36 @@ app.registerExtension({ return r; }; } - } + }, + + _addExtraNodeContextMenu(node, app) { + node.prototype.getExtraMenuOptions = function (_, options) { + if (isOutputNode(node)) { + const { potential_outputs } = getPotentialOutputsAndOutputNodes([this]); + const hasOutput = potential_outputs.length > 0; + options.push({ + content: "🏞️ Share Output", + disabled: !hasOutput, + callback: (obj) => { + if (!ShareDialog.instance) { + ShareDialog.instance = new ShareDialog(); + } + const shareButton = document.getElementById("shareButton"); + if (shareButton) { + const currentNode = this; + if (!OpenArtShareDialog.instance) { + OpenArtShareDialog.instance = new OpenArtShareDialog(); + } + OpenArtShareDialog.instance.selectedNodeId = currentNode.id; + if (!ShareDialog.instance) { + ShareDialog.instance = new ShareDialog(share_option); + } + ShareDialog.instance.selectedNodeId = currentNode.id; + shareButton.click(); + } + } + }, null); + } + } + }, }); diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index 9f100ab1..91082f41 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -8,6 +8,7 @@ export const SUPPORTED_OUTPUT_NODE_TYPES = [ "SaveImage", "VHS_VideoCombine", "ADE_AnimateDiffCombine", + "SaveAnimatedWEBP", ] var docStyle = document.createElement('style'); @@ -127,6 +128,17 @@ export function getPotentialOutputsAndOutputNodes(nodes) { } } } + else if (node.type === "SaveAnimatedWEBP") { + potential_output_nodes.push(node); + + // check if node has an 'images' array property + if (node.hasOwnProperty("images") && Array.isArray(node.images)) { + // iterate over the images array and add each image to the potential_outputs array + for (let j = 0; j < node.images.length; j++) { + potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); + } + } + } } return { potential_outputs, potential_output_nodes }; } @@ -156,7 +168,16 @@ export const showOpenArtShareDialog = () => { if (!OpenArtShareDialog.instance) { OpenArtShareDialog.instance = new OpenArtShareDialog(); } - OpenArtShareDialog.instance.show(); + + return app.graphToPrompt() + .then(prompt => { + // console.log({ prompt }) + return app.graph._nodes; + }) + .then(nodes => { + const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); + OpenArtShareDialog.instance.show({ potential_outputs, potential_output_nodes}); + }) } export const showShareDialog = async (share_option) => { @@ -199,7 +220,7 @@ export class ShareDialogChooser extends ComfyDialog { {}, [...this.createButtons()]), ]); - + this.selectedNodeId = null; } createButtons() { const buttons = [ @@ -832,6 +853,26 @@ export class ShareDialog extends ComfyDialog { } show({ potential_outputs, potential_output_nodes, share_option }) { + // Sort `potential_output_nodes` by node ID to make the order always + // consistent, but we should also keep `potential_outputs` in the same + // order as `potential_output_nodes`. + const potential_output_to_order = {}; + potential_output_nodes.forEach((node, index) => { + potential_output_to_order[node.id] =[node, potential_outputs[index]]; + }) + // Sort the object `potential_output_to_order` by key (node ID) + const sorted_potential_output_to_order = Object.fromEntries( + Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id) + ); + const sorted_potential_outputs = [] + const sorted_potential_output_nodes = [] + for (const [key, value] of Object.entries(sorted_potential_output_to_order)) { + sorted_potential_output_nodes.push(value[0]); + sorted_potential_outputs.push(value[1]); + } + potential_output_nodes = sorted_potential_output_nodes; + potential_outputs = sorted_potential_outputs; + // console.log({ potential_outputs, potential_output_nodes }) this.radio_buttons.innerHTML = ""; // clear the radio buttons const new_radio_buttons = $el("div", { @@ -841,6 +882,7 @@ export class ShareDialog extends ComfyDialog { 'max-height': '400px', } }, potential_outputs.map((output, index) => { + const potential_output_node = potential_output_nodes[index]; const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) let radio_button_img; if (output.type === "image" || output.type === "temp") { @@ -859,7 +901,17 @@ export class ShareDialog extends ComfyDialog { // } }, [output.title]) radio_button.style.color = "var(--fg-color)"; - radio_button.checked = index === 0; + + // Make the radio button checked if it's the selected node, + // otherwise make the first radio button checked. + if (this.selectedNodeId) { + if (this.selectedNodeId === potential_output_node.id) { + radio_button.checked = true; + } + } else { + radio_button.checked = index === 0; + } + if (radio_button.checked) { this.selectedOutputIndex = index; } diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index d0b7337e..f2fe1e2f 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -6,18 +6,35 @@ const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true"; //const DEFAULT_HOMEPAGE_URL = "http://localhost:8080/workflows/dev?developer=true"; - const API_ENDPOINT = "https://openart.ai/api"; +const API_ENDPOINT = "https://openart.ai/api"; //const API_ENDPOINT = "http://localhost:8080/api"; const style = ` -.openart-share-dialog a { - color: #f8f8f8; -} -.openart-share-dialog a:hover { - color: #007bff; -} + .openart-share-dialog a { + color: #f8f8f8; + } + .openart-share-dialog a:hover { + color: #007bff; + } + .output_label { + border: 5px solid transparent; + } + .output_label:hover { + border: 5px solid #59E8C6; + } + .output_label.checked { + border: 5px solid #59E8C6; + } `; +// Shared component styles +const sectionStyle = { + marginBottom: 0, + padding: 0, + borderRadius: "8px", + boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)", +}; + export class OpenArtShareDialog extends ComfyDialog { static instance = null; @@ -38,13 +55,14 @@ export class OpenArtShareDialog extends ComfyDialog { [$el("div.comfy-modal-content", {}, [...this.createButtons()])] ); this.selectedOutputIndex = 0; + this.selectedNodeId = null; this.uploadedImages = []; + this.selectedFile = null; } async readKey() { let key = "" try { - // console.log("Fetching openart key") key = await api.fetchApi(`/manager/get_openart_auth`) .then(response => response.json()) .then(data => { @@ -70,13 +88,6 @@ export class OpenArtShareDialog extends ComfyDialog { } createButtons() { - const sectionStyle = { - marginBottom: "10px", - padding: "15px", - borderRadius: "8px", - boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)", - }; - const inputStyle = { display: "block", minWidth: "500px", @@ -125,12 +136,20 @@ export class OpenArtShareDialog extends ComfyDialog { const file = e.target.files[0]; if (!file) { this.previewImage.src = ""; + this.previewImage.style.display = "none"; return; } const reader = new FileReader(); reader.onload = async (e) => { const imgData = e.target.result; this.previewImage.src = imgData; + this.previewImage.style.display = "block"; + this.selectedFile = null + // Once user uploads an image, we uncheck all radio buttons + this.radioButtons.forEach((ele) => { + ele.checked = false; + ele.parentElement.classList.remove("checked"); + }); }; reader.readAsDataURL(file); }); @@ -138,7 +157,7 @@ export class OpenArtShareDialog extends ComfyDialog { // preview image this.previewImage = $el("img", { src: "", - style: { maxWidth: "100%", maxHeight: "100px" }, + style: { maxWidth: "100%", maxHeight: "100px", display: "none" }, }); this.keyInput = $el("input", { @@ -148,7 +167,7 @@ export class OpenArtShareDialog extends ComfyDialog { }); this.NameInput = $el("input", { type: "text", - placeholder: "Name (required)", + placeholder: "Title (required)", style: inputStyle, }); this.descriptionInput = $el("textarea", { @@ -159,6 +178,19 @@ export class OpenArtShareDialog extends ComfyDialog { }, }); + // Header Section + const headerSection = $el("h3", { + textContent: "Share your workflow to OpenArt", + size: 3, + color: "white", + style: { + 'text-align': 'center', + color: 'white', + padding: '10px', + 'margin-bottom': '10px', + } + }); + // LinkSection this.communityLink = $el("a", { style: hyperLinkStyle, @@ -173,7 +205,7 @@ export class OpenArtShareDialog extends ComfyDialog { href: DEFAULT_HOMEPAGE_URL, target: "_blank" }, ["πŸ‘‰ Get your API key here"]) - const LinkSection = $el( + const linkSection = $el( "div", { style: { @@ -189,16 +221,25 @@ export class OpenArtShareDialog extends ComfyDialog { ); // Account Section - const AccountSection = $el("div", { style: sectionStyle }, [ + const accountSection = $el("div", { style: sectionStyle }, [ $el("label", { style: labelStyle }, ["OpenArt API Key"]), this.keyInput, ]); - // Additional Inputs Section - const additionalInputsSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["Image/Thumbnail (Required)"]), + // Output Upload Section + const outputUploadSection = $el("div", { style: sectionStyle }, [ + $el("label", { style: labelStyle }, ["Upload Image/Thumbnail (Required)"]), this.uploadImagesInput, this.previewImage, + ]); + + // Outputs Section + this.outputsSection = $el("div", { + id: "selectOutputs", + }, []); + + // Additional Inputs Section + const additionalInputsSection = $el("div", { style: sectionStyle }, [ $el("label", { style: labelStyle }, ["Workflow Information"]), this.NameInput, this.descriptionInput, @@ -256,8 +297,11 @@ export class OpenArtShareDialog extends ComfyDialog { // Composing the full layout const layout = [ - LinkSection, - AccountSection, + headerSection, + linkSection, + accountSection, + outputUploadSection, + this.outputsSection, additionalInputsSection, this.message, buttonsSection, @@ -347,6 +391,7 @@ export class OpenArtShareDialog extends ComfyDialog { async share() { const prompt = await app.graphToPrompt(); const workflowJSON = prompt["workflow"]; + const workflowAPIJSON = prompt["output"]; const form_values = { name: this.NameInput.value, description: this.descriptionInput.value, @@ -356,30 +401,41 @@ export class OpenArtShareDialog extends ComfyDialog { throw new Error("API key is required"); } - if (!this.uploadImagesInput.files[0]) { + if (!this.uploadImagesInput.files[0] && !this.selectedFile) { throw new Error("Thumbnail is required"); } if (!form_values.name) { - throw new Error("Name is required"); + throw new Error("Title is required"); } + const current_snapshot = await api.fetchApi(`/snapshot/get_current`) + .then(response => response.json()) + .catch(error => { + // console.log(error); + }); + + if (!this.uploadedImages.length) { - for (const file of this.uploadImagesInput.files) { - try { - await this.uploadThumbnail(file); - } catch (e) { - this.uploadedImages = []; - throw new Error(e.message); + if (this.selectedFile) { + await this.uploadThumbnail(this.selectedFile); + } else { + for (const file of this.uploadImagesInput.files) { + try { + await this.uploadThumbnail(file); + } catch (e) { + this.uploadedImages = []; + throw new Error(e.message); + } } - } - if (this.uploadImagesInput.files.length === 0) { - throw new Error("No thumbnail uploaded"); - } + if (this.uploadImagesInput.files.length === 0) { + throw new Error("No thumbnail uploaded"); + } - if (this.uploadImagesInput.files.length === 0) { - throw new Error("No thumbnail uploaded"); + if (this.uploadImagesInput.files.length === 0) { + throw new Error("No thumbnail uploaded"); + } } } @@ -393,6 +449,10 @@ export class OpenArtShareDialog extends ComfyDialog { workflow_json: workflowJSON, upload_images: this.uploadedImages, form_values, + advanced_config: { + workflow_api_json: workflowAPIJSON, + snapshot: current_snapshot, + } }), }, "Uploading workflow..." @@ -403,6 +463,18 @@ export class OpenArtShareDialog extends ComfyDialog { if (workflow_id) { const url = `https://openart.ai/workflows/-/-/${workflow_id}`; this.message.innerHTML = `Workflow has been shared successfully. Click here to view it.`; + this.previewImage.src = ""; + this.previewImage.style.display = "none"; + this.uploadedImages = []; + this.NameInput.value = ""; + this.descriptionInput.value = ""; + this.radioButtons.forEach((ele) => { + ele.checked = false; + ele.parentElement.classList.remove("checked"); + }); + this.selectedOutputIndex = 0; + this.selectedNodeId = null; + this.selectedFile = null; } } } catch (e) { @@ -410,9 +482,186 @@ export class OpenArtShareDialog extends ComfyDialog { } } + async fetchImageBlob(url) { + const response = await fetch(url); + const blob = await response.blob(); + return blob; + } + async show({ potential_outputs, potential_output_nodes } = {}) { + // Sort `potential_output_nodes` by node ID to make the order always + // consistent, but we should also keep `potential_outputs` in the same + // order as `potential_output_nodes`. + const potential_output_to_order = {}; + potential_output_nodes.forEach((node, index) => { + potential_output_to_order[node.id] =[node, potential_outputs[index]]; + }) + // Sort the object `potential_output_to_order` by key (node ID) + const sorted_potential_output_to_order = Object.fromEntries( + Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id) + ); + const sorted_potential_outputs = [] + const sorted_potential_output_nodes = [] + for (const [key, value] of Object.entries(sorted_potential_output_to_order)) { + sorted_potential_output_nodes.push(value[0]); + sorted_potential_outputs.push(value[1]); + } + potential_output_nodes = sorted_potential_output_nodes; + potential_outputs = sorted_potential_outputs; + + this.message.innerHTML = ""; + this.message.textContent = ""; this.element.style.display = "block"; + this.previewImage.src = ""; + this.previewImage.style.display = "none"; const key = await this.readKey(); this.keyInput.value = key; + + // If `selectedNodeId` is provided, we will select the corresponding radio + // button for the node. In addition, we move the selected radio button to + // the top of the list. + if (this.selectedNodeId) { + const index = potential_output_nodes.findIndex(node => node.id === this.selectedNodeId); + if (index >= 0) { + this.selectedOutputIndex = index; + } + } + + this.radioButtons = []; + const new_radio_buttons = $el("div", + { + id: "selectOutput-Options", + style: { + 'overflow-y': 'scroll', + 'max-height': '250px', + + 'display': 'grid', + 'grid-template-columns': 'repeat(auto-fit, minmax(100px, 1fr))', + 'grid-template-rows': 'auto', + 'grid-column-gap': '10px', + 'grid-row-gap': '10px', + 'margin-bottom': '10px', + 'padding': '10px', + 'border-radius': '8px', + 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.05)', + 'background-color': 'var(--bg-color)', + } + }, + potential_outputs.map((output, index) => { + const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) + let radio_button_img; + if (output.type === "image" || output.type === "temp") { + radio_button_img = $el("img", { src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, style: { width: "100px", height: "100px", objectFit: "cover", borderRadius: "5px" } }, []); + } else if (output.type === "output") { + radio_button_img = $el("img", { src: output.output.value, style: { width: "auto", height: "100px", objectFit: "cover", borderRadius: "5px" } }, []); + } else { + // unsupported output type + // this should never happen + // TODO + radio_button_img = $el("img", { src: "", style: { width: "auto", height: "100px" } }, []); + } + const radio_button_text = $el("span", { + style: { + color: 'gray', + display: 'block', + fontSize: '12px', + overflowX: 'hidden', + textOverflow: 'ellipsis', + textWrap: 'nowrap', + maxWidth: '100px', + } + }, [output.title]) + const node_id_chip = $el("span", { + style: { + color: '#FBFBFD', + display: 'block', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + fontSize: '12px', + overflowX: 'hidden', + padding: '2px 3px', + textOverflow: 'ellipsis', + textWrap: 'nowrap', + maxWidth: '100px', + position: 'absolute', + top: '3px', + left: '3px', + borderRadius: '3px', + } + }, [`Node: ${potential_output_nodes[index].id}`]) + radio_button.style.color = "var(--fg-color)"; + radio_button.checked = this.selectedOutputIndex === index; + + radio_button.onchange = async () => { + this.selectedOutputIndex = parseInt(radio_button.value); + + // Remove the "checked" class from all radio buttons + this.radioButtons.forEach((ele) => { + ele.parentElement.classList.remove("checked"); + }); + radio_button.parentElement.classList.add("checked"); + + this.fetchImageBlob(radio_button_img.src).then((blob) => { + const file = new File([blob], output.image.filename, { + type: blob.type, + }); + this.previewImage.src = radio_button_img.src; + this.previewImage.style.display = "block"; + this.selectedFile = file; + }) + }; + + if (radio_button.checked) { + this.fetchImageBlob(radio_button_img.src).then((blob) => { + const file = new File([blob], output.image.filename, { + type: blob.type, + }); + this.previewImage.src = radio_button_img.src; + this.previewImage.style.display = "block"; + this.selectedFile = file; + }) + } + + this.radioButtons.push(radio_button); + + return $el(`label.output_label${radio_button.checked ? '.checked' : ''}`, { + style: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + marginBottom: "10px", + cursor: "pointer", + position: 'relative', + } + }, [radio_button_img, radio_button_text, radio_button, node_id_chip]); + }) + ); + + const header = $el("div", + { + textContent: "Or choose an output below", + fontSize: '15px', + color: "white", + style: { + ...sectionStyle, + color: 'white', + }, + }, + [ + $el("p", { + textContent: "Scroll to see all", + size: 2, + color: "white", + style: { + color: 'white', + margin: '5px 0', + fontSize: '12px', + }, + }, []) + ] + ); + this.outputsSection.innerHTML = ""; + this.outputsSection.appendChild(header); + this.outputsSection.appendChild(new_radio_buttons); } } From 12e7f3648e2c386d7be91d3b4663197664c99ad9 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 24 Nov 2023 17:15:21 -0700 Subject: [PATCH 14/21] Add checkbox for participating the contest --- js/comfyui-share-openart.js | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index f2fe1e2f..9fcc5d37 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -245,6 +245,31 @@ export class OpenArtShareDialog extends ComfyDialog { this.descriptionInput, ]); + // OpenArt Contest Section + this.joinContestCheckbox = $el("input",{type:'checkbox', id:"join_contest"},[]) + this.joinContestDescription = $el("a", { + style: { + ...hyperLinkStyle, + display: 'inline-block', + color: "#59E8C6", + fontSize: '12px', + marginLeft: '10px', + marginBottom: 0, + }, + href: "https://contest.openart.ai/", + target: "_blank" + }, ["πŸ† I'm participating in the OpenArt workflow contest"]) + this.joinContestLabel = $el("label", { + style: { + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + } + },[this.joinContestCheckbox, this.joinContestDescription]) + const contestSection = $el("div", { style: sectionStyle }, [ + this.joinContestLabel, + ]); + // Message Section this.message = $el( "div", @@ -303,6 +328,7 @@ export class OpenArtShareDialog extends ComfyDialog { outputUploadSection, this.outputsSection, additionalInputsSection, + contestSection, this.message, buttonsSection, ]; @@ -439,6 +465,8 @@ export class OpenArtShareDialog extends ComfyDialog { } } + const join_contest = this.joinContestCheckbox.checked; + try { const response = await this.fetchApi( "/workflows/publish", @@ -452,7 +480,8 @@ export class OpenArtShareDialog extends ComfyDialog { advanced_config: { workflow_api_json: workflowAPIJSON, snapshot: current_snapshot, - } + }, + join_contest, }), }, "Uploading workflow..." @@ -507,7 +536,7 @@ export class OpenArtShareDialog extends ComfyDialog { sorted_potential_outputs.push(value[1]); } potential_output_nodes = sorted_potential_output_nodes; - potential_outputs = sorted_potential_outputs; + potential_outputs = sorted_potential_outputs.filter((output) => !!output); this.message.innerHTML = ""; this.message.textContent = ""; From 355991ecc95d377381a1b4b475fb47f814a21755 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 24 Nov 2023 17:28:16 -0700 Subject: [PATCH 15/21] Add contest notice --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9df5886a..fe07c816 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ ![menu](misc/menu.jpg) ## NOTICE -* If you wish to hide the 'Share' button, please refer to the settings menu. +* πŸ† John us for the [ComfyUI Workflow Contest](https://contest.openart.ai/), hosted by OpenArt AI (11.27.2023 - 12.15.2023). Our esteemed judge panel includes Scott E. Detweiler, Olivio Sarikas, MERJIC麦橘, among others. We're also thrilled to have the authors of ComfyUI Manager and AnimateDiff as our special guests! +* If you wish to hide the "Share" button, click "Manager" and choose "Share: None" option. * You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page. * Versions prior to V0.22.2 will no longer detect missing nodes unless using a local database. Please update ComfyUI-Manager to the latest version. From e32072ab3465827c4d0d3124e6f5809ea0ca9c3c Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 24 Nov 2023 19:06:40 -0700 Subject: [PATCH 16/21] Fix the original missing menu options --- js/comfyui-manager.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index d2e64916..091185c5 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -766,10 +766,19 @@ app.registerExtension({ }, _addExtraNodeContextMenu(node, app) { - node.prototype.getExtraMenuOptions = function (_, options) { - if (isOutputNode(node)) { + const origGetExtraMenuOptions = node.prototype.getExtraMenuOptions; + node.prototype.getExtraMenuOptions = function (_, options) { + origGetExtraMenuOptions?.apply?.(this, arguments); + if (isOutputNode(node)) { const { potential_outputs } = getPotentialOutputsAndOutputNodes([this]); const hasOutput = potential_outputs.length > 0; + + // Check if the previous menu option is `null`. If it's not, + // then we need to add a `null` as a separator. + if (options[options.length - 1] !== null) { + options.push(null); + } + options.push({ content: "🏞️ Share Output", disabled: !hasOutput, @@ -793,6 +802,6 @@ app.registerExtension({ } }, null); } - } + } }, }); From bb2a2ece881e72a8e8846b1084dcbdca5f03dc98 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 24 Nov 2023 19:44:58 -0700 Subject: [PATCH 17/21] Better layout for uploading thumbnails --- js/comfyui-share-openart.js | 39 ++++++++++++++----------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 9fcc5d37..c7b3cc29 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -33,6 +33,9 @@ const sectionStyle = { padding: 0, borderRadius: "8px", boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)", + display: "flex", + flexDirection: "column", + justifyContent: "center", }; export class OpenArtShareDialog extends ComfyDialog { @@ -109,7 +112,7 @@ export class OpenArtShareDialog extends ComfyDialog { const labelStyle = { color: "#f8f8f8", display: "block", - margin: "10px 0", + margin: "10px 0 0 0", fontWeight: "bold", textDecoration: "none", }; @@ -157,7 +160,7 @@ export class OpenArtShareDialog extends ComfyDialog { // preview image this.previewImage = $el("img", { src: "", - style: { maxWidth: "100%", maxHeight: "100px", display: "none" }, + style: { width: "100%", maxHeight: "100px", objectFit: "contain", display: "none" }, }); this.keyInput = $el("input", { @@ -186,8 +189,7 @@ export class OpenArtShareDialog extends ComfyDialog { style: { 'text-align': 'center', color: 'white', - padding: '10px', - 'margin-bottom': '10px', + margin: '0 0 10px 0', } }); @@ -222,15 +224,16 @@ export class OpenArtShareDialog extends ComfyDialog { // Account Section const accountSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["OpenArt API Key"]), + $el("label", { style: labelStyle }, ["1️⃣ OpenArt API Key"]), this.keyInput, ]); // Output Upload Section const outputUploadSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["Upload Image/Thumbnail (Required)"]), - this.uploadImagesInput, + $el("label", { style: {...labelStyle, margin: "10px 0 0 0"} }, ["2️⃣ Image/Thumbnail (Required)"]), + $el("div", { style: { ...labelStyle, margin: "5px 0 10px 0", fontSize: '12px', fontWeight: "normal"} }, ["Upload or choose one from the outputs"]), this.previewImage, + this.uploadImagesInput, ]); // Outputs Section @@ -240,7 +243,7 @@ export class OpenArtShareDialog extends ComfyDialog { // Additional Inputs Section const additionalInputsSection = $el("div", { style: sectionStyle }, [ - $el("label", { style: labelStyle }, ["Workflow Information"]), + $el("label", { style: labelStyle }, ["3️⃣ Workflow Information"]), this.NameInput, this.descriptionInput, ]); @@ -562,7 +565,7 @@ export class OpenArtShareDialog extends ComfyDialog { id: "selectOutput-Options", style: { 'overflow-y': 'scroll', - 'max-height': '250px', + 'max-height': '200px', 'display': 'grid', 'grid-template-columns': 'repeat(auto-fit, minmax(100px, 1fr))', @@ -666,29 +669,17 @@ export class OpenArtShareDialog extends ComfyDialog { }) ); - const header = $el("div", - { - textContent: "Or choose an output below", - fontSize: '15px', - color: "white", - style: { - ...sectionStyle, - color: 'white', - }, - }, - [ + const header = $el("p", { - textContent: "Scroll to see all", + textContent: this.radioButtons.length === 0 ? "Queue Prompt to see the outputs" : "Outputs (scroll to see all)", size: 2, color: "white", style: { color: 'white', - margin: '5px 0', + margin: '0 0 5px 0', fontSize: '12px', }, }, []) - ] - ); this.outputsSection.innerHTML = ""; this.outputsSection.appendChild(header); this.outputsSection.appendChild(new_radio_buttons); From ab257ed7507b8f533a686751f6772173608ce919 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Fri, 24 Nov 2023 21:56:14 -0700 Subject: [PATCH 18/21] Clear `this.uploadedImages` when opening the dialog --- js/comfyui-share-openart.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index c7b3cc29..39c398fe 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -548,6 +548,7 @@ export class OpenArtShareDialog extends ComfyDialog { this.previewImage.style.display = "none"; const key = await this.readKey(); this.keyInput.value = key; + this.uploadedImages = []; // If `selectedNodeId` is provided, we will select the corresponding radio // button for the node. In addition, we move the selected radio button to From 3dc35c049895186a86d0e9d0945a3a97e1b5dbb4 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Sat, 25 Nov 2023 00:54:20 -0700 Subject: [PATCH 19/21] Make sure arrays `potential_output_nodes` and `potential_outputs` have the same length to avoid `undefined` bug when sharing --- js/comfyui-share-common.js | 20 +++++++++----------- js/comfyui-share-openart.js | 4 ++-- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/js/comfyui-share-common.js b/js/comfyui-share-common.js index 91082f41..e821f867 100644 --- a/js/comfyui-share-common.js +++ b/js/comfyui-share-common.js @@ -47,30 +47,26 @@ export function getPotentialOutputsAndOutputNodes(nodes) { } if (node.type === "SaveImage") { - potential_output_nodes.push(node); - // check if node has an 'images' array property if (node.hasOwnProperty("images") && Array.isArray(node.images)) { // iterate over the images array and add each image to the potential_outputs array for (let j = 0; j < node.images.length; j++) { + potential_output_nodes.push(node); potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); } } } else if (node.type === "PreviewImage") { - potential_output_nodes.push(node); - // check if node has an 'images' array property if (node.hasOwnProperty("images") && Array.isArray(node.images)) { // iterate over the images array and add each image to the potential_outputs array for (let j = 0; j < node.images.length; j++) { + potential_output_nodes.push(node); potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); } } } else if (node.type === "VHS_VideoCombine") { - potential_output_nodes.push(node); - // check if node has a 'widgets' array property, with type 'image' if (node.hasOwnProperty("widgets") && Array.isArray(node.widgets)) { // iterate over the widgets array and add each image to the potential_outputs array @@ -84,6 +80,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) { if (parsedURLVals.type !== "output") { // TODO } + potential_output_nodes.push(node); potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } }); } } else if (node.widgets[j].type === "preview") { @@ -100,6 +97,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) { if (parsedURLVals.type !== "output") { // TODO } + potential_output_nodes.push(node); potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": `/view?filename=${parsedURLVals.filename}&subfolder=${parsedURLVals.subfolder}&type=${parsedURLVals.type}&format=${parsedURLVals.format}`, "format": parsedURLVals.format } }); } } @@ -107,8 +105,6 @@ export function getPotentialOutputsAndOutputNodes(nodes) { } } else if (node.type === "ADE_AnimateDiffCombine") { - potential_output_nodes.push(node); - // check if node has a 'widgets' array property, with type 'image' if (node.hasOwnProperty("widgets") && Array.isArray(node.widgets)) { // iterate over the widgets array and add each image to the potential_outputs array @@ -122,6 +118,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) { // TODO continue; } + potential_output_nodes.push(node); potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "type": parsedURLVals.type, "value": widgetValue, "format": parsedURLVals.format } }); } } @@ -129,17 +126,18 @@ export function getPotentialOutputsAndOutputNodes(nodes) { } } else if (node.type === "SaveAnimatedWEBP") { - potential_output_nodes.push(node); - // check if node has an 'images' array property if (node.hasOwnProperty("images") && Array.isArray(node.images)) { // iterate over the images array and add each image to the potential_outputs array for (let j = 0; j < node.images.length; j++) { + potential_output_nodes.push(node); potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); } } } } + + // Note: make sure that two arrays are the same length return { potential_outputs, potential_output_nodes }; } @@ -858,7 +856,7 @@ export class ShareDialog extends ComfyDialog { // order as `potential_output_nodes`. const potential_output_to_order = {}; potential_output_nodes.forEach((node, index) => { - potential_output_to_order[node.id] =[node, potential_outputs[index]]; + potential_output_to_order[node.id] = [node, potential_outputs[index]]; }) // Sort the object `potential_output_to_order` by key (node ID) const sorted_potential_output_to_order = Object.fromEntries( diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 39c398fe..11e65d92 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -526,7 +526,7 @@ export class OpenArtShareDialog extends ComfyDialog { // order as `potential_output_nodes`. const potential_output_to_order = {}; potential_output_nodes.forEach((node, index) => { - potential_output_to_order[node.id] =[node, potential_outputs[index]]; + potential_output_to_order[node.id] = [node, potential_outputs[index]]; }) // Sort the object `potential_output_to_order` by key (node ID) const sorted_potential_output_to_order = Object.fromEntries( @@ -539,7 +539,7 @@ export class OpenArtShareDialog extends ComfyDialog { sorted_potential_outputs.push(value[1]); } potential_output_nodes = sorted_potential_output_nodes; - potential_outputs = sorted_potential_outputs.filter((output) => !!output); + potential_outputs = sorted_potential_outputs; this.message.innerHTML = ""; this.message.textContent = ""; From e6f90270150794863d7ac24ec3dc29f8cd97f597 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Sat, 25 Nov 2023 01:47:22 -0700 Subject: [PATCH 20/21] Fix filename issue --- js/comfyui-share-openart.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 11e65d92..6bcbcaeb 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -583,10 +583,13 @@ export class OpenArtShareDialog extends ComfyDialog { potential_outputs.map((output, index) => { const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) let radio_button_img; + let filename; if (output.type === "image" || output.type === "temp") { radio_button_img = $el("img", { src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, style: { width: "100px", height: "100px", objectFit: "cover", borderRadius: "5px" } }, []); + filename = output.image.filename } else if (output.type === "output") { radio_button_img = $el("img", { src: output.output.value, style: { width: "auto", height: "100px", objectFit: "cover", borderRadius: "5px" } }, []); + filename = output.filename } else { // unsupported output type // this should never happen @@ -634,7 +637,7 @@ export class OpenArtShareDialog extends ComfyDialog { radio_button.parentElement.classList.add("checked"); this.fetchImageBlob(radio_button_img.src).then((blob) => { - const file = new File([blob], output.image.filename, { + const file = new File([blob], filename, { type: blob.type, }); this.previewImage.src = radio_button_img.src; @@ -645,7 +648,7 @@ export class OpenArtShareDialog extends ComfyDialog { if (radio_button.checked) { this.fetchImageBlob(radio_button_img.src).then((blob) => { - const file = new File([blob], output.image.filename, { + const file = new File([blob], filename, { type: blob.type, }); this.previewImage.src = radio_button_img.src; From 33293bd0a619b5f305b065bd6863ddc87a9a4171 Mon Sep 17 00:00:00 2001 From: johnqiao Date: Sat, 25 Nov 2023 02:05:56 -0700 Subject: [PATCH 21/21] Update the style when switching between upload thumbnail and select output --- js/comfyui-share-openart.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/js/comfyui-share-openart.js b/js/comfyui-share-openart.js index 6bcbcaeb..77af0c51 100644 --- a/js/comfyui-share-openart.js +++ b/js/comfyui-share-openart.js @@ -153,6 +153,11 @@ export class OpenArtShareDialog extends ComfyDialog { ele.checked = false; ele.parentElement.classList.remove("checked"); }); + + // Add the opacity style toggle here to indicate that they only need + // to upload one image or choose one from the outputs. + this.outputsSection.style.opacity = 0.35; + this.uploadImagesInput.style.opacity = 1; }; reader.readAsDataURL(file); }); @@ -160,7 +165,13 @@ export class OpenArtShareDialog extends ComfyDialog { // preview image this.previewImage = $el("img", { src: "", - style: { width: "100%", maxHeight: "100px", objectFit: "contain", display: "none" }, + style: { + width: "100%", + maxHeight: "100px", + objectFit: "contain", + display: "none", + marginTop: '10px', + }, }); this.keyInput = $el("input", { @@ -231,7 +242,6 @@ export class OpenArtShareDialog extends ComfyDialog { // Output Upload Section const outputUploadSection = $el("div", { style: sectionStyle }, [ $el("label", { style: {...labelStyle, margin: "10px 0 0 0"} }, ["2️⃣ Image/Thumbnail (Required)"]), - $el("div", { style: { ...labelStyle, margin: "5px 0 10px 0", fontSize: '12px', fontWeight: "normal"} }, ["Upload or choose one from the outputs"]), this.previewImage, this.uploadImagesInput, ]); @@ -644,6 +654,11 @@ export class OpenArtShareDialog extends ComfyDialog { this.previewImage.style.display = "block"; this.selectedFile = file; }) + + // Add the opacity style toggle here to indicate that they only need + // to upload one image or choose one from the outputs. + this.outputsSection.style.opacity = 1; + this.uploadImagesInput.style.opacity = 0.35; }; if (radio_button.checked) { @@ -655,6 +670,10 @@ export class OpenArtShareDialog extends ComfyDialog { this.previewImage.style.display = "block"; this.selectedFile = file; }) + // Add the opacity style toggle here to indicate that they only need + // to upload one image or choose one from the outputs. + this.outputsSection.style.opacity = 1; + this.uploadImagesInput.style.opacity = 0.35; } this.radioButtons.push(radio_button); @@ -675,7 +694,7 @@ export class OpenArtShareDialog extends ComfyDialog { const header = $el("p", { - textContent: this.radioButtons.length === 0 ? "Queue Prompt to see the outputs" : "Outputs (scroll to see all)", + textContent: this.radioButtons.length === 0 ? "Queue Prompt to see the outputs" : "Or choose one from the outputs (scroll to see all)", size: 2, color: "white", style: {