Merge branch 'ltdrdata:main' into main

This commit is contained in:
miranbrajsa 2024-06-12 17:06:28 +02:00 committed by GitHub
commit ed8c2c6578
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 3029 additions and 2852 deletions

View File

@ -86,6 +86,7 @@ This repository provides Colab notebooks that allow you to install and use Comfy
* Support for automatically installing dependencies of custom nodes upon restarting Colab notebooks.
## Changes
* **2.38** `Install Custom Nodes` menu is changed to `Custom Nodes Manager`.
* **2.21** [cm-cli](docs/en/cm-cli.md) tool is added.
* **2.4** Copy the connections of the nearest node by double-clicking.
* **2.2.3** Support Components System
@ -278,11 +279,11 @@ NODE_CLASS_MAPPINGS.update({
## Support of missing nodes installation
![missing-menu](misc/missing-menu.png)
![missing-menu](misc/missing-menu.jpg)
* When you click on the ```Install Missing Custom Nodes``` button in the menu, it displays a list of extension nodes that contain nodes not currently present in the workflow.
![missing-list](misc/missing-list.png)
![missing-list](misc/missing-list.jpg)
## Additional Feature

View File

@ -4014,6 +4014,17 @@
"install_type": "git-clone",
"description": "Animated optical illusions in ComfyUI"
},
{
"author": "ZHO-ZHO-ZHO",
"title": "ComfyUI-Qwen-2",
"id": "qwen-2",
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-2",
"files": [
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-2"
],
"install_type": "git-clone",
"description": "Using Qwen-2 in ComfyUI"
},
{
"author": "kenjiqq",
"title": "qq-nodes-comfyui",
@ -7445,7 +7456,7 @@
},
{
"author": "MarkoCa1",
"title": "ComfyUI_Text",
"title": "ComfyUI-Text",
"reference": "https://github.com/MarkoCa1/ComfyUI-Text",
"files": [
"https://github.com/MarkoCa1/ComfyUI-Text"
@ -7516,7 +7527,7 @@
"https://github.com/daxcay/ComfyUI-DRMN"
],
"install_type": "git-clone",
"description": "Data Research And Manipulators Nodes for Model Trainers, Artists, Designers and Animators. Captions, Visualizer, Text Manipulator"
"description": "Data Research And Manipulators Nodes for Model Trainers, Artists, Designers and Animators. Captions, Visualizer, Text Manipulator\nNOTE: This repo is renamed to ComfyUI-DataSet."
},
{
"author": "daxcay",
@ -8315,6 +8326,17 @@
"install_type": "git-clone",
"description": "This node allows you to use customnet."
},
{
"author": "smthemex",
"title": "ComfyUI_Pops",
"id": "pops",
"reference": "https://github.com/smthemex/ComfyUI_Pops",
"files": [
"https://github.com/smthemex/ComfyUI_Pops"
],
"install_type": "git-clone",
"description": "You can use [a/Popspaper](https://popspaper.github.io/pOps/) method in comfyUI"
},
{
"author": "choey",
"title": "Comfy-Topaz",
@ -10079,17 +10101,6 @@
"install_type": "git-clone",
"description": "ComfyUI implementation of [a/FlashFace: Human Image Personalization with High-fidelity Identity Preservation](https://github.com/ali-vilab/FlashFace)\nNOTE: You need to downalod models manually."
},
{
"author": "LZpenguin",
"title": "ComfyUI-Text",
"id": "comfy-text",
"reference": "https://github.com/LZpenguin/ComfyUI-Text",
"files": [
"https://github.com/LZpenguin/ComfyUI-Text"
],
"install_type": "git-clone",
"description": "Nodes:Add_text_by_mask"
},
{
"author": "denfrost",
"title": "Den_ComfyUI_Workflows",
@ -10166,6 +10177,17 @@
"install_type": "git-clone",
"description": "A set of tools for generating and altering sigmas in ComfyUI."
},
{
"author": "shobhitic",
"title": "PlusMinusTextClip",
"id": "plusminustextclip",
"reference": "https://github.com/shobhitic/ComfyUI-PlusMinusTextClip",
"files": [
"https://github.com/shobhitic/ComfyUI-PlusMinusTextClip"
],
"install_type": "git-clone",
"description": "This adds a node that has both the positive and negative prompts as input in one node. You can just add one node and be done with both Positive and Negative prompts, in place of adding two different nodes for them."
},

View File

@ -2506,17 +2506,6 @@
"title_aux": "ImagesGrid"
}
],
"https://github.com/LZpenguin/ComfyUI-Text": [
[
"CombinationText",
"PlaceholderText",
"ReplaceText",
"ShowText"
],
{
"title_aux": "ComfyUI-Text"
}
],
"https://github.com/LarryJane491/Image-Captioning-in-ComfyUI": [
[
"LoRA Caption Load",
@ -2796,6 +2785,17 @@
"title_aux": "ComfyUI-BadmanNodes"
}
],
"https://github.com/MarkoCa1/ComfyUI-Text": [
[
"CombinationText",
"PlaceholderText",
"ReplaceText",
"ShowText"
],
{
"title_aux": "ComfyUI-Text"
}
],
"https://github.com/MarkoCa1/ComfyUI_Segment_Mask": [
[
"AutomaticMask(segment anything)"
@ -5160,6 +5160,16 @@
"title_aux": "ComfyUI-Q-Align"
}
],
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-2": [
[
"Qwen2_Chat_Zho",
"Qwen2_ModelLoader_Zho",
"Qwen2_Zho"
],
{
"title_aux": "ComfyUI-Qwen-2"
}
],
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-VL-API": [
[
"QWenVL_API_S_Multi_Zho",
@ -5766,6 +5776,7 @@
"Bedrock - Claude Multimodal",
"Bedrock - SDXL",
"Bedrock - Titan Inpainting",
"Bedrock - Titan Outpainting",
"Bedrock - Titan Text to Image",
"Image From S3",
"Image From URL",
@ -5860,6 +5871,8 @@
"Mikey Sampler Base Only Advanced",
"Mikey Sampler Tiled",
"Mikey Sampler Tiled Base Only",
"MikeyLatentTileSampler",
"MikeyLatentTileSamplerCustom",
"MikeySamplerTiledAdvanced",
"MikeySamplerTiledAdvancedBaseOnly",
"MosaicExpandImage",
@ -6875,9 +6888,11 @@
"LayerColor: Levels",
"LayerColor: RGB",
"LayerColor: YUV",
"LayerFilter: AddGrain",
"LayerFilter: ChannelShake",
"LayerFilter: ColorMap",
"LayerFilter: Film",
"LayerFilter: FilmV2",
"LayerFilter: GaussianBlur",
"LayerFilter: HDREffects",
"LayerFilter: LightLeak",
@ -6897,6 +6912,7 @@
"LayerMask: MaskEdgeUltraDetail",
"LayerMask: MaskEdgeUltraDetail V2",
"LayerMask: MaskGradient",
"LayerMask: MaskGrain",
"LayerMask: MaskGrow",
"LayerMask: MaskInvert",
"LayerMask: MaskMotionBlur",
@ -6947,6 +6963,7 @@
"LayerUtility: GetImageSize",
"LayerUtility: GradientImage",
"LayerUtility: GradientImage V2",
"LayerUtility: HLFrequencyDetailRestore",
"LayerUtility: ImageAutoCrop",
"LayerUtility: ImageAutoCrop V2",
"LayerUtility: ImageBlend",
@ -9324,6 +9341,8 @@
"load_url",
"load_wikipedia",
"new_interpreter_tool",
"openai_tts",
"play_audio",
"show_text_party",
"start_dialog",
"start_workflow",
@ -10715,8 +10734,10 @@
"https://github.com/ljleb/comfy-mecha": [
[
"Blocks Mecha Hyper",
"Custom Code Mecha Recipe",
"Float Mecha Hyper",
"Mecha Custom Code Recipe",
"Mecha Merger",
"Mecha Recipe List",
"Model Mecha Recipe"
],
{
@ -11001,6 +11022,7 @@
"ImpactControlNetApplySEGS",
"ImpactControlNetClearSEGS",
"ImpactConvertDataType",
"ImpactCount_Elts_in_SEGS",
"ImpactDecomposeSEGS",
"ImpactDilateMask",
"ImpactDilateMaskInSEGS",
@ -11302,6 +11324,8 @@
],
"https://github.com/marduk191/comfyui-marnodes": [
[
"marduk191_5_text_string",
"marduk191_5way_text_switch",
"marduk191_workflow_settings"
],
{
@ -12602,6 +12626,7 @@
"ChatGPTOpenAI",
"CkptNames_",
"Color",
"CombineAudioVideo",
"ComparingTwoFrames_",
"CompositeImages_",
"DynamicDelayProcessor",
@ -12614,6 +12639,7 @@
"Font",
"GLIGENTextBoxApply_Advanced",
"GamePal",
"GenerateFramesByCount",
"GetImageSize_",
"GradientImage",
"GridDisplayAndSave",
@ -12622,12 +12648,14 @@
"ImageColorTransfer",
"ImageCropByAlpha",
"ImageListReplace_",
"ImageListToBatch_",
"ImagesPrompt_",
"IncrementingListNode_",
"IntNumber",
"JoinWithDelimiter",
"LimitNumber",
"ListSplit_",
"LoadAndCombinedAudio_",
"LoadImagesFromPath",
"LoadImagesFromURL",
"LoadImagesToBatch",
@ -12653,6 +12681,7 @@
"SaveImageAndMetadata_",
"SaveImageToLocal",
"SaveTripoSRMesh",
"ScenesNode_",
"ScreenShare",
"Seed_",
"ShowLayer",
@ -12762,6 +12791,14 @@
"title_aux": "ComfyUI-send-Eagle(slim)"
}
],
"https://github.com/shobhitic/ComfyUI-PlusMinusTextClip": [
[
"PlusMinusTextClip"
],
{
"title_aux": "PlusMinusTextClip"
}
],
"https://github.com/shockz0rz/ComfyUI_InterpolateEverything": [
[
"OpenposePreprocessorInterpolate"
@ -12980,6 +13017,20 @@
"title_aux": "ComfyUI_Pipeline_Tool"
}
],
"https://github.com/smthemex/ComfyUI_Pops": [
[
"Imgae_To_Path",
"Pops_Controlnet_Sampler",
"Pops_Ipadapter_Sampler",
"Pops_Mean_Sampler",
"Pops_Prior_Embedding",
"Pops_Repo_Choice",
"Pops_Unet_Sampler"
],
{
"title_aux": "ComfyUI_Pops"
}
],
"https://github.com/smthemex/ComfyUI_StableAudio_Open": [
[
"StableAudio_Open",
@ -13733,14 +13784,15 @@
],
"https://github.com/vanche1212/ComfyUI-ZMG-Nodes": [
[
"VC_Load_Video_Path_Unified_Output",
"VC_Load_Video_Upload_Unified_Output",
"VC_Video_Combine_Unified_Output",
"Waveform2Audio",
"\ud83d\ude0bAPI Request Node",
"\ud83d\ude0bJSON Parser Node",
"\ud83d\ude0bLoad Video Path Unified Output",
"\ud83d\ude0bOld Photo Colorization Node",
"\ud83d\ude0bOllama Request Node",
"\ud83d\ude0bSave Image Unified Output",
"\ud83d\ude0bVideo Combine Unified Output"
"\ud83d\ude0bSave Image Unified Output"
],
{
"title_aux": "ZMG PLUGIN"

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, 37, 3]
version = [2, 38]
version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '')
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))

View File

@ -507,6 +507,16 @@ async def fetch_customnode_list(request):
return web.json_response(json_obj, content_type='application/json')
@PromptServer.instance.routes.get("/customnode/alternatives")
async def fetch_customnode_alternatives(request):
alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
for item in alter_json['items']:
populate_markdown(item)
return web.json_response(alter_json, content_type='application/json')
@PromptServer.instance.routes.get("/alternatives/getlist")
async def fetch_alternatives_list(request):
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":

View File

@ -1,566 +0,0 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { install_checked_custom_node, manager_instance, rebootAPI } from "./common.js";
async function getAlterList() {
var mode = manager_instance.datasrc_combo.value;
var skip_update = "";
if(manager_instance.update_check_checkbox.checked)
skip_update = "&skip_update=true";
const response = await api.fetchApi(`/alternatives/getlist?mode=${mode}${skip_update}`);
const data = await response.json();
return data;
}
export class AlternativesInstaller extends ComfyDialog {
static instance = null;
install_buttons = [];
message_box = null;
data = null;
clear() {
this.install_buttons = [];
this.message_box = null;
this.data = null;
}
constructor(app, manager_dialog) {
super();
this.manager_dialog = manager_dialog;
this.search_keyword = '';
this.element = $el("div.comfy-modal", { parent: document.body }, []);
}
startInstall(target) {
const self = AlternativesInstaller.instance;
self.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
}
disableButtons() {
for(let i in this.install_buttons) {
this.install_buttons[i].disabled = true;
this.install_buttons[i].style.backgroundColor = 'gray';
}
}
apply_searchbox(data) {
let keyword = this.search_box.value.toLowerCase();
for(let i in this.grid_rows) {
let data1 = this.grid_rows[i].data;
let data2 = data1.custom_node;
if(!data2)
continue;
let content = data1.tags.toLowerCase() + data1.description.toLowerCase() + data2.author.toLowerCase() + data2.description.toLowerCase() + data2.title.toLowerCase();
if(this.filter && this.filter != '*') {
if(this.filter != data2.installed) {
this.grid_rows[i].control.style.display = 'none';
continue;
}
}
if(keyword == "")
this.grid_rows[i].control.style.display = null;
else if(content.includes(keyword)) {
this.grid_rows[i].control.style.display = null;
}
else {
this.grid_rows[i].control.style.display = 'none';
}
}
}
async invalidateControl() {
this.clear();
// splash
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
const msg = $el('div', {id:'custom-message'},
[$el('br'),
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
$el('br'),
'NOTE: Update only checks for extensions that have been fetched.',
$el('br')]);
msg.style.height = '100px';
msg.style.verticalAlign = 'middle';
this.element.appendChild(msg);
// invalidate
this.data = (await getAlterList()).items;
this.element.removeChild(msg);
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
this.createHeaderControls();
await this.createGrid();
this.apply_searchbox(this.data);
this.createBottomControls();
}
updateMessage(msg, btn_id) {
this.message_box.innerHTML = msg;
if(btn_id) {
const rebootButton = document.getElementById(btn_id);
const self = this;
rebootButton.addEventListener("click",
function() {
if(rebootAPI()) {
self.close();
self.manager_dialog.close();
}
});
}
}
invalidate_checks(is_checked, install_state) {
if(is_checked) {
for(let i in this.grid_rows) {
let data = this.grid_rows[i].data;
let checkbox = this.grid_rows[i].checkbox;
let buttons = this.grid_rows[i].buttons;
checkbox.disabled = data.custom_node.installed != install_state;
if(checkbox.disabled) {
for(let j in buttons) {
buttons[j].style.display = 'none';
}
}
else {
for(let j in buttons) {
buttons[j].style.display = null;
}
}
}
this.checkbox_all.disabled = false;
}
else {
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
if(checkbox.check)
return; // do nothing
}
// every checkbox is unchecked -> enable all checkbox
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
let buttons = this.grid_rows[i].buttons;
checkbox.disabled = false;
for(let j in buttons) {
buttons[j].style.display = null;
}
}
this.checkbox_all.checked = false;
this.checkbox_all.disabled = true;
}
}
check_all(is_checked) {
if(is_checked) {
// lookup first checked item's state
let check_state = null;
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
if(checkbox.checked) {
check_state = this.grid_rows[i].data.custom_node.installed;
}
}
if(check_state == null)
return;
// check only same state items
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
if(this.grid_rows[i].data.custom_node.installed == check_state)
checkbox.checked = true;
}
}
else {
// uncheck all
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
let buttons = this.grid_rows[i].buttons;
checkbox.checked = false;
checkbox.disabled = false;
for(let j in buttons) {
buttons[j].style.display = null;
}
}
this.checkbox_all.disabled = true;
}
}
async createGrid() {
var grid = document.createElement('table');
grid.setAttribute('id', 'alternatives-grid');
this.grid_rows = {};
let self = this;
var thead = document.createElement('thead');
var tbody = document.createElement('tbody');
var headerRow = document.createElement('tr');
thead.style.position = "sticky";
thead.style.top = "0px";
thead.style.borderCollapse = "collapse";
thead.style.tableLayout = "fixed";
var header0 = document.createElement('th');
header0.style.width = "20px";
this.checkbox_all = $el("input",{type:'checkbox', id:'check_all'},[]);
header0.appendChild(this.checkbox_all);
this.checkbox_all.checked = false;
this.checkbox_all.disabled = true;
this.checkbox_all.addEventListener('change', function() { self.check_all.call(self, self.checkbox_all.checked); });
var header1 = document.createElement('th');
header1.innerHTML = '&nbsp;&nbsp;ID&nbsp;&nbsp;';
header1.style.width = "20px";
var header2 = document.createElement('th');
header2.innerHTML = 'Tags';
header2.style.width = "10%";
var header3 = document.createElement('th');
header3.innerHTML = 'Author';
header3.style.width = "150px";
var header4 = document.createElement('th');
header4.innerHTML = 'Title';
header4.style.width = "20%";
var header5 = document.createElement('th');
header5.innerHTML = 'Description';
header5.style.width = "50%";
var header6 = document.createElement('th');
header6.innerHTML = 'Install';
header6.style.width = "130px";
header1.style.position = "sticky";
header1.style.top = "0px";
header2.style.position = "sticky";
header2.style.top = "0px";
header3.style.position = "sticky";
header3.style.top = "0px";
header4.style.position = "sticky";
header4.style.top = "0px";
header5.style.position = "sticky";
header5.style.top = "0px";
thead.appendChild(headerRow);
headerRow.appendChild(header0);
headerRow.appendChild(header1);
headerRow.appendChild(header2);
headerRow.appendChild(header3);
headerRow.appendChild(header4);
headerRow.appendChild(header5);
headerRow.appendChild(header6);
headerRow.style.backgroundColor = "Black";
headerRow.style.color = "White";
headerRow.style.textAlign = "center";
headerRow.style.width = "100%";
headerRow.style.padding = "0";
grid.appendChild(thead);
grid.appendChild(tbody);
if(this.data)
for (var i = 0; i < this.data.length; i++) {
const data = this.data[i];
var dataRow = document.createElement('tr');
let data0 = document.createElement('td');
let checkbox = $el("input",{type:'checkbox', id:`check_${i}`},[]);
data0.appendChild(checkbox);
checkbox.checked = false;
checkbox.addEventListener('change', function() { self.invalidate_checks.call(self, checkbox.checked, data.custom_node?.installed); });
var data1 = document.createElement('td');
data1.style.textAlign = "center";
data1.innerHTML = i+1;
var data2 = document.createElement('td');
data2.innerHTML = `&nbsp;${data.tags}`;
var data3 = document.createElement('td');
var data4 = document.createElement('td');
if(data.custom_node) {
data3.innerHTML = `&nbsp;${data.custom_node.author}`;
data4.innerHTML = `&nbsp;<a href=${data.custom_node.reference} target="_blank"><font color="skyblue"><b>${data.custom_node.title}</b></font></a>`;
}
else {
data3.innerHTML = `&nbsp;Unknown`;
data4.innerHTML = `&nbsp;Unknown`;
}
var data5 = document.createElement('td');
data5.innerHTML = data.description;
var data6 = document.createElement('td');
data6.style.textAlign = "center";
var installBtn = document.createElement('button');
var installBtn2 = null;
var installBtn3 = null;
if(data.custom_node) {
this.install_buttons.push(installBtn);
switch(data.custom_node.installed) {
case 'Disabled':
installBtn3 = document.createElement('button');
installBtn3.innerHTML = 'Enable';
installBtn3.style.backgroundColor = 'blue';
installBtn3.style.color = 'white';
this.install_buttons.push(installBtn3);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
installBtn.style.color = 'white';
break;
case 'Update':
installBtn2 = document.createElement('button');
installBtn2.innerHTML = 'Update';
installBtn2.style.backgroundColor = 'blue';
installBtn2.style.color = 'white';
this.install_buttons.push(installBtn2);
installBtn3 = document.createElement('button');
installBtn3.innerHTML = 'Disable';
installBtn3.style.backgroundColor = 'MediumSlateBlue';
installBtn3.style.color = 'white';
this.install_buttons.push(installBtn3);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
installBtn.style.color = 'white';
break;
case 'True':
installBtn3 = document.createElement('button');
installBtn3.innerHTML = 'Disable';
installBtn3.style.backgroundColor = 'MediumSlateBlue';
installBtn3.style.color = 'white';
this.install_buttons.push(installBtn3);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
installBtn.style.color = 'white';
break;
case 'False':
installBtn.innerHTML = 'Install';
installBtn.style.backgroundColor = 'black';
installBtn.style.color = 'white';
break;
default:
installBtn.innerHTML = 'Try Install';
installBtn.style.backgroundColor = 'Gray';
installBtn.style.color = 'white';
}
let j = i;
if(installBtn2 != null) {
installBtn2.style.width = "120px";
installBtn2.addEventListener('click', function() {
install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'update');
});
data6.appendChild(installBtn2);
}
if(installBtn3 != null) {
installBtn3.style.width = "120px";
installBtn3.addEventListener('click', function() {
install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'toggle_active');
});
data6.appendChild(installBtn3);
}
installBtn.style.width = "120px";
installBtn.addEventListener('click', function() {
if(this.innerHTML == 'Uninstall') {
if (confirm(`Are you sure uninstall ${data.title}?`)) {
install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'uninstall');
}
}
else {
install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'install');
}
});
data6.appendChild(installBtn);
}
dataRow.style.backgroundColor = "var(--bg-color)";
dataRow.style.color = "var(--fg-color)";
dataRow.style.textAlign = "left";
dataRow.appendChild(data0);
dataRow.appendChild(data1);
dataRow.appendChild(data2);
dataRow.appendChild(data3);
dataRow.appendChild(data4);
dataRow.appendChild(data5);
dataRow.appendChild(data6);
tbody.appendChild(dataRow);
let buttons = [];
if(installBtn) {
buttons.push(installBtn);
}
if(installBtn2) {
buttons.push(installBtn2);
}
if(installBtn3) {
buttons.push(installBtn3);
}
this.grid_rows[i] = {data:data, buttons:buttons, checkbox:checkbox, control:dataRow};
}
const panel = document.createElement('div');
panel.style.width = "100%";
panel.appendChild(grid);
function handleResize() {
const parentHeight = self.element.clientHeight;
const gridHeight = parentHeight - 200;
grid.style.height = gridHeight + "px";
}
window.addEventListener("resize", handleResize);
grid.style.position = "relative";
grid.style.display = "inline-block";
grid.style.width = "100%";
grid.style.height = "100%";
grid.style.overflowY = "scroll";
this.element.style.height = "85%";
this.element.style.width = "80%";
this.element.appendChild(panel);
handleResize();
}
createFilterCombo() {
let combo = document.createElement("select");
combo.style.cssFloat = "left";
combo.style.fontSize = "14px";
combo.style.padding = "4px";
combo.style.background = "black";
combo.style.marginLeft = "2px";
combo.style.width = "199px";
combo.id = `combo-manger-filter`;
combo.style.borderRadius = "15px";
let items =
[
{ value:'*', text:'Filter: all' },
{ value:'Disabled', text:'Filter: disabled' },
{ value:'Update', text:'Filter: update' },
{ value:'True', text:'Filter: installed' },
{ value:'False', text:'Filter: not-installed' },
];
items.forEach(item => {
const option = document.createElement("option");
option.value = item.value;
option.text = item.text;
combo.appendChild(option);
});
let self = this;
combo.addEventListener('change', function(event) {
self.filter = event.target.value;
self.apply_searchbox();
});
if(self.filter) {
combo.value = self.filter;
}
return combo;
}
createHeaderControls() {
let self = this;
this.search_box = $el('input.cm-search-filter', {type:'text', id:'manager-alternode-search-box', placeholder:'input search keyword', value:this.search_keyword}, []);
this.search_box.style.height = "25px";
this.search_box.onkeydown = (event) => {
if (event.key === 'Enter') {
self.search_keyword = self.search_box.value;
self.apply_searchbox();
}
if (event.key === 'Escape') {
self.search_keyword = self.search_box.value;
self.apply_searchbox();
}
};
let search_button = document.createElement("button");
search_button.className = "cm-small-button";
search_button.innerHTML = "Search";
search_button.onclick = () => {
self.search_keyword = self.search_box.value;
self.apply_searchbox();
};
search_button.style.display = "inline-block";
let filter_control = this.createFilterCombo();
filter_control.style.display = "inline-block";
let cell = $el('td', {width:'100%'}, [filter_control, this.search_box, ' ', search_button]);
let search_control = $el('table', {width:'100%'},
[
$el('tr', {}, [cell])
]
);
cell.style.textAlign = "right";
this.element.appendChild(search_control);
}
async createBottomControls() {
var close_button = document.createElement("button");
close_button.className = "cm-small-button";
close_button.innerHTML = "Close";
close_button.onclick = () => { this.close(); }
close_button.style.display = "inline-block";
this.message_box = $el('div', {id:'alternatives-installer-message'}, [$el('br'), '']);
this.message_box.style.height = '60px';
this.message_box.style.verticalAlign = 'middle';
this.element.appendChild(this.message_box);
this.element.appendChild(close_button);
}
async show() {
try {
this.invalidateControl();
this.element.style.display = "block";
this.element.style.zIndex = 10001;
}
catch(exception) {
app.ui.dialog.show(`Failed to get alternatives list. / ${exception}`);
console.error(exception);
}
}
}

View File

@ -11,8 +11,7 @@ import {
showYouMLShareDialog
} from "./comfyui-share-common.js";
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
import { CustomNodesInstaller } from "./custom-nodes-downloader.js";
import { AlternativesInstaller } from "./a1111-alter-downloader.js";
import { CustomNodesManager } from "./custom-nodes-manager.js";
import { SnapshotManager } from "./snapshot.js";
import { ModelInstaller } from "./model-downloader.js";
import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models, show_message } from "./common.js";
@ -568,10 +567,10 @@ async function fetchUpdates(update_check_checkbox) {
async function() {
app.ui.dialog.close();
if(!CustomNodesInstaller.instance)
CustomNodesInstaller.instance = new CustomNodesInstaller(app, self);
await CustomNodesInstaller.instance.show(CustomNodesInstaller.ShowMode.UPDATE);
if(!CustomNodesManager.instance) {
CustomNodesManager.instance = new CustomNodesManager(app, self);
}
await CustomNodesManager.instance.show(CustomNodesManager.ShowMode.UPDATE);
}
);
@ -714,12 +713,13 @@ class ManagerMenuDialog extends ComfyDialog {
[
$el("button.cm-button", {
type: "button",
textContent: "Install Custom Nodes",
textContent: "Custom Nodes Manager",
onclick:
() => {
if(!CustomNodesInstaller.instance)
CustomNodesInstaller.instance = new CustomNodesInstaller(app, self);
CustomNodesInstaller.instance.show(CustomNodesInstaller.ShowMode.NORMAL);
if(!CustomNodesManager.instance) {
CustomNodesManager.instance = new CustomNodesManager(app, self);
}
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.NORMAL);
}
}),
@ -728,9 +728,10 @@ class ManagerMenuDialog extends ComfyDialog {
textContent: "Install Missing Custom Nodes",
onclick:
() => {
if(!CustomNodesInstaller.instance)
CustomNodesInstaller.instance = new CustomNodesInstaller(app, self);
CustomNodesInstaller.instance.show(CustomNodesInstaller.ShowMode.MISSING_NODES);
if(!CustomNodesManager.instance) {
CustomNodesManager.instance = new CustomNodesManager(app, self);
}
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.MISSING);
}
}),
@ -768,9 +769,10 @@ class ManagerMenuDialog extends ComfyDialog {
textContent: "Alternatives of A1111",
onclick:
() => {
if(!AlternativesInstaller.instance)
AlternativesInstaller.instance = new AlternativesInstaller(app, self);
AlternativesInstaller.instance.show();
if(!CustomNodesManager.instance) {
CustomNodesManager.instance = new CustomNodesManager(app, self);
}
CustomNodesManager.instance.show(CustomNodesManager.ShowMode.ALTERNATIVES);
}
}),

View File

@ -24,72 +24,6 @@ export function rebootAPI() {
return false;
}
export async function install_checked_custom_node(grid_rows, target_i, caller, mode) {
if(caller) {
let failed = '';
caller.disableButtons();
for(let i in grid_rows) {
if(!grid_rows[i].checkbox.checked && i != target_i)
continue;
var target;
if(grid_rows[i].data.custom_node) {
target = grid_rows[i].data.custom_node;
}
else {
target = grid_rows[i].data;
}
caller.startInstall(target);
try {
const response = await api.fetchApi(`/customnode/${mode}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(target)
});
if(response.status == 403) {
show_message('This action is not allowed with this security level configuration.');
caller.updateMessage('');
await caller.invalidateControl();
return;
}
if(response.status == 404) {
show_message('With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.');
caller.updateMessage('');
await caller.invalidateControl();
return;
}
if(response.status == 400) {
show_message(`${mode} failed: ${target.title}`);
continue;
}
const status = await response.json();
app.ui.dialog.close();
target.installed = 'True';
continue;
}
catch(exception) {
failed += `<BR> ${target.title}`;
}
}
if(failed != '') {
show_message(`${mode} failed: ${failed}`);
}
await caller.invalidateControl();
caller.updateMessage("<BR>To apply the installed/updated/disabled/enabled custom node, please <button id='cm-reboot-button1' class='cm-small-button'>RESTART</button> ComfyUI. And refresh browser.", 'cm-reboot-button1');
}
};
export var manager_instance = null;
export function setManagerInstance(obj) {

View File

@ -1,951 +0,0 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { install_checked_custom_node, manager_instance, rebootAPI } from "./common.js";
async function getCustomNodes() {
var mode = manager_instance.datasrc_combo.value;
var skip_update = "";
if(manager_instance.update_check_checkbox.checked)
skip_update = "&skip_update=true";
const response = await api.fetchApi(`/customnode/getlist?mode=${mode}${skip_update}`);
const data = await response.json();
return data;
}
async function getCustomnodeMappings() {
var mode = manager_instance.datasrc_combo.value;
const response = await api.fetchApi(`/customnode/getmappings?mode=${mode}`);
const data = await response.json();
return data;
}
async function getConflictMappings() {
var mode = manager_instance.datasrc_combo.value;
const response = await api.fetchApi(`/customnode/getmappings?mode=${mode}`);
const data = await response.json();
let node_to_extensions_map = {};
for(let k in data) {
for(let i in data[k][0]) {
let node = data[k][0][i];
let l = node_to_extensions_map[node];
if(!l) {
l = [];
node_to_extensions_map[node] = l;
}
l.push(k);
}
}
let conflict_map = {};
for(let node in node_to_extensions_map) {
if(node_to_extensions_map[node].length > 1) {
for(let i in node_to_extensions_map[node]) {
let extension = node_to_extensions_map[node][i];
let l = conflict_map[extension];
if(!l) {
l = [];
conflict_map[extension] = l;
}
for(let j in node_to_extensions_map[node]) {
let extension2 = node_to_extensions_map[node][j];
if(extension != extension2)
l.push([node, extension2]);
}
}
}
}
return conflict_map;
}
async function getUnresolvedNodesInComponent() {
try {
var mode = manager_instance.datasrc_combo.value;
const response = await api.fetchApi(`/component/get_unresolved`);
const data = await response.json();
return data.nodes;
}
catch {
return [];
}
}
export class CustomNodesInstaller extends ComfyDialog {
static instance = null;
install_buttons = [];
message_box = null;
data = null;
static ShowMode = {
NORMAL: 0,
MISSING_NODES: 1,
UPDATE: 2,
};
clear() {
this.install_buttons = [];
this.message_box = null;
this.data = null;
}
constructor(app, manager_dialog) {
super();
this.manager_dialog = manager_dialog;
this.search_keyword = '';
this.element = $el("div.comfy-modal", { parent: document.body }, []);
this.currentSortProperty = ''; // The property currently being sorted
this.currentSortAscending = true; // The direction of the current sort
}
startInstall(target) {
const self = CustomNodesInstaller.instance;
self.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
}
disableButtons() {
for(let i in this.install_buttons) {
this.install_buttons[i].disabled = true;
this.install_buttons[i].style.backgroundColor = 'gray';
}
}
apply_searchbox(data) {
let keyword = this.search_box.value.toLowerCase();
for(let i in this.grid_rows) {
let data = this.grid_rows[i].data;
let content = data.author.toLowerCase() + data.description.toLowerCase() + data.title.toLowerCase() + data.reference.toLowerCase();
if(this.filter && this.filter != '*') {
if(this.filter == 'True' && (data.installed == 'Update' || data.installed == 'Fail')) {
this.grid_rows[i].control.style.display = null;
}
else if(this.filter != data.installed) {
this.grid_rows[i].control.style.display = 'none';
continue;
}
}
if(keyword == "")
this.grid_rows[i].control.style.display = null;
else if(content.includes(keyword)) {
this.grid_rows[i].control.style.display = null;
}
else {
this.grid_rows[i].control.style.display = 'none';
}
}
}
async filter_missing_node(data) {
const mappings = await getCustomnodeMappings();
// build regex->url map
const regex_to_url = [];
for (let i in data) {
if(data[i]['nodename_pattern']) {
let item = {regex: new RegExp(data[i].nodename_pattern), url: data[i].files[0]};
regex_to_url.push(item);
}
}
// build name->url map
const name_to_urls = {};
for (const url in mappings) {
const names = mappings[url];
for(const name in names[0]) {
let v = name_to_urls[names[0][name]];
if(v == undefined) {
v = [];
name_to_urls[names[0][name]] = v;
}
v.push(url);
}
}
const registered_nodes = new Set();
for (let i in LiteGraph.registered_node_types) {
registered_nodes.add(LiteGraph.registered_node_types[i].type);
}
const missing_nodes = new Set();
const workflow = app.graph.serialize();
const group_nodes = workflow.extra && workflow.extra.groupNodes ? workflow.extra.groupNodes : [];
let nodes = workflow.nodes;
for (let i in group_nodes) {
let group_node = group_nodes[i];
nodes = nodes.concat(group_node.nodes);
}
for (let i in nodes) {
const node_type = nodes[i].type;
if(node_type.startsWith('workflow/'))
continue;
if (!registered_nodes.has(node_type)) {
const urls = name_to_urls[node_type.trim()];
if(urls)
urls.forEach(url => {
missing_nodes.add(url);
});
else {
for(let j in regex_to_url) {
if(regex_to_url[j].regex.test(node_type)) {
missing_nodes.add(regex_to_url[j].url);
}
}
}
}
}
let unresolved_nodes = await getUnresolvedNodesInComponent();
for (let i in unresolved_nodes) {
let node_type = unresolved_nodes[i];
const url = name_to_urls[node_type];
if(url)
missing_nodes.add(url);
}
return data.filter(node => node.files.some(file => missing_nodes.has(file)));
}
async invalidateControl() {
this.clear();
// splash
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
const msg = $el('div', {id:'custom-message'},
[$el('br'),
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
$el('br'),
'NOTE: Update only checks for extensions that have been fetched.',
$el('br')]);
msg.style.height = '100px';
msg.style.verticalAlign = 'middle';
msg.style.color = "var(--fg-color)";
this.element.appendChild(msg);
// invalidate
let data = await getCustomNodes();
this.data = data.custom_nodes;
this.channel = data.channel;
this.conflict_mappings = await getConflictMappings();
if(this.show_mode == CustomNodesInstaller.ShowMode.MISSING_NODES)
this.data = await this.filter_missing_node(this.data);
this.element.removeChild(msg);
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
this.createHeaderControls();
await this.createGrid();
this.apply_searchbox(this.data);
this.createBottomControls();
}
updateMessage(msg, btn_id) {
this.message_box.innerHTML = msg;
if(btn_id) {
const rebootButton = document.getElementById(btn_id);
const self = this;
rebootButton.addEventListener("click",
function() {
if(rebootAPI()) {
self.close();
self.manager_dialog.close();
}
});
console.log(rebootButton);
}
}
invalidate_checks(is_checked, install_state) {
if(is_checked) {
for(let i in this.grid_rows) {
let data = this.grid_rows[i].data;
let checkbox = this.grid_rows[i].checkbox;
let buttons = this.grid_rows[i].buttons;
checkbox.disabled = data.installed != install_state;
if(checkbox.disabled) {
for(let j in buttons) {
buttons[j].style.display = 'none';
}
}
else {
for(let j in buttons) {
buttons[j].style.display = null;
}
}
}
this.checkbox_all.disabled = false;
}
else {
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
if(checkbox.check)
return; // do nothing
}
// every checkbox is unchecked -> enable all checkbox
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
let buttons = this.grid_rows[i].buttons;
checkbox.disabled = false;
for(let j in buttons) {
buttons[j].style.display = null;
}
}
this.checkbox_all.checked = false;
this.checkbox_all.disabled = true;
}
}
check_all(is_checked) {
if(is_checked) {
// lookup first checked item's state
let check_state = null;
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
if(checkbox.checked) {
check_state = this.grid_rows[i].data.installed;
}
}
if(check_state == null)
return;
// check only same state items
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
if(this.grid_rows[i].data.installed == check_state)
checkbox.checked = true;
}
}
else {
// uncheck all
for(let i in this.grid_rows) {
let checkbox = this.grid_rows[i].checkbox;
let buttons = this.grid_rows[i].buttons;
checkbox.checked = false;
checkbox.disabled = false;
for(let j in buttons) {
buttons[j].style.display = null;
}
}
this.checkbox_all.disabled = true;
}
}
sortData(property, ascending = true) {
this.data.sort((a, b) => {
// Check if either value is -1 and handle accordingly
if (a[property] === -1) return 1; // Always put a at the end if its value is -1
if (b[property] === -1) return -1; // Always put b at the end if its value is -1
// And be careful here, (-1<'2024-01-01') and (-1>'2024-01-01') are both false! So I handle -1 seperately.
if (a[property] < b[property]) return ascending ? -1 : 1;
if (a[property] > b[property]) return ascending ? 1 : -1;
return 0;
});
}
resetHeaderStyles() {
const headers = ['th_author', 'th_title', 'th_stars', 'th_last_update']; // Add the IDs of all your sortable headers here
headers.forEach(headerId => {
const header = this.element.querySelector(`#${headerId}`);
if (header) {
header.style.backgroundColor = ''; // Reset to default background color
// Add other style resets if necessary
}
});
}
toggleSort(property) {
// If currently sorted by this property, toggle the direction; else, sort ascending
if (this.currentSortProperty === property) {
this.currentSortAscending = !this.currentSortAscending;
} else {
this.currentSortAscending = false;
}
this.currentSortProperty = property;
this.resetHeaderStyles(); // Reset styles of all sortable headers
// Determine the ID of the header based on the property
let headerId = '';
if (property === 'stars') {
headerId = 'th_stars';
} else if (property === 'last_update') {
headerId = 'th_last_update';
} else if (property === 'author') {
headerId = 'th_author';
} else if (property === 'title') {
headerId = 'th_title';
}
// If we have a valid headerId, change its style to indicate it's the active sort column
if (headerId) {
const activeHeader = this.element.querySelector(`#${headerId}`);
if (activeHeader) {
activeHeader.style.backgroundColor = '#222';
// Slightly brighter. Add other style changes if necessary.
}
}
// Call sortData with the current property and direction
this.sortData(property, this.currentSortAscending);
// Refresh the grid to display sorted data
this.createGrid();
this.apply_searchbox(this.data);
}
async createGrid() {
// Remove existing table if present
var grid = this.element.querySelector('#custom-nodes-grid');
var panel;
let self = this;
if (grid) {
grid.querySelector('tbody').remove();
panel = grid.parentNode;
} else {
grid = document.createElement('table');
grid.setAttribute('id', 'custom-nodes-grid');
this.grid_rows = {};
var thead = document.createElement('thead');
var headerRow = document.createElement('tr');
thead.style.position = "sticky";
thead.style.top = "0px";
thead.style.borderCollapse = "collapse";
thead.style.tableLayout = "fixed";
var header0 = document.createElement('th');
header0.style.width = "20px";
this.checkbox_all = $el("input",{type:'checkbox', id:'check_all'},[]);
header0.appendChild(this.checkbox_all);
this.checkbox_all.checked = false;
this.checkbox_all.disabled = true;
this.checkbox_all.addEventListener('change', function() { self.check_all.call(self, self.checkbox_all.checked); });
var header1 = document.createElement('th');
header1.innerHTML = '&nbsp;&nbsp;ID&nbsp;&nbsp;';
header1.style.width = "20px";
var header2 = document.createElement('th');
header2.innerHTML = 'Author';
header2.style.width = "150px";
header2.style.cursor = 'pointer';
header2.setAttribute('id', 'th_author');
header2.onclick = () => this.toggleSort('author');
var header3 = document.createElement('th');
header3.innerHTML = 'Name';
header3.style.width = "20%";
header3.style.cursor = 'pointer';
header3.setAttribute('id', 'th_title');
header3.onclick = () => this.toggleSort('title');
var header4 = document.createElement('th');
header4.innerHTML = 'Description';
header4.style.width = "60%";
// header4.classList.add('expandable-column');
var header5 = document.createElement('th');
header5.innerHTML = '&nbsp;&nbsp;&nbsp;★&nbsp;&nbsp;&nbsp;';
header5.style.width = "130px";
header5.setAttribute('id', 'th_stars');
header5.style.cursor = 'pointer';
header5.onclick = () => this.toggleSort('stars');
var header6 = document.createElement('th');
header6.innerHTML = 'Last Update';
header6.style.width = "130px";
header6.setAttribute('id', 'th_last_update');
header6.style.cursor = 'pointer';
header6.onclick = () => this.toggleSort('last_update');
var header7 = document.createElement('th');
header7.innerHTML = 'Install';
header7.style.width = "130px";
header0.style.position = "sticky";
header0.style.top = "0px";
header1.style.position = "sticky";
header1.style.top = "0px";
header2.style.position = "sticky";
header2.style.top = "0px";
header3.style.position = "sticky";
header3.style.top = "0px";
header4.style.position = "sticky";
header4.style.top = "0px";
header5.style.position = "sticky";
header5.style.top = "0px";
header6.style.position = "sticky";
header6.style.top = "0px";
header7.style.position = "sticky";
header7.style.top = "0px";
thead.appendChild(headerRow);
headerRow.appendChild(header0);
headerRow.appendChild(header1);
headerRow.appendChild(header2);
headerRow.appendChild(header3);
headerRow.appendChild(header4);
headerRow.appendChild(header5);
headerRow.appendChild(header6);
headerRow.appendChild(header7);
headerRow.style.backgroundColor = "Black";
headerRow.style.color = "White";
headerRow.style.textAlign = "center";
headerRow.style.width = "100%";
headerRow.style.padding = "0";
grid.appendChild(thead);
panel = document.createElement('div');
panel.style.width = "100%";
panel.appendChild(grid);
this.element.appendChild(panel);
}
var tbody = document.createElement('tbody');
grid.appendChild(tbody);
if(this.data)
for (var i = 0; i < this.data.length; i++) {
const data = this.data[i];
let dataRow = document.createElement('tr');
let data0 = document.createElement('td');
let checkbox = $el("input",{type:'checkbox', id:`check_${i}`},[]);
data0.appendChild(checkbox);
checkbox.checked = false;
checkbox.addEventListener('change', function() { self.invalidate_checks.call(self, checkbox.checked, data.installed); });
var data1 = document.createElement('td');
data1.style.textAlign = "center";
data1.innerHTML = i+1;
var data2 = document.createElement('td');
data2.style.maxWidth = "120px";
data2.className = "cm-node-author"
data2.textContent = ` ${data.author}`;
if(data.trust) {
data2.innerHTML = `<span title="This author has been active for more than six months in GitHub">✅${data.author}</span>`;
}
else {
data2.textContent = ` ${data.author}`;
}
data2.style.whiteSpace = "nowrap";
data2.style.overflow = "hidden";
data2.style.textOverflow = "ellipsis";
var data3 = document.createElement('td');
data3.style.maxWidth = "200px";
data3.style.wordWrap = "break-word";
data3.className = "cm-node-name"
data3.innerHTML = `&nbsp;<a href=${data.reference} target="_blank"><font color="skyblue"><b>${data.title}</b></font></a>`;
if(data.installed == 'Fail')
data3.innerHTML = '&nbsp;<font color="BLACK"><B>(IMPORT FAILED)</B></font>' + data3.innerHTML;
var data4 = document.createElement('td');
data4.innerHTML = data.description;
data4.className = "cm-node-desc"
let conflicts = this.conflict_mappings[data.files[0]];
if(conflicts) {
let buf = '<p class="cm-conflicted-nodes-text"><B><font color="BLACK">Conflicted Nodes:</FONT></B><BR>';
for(let k in conflicts) {
let node_name = conflicts[k][0];
let extension_name = conflicts[k][1].split('/').pop();
if(extension_name.endsWith('/')) {
extension_name = extension_name.slice(0, -1);
}
if(node_name.endsWith('.git')) {
extension_name = extension_name.slice(0, -4);
}
buf += `<B>${node_name}</B> [${extension_name}], `;
}
if(buf.endsWith(', ')) {
buf = buf.slice(0, -2);
}
buf += "</p>";
data4.innerHTML += buf;
}
var data5 = document.createElement('td');
data5.style.maxWidth = "100px";
data5.className = "cm-node-stars"
if(data.stars < 0) {
data5.textContent = 'N/A';
}
else {
data5.textContent = `${data.stars}`;
}
data5.style.whiteSpace = "nowrap";
data5.style.overflow = "hidden";
data5.style.textOverflow = "ellipsis";
data5.style.textAlign = "center";
var lastUpdateDate = new Date();
var data6 = document.createElement('td');
data6.style.maxWidth = "100px";
data6.className = "cm-node-last-update";
if(data.last_update < 0) {
data6.textContent = 'N/A';
}
else {
data6.textContent = `${data.last_update}`.split(' ')[0];
}
data6.style.whiteSpace = "nowrap";
data6.style.overflow = "hidden";
data6.style.textOverflow = "ellipsis";
data6.style.textAlign = "center";
var data7 = document.createElement('td');
data7.style.textAlign = "center";
var installBtn = document.createElement('button');
installBtn.className = "cm-btn-install";
var installBtn2 = null;
var installBtn3 = null;
var installBtn4 = null;
this.install_buttons.push(installBtn);
switch(data.installed) {
case 'Disabled':
installBtn3 = document.createElement('button');
installBtn3.innerHTML = 'Enable';
installBtn3.className = "cm-btn-enable";
installBtn3.style.backgroundColor = 'blue';
installBtn3.style.color = 'white';
this.install_buttons.push(installBtn3);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'Update':
installBtn2 = document.createElement('button');
installBtn2.innerHTML = 'Update';
installBtn2.className = "cm-btn-update";
installBtn2.style.backgroundColor = 'blue';
installBtn2.style.color = 'white';
this.install_buttons.push(installBtn2);
installBtn3 = document.createElement('button');
installBtn3.innerHTML = 'Disable';
installBtn3.className = "cm-btn-disable";
installBtn3.style.backgroundColor = 'MediumSlateBlue';
installBtn3.style.color = 'white';
this.install_buttons.push(installBtn3);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'Fail':
installBtn4 = document.createElement('button');
installBtn4.innerHTML = 'Try fix';
installBtn4.className = "cm-btn-disable";
installBtn4.style.backgroundColor = '#6495ED';
installBtn4.style.color = 'white';
this.install_buttons.push(installBtn4);
case 'True':
if(manager_instance.update_check_checkbox.checked) {
installBtn2 = document.createElement('button');
installBtn2.innerHTML = 'Try update';
installBtn2.className = "cm-btn-update";
installBtn2.style.backgroundColor = 'Gray';
installBtn2.style.color = 'white';
this.install_buttons.push(installBtn2);
}
installBtn3 = document.createElement('button');
installBtn3.innerHTML = 'Disable';
installBtn3.className = "cm-btn-disable";
installBtn3.style.backgroundColor = 'MediumSlateBlue';
installBtn3.style.color = 'white';
this.install_buttons.push(installBtn3);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'False':
installBtn.innerHTML = 'Install';
installBtn.style.backgroundColor = 'black';
installBtn.style.color = 'white';
break;
default:
installBtn.innerHTML = `Try Install`;
installBtn.style.backgroundColor = 'Gray';
installBtn.style.color = 'white';
}
let j = i;
if(installBtn2 != null) {
installBtn2.style.width = "120px";
installBtn2.addEventListener('click', function() {
install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'update');
});
data7.appendChild(installBtn2);
}
if(installBtn3 != null) {
installBtn3.style.width = "120px";
installBtn3.addEventListener('click', function() {
install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'toggle_active');
});
data7.appendChild(installBtn3);
}
if(installBtn4 != null) {
installBtn4.style.width = "120px";
installBtn4.addEventListener('click', function() {
install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'fix');
});
data7.appendChild(installBtn4);
}
installBtn.style.width = "120px";
installBtn.addEventListener('click', function() {
if(this.innerHTML == 'Uninstall') {
if (confirm(`Are you sure uninstall ${data.title}?`)) {
install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'uninstall');
}
}
else {
install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'install');
}
});
if(!data.author.startsWith('#NOTICE')){
data7.appendChild(installBtn);
}
if(data.installed == 'Fail' || data.author.startsWith('#NOTICE'))
dataRow.style.backgroundColor = "#880000";
else
dataRow.style.backgroundColor = "var(--bg-color)";
dataRow.style.color = "var(--fg-color)";
dataRow.style.textAlign = "left";
dataRow.appendChild(data0);
dataRow.appendChild(data1);
dataRow.appendChild(data2);
dataRow.appendChild(data3);
dataRow.appendChild(data4);
dataRow.appendChild(data5);
dataRow.appendChild(data6);
dataRow.appendChild(data7);
tbody.appendChild(dataRow);
let buttons = [];
if(installBtn) {
buttons.push(installBtn);
}
if(installBtn2) {
buttons.push(installBtn2);
}
if(installBtn3) {
buttons.push(installBtn3);
}
this.grid_rows[i] = {data:data, buttons:buttons, checkbox:checkbox, control:dataRow};
}
function handleResize() {
const parentHeight = self.element.clientHeight;
const gridHeight = parentHeight - 200;
grid.style.height = gridHeight + "px";
}
window.addEventListener("resize", handleResize);
grid.style.position = "relative";
grid.style.display = "inline-block";
grid.style.width = "100%";
grid.style.height = "100%";
grid.style.overflowY = "scroll";
this.element.style.height = "85%";
this.element.style.width = "80%";
handleResize();
}
createFilterCombo() {
let combo = document.createElement("select");
combo.style.cssFloat = "left";
combo.style.fontSize = "14px";
combo.style.padding = "4px";
combo.style.background = "black";
combo.style.marginLeft = "2px";
combo.style.width = "199px";
combo.id = `combo-manger-filter`;
combo.style.borderRadius = "15px";
let items =
[
{ value:'*', text:'Filter: all' },
{ value:'Disabled', text:'Filter: disabled' },
{ value:'Update', text:'Filter: update' },
{ value:'True', text:'Filter: installed' },
{ value:'False', text:'Filter: not-installed' },
{ value:'Fail', text:'Filter: import failed' },
];
items.forEach(item => {
const option = document.createElement("option");
option.value = item.value;
option.text = item.text;
combo.appendChild(option);
});
if(this.show_mode == CustomNodesInstaller.ShowMode.UPDATE) {
this.filter = 'Update';
}
else if(this.show_mode == CustomNodesInstaller.ShowMode.MISSING_NODES) {
this.filter = '*';
}
let self = this;
combo.addEventListener('change', function(event) {
self.filter = event.target.value;
self.apply_searchbox();
});
if(self.filter) {
combo.value = self.filter;
}
return combo;
}
createHeaderControls() {
let self = this;
this.search_box = $el('input.cm-search-filter', {type:'text', id:'manager-customnode-search-box', placeholder:'input search keyword', value:this.search_keyword}, []);
this.search_box.style.height = "25px";
this.search_box.onkeydown = (event) => {
if (event.key === 'Enter') {
self.search_keyword = self.search_box.value;
self.apply_searchbox();
}
if (event.key === 'Escape') {
self.search_keyword = self.search_box.value;
self.apply_searchbox();
}
};
let search_button = document.createElement("button");
search_button.className = "cm-small-button";
search_button.innerHTML = "Search";
search_button.onclick = () => {
self.search_keyword = self.search_box.value;
self.apply_searchbox();
};
search_button.style.display = "inline-block";
let filter_control = this.createFilterCombo();
filter_control.style.display = "inline-block";
let channel_badge = '';
if(this.channel != 'default') {
channel_badge = $el('span', {id:'cm-channel-badge'}, [`Channel: ${this.channel} (Incomplete list)`]);
}
else {
}
let cell = $el('td', {width:'100%'}, [filter_control, channel_badge, this.search_box, ' ', search_button]);
let search_control = $el('table', {width:'100%'},
[
$el('tr', {}, [cell])
]
);
cell.style.textAlign = "right";
this.element.appendChild(search_control);
}
async createBottomControls() {
var close_button = document.createElement("button");
close_button.className = "cm-small-button";
close_button.innerHTML = "Close";
close_button.onclick = () => { this.close(); }
close_button.style.display = "inline-block";
this.message_box = $el('div', {id:'custom-installer-message'}, [$el('br'), '']);
this.message_box.style.height = '60px';
this.message_box.style.verticalAlign = 'middle';
this.element.appendChild(this.message_box);
this.element.appendChild(close_button);
}
async show(show_mode) {
this.show_mode = show_mode;
if(this.show_mode != CustomNodesInstaller.ShowMode.NORMAL) {
this.search_keyword = '';
}
try {
this.invalidateControl();
this.element.style.display = "block";
this.element.style.zIndex = 10001;
}
catch(exception) {
app.ui.dialog.show(`Failed to get custom node list. / ${exception}`);
}
}
}

1524
js/custom-nodes-manager.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { install_checked_custom_node, manager_instance, rebootAPI, show_message } from "./common.js";
import { manager_instance, rebootAPI, show_message } from "./common.js";
async function install_model(target) {
if(ModelInstaller.instance) {

1
js/turbogrid.esm.js Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 91 KiB

BIN
misc/missing-list.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

BIN
misc/missing-menu.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -11,6 +11,60 @@
{
"author": "jtydhr88",
"title": "ComfyUI-Unique3D",
"id": "unique3d",
"reference": "https://github.com/jtydhr88/ComfyUI-Unique3D",
"files": [
"https://github.com/jtydhr88/ComfyUI-Unique3D"
],
"install_type": "git-clone",
"description": "ComfyUI Unique3D is custom nodes that running [a/AiuniAI/Unique3D](https://github.com/AiuniAI/Unique3D) into ComfyUI."
},
{
"author": "kycg",
"title": "comfyui-Kwtoolset",
"id": "kwtoolset",
"reference": "https://github.com/kycg/comfyui-Kwtoolset",
"files": [
"https://github.com/kycg/comfyui-Kwtoolset"
],
"install_type": "git-clone",
"description": "Nodes:KwtoolsetLoraLoaderwithpreview, KwtoolsetCheckpointLoaderwithpreview, KwtoolsetLoadCheckpointsBatch, KwtoolsetGrowMaskPlus, KwtoolsetGetHipMask, KwtoolsetGetHipMasktest, KwtoolsetGetImageSize, KWPositiveString, KWNagetiveString, KWanywhereString, KwtoolsetChangeOpenpose, ..."
},
{
"author": "mashb1t",
"title": "ComfyUI mashb1t nodes",
"id": "mashb1t",
"reference": "https://github.com/mashb1t/comfyui-nodes-mashb1t",
"files": [
"https://github.com/mashb1t/comfyui-nodes-mashb1t"
],
"install_type": "git-clone",
"description": "This Python script is an optional add-on to the Comfy UI stable diffusion client."
},
{
"author": "immersiveexperience",
"title": "ie-comfyui-color-nodes",
"reference": "https://github.com/immersiveexperience/ie-comfyui-color-nodes",
"files": [
"https://github.com/immersiveexperience/ie-comfyui-color-nodes"
],
"install_type": "git-clone",
"description": "Custom ComfyUI nodes for simple color correction."
},
{
"author": "LZpenguin",
"title": "ComfyUI-Text",
"id": "comfy-text",
"reference": "https://github.com/LZpenguin/ComfyUI-Text",
"files": [
"https://github.com/LZpenguin/ComfyUI-Text"
],
"install_type": "git-clone",
"description": "Nodes:Add_text_by_mask.[w/This custom node cannot be installed simultaneously as it has the same repository name as MarkoCa1/ComfyUI-Text.]"
},
{
"author": "yushan777",
"title": "Y7 Nodes for ComfyUI",

View File

@ -14,7 +14,28 @@
{
"author": "smthemex",
"title": "ComfyUI_Pops",
"id": "pops",
"reference": "https://github.com/smthemex/ComfyUI_Pops",
"files": [
"https://github.com/smthemex/ComfyUI_Pops"
],
"install_type": "git-clone",
"description": "You can use [a/Popspaper](https://popspaper.github.io/pOps/) method in comfyUI"
},
{
"author": "shobhitic",
"title": "PlusMinusTextClip",
"id": "plusminustextclip",
"reference": "https://github.com/shobhitic/ComfyUI-PlusMinusTextClip",
"files": [
"https://github.com/shobhitic/ComfyUI-PlusMinusTextClip"
],
"install_type": "git-clone",
"description": "This adds a node that has both the positive and negative prompts as input in one node. You can just add one node and be done with both Positive and Negative prompts, in place of adding two different nodes for them."
},
{
"author": "huchenlei",
"title": "ComfyUI_omost",
@ -38,6 +59,17 @@
"description": "[a/DenseDiffusion](https://github.com/naver-ai/DenseDiffusion) custom node for ComfyUI."
},
{
"author": "ZHO-ZHO-ZHO",
"title": "ComfyUI-Qwen-2",
"id": "qwen-2",
"reference": "https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-2",
"files": [
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-2"
],
"install_type": "git-clone",
"description": "Using Qwen-2 in ComfyUI"
},
{
"author": "BenNarum",
"title": "SigmaWaveFormNodes",
@ -104,17 +136,6 @@
"install_type": "git-clone",
"description": "Custom nodes make easy Advanced Workflows. Focus on Image/Video and ControlNet efficiency and performances. Manipulation of Latent Space, Automatic pipeline with a bit efforts."
},
{
"author": "LZpenguin",
"title": "ComfyUI-Text",
"id": "comfy-text",
"reference": "https://github.com/LZpenguin/ComfyUI-Text",
"files": [
"https://github.com/LZpenguin/ComfyUI-Text"
],
"install_type": "git-clone",
"description": "Nodes:Add_text_by_mask"
},
{
"author": "seghier",
"title": "ComfyUI_LibreTranslate",

View File

@ -2506,17 +2506,6 @@
"title_aux": "ImagesGrid"
}
],
"https://github.com/LZpenguin/ComfyUI-Text": [
[
"CombinationText",
"PlaceholderText",
"ReplaceText",
"ShowText"
],
{
"title_aux": "ComfyUI-Text"
}
],
"https://github.com/LarryJane491/Image-Captioning-in-ComfyUI": [
[
"LoRA Caption Load",
@ -2796,6 +2785,17 @@
"title_aux": "ComfyUI-BadmanNodes"
}
],
"https://github.com/MarkoCa1/ComfyUI-Text": [
[
"CombinationText",
"PlaceholderText",
"ReplaceText",
"ShowText"
],
{
"title_aux": "ComfyUI-Text"
}
],
"https://github.com/MarkoCa1/ComfyUI_Segment_Mask": [
[
"AutomaticMask(segment anything)"
@ -5160,6 +5160,16 @@
"title_aux": "ComfyUI-Q-Align"
}
],
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-2": [
[
"Qwen2_Chat_Zho",
"Qwen2_ModelLoader_Zho",
"Qwen2_Zho"
],
{
"title_aux": "ComfyUI-Qwen-2"
}
],
"https://github.com/ZHO-ZHO-ZHO/ComfyUI-Qwen-VL-API": [
[
"QWenVL_API_S_Multi_Zho",
@ -5766,6 +5776,7 @@
"Bedrock - Claude Multimodal",
"Bedrock - SDXL",
"Bedrock - Titan Inpainting",
"Bedrock - Titan Outpainting",
"Bedrock - Titan Text to Image",
"Image From S3",
"Image From URL",
@ -5860,6 +5871,8 @@
"Mikey Sampler Base Only Advanced",
"Mikey Sampler Tiled",
"Mikey Sampler Tiled Base Only",
"MikeyLatentTileSampler",
"MikeyLatentTileSamplerCustom",
"MikeySamplerTiledAdvanced",
"MikeySamplerTiledAdvancedBaseOnly",
"MosaicExpandImage",
@ -6875,9 +6888,11 @@
"LayerColor: Levels",
"LayerColor: RGB",
"LayerColor: YUV",
"LayerFilter: AddGrain",
"LayerFilter: ChannelShake",
"LayerFilter: ColorMap",
"LayerFilter: Film",
"LayerFilter: FilmV2",
"LayerFilter: GaussianBlur",
"LayerFilter: HDREffects",
"LayerFilter: LightLeak",
@ -6897,6 +6912,7 @@
"LayerMask: MaskEdgeUltraDetail",
"LayerMask: MaskEdgeUltraDetail V2",
"LayerMask: MaskGradient",
"LayerMask: MaskGrain",
"LayerMask: MaskGrow",
"LayerMask: MaskInvert",
"LayerMask: MaskMotionBlur",
@ -6947,6 +6963,7 @@
"LayerUtility: GetImageSize",
"LayerUtility: GradientImage",
"LayerUtility: GradientImage V2",
"LayerUtility: HLFrequencyDetailRestore",
"LayerUtility: ImageAutoCrop",
"LayerUtility: ImageAutoCrop V2",
"LayerUtility: ImageBlend",
@ -9324,6 +9341,8 @@
"load_url",
"load_wikipedia",
"new_interpreter_tool",
"openai_tts",
"play_audio",
"show_text_party",
"start_dialog",
"start_workflow",
@ -10715,8 +10734,10 @@
"https://github.com/ljleb/comfy-mecha": [
[
"Blocks Mecha Hyper",
"Custom Code Mecha Recipe",
"Float Mecha Hyper",
"Mecha Custom Code Recipe",
"Mecha Merger",
"Mecha Recipe List",
"Model Mecha Recipe"
],
{
@ -11001,6 +11022,7 @@
"ImpactControlNetApplySEGS",
"ImpactControlNetClearSEGS",
"ImpactConvertDataType",
"ImpactCount_Elts_in_SEGS",
"ImpactDecomposeSEGS",
"ImpactDilateMask",
"ImpactDilateMaskInSEGS",
@ -11302,6 +11324,8 @@
],
"https://github.com/marduk191/comfyui-marnodes": [
[
"marduk191_5_text_string",
"marduk191_5way_text_switch",
"marduk191_workflow_settings"
],
{
@ -12602,6 +12626,7 @@
"ChatGPTOpenAI",
"CkptNames_",
"Color",
"CombineAudioVideo",
"ComparingTwoFrames_",
"CompositeImages_",
"DynamicDelayProcessor",
@ -12614,6 +12639,7 @@
"Font",
"GLIGENTextBoxApply_Advanced",
"GamePal",
"GenerateFramesByCount",
"GetImageSize_",
"GradientImage",
"GridDisplayAndSave",
@ -12622,12 +12648,14 @@
"ImageColorTransfer",
"ImageCropByAlpha",
"ImageListReplace_",
"ImageListToBatch_",
"ImagesPrompt_",
"IncrementingListNode_",
"IntNumber",
"JoinWithDelimiter",
"LimitNumber",
"ListSplit_",
"LoadAndCombinedAudio_",
"LoadImagesFromPath",
"LoadImagesFromURL",
"LoadImagesToBatch",
@ -12653,6 +12681,7 @@
"SaveImageAndMetadata_",
"SaveImageToLocal",
"SaveTripoSRMesh",
"ScenesNode_",
"ScreenShare",
"Seed_",
"ShowLayer",
@ -12762,6 +12791,14 @@
"title_aux": "ComfyUI-send-Eagle(slim)"
}
],
"https://github.com/shobhitic/ComfyUI-PlusMinusTextClip": [
[
"PlusMinusTextClip"
],
{
"title_aux": "PlusMinusTextClip"
}
],
"https://github.com/shockz0rz/ComfyUI_InterpolateEverything": [
[
"OpenposePreprocessorInterpolate"
@ -12980,6 +13017,20 @@
"title_aux": "ComfyUI_Pipeline_Tool"
}
],
"https://github.com/smthemex/ComfyUI_Pops": [
[
"Imgae_To_Path",
"Pops_Controlnet_Sampler",
"Pops_Ipadapter_Sampler",
"Pops_Mean_Sampler",
"Pops_Prior_Embedding",
"Pops_Repo_Choice",
"Pops_Unet_Sampler"
],
{
"title_aux": "ComfyUI_Pops"
}
],
"https://github.com/smthemex/ComfyUI_StableAudio_Open": [
[
"StableAudio_Open",
@ -13733,14 +13784,15 @@
],
"https://github.com/vanche1212/ComfyUI-ZMG-Nodes": [
[
"VC_Load_Video_Path_Unified_Output",
"VC_Load_Video_Upload_Unified_Output",
"VC_Video_Combine_Unified_Output",
"Waveform2Audio",
"\ud83d\ude0bAPI Request Node",
"\ud83d\ude0bJSON Parser Node",
"\ud83d\ude0bLoad Video Path Unified Output",
"\ud83d\ude0bOld Photo Colorization Node",
"\ud83d\ude0bOllama Request Node",
"\ud83d\ude0bSave Image Unified Output",
"\ud83d\ude0bVideo Combine Unified Output"
"\ud83d\ude0bSave Image Unified Output"
],
{
"title_aux": "ZMG PLUGIN"

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.37.3"
version = "2.38"
license = "LICENSE"
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]

View File

@ -457,7 +457,13 @@ def gen_json(node_info):
git_url, title, preemptions, node_pattern = node_info[extension]
with open(node_list_json_path, 'r', encoding='utf-8') as f:
node_list_json = json.load(f)
try:
node_list_json = json.load(f)
except Exception as e:
print(f"\nERROR: Invalid json format '{node_list_json_path}'")
print("------------------------------------------------------")
print(e)
print("------------------------------------------------------")
metadata_in_url = {}
if git_url not in data: