Merge remote-tracking branch 'upstream/main'

This commit is contained in:
kijai 2024-07-05 11:41:15 +03:00
commit 186ef9df45
14 changed files with 2441 additions and 1337 deletions

View File

@ -201,7 +201,7 @@ cm_ctx = Ctx()
def install_node(node_name, is_all=False, cnt_msg=''):
if '://' in node_name:
if core.is_valid_url(node_name):
# install via urls
res = core.gitclone_install([node_name])
if not res:

View File

@ -3319,6 +3319,17 @@
"install_type": "git-clone",
"description": "Nodes:Edit Mask"
},
{
"author": "shadowcz007",
"title": "comfyui-liveportrait",
"id": "liveportrait",
"reference": "https://github.com/shadowcz007/comfyui-liveportrait",
"files": [
"https://github.com/shadowcz007/comfyui-liveportrait"
],
"install_type": "git-clone",
"description": "The ComfyUI version of [a/LivePortrait](https://github.com/KwaiVGI/LivePortrait)."
},
{
"author": "ostris",
"title": "Ostris Nodes ComfyUI",
@ -11081,7 +11092,28 @@
"install_type": "git-clone",
"description": "Content generation with open source models in comfyui via graq api implementation."
},
{
"author": "Tritant",
"title": "ComfyUI-CreaPrompt",
"id": "creaprompt",
"reference": "https://github.com/tritant/ComfyUI_CreaPrompt",
"files": [
"https://github.com/tritant/ComfyUI_CreaPrompt"
],
"install_type": "git-clone",
"description": "Generate random prompts easily."
},
{
"author": "MiddleKD",
"title": "ComfyUI-productfix",
"id": "productfix",
"reference": "https://github.com/MiddleKD/ComfyUI-productfix",
"files": [
"https://github.com/MiddleKD/ComfyUI-productfix"
],
"install_type": "git-clone",
"description": "This is a ComfyUI custom node that helps generate images for e-commerce products while maintaining the text, logo, and details of the original product."
},

View File

@ -1999,7 +1999,9 @@
],
"https://github.com/GraftingRayman/ComfyUI_GraftingRayman": [
[
"GR Background Remover REMBG",
"GR Checkered Board",
"GR Counter",
"GR Flip Tile Random Inverted",
"GR Flip Tile Random Red Ring",
"GR Image Details Displayer",
@ -2389,6 +2391,7 @@
],
"https://github.com/JayLyu/ComfyUI_BaiKong_Node": [
[
"BK_ColorLimit",
"BK_ColorSelector",
"BK_GradientImage",
"BK_Img2Color"
@ -3016,6 +3019,21 @@
"title_aux": "Mosaica"
}
],
"https://github.com/MiddleKD/ComfyUI-productfix": [
[
"ApplyLatentInjection",
"DetailTransferAdd",
"DetailTransferLatentAdd",
"GetTextMask",
"ResetModelPatcherCalculateWeight",
"VQDecoder",
"VQEncoder",
"VQLoader"
],
{
"title_aux": "ComfyUI-productfix"
}
],
"https://github.com/MilitantHitchhiker/MilitantHitchhiker-SwitchbladePack": [
[
"IntegratedRandomPromptGenerator",
@ -3564,6 +3582,7 @@
"LatentInsertWithBBox(FaceParsing)",
"LatentSize(FaceParsing)",
"MaskComposite(FaceParsing)",
"MaskInsertWithBBox(FaceParsing)",
"MaskListComposite(FaceParsing)",
"MaskListSelect(FaceParsing)",
"MaskToBBox(FaceParsing)",
@ -9803,6 +9822,8 @@
"github_tool",
"google_tool",
"interpreter_tool",
"json_get_value",
"json_parser",
"keyword_tool",
"listen_audio",
"llama_guff_loader",
@ -9832,6 +9853,7 @@
"tool_combine",
"tool_combine_plus",
"weather_tool",
"weekday_tool",
"wikipedia_tool",
"work_wechat",
"work_wechat_tool",
@ -11189,6 +11211,32 @@
"title_aux": "Marigold depth estimation in ComfyUI"
}
],
"https://github.com/kijai/ComfyUI-MimicMotionWrapper": [
[
"DiffusersScheduler",
"DownloadAndLoadMimicMotionModel",
"MimicMotionDecode",
"MimicMotionGetPoses",
"MimicMotionSampler"
],
{
"title_aux": "ComfyUI-MimicMotionWrapper"
}
],
"https://github.com/kijai/ComfyUI-OpenDiTWrapper": [
[
"DownloadAndLoadOpenDiTT5Model",
"DownloadAndLoadOpenSoraModel",
"DownloadAndLoadOpenSoraVAE",
"OpenDiTConditioning",
"OpenDiTSampler",
"OpenSoraDecode",
"OpenSoraEncodeReference"
],
{
"title_aux": "ComfyUI-OpenDiTWrapper"
}
],
"https://github.com/kijai/ComfyUI-SUPIR": [
[
"SUPIR_Upscale",
@ -11460,7 +11508,9 @@
"TclExtractFramesFromVideo",
"TclExtractFramesFromVideoFile",
"TclFrames2Video",
"TclSaveVideoFromFrames"
"TclSaveVideoFromFrames",
"TclYoloV8Segmentation",
"TclYoloV9Segmentation"
],
{
"title_aux": "ComfyUI-TRA"
@ -13021,6 +13071,7 @@
"ConditioningMultiplier_PoP",
"ConditioningNormalizer_PoP",
"DallE3_PoP",
"EfficientAttention",
"LoadImageResizer_PoP",
"LoraStackLoader10_PoP",
"LoraStackLoader_PoP",
@ -13579,6 +13630,14 @@
"title_aux": "comfyui-edit-mask"
}
],
"https://github.com/shadowcz007/comfyui-liveportrait": [
[
"LivePortraitNode"
],
{
"title_aux": "comfyui-liveportrait"
}
],
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
[
"3DImage",
@ -14600,6 +14659,14 @@
"title_aux": "quadmoon's ComfyUI nodes"
}
],
"https://github.com/tritant/ComfyUI_CreaPrompt": [
[
"CreaPrompt"
],
{
"title_aux": "ComfyUI-CreaPrompt"
}
],
"https://github.com/trojblue/trNodes": [
[
"trColorCorrection",

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ sys.path.append(glob_path)
import cm_global
from manager_util import *
version = [2, 43]
version = [2, 44, 1]
version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '')
@ -515,10 +515,16 @@ class GitProgress(RemoteProgress):
def is_valid_url(url):
try:
# Check for HTTP/HTTPS URL format
result = urlparse(url)
return all([result.scheme, result.netloc])
except ValueError:
return False
if all([result.scheme, result.netloc]):
return True
finally:
# Check for SSH git URL format
pattern = re.compile(r"^(.+@|ssh:\/\/).+:.+$")
if pattern.match(url):
return True
return False
def gitclone_install(files, instant_execution=False, msg_prefix=''):

View File

@ -1,22 +1,22 @@
import { api } from "../../scripts/api.js";
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { $el, ComfyDialog } from "../../scripts/ui.js";
import {
ShareDialog,
SUPPORTED_OUTPUT_NODE_TYPES,
getPotentialOutputsAndOutputNodes,
ShareDialog,
ShareDialogChooser,
getPotentialOutputsAndOutputNodes,
showOpenArtShareDialog,
showShareDialog,
showYouMLShareDialog
} from "./comfyui-share-common.js";
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
import { free_models, install_pip, install_via_git_url, manager_instance, rebootAPI, setManagerInstance, show_message } from "./common.js";
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
import { CustomNodesManager } from "./custom-nodes-manager.js";
import { ModelManager } from "./model-manager.js";
import { SnapshotManager } from "./snapshot.js";
import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models, show_message } from "./common.js";
import { ComponentBuilderDialog, load_components, set_component_policy, getPureName } from "./components-manager.js";
import { set_double_click_policy } from "./node_fixer.js";
import { SnapshotManager } from "./snapshot.js";
var docStyle = document.createElement('style');
docStyle.innerHTML = `
@ -897,6 +897,7 @@ class ManagerMenuDialog extends ComfyDialog {
['youml', 'YouML'],
['matrix', 'Matrix Server'],
['comfyworkflows', 'ComfyWorkflows'],
['copus', 'Copus'],
['all', 'All'],
];
for (const option of share_options) {
@ -1234,6 +1235,15 @@ class ManagerMenuDialog extends ComfyDialog {
modifyButtonStyle(url);
},
},
{
title: "Open 'Copus.io'",
callback: () => {
const url = "https://www.copus.io";
localStorage.setItem("wg_last_visited", url);
window.open(url, url);
modifyButtonStyle(url);
},
},
{
title: "Close",
callback: () => {

View File

@ -1,6 +1,7 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { app } from "../../scripts/app.js";
import { $el, ComfyDialog } from "../../scripts/ui.js";
import { CopusShareDialog } from "./comfyui-share-copus.js";
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
import { YouMLShareDialog } from "./comfyui-share-youml.js";
@ -187,6 +188,21 @@ export const shareToEsheep= () => {
})
}
export const showCopusShareDialog = () => {
if (!CopusShareDialog.instance) {
CopusShareDialog.instance = new CopusShareDialog();
}
return app.graphToPrompt()
.then(prompt => {
return app.graph._nodes;
})
.then(nodes => {
const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes);
CopusShareDialog.instance.show({ potential_outputs, potential_output_nodes});
})
}
export const showOpenArtShareDialog = () => {
if (!OpenArtShareDialog.instance) {
OpenArtShareDialog.instance = new OpenArtShareDialog();
@ -316,6 +332,16 @@ export class ShareDialogChooser extends ComfyDialog {
this.close();
}
},
{
key: "Copus",
textContent: "Copus",
website: "https://www.copus.io",
description: "🔴 Permanently store and secure ownership of your workflow on the open-source platform: <a style='color:white;' href='https://copus.io' target='_blank'>Copus.io</a>",
onclick: () => {
showCopusShareDialog();
this.close();
}
},
];
function createShareButtonsWithDescriptions() {

892
js/comfyui-share-copus.js Normal file
View File

@ -0,0 +1,892 @@
import { app } from "../../scripts/app.js";
import { $el, ComfyDialog } from "../../scripts/ui.js";
const env = "prod";
let DEFAULT_HOMEPAGE_URL = "https://copus.io";
let API_ENDPOINT = "https://api.client.prod.copus.io/copus-client";
if (env !== "prod") {
API_ENDPOINT = "https://api.dev.copus.io/copus-client";
DEFAULT_HOMEPAGE_URL = "https://test.copus.io";
}
const style = `
.copus-share-dialog a {
color: #f8f8f8;
}
.copus-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)",
display: "flex",
flexDirection: "column",
justifyContent: "center",
position: "relative",
};
export class CopusShareDialog extends ComfyDialog {
static instance = null;
constructor() {
super();
$el("style", {
textContent: style,
parent: document.head,
});
this.element = $el(
"div.comfy-modal.copus-share-dialog",
{
parent: document.body,
style: {
"overflow-y": "auto",
},
},
[$el("div.comfy-modal-content", {}, [...this.createButtons()])]
);
this.selectedOutputIndex = 0;
this.selectedNodeId = null;
this.uploadedImages = [];
this.allFilesImages = [];
this.selectedFile = null;
this.allFiles = [];
this.titleNum = 0;
}
createButtons() {
const inputStyle = {
display: "block",
minWidth: "500px",
width: "100%",
padding: "10px",
margin: "10px 0",
borderRadius: "4px",
border: "1px solid #ddd",
boxSizing: "border-box",
};
const textAreaStyle = {
display: "block",
minWidth: "500px",
width: "100%",
padding: "10px",
margin: "10px 0",
borderRadius: "4px",
border: "1px solid #ddd",
boxSizing: "border-box",
minHeight: "100px",
background: "#222",
resize: "vertical",
color: "#f2f2f2",
fontFamily: "Arial",
fontWeight: "400",
fontSize: "15px",
};
const hyperLinkStyle = {
display: "block",
marginBottom: "15px",
fontWeight: "bold",
fontSize: "14px",
};
const labelStyle = {
color: "#f8f8f8",
display: "block",
margin: "10px 0 0 0",
fontWeight: "bold",
textDecoration: "none",
};
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,
accept: "image/*",
});
this.uploadImagesInput.addEventListener("change", async (e) => {
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");
});
// 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);
});
// preview image
this.previewImage = $el("img", {
src: "",
style: {
width: "100%",
maxHeight: "100px",
objectFit: "contain",
display: "none",
marginTop: "10px",
},
});
this.keyInput = $el("input", {
type: "password",
placeholder: "Copy & paste your API key",
style: inputStyle,
});
this.TitleInput = $el("input", {
type: "text",
placeholder: "Title (Required)",
style: inputStyle,
maxLength: "70",
oninput: () => {
const titleNum = this.TitleInput.value.length;
titleNumDom.textContent = `${titleNum}/70`;
},
});
this.SubTitleInput = $el("input", {
type: "text",
placeholder: "Subtitle (Optional)",
style: inputStyle,
maxLength: "70",
oninput: () => {
const titleNum = this.SubTitleInput.value.length;
subTitleNumDom.textContent = `${titleNum}/70`;
},
});
this.descriptionInput = $el("textarea", {
placeholder: "Content (Optional)",
style: {
...textAreaStyle,
minHeight: "100px",
},
});
// Header Section
const headerSection = $el("h3", {
textContent: "Share your workflow to Copus",
size: 3,
color: "white",
style: {
"text-align": "center",
color: "white",
margin: "0 0 10px 0",
},
});
this.getAPIKeyLink = $el(
"a",
{
style: {
...hyperLinkStyle,
color: "#59E8C6",
},
href: `${DEFAULT_HOMEPAGE_URL}?fromPage=comfyUI`,
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("label", { style: labelStyle }, ["1⃣ Copus API Key"]),
this.keyInput,
]);
// Output Upload Section
const outputUploadSection = $el("div", { style: sectionStyle }, [
$el(
"label",
{
style: {
...labelStyle,
margin: "10px 0 0 0",
},
},
["2⃣ Image/Thumbnail (Required)"]
),
this.previewImage,
this.uploadImagesInput,
]);
// Outputs Section
this.outputsSection = $el(
"div",
{
id: "selectOutputs",
},
[]
);
const titleNumDom = $el(
"label",
{
style: {
fontSize: "12px",
position: "absolute",
right: "10px",
bottom: "-10px",
color: "#999",
},
},
["0/70"]
);
const subTitleNumDom = $el(
"label",
{
style: {
fontSize: "12px",
position: "absolute",
right: "10px",
bottom: "-10px",
color: "#999",
},
},
["0/70"]
);
const descriptionNumDom = $el(
"label",
{
style: {
fontSize: "12px",
position: "absolute",
right: "10px",
bottom: "-10px",
color: "#999",
},
},
["0/70"]
);
// Additional Inputs Section
const additionalInputsSection = $el(
"div",
{ style: { ...sectionStyle, } },
[
$el("label", { style: labelStyle }, ["3⃣ Title "]),
this.TitleInput,
titleNumDom,
]
);
const SubtitleSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["4⃣ Subtitle "]),
this.SubTitleInput,
subTitleNumDom,
]);
const DescriptionSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["5⃣ Description "]),
this.descriptionInput,
// descriptionNumDom,
]);
// switch between outputs section and additional inputs section
this.radioButtons = [];
this.radioButtonsCheck = $el("input", {
type: "radio",
name: "output_type",
value: "0",
id: "blockchain1",
checked: true,
});
this.radioButtonsCheckOff = $el("input", {
type: "radio",
name: "output_type",
value: "1",
id: "blockchain",
});
const blockChainSection = $el("div", { style: sectionStyle }, [
$el("label", { style: labelStyle }, ["6⃣ Store on blockchain "]),
$el(
"label",
{
style: {
marginTop: "10px",
display: "flex",
alignItems: "center",
cursor: "pointer",
},
},
[
this.radioButtonsCheck,
$el("span", { style: { marginLeft: "5px" } }, ["ON"]),
]
),
$el(
"label",
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
[
this.radioButtonsCheckOff,
$el("span", { style: { marginLeft: "5px" } }, ["OFF"]),
]
),
$el(
"p",
{ style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } },
["Secure ownership with a permanent & decentralized storage"]
),
]);
// Message Section
this.message = $el(
"div",
{
style: {
color: "#ff3d00",
textAlign: "center",
padding: "10px",
fontSize: "20px",
},
},
[]
);
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,
]
);
// Composing the full layout
const layout = [
headerSection,
linkSection,
accountSection,
outputUploadSection,
this.outputsSection,
additionalInputsSection,
SubtitleSection,
DescriptionSection,
// contestSection,
blockChainSection,
this.message,
buttonsSection,
];
return layout;
}
/**
* api
* @param {url} path
* @param {params} options
* @param {statusText} statusText
* @returns
*/
async fetchApi(path, options, statusText) {
if (statusText) {
this.message.textContent = statusText;
}
const fullPath = new URL(API_ENDPOINT + path);
const response = await fetch(fullPath, options);
if (!response.ok) {
throw new Error(response.statusText);
}
if (statusText) {
this.message.textContent = "";
}
const data = await response.json();
return {
ok: response.ok,
statusText: response.statusText,
status: response.status,
data,
};
}
/**
* @param {file} uploadFile
*/
async uploadThumbnail(uploadFile, type) {
const form = new FormData();
form.append("file", uploadFile);
form.append("apiToken", this.keyInput.value);
try {
const res = await this.fetchApi(
`/client/common/opus/uploadImage`,
{
method: "POST",
body: form,
},
"Uploading thumbnail..."
);
if (res.status && res.data.status && res.data) {
const { data } = res.data;
if (type) {
this.allFilesImages.push({
url: data,
});
}
this.uploadedImages.push({
url: data,
});
} else {
throw new Error("make sure your API key is correct and try again later");
}
} 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() {
this.message.textContent = "";
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";
}
/**
* share
* @param {string} title
* @param {string} subtitle
* @param {string} content
* @param {boolean} storeOnChain
* @param {string} coverUrl
* @param {string[]} imageUrls
* @param {string} apiToken
*/
async share() {
const prompt = await app.graphToPrompt();
const workflowJSON = prompt["workflow"];
const form_values = {
title: this.TitleInput.value,
subTitle: this.SubTitleInput.value,
content: this.descriptionInput.value,
storeOnChain: this.radioButtonsCheck.checked ? true : false,
};
if (!this.keyInput.value) {
throw new Error("API key is required");
}
if (!this.uploadImagesInput.files[0] && !this.selectedFile) {
throw new Error("Thumbnail is required");
}
if (!form_values.title) {
throw new Error("Title is required");
}
if (!this.uploadedImages.length) {
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.allFiles.length > 0) {
for (const file of this.allFiles) {
try {
await this.uploadThumbnail(file, true);
} catch (e) {
this.allFilesImages = [];
throw new Error(e.message);
}
}
}
try {
const res = await this.fetchApi(
"/client/common/opus/shareFromComfyUI",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
workflowJson: workflowJSON,
apiToken: this.keyInput.value,
coverUrl: this.uploadedImages[0].url,
imageUrls: this.allFilesImages.map((image) => image.url),
...form_values,
}),
},
"Uploading workflow..."
);
if (res.status && res.data.status && res.data) {
localStorage.setItem("copus_token",this.keyInput.value);
const { data } = res.data;
if (data) {
const url = `${DEFAULT_HOMEPAGE_URL}/work/${data}`;
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.allFilesImages = [];
this.allFiles = [];
this.TitleInput.value = "";
this.SubTitleInput.value = "";
this.descriptionInput.value = "";
this.selectedFile = null;
}
}
} catch (e) {
throw new Error("Error sharing workflow: " + e.message);
}
}
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) => {
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)
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;
const apiToken = localStorage.getItem("copus_token");
this.message.innerHTML = "";
this.message.textContent = "";
this.element.style.display = "block";
this.previewImage.src = "";
this.previewImage.style.display = "none";
this.keyInput.value = apiToken!=null?apiToken:"";
this.uploadedImages = [];
this.allFilesImages = [];
this.allFiles = [];
// 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": "200px",
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 { node_id } = output;
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
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: ${node_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], filename, {
type: blob.type,
});
this.previewImage.src = radio_button_img.src;
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) {
this.fetchImageBlob(radio_button_img.src).then((blob) => {
const file = new File([blob], filename, {
type: blob.type,
});
this.previewImage.src = radio_button_img.src;
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);
let src = "";
if (output.type === "image" || output.type === "temp") {
filename = output.image.filename;
src = `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`;
} else if (output.type === "output") {
src = output.output.value;
filename = output.filename;
}
if (src) {
this.fetchImageBlob(src).then((blob) => {
const file = new File([blob], filename, {
type: blob.type,
});
this.allFiles.push(file);
});
}
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(
"p",
{
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: {
color: "white",
margin: "0 0 5px 0",
fontSize: "12px",
},
},
[]
);
this.outputsSection.innerHTML = "";
this.outputsSection.appendChild(header);
this.outputsSection.appendChild(new_radio_buttons);
}
}

View File

@ -34,8 +34,9 @@ function isValidURL(url) {
if(url.includes('&'))
return false;
const pattern = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/;
return pattern.test(url);
const http_pattern = /^(https?|ftp):\/\/[^\s$?#]+$/;
const ssh_pattern = /^(.+@|ssh:\/\/).+:.+$/;
return http_pattern.test(url) || ssh_pattern.test(url);
}
export async function install_pip(packages) {

View File

@ -10,17 +10,6 @@
},
{
"author": "kijai",
"title": "MimicMotion wrapper for ComfyUI [WIP]",
"id": "mimicmotion-wrapper",
"reference": "https://github.com/kijai/ComfyUI-MimicMotionWrapper",
"files": [
"https://github.com/kijai/ComfyUI-MimicMotionWrapper"
],
"install_type": "git-clone",
"description": "Original repo: [a/https://github.com/tencent/MimicMotion](https://github.com/tencent/MimicMotion)"
},
{
"author": "thderoo",
"title": "_topfun_s_nodes",
@ -41,17 +30,6 @@
"install_type": "git-clone",
"description": "This repository provides developers with a way to better manage their ComfyUI model memory. It includes nodes that allow developers to either unload all models or unload one model at a time. These nodes are designed as pass-through nodes, so they can be used anywhere in the flow. The nodes can be found in the 'Unload Model' section.[w/These are massive hammers, and it could be possible to break things, please don't use them if you need finesse.]"
},
{
"author": "kijai",
"title": "ComfyUI-OpenDiTWrapper [WIP]",
"id": "open-dit-wrapper",
"reference": "https://github.com/kijai/ComfyUI-OpenDiTWrapper",
"files": [
"https://github.com/kijai/ComfyUI-OpenDiTWrapper"
],
"install_type": "git-clone",
"description": "Original repo: [a/https://github.com/NUS-HPC-AI-Lab/OpenDiT](https://github.com/NUS-HPC-AI-Lab/OpenDiT)"
},
{
"author": "GeekyGhost",
"title": "ComfyUI-GeekyRemB v2",

View File

@ -13,6 +13,61 @@
{
"author": "shadowcz007",
"title": "comfyui-liveportrait",
"id": "liveportrait",
"reference": "https://github.com/shadowcz007/comfyui-liveportrait",
"files": [
"https://github.com/shadowcz007/comfyui-liveportrait"
],
"install_type": "git-clone",
"description": "The ComfyUI version of [a/LivePortrait](https://github.com/KwaiVGI/LivePortrait)."
},
{
"author": "MiddleKD",
"title": "ComfyUI-productfix",
"id": "productfix",
"reference": "https://github.com/MiddleKD/ComfyUI-productfix",
"files": [
"https://github.com/MiddleKD/ComfyUI-productfix"
],
"install_type": "git-clone",
"description": "This is a ComfyUI custom node that helps generate images for e-commerce products while maintaining the text, logo, and details of the original product."
},
{
"author": "Tritant",
"title": "ComfyUI-CreaPrompt",
"id": "creaprompt",
"reference": "https://github.com/tritant/ComfyUI_CreaPrompt",
"files": [
"https://github.com/tritant/ComfyUI_CreaPrompt"
],
"install_type": "git-clone",
"description": "Generate random prompts easily."
},
{
"author": "kijai",
"title": "ComfyUI-MimicMotionWrapper",
"id": "mimicmotion-kijai",
"reference": "https://github.com/kijai/ComfyUI-MimicMotionWrapper",
"files": [
"https://github.com/kijai/ComfyUI-MimicMotionWrapper"
],
"install_type": "git-clone",
"description": "Optimized wrapper nodes for MimicMotion: [a/https://github.com/tencent/MimicMotion](https://github.com/tencent/MimicMotion)"
},
{
"author": "kijai",
"title": "ComfyUI-OpenDiTWrapper",
"id": "opendit-kijai",
"reference": "https://github.com/kijai/ComfyUI-OpenDiTWrapper",
"files": [
"https://github.com/kijai/ComfyUI-OpenDiTWrapper"
],
"install_type": "git-clone",
"description": "Wrapper nodes for OpenDiT: [a/OpenDiT](https://github.com/NUS-HPC-AI-Lab/OpenDiT/), supports Open-Sora t2i and i2i"
},
{
"author": "smthemex",
"title": "ComfyUI_MS_Diffusion",
@ -639,61 +694,6 @@
],
"install_type": "git-clone",
"description": "Nodes:Load Ultralytics Model, Ultralytics Inference, Ultralytics Visualization, Convert to Dictionary, BBox to XYWH"
},
{
"author": "chris-the-wiz",
"title": "EmbeddingsCurveEditor_ComfyUI",
"id": "embeddings-curve-editor",
"reference": "https://github.com/chris-the-wiz/EmbeddingsCurveEditor_ComfyUI",
"files": [
"https://github.com/chris-the-wiz/EmbeddingsCurveEditor_ComfyUI"
],
"install_type": "git-clone",
"description": "Edit embeddings with a curve. Actually should work on any 1D input tensor. Tested with IPAdapter-Plus."
},
{
"author": "AustinMroz",
"title": "ComfyUI-WorkflowCheckpointing",
"id": "workflowcheckpointing",
"reference": "https://github.com/AustinMroz/ComfyUI-WorkflowCheckpointing",
"files": [
"https://github.com/AustinMroz/ComfyUI-WorkflowCheckpointing"
],
"install_type": "git-clone",
"description": "Automatically creates checkpoints during workflow execution. If If an workflow is canceled or ComfyUI crashes mid-execution, then these checkpoints are used when the workflow is re-queued to resume execution with minimal progress loss."
},
{
"author": "TRI3D-LC",
"title": "ComfyUI-MiroBoard",
"id": "miroboard",
"reference": "https://github.com/TRI3D-LC/ComfyUI-MiroBoard",
"files": [
"https://github.com/TRI3D-LC/ComfyUI-MiroBoard"
],
"install_type": "git-clone",
"description": "Nodes: add-image-miro-board."
},
{
"author": "SamKhoze",
"title": "DeepFuze",
"id": "deepfuze",
"reference": "https://github.com/SamKhoze/ComfyUI-DeepFuze",
"files": [
"https://github.com/SamKhoze/ComfyUI-DeepFuze"
],
"install_type": "git-clone",
"description": "DeepFuze is a state-of-the-art deep learning tool that seamlessly integrates with ComfyUI to revolutionize facial transformations, lipsyncing, video generation, voice cloning, face swapping, and lipsync translation. Leveraging advanced algorithms, DeepFuze enables users to combine audio and video with unparalleled realism, ensuring perfectly synchronized facial movements. This innovative solution is ideal for content creators, animators, developers, and anyone seeking to elevate their video editing projects with sophisticated AI-driven features."
},
{
"author": "AIFSH",
"title": "ComfyUI-UniAnimate",
"id": "unianimate",
"reference": "https://github.com/AIFSH/ComfyUI-UniAnimate",
"files": [
"https://github.com/AIFSH/ComfyUI-UniAnimate"
],
"install_type": "git-clone",
"description": "a comfyui custom node for [a/UniAnimate](https://github.com/ali-vilab/UniAnimate)"
}
]
}

View File

@ -1999,7 +1999,9 @@
],
"https://github.com/GraftingRayman/ComfyUI_GraftingRayman": [
[
"GR Background Remover REMBG",
"GR Checkered Board",
"GR Counter",
"GR Flip Tile Random Inverted",
"GR Flip Tile Random Red Ring",
"GR Image Details Displayer",
@ -2389,6 +2391,7 @@
],
"https://github.com/JayLyu/ComfyUI_BaiKong_Node": [
[
"BK_ColorLimit",
"BK_ColorSelector",
"BK_GradientImage",
"BK_Img2Color"
@ -3016,6 +3019,21 @@
"title_aux": "Mosaica"
}
],
"https://github.com/MiddleKD/ComfyUI-productfix": [
[
"ApplyLatentInjection",
"DetailTransferAdd",
"DetailTransferLatentAdd",
"GetTextMask",
"ResetModelPatcherCalculateWeight",
"VQDecoder",
"VQEncoder",
"VQLoader"
],
{
"title_aux": "ComfyUI-productfix"
}
],
"https://github.com/MilitantHitchhiker/MilitantHitchhiker-SwitchbladePack": [
[
"IntegratedRandomPromptGenerator",
@ -3564,6 +3582,7 @@
"LatentInsertWithBBox(FaceParsing)",
"LatentSize(FaceParsing)",
"MaskComposite(FaceParsing)",
"MaskInsertWithBBox(FaceParsing)",
"MaskListComposite(FaceParsing)",
"MaskListSelect(FaceParsing)",
"MaskToBBox(FaceParsing)",
@ -9803,6 +9822,8 @@
"github_tool",
"google_tool",
"interpreter_tool",
"json_get_value",
"json_parser",
"keyword_tool",
"listen_audio",
"llama_guff_loader",
@ -9832,6 +9853,7 @@
"tool_combine",
"tool_combine_plus",
"weather_tool",
"weekday_tool",
"wikipedia_tool",
"work_wechat",
"work_wechat_tool",
@ -11189,6 +11211,32 @@
"title_aux": "Marigold depth estimation in ComfyUI"
}
],
"https://github.com/kijai/ComfyUI-MimicMotionWrapper": [
[
"DiffusersScheduler",
"DownloadAndLoadMimicMotionModel",
"MimicMotionDecode",
"MimicMotionGetPoses",
"MimicMotionSampler"
],
{
"title_aux": "ComfyUI-MimicMotionWrapper"
}
],
"https://github.com/kijai/ComfyUI-OpenDiTWrapper": [
[
"DownloadAndLoadOpenDiTT5Model",
"DownloadAndLoadOpenSoraModel",
"DownloadAndLoadOpenSoraVAE",
"OpenDiTConditioning",
"OpenDiTSampler",
"OpenSoraDecode",
"OpenSoraEncodeReference"
],
{
"title_aux": "ComfyUI-OpenDiTWrapper"
}
],
"https://github.com/kijai/ComfyUI-SUPIR": [
[
"SUPIR_Upscale",
@ -11460,7 +11508,9 @@
"TclExtractFramesFromVideo",
"TclExtractFramesFromVideoFile",
"TclFrames2Video",
"TclSaveVideoFromFrames"
"TclSaveVideoFromFrames",
"TclYoloV8Segmentation",
"TclYoloV9Segmentation"
],
{
"title_aux": "ComfyUI-TRA"
@ -13021,6 +13071,7 @@
"ConditioningMultiplier_PoP",
"ConditioningNormalizer_PoP",
"DallE3_PoP",
"EfficientAttention",
"LoadImageResizer_PoP",
"LoraStackLoader10_PoP",
"LoraStackLoader_PoP",
@ -13579,6 +13630,14 @@
"title_aux": "comfyui-edit-mask"
}
],
"https://github.com/shadowcz007/comfyui-liveportrait": [
[
"LivePortraitNode"
],
{
"title_aux": "comfyui-liveportrait"
}
],
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
[
"3DImage",
@ -14600,6 +14659,14 @@
"title_aux": "quadmoon's ComfyUI nodes"
}
],
"https://github.com/tritant/ComfyUI_CreaPrompt": [
[
"CreaPrompt"
],
{
"title_aux": "ComfyUI-CreaPrompt"
}
],
"https://github.com/trojblue/trNodes": [
[
"trColorCorrection",

View File

@ -62,7 +62,7 @@ check_file_logging()
comfy_path = os.environ.get('COMFYUI_PATH')
if comfy_path is None:
comfy_path = os.path.abspath(sys.modules['__main__'].__file__)
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__))
sys.__comfyui_manager_register_message_collapse = register_message_collapse
sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension

View File

@ -1,7 +1,7 @@
[project]
name = "comfyui-manager"
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
version = "2.43"
version = "2.44.1"
license = "LICENSE"
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]