mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-17 18:33:05 +08:00
Right click to share; Support SaveAnimatedWEBP as the output node
This commit is contained in:
parent
7cadf87eef
commit
20bffcaa40
20
__init__.py
20
__init__.py
@ -826,14 +826,22 @@ def get_current_snapshot():
|
|||||||
|
|
||||||
|
|
||||||
def save_snapshot_with_postfix(postfix):
|
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")
|
date_time_format = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
file_name = f"{date_time_format}_{postfix}"
|
file_name = f"{date_time_format}_{postfix}"
|
||||||
|
|
||||||
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{file_name}.json")
|
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{file_name}.json")
|
||||||
with open(path, "w") as json_file:
|
with open(path, "w") as json_file:
|
||||||
json.dump(get_current_snapshot(), json_file, indent=4)
|
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")
|
@server.PromptServer.instance.routes.get("/snapshot/save")
|
||||||
|
|||||||
@ -2,6 +2,7 @@ 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";
|
||||||
import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes, ShareDialogChooser, showOpenArtShareDialog, showShareDialog } from "./comfyui-share-common.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 { CustomNodesInstaller } from "./custom-nodes-downloader.js";
|
||||||
import { AlternativesInstaller } from "./a1111-alter-downloader.js";
|
import { AlternativesInstaller } from "./a1111-alter-downloader.js";
|
||||||
import { SnapshotManager } from "./snapshot.js";
|
import { SnapshotManager } from "./snapshot.js";
|
||||||
@ -256,6 +257,18 @@ function newDOMTokenList(initialTokens) {
|
|||||||
return classList;
|
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 {
|
class ManagerMenuDialog extends ComfyDialog {
|
||||||
@ -695,10 +708,12 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._addExtraNodeContextMenu(nodeType, app);
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadedGraphNode(node, app) {
|
async loadedGraphNode(node, app) {
|
||||||
if (node.has_errors) {
|
if (node.has_errors) {
|
||||||
const onDrawForeground = node.onDrawForeground;
|
const onDrawForeground = node.onDrawForeground;
|
||||||
node.onDrawForeground = function (ctx) {
|
node.onDrawForeground = function (ctx) {
|
||||||
const r = onDrawForeground?.apply?.(this, arguments);
|
const r = onDrawForeground?.apply?.(this, arguments);
|
||||||
@ -748,5 +763,36 @@ app.registerExtension({
|
|||||||
return r;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const SUPPORTED_OUTPUT_NODE_TYPES = [
|
|||||||
"SaveImage",
|
"SaveImage",
|
||||||
"VHS_VideoCombine",
|
"VHS_VideoCombine",
|
||||||
"ADE_AnimateDiffCombine",
|
"ADE_AnimateDiffCombine",
|
||||||
|
"SaveAnimatedWEBP",
|
||||||
]
|
]
|
||||||
|
|
||||||
var docStyle = document.createElement('style');
|
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 };
|
return { potential_outputs, potential_output_nodes };
|
||||||
}
|
}
|
||||||
@ -156,7 +168,16 @@ export const showOpenArtShareDialog = () => {
|
|||||||
if (!OpenArtShareDialog.instance) {
|
if (!OpenArtShareDialog.instance) {
|
||||||
OpenArtShareDialog.instance = new OpenArtShareDialog();
|
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) => {
|
export const showShareDialog = async (share_option) => {
|
||||||
@ -199,7 +220,7 @@ export class ShareDialogChooser extends ComfyDialog {
|
|||||||
{},
|
{},
|
||||||
[...this.createButtons()]),
|
[...this.createButtons()]),
|
||||||
]);
|
]);
|
||||||
|
this.selectedNodeId = null;
|
||||||
}
|
}
|
||||||
createButtons() {
|
createButtons() {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
@ -832,6 +853,26 @@ export class ShareDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
// 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 })
|
// console.log({ potential_outputs, potential_output_nodes })
|
||||||
this.radio_buttons.innerHTML = ""; // clear the radio buttons
|
this.radio_buttons.innerHTML = ""; // clear the radio buttons
|
||||||
const new_radio_buttons = $el("div", {
|
const new_radio_buttons = $el("div", {
|
||||||
@ -841,6 +882,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 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") {
|
||||||
@ -859,7 +901,17 @@ export class ShareDialog extends ComfyDialog {
|
|||||||
// }
|
// }
|
||||||
}, [output.title])
|
}, [output.title])
|
||||||
radio_button.style.color = "var(--fg-color)";
|
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) {
|
if (radio_button.checked) {
|
||||||
this.selectedOutputIndex = index;
|
this.selectedOutputIndex = index;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 = "https://openart.ai/workflows/dev?developer=true";
|
||||||
//const DEFAULT_HOMEPAGE_URL = "http://localhost:8080/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 API_ENDPOINT = "http://localhost:8080/api";
|
||||||
|
|
||||||
const style = `
|
const style = `
|
||||||
.openart-share-dialog a {
|
.openart-share-dialog a {
|
||||||
color: #f8f8f8;
|
color: #f8f8f8;
|
||||||
}
|
}
|
||||||
.openart-share-dialog a:hover {
|
.openart-share-dialog a:hover {
|
||||||
color: #007bff;
|
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 {
|
export class OpenArtShareDialog extends ComfyDialog {
|
||||||
static instance = null;
|
static instance = null;
|
||||||
|
|
||||||
@ -38,13 +55,14 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
[$el("div.comfy-modal-content", {}, [...this.createButtons()])]
|
[$el("div.comfy-modal-content", {}, [...this.createButtons()])]
|
||||||
);
|
);
|
||||||
this.selectedOutputIndex = 0;
|
this.selectedOutputIndex = 0;
|
||||||
|
this.selectedNodeId = null;
|
||||||
this.uploadedImages = [];
|
this.uploadedImages = [];
|
||||||
|
this.selectedFile = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async readKey() {
|
async readKey() {
|
||||||
let key = ""
|
let key = ""
|
||||||
try {
|
try {
|
||||||
// console.log("Fetching openart key")
|
|
||||||
key = await api.fetchApi(`/manager/get_openart_auth`)
|
key = await api.fetchApi(`/manager/get_openart_auth`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -70,13 +88,6 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createButtons() {
|
createButtons() {
|
||||||
const sectionStyle = {
|
|
||||||
marginBottom: "10px",
|
|
||||||
padding: "15px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
|
|
||||||
};
|
|
||||||
|
|
||||||
const inputStyle = {
|
const inputStyle = {
|
||||||
display: "block",
|
display: "block",
|
||||||
minWidth: "500px",
|
minWidth: "500px",
|
||||||
@ -125,12 +136,20 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
this.previewImage.src = "";
|
this.previewImage.src = "";
|
||||||
|
this.previewImage.style.display = "none";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async (e) => {
|
reader.onload = async (e) => {
|
||||||
const imgData = e.target.result;
|
const imgData = e.target.result;
|
||||||
this.previewImage.src = imgData;
|
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);
|
reader.readAsDataURL(file);
|
||||||
});
|
});
|
||||||
@ -138,7 +157,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
// preview image
|
// preview image
|
||||||
this.previewImage = $el("img", {
|
this.previewImage = $el("img", {
|
||||||
src: "",
|
src: "",
|
||||||
style: { maxWidth: "100%", maxHeight: "100px" },
|
style: { maxWidth: "100%", maxHeight: "100px", display: "none" },
|
||||||
});
|
});
|
||||||
|
|
||||||
this.keyInput = $el("input", {
|
this.keyInput = $el("input", {
|
||||||
@ -148,7 +167,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
this.NameInput = $el("input", {
|
this.NameInput = $el("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
placeholder: "Name (required)",
|
placeholder: "Title (required)",
|
||||||
style: inputStyle,
|
style: inputStyle,
|
||||||
});
|
});
|
||||||
this.descriptionInput = $el("textarea", {
|
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
|
// LinkSection
|
||||||
this.communityLink = $el("a", {
|
this.communityLink = $el("a", {
|
||||||
style: hyperLinkStyle,
|
style: hyperLinkStyle,
|
||||||
@ -173,7 +205,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
href: DEFAULT_HOMEPAGE_URL,
|
href: DEFAULT_HOMEPAGE_URL,
|
||||||
target: "_blank"
|
target: "_blank"
|
||||||
}, ["👉 Get your API key here"])
|
}, ["👉 Get your API key here"])
|
||||||
const LinkSection = $el(
|
const linkSection = $el(
|
||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@ -189,16 +221,25 @@ 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 }, ["OpenArt API Key"]),
|
$el("label", { style: labelStyle }, ["OpenArt API Key"]),
|
||||||
this.keyInput,
|
this.keyInput,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Additional Inputs Section
|
// Output Upload Section
|
||||||
const additionalInputsSection = $el("div", { style: sectionStyle }, [
|
const outputUploadSection = $el("div", { style: sectionStyle }, [
|
||||||
$el("label", { style: labelStyle }, ["Image/Thumbnail (Required)"]),
|
$el("label", { style: labelStyle }, ["Upload Image/Thumbnail (Required)"]),
|
||||||
this.uploadImagesInput,
|
this.uploadImagesInput,
|
||||||
this.previewImage,
|
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"]),
|
$el("label", { style: labelStyle }, ["Workflow Information"]),
|
||||||
this.NameInput,
|
this.NameInput,
|
||||||
this.descriptionInput,
|
this.descriptionInput,
|
||||||
@ -256,8 +297,11 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
|
|
||||||
// Composing the full layout
|
// Composing the full layout
|
||||||
const layout = [
|
const layout = [
|
||||||
LinkSection,
|
headerSection,
|
||||||
AccountSection,
|
linkSection,
|
||||||
|
accountSection,
|
||||||
|
outputUploadSection,
|
||||||
|
this.outputsSection,
|
||||||
additionalInputsSection,
|
additionalInputsSection,
|
||||||
this.message,
|
this.message,
|
||||||
buttonsSection,
|
buttonsSection,
|
||||||
@ -347,6 +391,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
async share() {
|
async share() {
|
||||||
const prompt = await app.graphToPrompt();
|
const prompt = await app.graphToPrompt();
|
||||||
const workflowJSON = prompt["workflow"];
|
const workflowJSON = prompt["workflow"];
|
||||||
|
const workflowAPIJSON = prompt["output"];
|
||||||
const form_values = {
|
const form_values = {
|
||||||
name: this.NameInput.value,
|
name: this.NameInput.value,
|
||||||
description: this.descriptionInput.value,
|
description: this.descriptionInput.value,
|
||||||
@ -356,30 +401,41 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
throw new Error("API key is required");
|
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");
|
throw new Error("Thumbnail is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!form_values.name) {
|
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) {
|
if (!this.uploadedImages.length) {
|
||||||
for (const file of this.uploadImagesInput.files) {
|
if (this.selectedFile) {
|
||||||
try {
|
await this.uploadThumbnail(this.selectedFile);
|
||||||
await this.uploadThumbnail(file);
|
} else {
|
||||||
} catch (e) {
|
for (const file of this.uploadImagesInput.files) {
|
||||||
this.uploadedImages = [];
|
try {
|
||||||
throw new Error(e.message);
|
await this.uploadThumbnail(file);
|
||||||
|
} catch (e) {
|
||||||
|
this.uploadedImages = [];
|
||||||
|
throw new Error(e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (this.uploadImagesInput.files.length === 0) {
|
if (this.uploadImagesInput.files.length === 0) {
|
||||||
throw new Error("No thumbnail uploaded");
|
throw new Error("No thumbnail uploaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.uploadImagesInput.files.length === 0) {
|
if (this.uploadImagesInput.files.length === 0) {
|
||||||
throw new Error("No thumbnail uploaded");
|
throw new Error("No thumbnail uploaded");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +449,10 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
workflow_json: workflowJSON,
|
workflow_json: workflowJSON,
|
||||||
upload_images: this.uploadedImages,
|
upload_images: this.uploadedImages,
|
||||||
form_values,
|
form_values,
|
||||||
|
advanced_config: {
|
||||||
|
workflow_api_json: workflowAPIJSON,
|
||||||
|
snapshot: current_snapshot,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"Uploading workflow..."
|
"Uploading workflow..."
|
||||||
@ -403,6 +463,18 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
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>`;
|
||||||
|
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) {
|
} 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 } = {}) {
|
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.element.style.display = "block";
|
||||||
|
this.previewImage.src = "";
|
||||||
|
this.previewImage.style.display = "none";
|
||||||
const key = await this.readKey();
|
const key = await this.readKey();
|
||||||
this.keyInput.value = key;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user