├── README.MD ├── comfyui_a1111_prompt_array_generator.py ├── custom_nodes └── clip_text_encoder_a1111.py └── docs ├── concept_annotated.jpg ├── generated_annotated.jpg ├── sample_workflow_50steps.json └── workflow_built_annotated.jpg /README.MD: -------------------------------------------------------------------------------- 1 | # ComfyUI A1111-like Prompt Custom Node Solution 2 | 3 | ## How does it work? 4 | 5 | * This repository contains: 6 | * The script `comfyui_a1111_prompt_array_generator.py` for generating the required workflow block because it'd be extremely tedious to create the workflow manually. 7 | * Two new ComfyUI nodes: 8 | * `CLIPTextEncodeA1111`: A variant of `CLIPTextEncode` that converts A1111-like prompt into standard prompt 9 | * `RerouteTextForCLIPTextEncodeA1111`: A kind of `Reroute` for `Text` data type. Workaround for [a ComfyUI bug that prevents Primitive node from being connected with Reroute node](https://github.com/comfyanonymous/ComfyUI/issues/976) 10 | 11 | The idea is each step would require a positive `CLIPTextEncodeA1111` node, a negative `CLIPTextEncodeA1111` node and a `KSamplerAdvanced` node. Each node would have information on which particular step it's processing on. Then the `CLIPTextEncodeA1111` node would based on its step information to convert the A1111-like prompt to the actual prompt to be sent to the `KSamplerAdvanced` of that step. 12 | 13 | ![Diagram explaining how the ComfyUI A1111-like prompt solution work](./docs/concept_annotated.jpg) 14 | 15 | ## Syntax 16 | 17 | * Alternating Words `[foo|bar]`: step 0 is `foo`, step 1 is `bar`, step 2 is `foo` and so on. Three or more words are also supported. 18 | * Prompt Editing `[foo|bar|0.3]`: Uses `foo` for the first 30% of the steps and `bar` for the remaining steps 19 | * Warning: This is incompatible with A1111! It uses `|` instead of `:` to avoid conflict with the embedding syntax of ComfyUI. 20 | * Detection algorithm: If it's three words and the last one is a number, it's Prompt Editing. Otherwise it's Alternating Words 21 | * Recursion is supported. Example: `[[foo|bar]|baz|0.6]` means using `foo` and `bar` every other step for the first 60% of steps, then use `baz` for the remaining steps 22 | 23 | ## Installation and Usage 24 | 25 | ### Option A: Quick Start 26 | 27 | 1. Copy `custom_nodes/clip_text_encoder_a1111.py` to the `custom_nodes` directory of ComfyUI 28 | 2. Quick Start: Load [./docs/sample_workflow_50steps.json](./docs/sample_workflow_50steps.json) and that's it! 29 | 30 | ### Option B: Build your own workflow 31 | 32 | 1. Copy `custom_nodes/clip_text_encoder_a1111.py` to the `custom_nodes` directory of ComfyUI 33 | 2. Run `python3 comfyui_a1111_prompt_array_generator.py 50 > workflow.json`. A workflow block would be generated as shown below: 34 | * Usage: `python3 comfyui_a1111_prompt_array_generator.py [step_start inclusive (default:0)] [step_end exclusive (default:max)]` 35 | ![An image showing generated workflow block](./docs/generated_annotated.jpg) 36 | 3. Load the newly generated `workflow.json` to ComfyUI 37 | * Advanced usage: Use CTRL+Drag to select multiple nodes and then CTRL+C to copy the workflow block. Then you can use CTRL+V to paste the workflow block to another window. 38 | 4. Manually build the workflow around it and enjoy! And example is shown below: 39 | ![An image showing the full workflow](./docs/workflow_built_annotated.jpg) 40 | 41 | ## TODO 42 | 43 | Implement a script that converts any existing workflow into the one that utilizes `CLIPTextEncodeA1111`, which'd enable controlnet support. 44 | -------------------------------------------------------------------------------- /comfyui_a1111_prompt_array_generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # BSD 2-Clause License 3 | # 4 | # Copyright (c) 2023, Wong "Sadale" Cho Ching 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # 1. Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # 2. Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | import copy 28 | import json 29 | import sys 30 | 31 | node_template = { 32 | "PrimitiveNode": { 33 | "id": 9999, "type": "PrimitiveNode", "pos": [9999,9999], "size": {"0": 210,"1": 82}, "flags": {}, "order": 9999, "mode": 0, 34 | "outputs": [ 35 | {"name": "FLOAT","type": "FLOAT","links": None,"slot_index": 0,"widget": {"name": "cfg","config": ["FLOAT",{"default": 8,"min": 0,"max": 100}]}} 36 | ], 37 | "title": "DUMMY", "properties": {}, "widgets_values": None 38 | }, 39 | "Reroute": { "id": 9999, "type": "Reroute", "pos": [9999, 9999], "size": [90.4, 26], "flags": {}, "order": 9999, "mode": 0, 40 | "inputs": [ { "name": "", "type": "*", "link": None, "slot_index": 0 } ], 41 | "outputs": [ { "name": "LATENT", "type": "LATENT", "links": None, "slot_index": 0 } ], 42 | "properties": { "showOutputText": True, "horizontal": False } 43 | }, 44 | "RerouteTextForCLIPTextEncodeA1111": { 45 | "id": 9999, "type": "RerouteTextForCLIPTextEncodeA1111", "pos": [9999,9999],"size": [210,46],"flags": {"collapsed": True},"order": 9999,"mode": 0, 46 | "inputs": [{"name": "text","type": "STRING","link": None,"widget": {"name": "text","config": ["STRING",{"multiline": True}]}}], 47 | "outputs": [{"name": "STRING","type": "STRING","links": None,"shape": 3}],"title": "DUMMY","properties": {"Node name for S&R": "RerouteTextForCLIPTextEncodeA1111"}, 48 | "widgets_values": [""] 49 | }, 50 | "CLIPTextEncode": { "id": 9999, "type": "CLIPTextEncode", "pos": [9999, 9999], "size": {"0": 425, "1": 180 }, "flags": { "collapsed": True }, 51 | "order": 9999, "mode": 0, "inputs": [ { "name": "clip", "type": "CLIP", "link": 9999 } ], 52 | "outputs": [ {"name": "CONDITIONING", "type": "CONDITIONING", "links": None, "slot_index": 0 } ], 53 | "title": "DUMMY", "properties": { "Node name for S&R": "CLIPTextEncode" }, "widgets_values": [ "DUMMY" ] 54 | }, 55 | "CLIPTextEncodeA1111": {"id": 9999,"type": "CLIPTextEncodeA1111","pos": [9999,9999],"size": [261,124],"flags":{"collapsed": True},"order": 9999,"mode": 0, 56 | "inputs": [{"name": "clip","type": "CLIP","link": None,"slot_index": 0}, 57 | {"name": "text","type": "STRING","link": None,"widget": {"name": "text","config": ["STRING",{"multiline": True}]}}], 58 | "outputs": [{"name": "CONDITIONING","type": "CONDITIONING","links": None,"shape": 3,"slot_index": 0}], 59 | "properties": {"Node name for S&R": "CLIPTextEncodeA1111"}, 60 | "widgets_values": ["DUMMY",9999,9999] 61 | }, 62 | "KSamplerAdvanced": { "id": 9999, "type": "KSamplerAdvanced", "pos": [9999, 9999], "size": [315, 334], "flags": {"collapsed": True}, "order": 9999, "mode": 0, 63 | "inputs": [{ "name": "model", "type": "MODEL", "link": 9999, "slot_index": 0 }, 64 | {"name": "positive", "type": "CONDITIONING", "link": 9999, "slot_index": 1 }, 65 | {"name": "negative", "type": "CONDITIONING", "link": 9999, "slot_index": 2 }, 66 | {"name": "latent_image", "type": "LATENT", "link": 9999, "slot_index": 3 }, 67 | {"name": "sampler_name", "type": "euler,euler_ancestral,heun,dpm_2,dpm_2_ancestral,lms,dpm_fast,dpm_adaptive,dpmpp_2s_ancestral,dpmpp_sde,dpmpp_sde_gpu,dpmpp_2m,dpmpp_2m_sde,dpmpp_2m_sde_gpu,ddim,uni_pc,uni_pc_bh2", "link": 9999, "widget": { "name": "sampler_name", "config": [["euler","euler_ancestral","heun","dpm_2","dpm_2_ancestral","lms","dpm_fast","dpm_adaptive","dpmpp_2s_ancestral","dpmpp_sde","dpmpp_sde_gpu","dpmpp_2m","dpmpp_2m_sde","dpmpp_2m_sde_gpu","ddim","uni_pc","uni_pc_bh2"]] } }, 68 | {"name": "scheduler","type": "normal,karras,exponential,simple,ddim_uniform","link": 9999,"widget": {"name": "scheduler","config": [["normal","karras","exponential","simple","ddim_uniform"]]}}, 69 | {"name": "cfg","type": "FLOAT","link": 9999,"widget": {"name": "cfg","config": ["FLOAT",{"default": 8,"min": 0,"max": 100}]}}, 70 | {"name": "noise_seed","type": "INT","link": 9999,"widget": {"name": "noise_seed","config": ["INT",{"default": 0,"min": 0,"max": 18446744073709552000}]}} 71 | ], 72 | "outputs": [{"name": "LATENT","type": "LATENT","links": None,"shape": 3,"slot_index": 0}], 73 | "title": "DUMMY", 74 | "properties": {"Node name for S&R": "KSamplerAdvanced"}, 75 | "widgets_values": ["disable",605756652843224,"randomize",20,8,"euler","normal",0,1,"enable"] 76 | } 77 | } 78 | 79 | json_result = { 80 | "last_node_id": 9999, 81 | "last_link_id": 9999, 82 | "nodes": [], 83 | "links": [], 84 | "groups": [ { "title": "Generator", "bounding": [ 9999, 9999, 9999, 9999 ], "color": "#3f789e"}], 85 | "config": {}, 86 | "extra": {}, 87 | "version": 0.4 88 | } 89 | 90 | data_types = ["LATENT", "CLIP", "MODEL"] 91 | 92 | def create_node(node_type, node_id, title, x, y): 93 | ret = copy.deepcopy(node_template[node_type]) 94 | ret["id"] = node_id 95 | ret["order"] = node_id 96 | if title is not None: 97 | ret["title"] = title 98 | ret["pos"][0] = x 99 | ret["pos"][1] = y 100 | return ret 101 | 102 | def link_node(link_id, src, src_index, dst, dst_index=0): 103 | if src["outputs"][src_index]["links"] is None: 104 | src["outputs"][src_index]["links"] = [] 105 | src["outputs"][src_index]["links"].append(link_id) 106 | 107 | if src["type"] in ["PrimitiveNode", "Reroute", "RerouteTextForCLIPTextEncodeA1111"]: 108 | src["outputs"][src_index]["name"] = dst["inputs"][dst_index]["name"] 109 | src["outputs"][src_index]["type"] = dst["inputs"][dst_index]["type"] 110 | if "widget" in src["outputs"][src_index]: 111 | src["outputs"][src_index]["widget"]["name"] = dst["inputs"][dst_index]["name"] 112 | src["outputs"][src_index]["widget"]["config"] = dst["inputs"][dst_index]["widget"]["config"] 113 | 114 | # Special handling if we're linking to KSamplerAdvanced or CLIPTextEncode or CLIPTextEncodeA1111: Need to copy content based on the mapping of the dst node itself. 115 | if dst["type"] == "KSamplerAdvanced": 116 | mapping = {"cfg": 4, "sampler_name": 5, "scheduler": 6} 117 | if dst["inputs"][dst_index]["name"] == "noise_seed": 118 | src["widgets_values"] = [ 119 | dst["widgets_values"][1], # seed 120 | dst["widgets_values"][2] # seed policy 121 | ] 122 | elif dst["inputs"][dst_index]["name"] in mapping: 123 | src["widgets_values"] = [ 124 | dst["widgets_values"][ mapping[dst["inputs"][dst_index]["name"]] ], 125 | "fixed" 126 | ] 127 | elif dst["type"] in ["CLIPTextEncode", "CLIPTextEncodeA1111"]: 128 | mapping = {"text": 0} 129 | if dst["inputs"][dst_index]["name"] in mapping: 130 | src["widgets_values"] = [ 131 | dst["widgets_values"][ mapping[dst["inputs"][dst_index]["name"]] ] 132 | ] 133 | 134 | dst["inputs"][dst_index]["link"] = link_id 135 | 136 | return [link_id, src["id"], src_index, dst["id"], dst_index] 137 | 138 | 139 | if len(sys.argv) < 2: 140 | print(f"Usage: {sys.argv[0]} [step_start inclusive (default:0)] [step_end exclusive (default:max)]") 141 | exit(1) 142 | 143 | steps = int(sys.argv[1]) 144 | step_start = 0 145 | step_end = steps 146 | if len(sys.argv) > 2: 147 | step_start = int(sys.argv[2]) 148 | if len(sys.argv) > 3: 149 | step_end = int(sys.argv[3]) 150 | 151 | GROUP_X = 100 152 | GROUP_Y = 100 153 | 154 | INPUT_X = GROUP_X 155 | INPUT_Y = GROUP_Y 156 | model_input = create_node("Reroute", 1, None, INPUT_X, INPUT_Y+26*0) 157 | clip_input = create_node("Reroute", 2, None, INPUT_X, INPUT_Y+26*1) 158 | latent_input = create_node("Reroute", 3, None, INPUT_X, INPUT_Y+26*2) 159 | positive_input = create_node("RerouteTextForCLIPTextEncodeA1111", 4, "+ve", INPUT_X, INPUT_Y+32+26*3) 160 | negative_input = create_node("RerouteTextForCLIPTextEncodeA1111", 5, "-ve", INPUT_X, INPUT_Y+32+26*4) 161 | latent_output = create_node("Reroute", 6, None, INPUT_X+325, INPUT_Y+26*0) 162 | 163 | CONFIG_X = INPUT_X 164 | CONFIG_Y = INPUT_Y+26*5+32 165 | seed = create_node("PrimitiveNode", 7, "Seed", CONFIG_X, CONFIG_Y+(82+32)*0) 166 | sampler = create_node("PrimitiveNode", 8, "Sampler", CONFIG_X+210, CONFIG_Y+(82+32)*0) 167 | scheduler = create_node("PrimitiveNode", 9, "Scheduler", CONFIG_X, CONFIG_Y+(82+32)*1) 168 | cfg = create_node("PrimitiveNode", 10, "Cfg", CONFIG_X+210, CONFIG_Y+(82+32)*1) 169 | 170 | 171 | GENERATOR_X = GROUP_X 172 | GENERATOR_Y = CONFIG_Y+(82+32)*2 173 | 174 | NODE_START_INDEX = 11 175 | 176 | link_table = [] 177 | generator_nodes = [] 178 | k_prev = None 179 | for i in range(step_start, step_end): 180 | p = create_node("CLIPTextEncodeA1111", NODE_START_INDEX+i*3+0, f"P{i}", GENERATOR_X, GENERATOR_Y+(0 if i==step_start else 32)+(i-step_start)*2) 181 | n = create_node("CLIPTextEncodeA1111", NODE_START_INDEX+i*3+1, f"N{i}", GENERATOR_X+100, GENERATOR_Y+(0 if i==step_start else 32)+(i-step_start)*2) 182 | 183 | p["widgets_values"][0] = "beautiful scenery nature glass bottle landscape, , purple galaxy bottle," 184 | n["widgets_values"][0] = "text, watermark" 185 | p["widgets_values"][1] = i 186 | n["widgets_values"][1] = i 187 | p["widgets_values"][2] = step_end-step_start 188 | n["widgets_values"][2] = step_end-step_start 189 | 190 | link_table.append(link_node(len(link_table)+1, clip_input, 0, p, 0)) 191 | link_table.append(link_node(len(link_table)+1, clip_input, 0, n, 0)) 192 | link_table.append(link_node(len(link_table)+1, positive_input, 0, p, 1)) 193 | link_table.append(link_node(len(link_table)+1, negative_input, 0, n, 1)) 194 | k = create_node("KSamplerAdvanced", NODE_START_INDEX+i*3+2, f"K{i}", GENERATOR_X+200, GENERATOR_Y+(0 if i==step_start else 32)+(i-step_start)*2) 195 | k["widgets_values"] = [ 196 | "enable" if i==0 else "disable", # add_noise 197 | 156680208700286 if i==0 else 0, # seed 198 | "randomize" if i==0 else "fixed", # seed policy 199 | steps, # steps 200 | 8.0, # cfg 201 | "euler", # sampler 202 | "normal", # schedule 203 | i, # start at step 204 | i+1, # end at step 205 | "disable" if i==steps-1 else "enable" # return with noise 206 | ] 207 | link_table.append(link_node(len(link_table)+1, model_input, 0, k, 0)) 208 | link_table.append(link_node(len(link_table)+1, p, 0, k, 1)) 209 | link_table.append(link_node(len(link_table)+1, n, 0, k, 2)) 210 | link_table.append(link_node(len(link_table)+1, sampler, 0, k, 4)) 211 | link_table.append(link_node(len(link_table)+1, scheduler, 0, k, 5)) 212 | link_table.append(link_node(len(link_table)+1, cfg, 0, k, 6)) 213 | 214 | if i == 0: 215 | link_table.append(link_node(len(link_table)+1, seed, 0, k, 7)) 216 | else: 217 | k["inputs"] = k["inputs"][:-1] # No noise seed input for the remaining nodes 218 | if k_prev is None: 219 | link_table.append(link_node(len(link_table)+1, latent_input, 0, k, 3)) 220 | else: 221 | link_table.append(link_node(len(link_table)+1, k_prev, 0, k, 3)) # link the previous latent output to the current latent input 222 | k_prev = k 223 | generator_nodes += [p, n, k] 224 | if i == step_end-1: 225 | link_table.append(link_node(len(link_table)+1, k, 0, latent_output)) 226 | 227 | json_result["groups"][0]["bounding"] = [GROUP_X, GROUP_Y-40, 420, (GENERATOR_Y-GROUP_Y)+80+(step_end-step_start)*2] 228 | json_result["groups"][0]["title"] = f"Step {step_start}-{step_end-1}" 229 | json_result["nodes"] = [seed, sampler, scheduler, cfg, model_input, clip_input, latent_input, positive_input, negative_input, latent_output] + generator_nodes 230 | json_result["links"] = link_table 231 | json_result["last_node_id"] = len(json_result["nodes"]) 232 | json_result["last_link_id"] = len(json_result["links"]) 233 | 234 | print(json.dumps(json_result, indent=4)) 235 | -------------------------------------------------------------------------------- /custom_nodes/clip_text_encoder_a1111.py: -------------------------------------------------------------------------------- 1 | # BSD 2-Clause License 2 | # 3 | # Copyright (c) 2023, Wong "Sadale" Cho Ching 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | def prompt_expander(text, cur_step, steps, handle_escape_character=True): 27 | ret = '' 28 | escaping = False 29 | depth = 0 30 | 31 | substitution_parameters = [] 32 | substitution_str = '' 33 | for i, c in enumerate(text): 34 | if not escaping: 35 | if handle_escape_character and c == '\\': 36 | escaping = True 37 | continue 38 | elif c == '[': 39 | if depth == 0: 40 | substitution_parameters = [] 41 | substitution_str = '' 42 | depth += 1 43 | continue 44 | depth += 1 45 | elif c == ']': 46 | depth -= 1 47 | if depth < 0: 48 | raise Exception(f"Prompt Error: Extra closing bracket at index {i}") 49 | elif depth == 0: 50 | substitution_parameters.append(substitution_str) 51 | substitution_str = '' 52 | if len(substitution_parameters) == 3 and substitution_parameters[2].replace('.','',1).isdigit(): 53 | threshold = round(float(substitution_parameters[2])*steps) 54 | ret += prompt_expander(substitution_parameters[0] if cur_step < threshold else substitution_parameters[1], cur_step, steps, False) 55 | else: 56 | ret += prompt_expander(substitution_parameters[cur_step%len(substitution_parameters)], cur_step, steps, False) 57 | continue 58 | elif depth == 1 and c == '|': 59 | substitution_parameters.append(substitution_str) 60 | substitution_str = '' 61 | continue 62 | 63 | if depth == 0: 64 | ret += c 65 | else: 66 | substitution_str += c 67 | escaping = False 68 | if depth != 0: 69 | raise Exception("Prompt Error: Missing closing bracket at the end of prompt") 70 | return ret 71 | 72 | class CLIPTextEncodeA1111: 73 | @classmethod 74 | def INPUT_TYPES(s): 75 | return {"required": {"text": ("STRING", {"multiline": True}), "clip": ("CLIP", ), "cur_step": ("INT", {"default": 0, "min": 0, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000}) }} 76 | RETURN_TYPES = ("CONDITIONING", ) 77 | FUNCTION = "encode" 78 | 79 | CATEGORY = "conditioning" 80 | 81 | def encode(self, clip, text, cur_step, steps): 82 | tokens = clip.tokenize(prompt_expander(text,cur_step,steps)) 83 | cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) 84 | return ([[cond, {"pooled_output": pooled}]],) 85 | 86 | class RerouteText: 87 | @classmethod 88 | def INPUT_TYPES(s): 89 | return {"required": {"text": ("STRING", {"multiline": True}), }} 90 | RETURN_TYPES = ("STRING", ) 91 | FUNCTION = "reroute" 92 | 93 | CATEGORY = "utils" 94 | 95 | def reroute(self, text): 96 | return (text, ) 97 | 98 | NODE_CLASS_MAPPINGS = { 99 | "CLIPTextEncodeA1111": CLIPTextEncodeA1111, 100 | "RerouteTextForCLIPTextEncodeA1111": RerouteText, 101 | } 102 | 103 | NODE_DISPLAY_NAME_MAPPINGS = { 104 | "CLIPTextEncodeA1111": "CLIP Text Encode A1111 (Prompt)", 105 | "RerouteTextForCLIPTextEncodeA1111": "Reroute Text", 106 | } 107 | -------------------------------------------------------------------------------- /docs/concept_annotated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SadaleNet/CLIPTextEncodeA1111-ComfyUI/b34783a2baea99245cee564e0ea9087b7076beb6/docs/concept_annotated.jpg -------------------------------------------------------------------------------- /docs/generated_annotated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SadaleNet/CLIPTextEncodeA1111-ComfyUI/b34783a2baea99245cee564e0ea9087b7076beb6/docs/generated_annotated.jpg -------------------------------------------------------------------------------- /docs/workflow_built_annotated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SadaleNet/CLIPTextEncodeA1111-ComfyUI/b34783a2baea99245cee564e0ea9087b7076beb6/docs/workflow_built_annotated.jpg --------------------------------------------------------------------------------