Merge branch 'ltdrdata:main' into main
@ -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
|
||||
|
||||

|
||||

|
||||
|
||||
* 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.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## Additional Feature
|
||||
|
||||
@ -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."
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
2399
github-stats.json
@ -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__), '..'))
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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 = ' ID ';
|
||||
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 = ` ${data.tags}`;
|
||||
var data3 = document.createElement('td');
|
||||
var data4 = document.createElement('td');
|
||||
if(data.custom_node) {
|
||||
data3.innerHTML = ` ${data.custom_node.author}`;
|
||||
data4.innerHTML = ` <a href=${data.custom_node.reference} target="_blank"><font color="skyblue"><b>${data.custom_node.title}</b></font></a>`;
|
||||
}
|
||||
else {
|
||||
data3.innerHTML = ` Unknown`;
|
||||
data4.innerHTML = ` 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
66
js/common.js
@ -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) {
|
||||
|
||||
@ -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 = ' ID ';
|
||||
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 = ' ★ ';
|
||||
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 = ` <a href=${data.reference} target="_blank"><font color="skyblue"><b>${data.title}</b></font></a>`;
|
||||
if(data.installed == 'Fail')
|
||||
data3.innerHTML = ' <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
@ -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
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 160 KiB |
BIN
misc/main.png
|
Before Width: | Height: | Size: 28 KiB |
BIN
misc/menu.jpg
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 91 KiB |
BIN
misc/missing-list.jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 198 KiB |
BIN
misc/missing-menu.jpg
Normal file
|
After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 29 KiB |
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"]
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||