diff --git a/README.md b/README.md index 853c9e6..dd0dcf3 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ This repository offers various extension nodes for ComfyUI. Nodes here have diff * `Prompt Extractor (Inspire)`: This node reads prompt information from the image's metadata. Since it retrieves all the text, you need to directly specify the prompts to be used for `positive` and `negative` as indicated in the info. * `Global Seed (Inspire)`: This is a node that controls the global seed without a separate connection line. It only controls when the widget's name is 'seed' or 'noise_seed'. Additionally, if 'control_before_generate' is checked, it controls the seed before executing the prompt. * Seeds that have been converted into inputs are excluded from the target. If you want to control the seed separately, convert it into an input and control it separately. + * `Global Sampler (Inspire)`: This node is similar to GlobalSeed and can simultaneously set the sampler_name and scheduler for all nodes in the workflow. + * It applies only to nodes that have both sampler_name and scheduler, and it won't be effective if `GlobalSampler` is muted. + * If some of the `sampler_name` and `scheduler` have been converted to input and connected to Primitive node, it will not apply only to the converted widget. The widget that has not been converted to input will still be affected. * `Bind [ImageList, PromptList] (Inspire)`: Bind Image list and zipped prompt list to export `image`, `positive`, `negative`, and `prompt_label` in a list format. If there are more prompts than images, the excess prompts are ignored, and if there are not enough, the remainder is filled with default input based on the images. * `Wildcard Encode (Inspire)`: The combination node of [ImpactWildcardEncode](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/ImpactWildcard.md) and BlenderNeko's [CLIP Text Encode (Advanced)](https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb). * To use this node, you need both the [Impact Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) and the [Advanced CLIP Text Encode]((https://github.com/BlenderNeko/ComfyUI_ADV_CLIP_emb)) extensions. diff --git a/__init__.py b/__init__.py index c74add2..afaf210 100644 --- a/__init__.py +++ b/__init__.py @@ -7,7 +7,7 @@ import importlib -print(f"### Loading: ComfyUI-Inspire-Pack (V0.53)") +print(f"### Loading: ComfyUI-Inspire-Pack (V0.54)") node_list = [ "lora_block_weight", diff --git a/inspire/inspire_server.py b/inspire/inspire_server.py index 8edabef..18f9042 100644 --- a/inspire/inspire_server.py +++ b/inspire/inspire_server.py @@ -99,11 +99,10 @@ def control_seed(v): def prompt_seed_update(json_data): try: - seed_widget_map = json_data['extra_data']['extra_pnginfo']['workflow']['seed_widgets'] - except: + widget_idx_map = json_data['extra_data']['extra_pnginfo']['workflow']['widget_idx_map'] + except Exception: return None - seed_widget_map = json_data['extra_data']['extra_pnginfo']['workflow']['seed_widgets'] value = None mode = None node = None @@ -132,7 +131,7 @@ def prompt_seed_update(json_data): if isinstance(v2, str) and '$GlobalSeed.value$' in v2: v['inputs'][k2] = v2.replace('$GlobalSeed.value$', str(value)) - if k not in seed_widget_map: + if k not in widget_idx_map or ('seed' not in widget_idx_map[k] and 'noise_seed' not in widget_idx_map[k]): continue if 'seed' in v['inputs']: @@ -156,7 +155,7 @@ def prompt_seed_update(json_data): def workflow_seed_update(json_data): nodes = json_data['extra_data']['extra_pnginfo']['workflow']['nodes'] - seed_widget_map = json_data['extra_data']['extra_pnginfo']['workflow']['seed_widgets'] + widget_idx_map = json_data['extra_data']['extra_pnginfo']['workflow']['widget_idx_map'] prompt = json_data['prompt'] updated_seed_map = {} @@ -167,18 +166,67 @@ def workflow_seed_update(json_data): if node['type'] == 'GlobalSeed //Inspire': value = prompt[node_id]['inputs']['value'] node['widgets_values'][0] = value - elif node_id in seed_widget_map: - widget_idx = seed_widget_map[node_id] - + elif node_id in widget_idx_map: + widget_idx = None + seed = None if 'noise_seed' in prompt[node_id]['inputs']: seed = prompt[node_id]['inputs']['noise_seed'] - else: + widget_idx = widget_idx_map[node_id].get('noise_seed') + elif 'seed' in prompt[node_id]['inputs']: seed = prompt[node_id]['inputs']['seed'] + widget_idx = widget_idx_map[node_id].get('seed') - node['widgets_values'][widget_idx] = seed - updated_seed_map[node_id] = seed + if widget_idx is not None: + node['widgets_values'][widget_idx] = seed + updated_seed_map[node_id] = seed - server.PromptServer.instance.send_sync("inspire-global-seed", {"id": node_id, "value": value, "seed_map": updated_seed_map}) + server.PromptServer.instance.send_sync("inspire-global-seed", {"value": value, "seed_map": updated_seed_map}) + + +def prompt_sampler_update(json_data): + try: + widget_idx_map = json_data['extra_data']['extra_pnginfo']['workflow']['widget_idx_map'] + except Exception: + return None + + nodes = json_data['extra_data']['extra_pnginfo']['workflow']['nodes'] + prompt = json_data['prompt'] + + sampler_name = None + scheduler = None + + updated_nodes = set() + + for v in prompt.values(): + cls = v.get('class_type') + if cls == 'GlobalSampler //Inspire': + sampler_name = v['inputs']['sampler_name'] + scheduler = v['inputs']['scheduler'] + + if sampler_name is not None: + for k, v in json_data['prompt'].items(): + if v.get('class_type') == 'GlobalSampler //Inspire': + continue + + if ('sampler_name' in v['inputs'] and 'scheduler' in v['inputs'] and + isinstance(v['inputs']['sampler_name'], str) and 'scheduler' in v['inputs']): + v['inputs']['sampler_name'] = sampler_name + v['inputs']['scheduler'] = scheduler + server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": 'sampler_name', "type": "text", "data": sampler_name}) + server.PromptServer.instance.send_sync("inspire-node-feedback", {"node_id": k, "widget_name": 'scheduler', "type": "text", "data": scheduler}) + + updated_nodes.add(k) + + for node in nodes: + node_id = str(node['id']) + + if node_id in prompt and node_id in widget_idx_map: + sampler_widget_idx = widget_idx_map[node_id].get('sampler_name') + scheduler_widget_idx = widget_idx_map[node_id].get('scheduler') + if sampler_widget_idx is not None: + node['widgets_values'][sampler_widget_idx] = sampler_name + if scheduler_widget_idx is not None: + node['widgets_values'][scheduler_widget_idx] = scheduler def workflow_loadimage_update(json_data): @@ -240,6 +288,8 @@ def onprompt(json_data): if is_changed: workflow_seed_update(json_data) + prompt_sampler_update(json_data) + workflow_loadimage_update(json_data) populate_wildcards(json_data) diff --git a/inspire/prompt_support.py b/inspire/prompt_support.py index af99995..1afff86 100644 --- a/inspire/prompt_support.py +++ b/inspire/prompt_support.py @@ -335,6 +335,27 @@ class GlobalSeed: return {} +class GlobalSampler: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS, ), + } + } + + RETURN_TYPES = () + FUNCTION = "doit" + + CATEGORY = "InspirePack/Prompt" + + OUTPUT_NODE = True + + def doit(self, **kwargs): + return {} + + class BindImageListPromptList: @classmethod def INPUT_TYPES(s): @@ -599,6 +620,7 @@ NODE_CLASS_MAPPINGS = { "ZipPrompt //Inspire": ZipPrompt, "PromptExtractor //Inspire": PromptExtractor, "GlobalSeed //Inspire": GlobalSeed, + "GlobalSampler //Inspire": GlobalSampler, "BindImageListPromptList //Inspire": BindImageListPromptList, "WildcardEncode //Inspire": WildcardEncodeInspire, "PromptBuilder //Inspire": PromptBuilder, @@ -614,6 +636,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ZipPrompt //Inspire": "Zip Prompt (Inspire)", "PromptExtractor //Inspire": "Prompt Extractor (Inspire)", "GlobalSeed //Inspire": "Global Seed (Inspire)", + "GlobalSampler //Inspire": "Global Sampler (Inspire)", "BindImageListPromptList //Inspire": "Bind [ImageList, PromptList] (Inspire)", "WildcardEncode //Inspire": "Wildcard Encode (Inspire)", "PromptBuilder //Inspire": "Prompt Builder (Inspire)", diff --git a/js/prompt.js b/js/prompt.js index 1db2040..8446d41 100644 --- a/js/prompt.js +++ b/js/prompt.js @@ -165,4 +165,32 @@ app.registerExtension({ }); } } -}); \ No newline at end of file +}); + + + + +const original_queuePrompt = api.queuePrompt; +async function queuePrompt_with_widget_idxs(number, { output, workflow }) { + workflow.widget_idx_map = {}; + + for(let i in app.graph._nodes_by_id) { + let widgets = app.graph._nodes_by_id[i].widgets; + if(widgets) { + for(let j in widgets) { + if(['seed', 'noise_seed', 'sampler_name', 'scheduler'].includes(widgets[j].name) + && widgets[j].type != 'converted-widget') { + if(workflow.widget_idx_map[i] == undefined) { + workflow.widget_idx_map[i] = {}; + } + + workflow.widget_idx_map[i][widgets[j].name] = parseInt(j); + } + } + } + } + + return await original_queuePrompt.call(api, number, { output, workflow }); +} + +api.queuePrompt = queuePrompt_with_widget_idxs; \ No newline at end of file diff --git a/js/seed.js b/js/seed.js index 8233c06..d70ee95 100644 --- a/js/seed.js +++ b/js/seed.js @@ -4,44 +4,24 @@ function globalSeedHandler(event) { let nodes = app.graph._nodes_by_id; for(let i in nodes) { - let node = nodes[i]; + let node = nodes[i]; - if(node.type == 'GlobalSeed //Inspire') { - if(node.widgets) { - const w = node.widgets.find((w) => w.name == 'value'); - const last_w = node.widgets.find((w) => w.name == 'last_seed'); - last_w.value = w.value; - w.value = event.detail.value; - } - } - else - if(node.widgets) { - const w = node.widgets.find((w) => (w.name == 'seed' || w.name == 'noise_seed') && w.type == 'number'); - if(w && event.detail.seed_map[node.id] != undefined) { - w.value = event.detail.seed_map[node.id]; - } - } + if(node.type == 'GlobalSeed //Inspire') { + if(node.widgets) { + const w = node.widgets.find((w) => w.name == 'value'); + const last_w = node.widgets.find((w) => w.name == 'last_seed'); + last_w.value = w.value; + w.value = event.detail.value; + } + } + else + if(node.widgets) { + const w = node.widgets.find((w) => (w.name == 'seed' || w.name == 'noise_seed') && w.type == 'number'); + if(w && event.detail.seed_map[node.id] != undefined) { + w.value = event.detail.seed_map[node.id]; + } + } } } -api.addEventListener("inspire-global-seed", globalSeedHandler); - - -const original_queuePrompt = api.queuePrompt; -async function queuePrompt_with_seed(number, { output, workflow }) { - workflow.seed_widgets = {}; - - for(let i in app.graph._nodes_by_id) { - let widgets = app.graph._nodes_by_id[i].widgets; - if(widgets) { - for(let j in widgets) { - if((widgets[j].name == 'seed' || widgets[j].name == 'noise_seed') && widgets[j].type != 'converted-widget') - workflow.seed_widgets[i] = parseInt(j); - } - } - } - - return await original_queuePrompt.call(api, number, { output, workflow }); -} - -api.queuePrompt = queuePrompt_with_seed; \ No newline at end of file +api.addEventListener("inspire-global-seed", globalSeedHandler); \ No newline at end of file