Fix bug: show all the outputs from the same node while sharing

This commit is contained in:
johnqiao 2023-11-25 15:50:41 -07:00
parent e53c707d99
commit ae95b8970b
2 changed files with 203 additions and 160 deletions

View File

@ -52,7 +52,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) {
// iterate over the images array and add each image to the potential_outputs array // iterate over the images array and add each image to the potential_outputs array
for (let j = 0; j < node.images.length; j++) { for (let j = 0; j < node.images.length; j++) {
potential_output_nodes.push(node); potential_output_nodes.push(node);
potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title, "node_id": node.id });
} }
} }
} }
@ -62,7 +62,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) {
// iterate over the images array and add each image to the potential_outputs array // iterate over the images array and add each image to the potential_outputs array
for (let j = 0; j < node.images.length; j++) { for (let j = 0; j < node.images.length; j++) {
potential_output_nodes.push(node); potential_output_nodes.push(node);
potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title, "node_id": node.id });
} }
} }
} }
@ -81,7 +81,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) {
// TODO // TODO
} }
potential_output_nodes.push(node); 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 } }); potential_outputs.push({ "type": "output", 'title': node.title, "node_id": node.id , "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } });
} }
} else if (node.widgets[j].type === "preview") { } else if (node.widgets[j].type === "preview") {
const widgetValue = node.widgets[j].value; const widgetValue = node.widgets[j].value;
@ -98,7 +98,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) {
// TODO // TODO
} }
potential_output_nodes.push(node); 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 } }); potential_outputs.push({ "type": "output", 'title': node.title, "node_id": node.id , "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": `/view?filename=${parsedURLVals.filename}&subfolder=${parsedURLVals.subfolder}&type=${parsedURLVals.type}&format=${parsedURLVals.format}`, "format": parsedURLVals.format } });
} }
} }
} }
@ -137,7 +137,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) {
} }
} }
// Note: make sure that two arrays are the same length // Note: make sure that two arrays are the same length
return { potential_outputs, potential_output_nodes }; return { potential_outputs, potential_output_nodes };
} }
@ -850,29 +850,34 @@ export class ShareDialog extends ComfyDialog {
return res; return res;
} }
show({ potential_outputs, potential_output_nodes, share_option }) { show({potential_outputs, potential_output_nodes, share_option}) {
// Sort `potential_output_nodes` by node ID to make the order always // Sort `potential_output_nodes` by node ID to make the order always
// consistent, but we should also keep `potential_outputs` in the same // consistent, but we should also keep `potential_outputs` in the same
// order as `potential_output_nodes`. // order as `potential_output_nodes`.
const potential_output_to_order = {}; const potential_output_to_order = {};
potential_output_nodes.forEach((node, index) => { potential_output_nodes.forEach((node, index) => {
potential_output_to_order[node.id] = [node, potential_outputs[index]]; if (node.id in potential_output_to_order) {
}) potential_output_to_order[node.id][1].push(potential_outputs[index]);
// Sort the object `potential_output_to_order` by key (node ID) } else {
const sorted_potential_output_to_order = Object.fromEntries( potential_output_to_order[node.id] = [node, [potential_outputs[index]]];
Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id) }
); })
const sorted_potential_outputs = [] // Sort the object `potential_output_to_order` by key (node ID)
const sorted_potential_output_nodes = [] const sorted_potential_output_to_order = Object.fromEntries(
for (const [key, value] of Object.entries(sorted_potential_output_to_order)) { Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id)
sorted_potential_output_nodes.push(value[0]); );
sorted_potential_outputs.push(value[1]); const sorted_potential_outputs = []
} const sorted_potential_output_nodes = []
potential_output_nodes = sorted_potential_output_nodes; for (const [key, value] of Object.entries(sorted_potential_output_to_order)) {
potential_outputs = sorted_potential_outputs; 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 }) // console.log({ potential_outputs, potential_output_nodes })
this.radio_buttons.innerHTML = ""; // clear the radio buttons this.radio_buttons.innerHTML = ""; // clear the radio buttons
let is_radio_button_checked = false; // only check the first radio button if multiple images from the same node
const new_radio_buttons = $el("div", { const new_radio_buttons = $el("div", {
id: "selectOutput-Options", id: "selectOutput-Options",
style: { style: {
@ -880,7 +885,7 @@ export class ShareDialog extends ComfyDialog {
'max-height': '400px', 'max-height': '400px',
} }
}, potential_outputs.map((output, index) => { }, potential_outputs.map((output, index) => {
const potential_output_node = potential_output_nodes[index]; const {node_id} = output;
const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, [])
let radio_button_img; let radio_button_img;
if (output.type === "image" || output.type === "temp") { if (output.type === "image" || output.type === "temp") {
@ -903,8 +908,9 @@ export class ShareDialog extends ComfyDialog {
// Make the radio button checked if it's the selected node, // Make the radio button checked if it's the selected node,
// otherwise make the first radio button checked. // otherwise make the first radio button checked.
if (this.selectedNodeId) { if (this.selectedNodeId) {
if (this.selectedNodeId === potential_output_node.id) { if (this.selectedNodeId === node_id && !is_radio_button_checked) {
radio_button.checked = true; radio_button.checked = true;
is_radio_button_checked = true;
} }
} else { } else {
radio_button.checked = index === 0; radio_button.checked = index === 0;

View File

@ -1,6 +1,6 @@
import { app } from "../../scripts/app.js"; 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"; import {ComfyDialog, $el} from "../../scripts/ui.js";
const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key"; const LOCAL_STORAGE_KEY = "openart_comfy_workflow_key";
const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true"; const DEFAULT_HOMEPAGE_URL = "https://openart.ai/workflows/dev?developer=true";
@ -83,11 +83,11 @@ export class OpenArtShareDialog extends ComfyDialog {
async saveKey(value) { async saveKey(value) {
await api.fetchApi(`/manager/set_openart_auth`, { await api.fetchApi(`/manager/set_openart_auth`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
openart_key: value openart_key: value
}) })
}); });
} }
createButtons() { createButtons() {
@ -195,14 +195,14 @@ export class OpenArtShareDialog extends ComfyDialog {
// Header Section // Header Section
const headerSection = $el("h3", { const headerSection = $el("h3", {
textContent: "Share your workflow to OpenArt", textContent: "Share your workflow to OpenArt",
size: 3, size: 3,
color: "white", color: "white",
style: { style: {
'text-align': 'center', 'text-align': 'center',
color: 'white', color: 'white',
margin: '0 0 10px 0', margin: '0 0 10px 0',
} }
}); });
// LinkSection // LinkSection
this.communityLink = $el("a", { this.communityLink = $el("a", {
@ -234,14 +234,19 @@ export class OpenArtShareDialog extends ComfyDialog {
); );
// Account Section // Account Section
const accountSection = $el("div", { style: sectionStyle }, [ const accountSection = $el("div", {style: sectionStyle}, [
$el("label", { style: labelStyle }, ["1⃣ OpenArt API Key"]), $el("label", {style: labelStyle}, ["1⃣ OpenArt API Key"]),
this.keyInput, this.keyInput,
]); ]);
// Output Upload Section // Output Upload Section
const outputUploadSection = $el("div", { style: sectionStyle }, [ const outputUploadSection = $el("div", {style: sectionStyle}, [
$el("label", { style: {...labelStyle, margin: "10px 0 0 0"} }, ["2⃣ Image/Thumbnail (Required)"]), $el("label", {
style: {
...labelStyle,
margin: "10px 0 0 0"
}
}, ["2⃣ Image/Thumbnail (Required)"]),
this.previewImage, this.previewImage,
this.uploadImagesInput, this.uploadImagesInput,
]); ]);
@ -249,17 +254,20 @@ export class OpenArtShareDialog extends ComfyDialog {
// Outputs Section // Outputs Section
this.outputsSection = $el("div", { this.outputsSection = $el("div", {
id: "selectOutputs", id: "selectOutputs",
}, []); }, []);
// Additional Inputs Section // Additional Inputs Section
const additionalInputsSection = $el("div", { style: sectionStyle }, [ const additionalInputsSection = $el("div", {style: sectionStyle}, [
$el("label", { style: labelStyle }, ["3⃣ Workflow Information"]), $el("label", {style: labelStyle}, ["3⃣ Workflow Information"]),
this.NameInput, this.NameInput,
this.descriptionInput, this.descriptionInput,
]); ]);
// OpenArt Contest Section // OpenArt Contest Section
this.joinContestCheckbox = $el("input",{type:'checkbox', id:"join_contest"},[]) this.joinContestCheckbox = $el("input", {
type: 'checkbox',
id: "join_contest"
}, [])
this.joinContestDescription = $el("a", { this.joinContestDescription = $el("a", {
style: { style: {
...hyperLinkStyle, ...hyperLinkStyle,
@ -278,12 +286,12 @@ export class OpenArtShareDialog extends ComfyDialog {
alignItems: 'center', alignItems: 'center',
cursor: 'pointer', cursor: 'pointer',
} }
},[this.joinContestCheckbox, this.joinContestDescription]) }, [this.joinContestCheckbox, this.joinContestDescription])
const contestSection = $el("div", { style: sectionStyle }, [ const contestSection = $el("div", {style: sectionStyle}, [
this.joinContestLabel, this.joinContestLabel,
]); ]);
// Message Section // Message Section
this.message = $el( this.message = $el(
"div", "div",
{ {
@ -397,7 +405,7 @@ export class OpenArtShareDialog extends ComfyDialog {
); );
if (res.ok && res.data) { if (res.ok && res.data) {
const { image_url, width, height } = res.data; const {image_url, width, height} = res.data;
this.uploadedImages.push({ this.uploadedImages.push({
url: image_url, url: image_url,
width, width,
@ -485,7 +493,7 @@ export class OpenArtShareDialog extends ComfyDialog {
"/workflows/publish", "/workflows/publish",
{ {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: {"Content-Type": "application/json"},
body: JSON.stringify({ body: JSON.stringify({
workflow_json: workflowJSON, workflow_json: workflowJSON,
upload_images: this.uploadedImages, upload_images: this.uploadedImages,
@ -501,7 +509,7 @@ export class OpenArtShareDialog extends ComfyDialog {
); );
if (response.ok) { if (response.ok) {
const { workflow_id } = response.data; const {workflow_id} = response.data;
if (workflow_id) { if (workflow_id) {
const url = `https://openart.ai/workflows/-/-/${workflow_id}`; const url = `https://openart.ai/workflows/-/-/${workflow_id}`;
this.message.innerHTML = `Workflow has been shared successfully. <a href="${url}" target="_blank">Click here to view it.</a>`; this.message.innerHTML = `Workflow has been shared successfully. <a href="${url}" target="_blank">Click here to view it.</a>`;
@ -530,23 +538,27 @@ export class OpenArtShareDialog extends ComfyDialog {
return blob; return blob;
} }
async show({ potential_outputs, potential_output_nodes } = {}) { async show({potential_outputs, potential_output_nodes} = {}) {
// Sort `potential_output_nodes` by node ID to make the order always // Sort `potential_output_nodes` by node ID to make the order always
// consistent, but we should also keep `potential_outputs` in the same // consistent, but we should also keep `potential_outputs` in the same
// order as `potential_output_nodes`. // order as `potential_output_nodes`.
const potential_output_to_order = {}; const potential_output_to_order = {};
potential_output_nodes.forEach((node, index) => { potential_output_nodes.forEach((node, index) => {
potential_output_to_order[node.id] = [node, potential_outputs[index]]; if (node.id in potential_output_to_order) {
potential_output_to_order[node.id][1].push(potential_outputs[index]);
} else {
potential_output_to_order[node.id] = [node, [potential_outputs[index]]];
}
}) })
// Sort the object `potential_output_to_order` by key (node ID) // Sort the object `potential_output_to_order` by key (node ID)
const sorted_potential_output_to_order = Object.fromEntries( const sorted_potential_output_to_order = Object.fromEntries(
Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id) Object.entries(potential_output_to_order).sort((a, b) => a[0].id - b[0].id)
); );
const sorted_potential_outputs = [] const sorted_potential_outputs = []
const sorted_potential_output_nodes = [] const sorted_potential_output_nodes = []
for (const [key, value] of Object.entries(sorted_potential_output_to_order)) { for (const [key, value] of Object.entries(sorted_potential_output_to_order)) {
sorted_potential_output_nodes.push(value[0]); sorted_potential_output_nodes.push(value[0]);
sorted_potential_outputs.push(value[1]); sorted_potential_outputs.push(...value[1]);
} }
potential_output_nodes = sorted_potential_output_nodes; potential_output_nodes = sorted_potential_output_nodes;
potential_outputs = sorted_potential_outputs; potential_outputs = sorted_potential_outputs;
@ -561,9 +573,9 @@ export class OpenArtShareDialog extends ComfyDialog {
this.uploadedImages = []; this.uploadedImages = [];
// If `selectedNodeId` is provided, we will select the corresponding radio // If `selectedNodeId` is provided, we will select the corresponding radio
// button for the node. In addition, we move the selected radio button to // button for the node. In addition, we move the selected radio button to
// the top of the list. // the top of the list.
if (this.selectedNodeId) { if (this.selectedNodeId) {
const index = potential_output_nodes.findIndex(node => node.id === this.selectedNodeId); const index = potential_output_nodes.findIndex(node => node.id === this.selectedNodeId);
if (index >= 0) { if (index >= 0) {
this.selectedOutputIndex = index; this.selectedOutputIndex = index;
@ -573,13 +585,13 @@ export class OpenArtShareDialog extends ComfyDialog {
this.radioButtons = []; this.radioButtons = [];
const new_radio_buttons = $el("div", const new_radio_buttons = $el("div",
{ {
id: "selectOutput-Options", id: "selectOutput-Options",
style: { style: {
'overflow-y': 'scroll', 'overflow-y': 'scroll',
'max-height': '200px', 'max-height': '200px',
'display': 'grid', 'display': 'grid',
'grid-template-columns': 'repeat(auto-fit, minmax(100px, 1fr))', 'grid-template-columns': 'repeat(auto-fit, minmax(100px, 1fr))',
'grid-template-rows': 'auto', 'grid-template-rows': 'auto',
'grid-column-gap': '10px', 'grid-column-gap': '10px',
'grid-row-gap': '10px', 'grid-row-gap': '10px',
@ -588,121 +600,146 @@ export class OpenArtShareDialog extends ComfyDialog {
'border-radius': '8px', 'border-radius': '8px',
'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.05)', 'box-shadow': '0 2px 4px rgba(0, 0, 0, 0.05)',
'background-color': 'var(--bg-color)', 'background-color': 'var(--bg-color)',
} }
}, },
potential_outputs.map((output, index) => { potential_outputs.map((output, index) => {
const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) const {node_id} = output;
let radio_button_img; const radio_button = $el("input", {
let filename; type: 'radio',
if (output.type === "image" || output.type === "temp") { name: "selectOutputImages",
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" } }, []); value: index,
filename = output.image.filename required: index === 0
} else if (output.type === "output") { }, [])
radio_button_img = $el("img", { src: output.output.value, style: { width: "auto", height: "100px", objectFit: "cover", borderRadius: "5px" } }, []); let radio_button_img;
filename = output.filename let filename;
} else { if (output.type === "image" || output.type === "temp") {
// unsupported output type radio_button_img = $el("img", {
// this should never happen src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`,
// TODO style: {
radio_button_img = $el("img", { src: "", style: { width: "auto", height: "100px" } }, []); width: "100px",
} height: "100px",
const radio_button_text = $el("span", { objectFit: "cover",
style: { borderRadius: "5px"
color: 'gray', }
display: 'block', }, []);
fontSize: '12px', filename = output.image.filename
overflowX: 'hidden', } else if (output.type === "output") {
textOverflow: 'ellipsis', radio_button_img = $el("img", {
textWrap: 'nowrap', src: output.output.value,
maxWidth: '100px', style: {
} width: "auto",
}, [output.title]) height: "100px",
const node_id_chip = $el("span", { objectFit: "cover",
style: { borderRadius: "5px"
color: '#FBFBFD', }
display: 'block', }, []);
backgroundColor: 'rgba(0, 0, 0, 0.5)', filename = output.filename
fontSize: '12px', } else {
overflowX: 'hidden', // unsupported output type
padding: '2px 3px', // this should never happen
textOverflow: 'ellipsis', // TODO
textWrap: 'nowrap', radio_button_img = $el("img", {
maxWidth: '100px', src: "",
position: 'absolute', style: {width: "auto", height: "100px"}
top: '3px', }, []);
left: '3px', }
borderRadius: '3px', const radio_button_text = $el("span", {
} style: {
}, [`Node: ${potential_output_nodes[index].id}`]) color: 'gray',
radio_button.style.color = "var(--fg-color)"; display: 'block',
radio_button.checked = this.selectedOutputIndex === index; 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: ${node_id}`])
radio_button.style.color = "var(--fg-color)";
radio_button.checked = this.selectedOutputIndex === index;
radio_button.onchange = async () => { radio_button.onchange = async () => {
this.selectedOutputIndex = parseInt(radio_button.value); this.selectedOutputIndex = parseInt(radio_button.value);
// Remove the "checked" class from all radio buttons // Remove the "checked" class from all radio buttons
this.radioButtons.forEach((ele) => { this.radioButtons.forEach((ele) => {
ele.parentElement.classList.remove("checked"); ele.parentElement.classList.remove("checked");
}); });
radio_button.parentElement.classList.add("checked"); radio_button.parentElement.classList.add("checked");
this.fetchImageBlob(radio_button_img.src).then((blob) => { this.fetchImageBlob(radio_button_img.src).then((blob) => {
const file = new File([blob], filename, { const file = new File([blob], filename, {
type: blob.type, type: blob.type,
}); });
this.previewImage.src = radio_button_img.src; this.previewImage.src = radio_button_img.src;
this.previewImage.style.display = "block"; this.previewImage.style.display = "block";
this.selectedFile = file; this.selectedFile = file;
}) })
// Add the opacity style toggle here to indicate that they only need // Add the opacity style toggle here to indicate that they only need
// to upload one image or choose one from the outputs. // to upload one image or choose one from the outputs.
this.outputsSection.style.opacity = 1; this.outputsSection.style.opacity = 1;
this.uploadImagesInput.style.opacity = 0.35; this.uploadImagesInput.style.opacity = 0.35;
}; };
if (radio_button.checked) { if (radio_button.checked) {
this.fetchImageBlob(radio_button_img.src).then((blob) => { this.fetchImageBlob(radio_button_img.src).then((blob) => {
const file = new File([blob], filename, { const file = new File([blob], filename, {
type: blob.type, type: blob.type,
}); });
this.previewImage.src = radio_button_img.src; this.previewImage.src = radio_button_img.src;
this.previewImage.style.display = "block"; this.previewImage.style.display = "block";
this.selectedFile = file; this.selectedFile = file;
}) })
// Add the opacity style toggle here to indicate that they only need // Add the opacity style toggle here to indicate that they only need
// to upload one image or choose one from the outputs. // to upload one image or choose one from the outputs.
this.outputsSection.style.opacity = 1; this.outputsSection.style.opacity = 1;
this.uploadImagesInput.style.opacity = 0.35; this.uploadImagesInput.style.opacity = 0.35;
} }
this.radioButtons.push(radio_button); this.radioButtons.push(radio_button);
return $el(`label.output_label${radio_button.checked ? '.checked' : ''}`, { return $el(`label.output_label${radio_button.checked ? '.checked' : ''}`, {
style: { style: {
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
marginBottom: "10px", marginBottom: "10px",
cursor: "pointer", cursor: "pointer",
position: 'relative', position: 'relative',
} }
}, [radio_button_img, radio_button_text, radio_button, node_id_chip]); }, [radio_button_img, radio_button_text, radio_button, node_id_chip]);
}) })
); );
const header = const header =
$el("p", { $el("p", {
textContent: this.radioButtons.length === 0 ? "Queue Prompt to see the outputs" : "Or choose one from the 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, size: 2,
color: "white", color: "white",
style: { style: {
color: 'white', color: 'white',
margin: '0 0 5px 0', margin: '0 0 5px 0',
fontSize: '12px', fontSize: '12px',
}, },
}, []) }, [])
this.outputsSection.innerHTML = ""; this.outputsSection.innerHTML = "";
this.outputsSection.appendChild(header); this.outputsSection.appendChild(header);
this.outputsSection.appendChild(new_radio_buttons); this.outputsSection.appendChild(new_radio_buttons);