|
1 | | -from inspect import cleandoc |
2 | 1 | import hashlib |
3 | 2 | import json |
4 | 3 | import os |
5 | 4 | import requests |
6 | | - |
7 | 5 | import folder_paths |
8 | | -class Example: |
9 | | - """ |
10 | | - A example node |
11 | | -
|
12 | | - Class methods |
13 | | - ------------- |
14 | | - INPUT_TYPES (dict): |
15 | | - Tell the main program input parameters of nodes. |
16 | | - IS_CHANGED: |
17 | | - optional method to control when the node is re executed. |
18 | | -
|
19 | | - Attributes |
20 | | - ---------- |
21 | | - RETURN_TYPES (`tuple`): |
22 | | - The type of each element in the output tulple. |
23 | | - RETURN_NAMES (`tuple`): |
24 | | - Optional: The name of each output in the output tulple. |
25 | | - FUNCTION (`str`): |
26 | | - The name of the entry-point method. For example, if `FUNCTION = "execute"` then it will run Example().execute() |
27 | | - OUTPUT_NODE ([`bool`]): |
28 | | - If this node is an output node that outputs a result/image from the graph. The SaveImage node is an example. |
29 | | - The backend iterates on these output nodes and tries to execute all their parents if their parent graph is properly connected. |
30 | | - Assumed to be False if not present. |
31 | | - CATEGORY (`str`): |
32 | | - The category the node should appear in the UI. |
33 | | - execute(s) -> tuple || None: |
34 | | - The entry point method. The name of this method must be the same as the value of property `FUNCTION`. |
35 | | - For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`. |
36 | | - """ |
37 | | - def __init__(self): |
38 | | - pass |
39 | | - |
40 | | - @classmethod |
41 | | - def INPUT_TYPES(s): |
42 | | - """ |
43 | | - Return a dictionary which contains config for all input fields. |
44 | | - Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". |
45 | | - Input types "INT", "STRING" or "FLOAT" are special values for fields on the node. |
46 | | - The type can be a list for selection. |
47 | | -
|
48 | | - Returns: `dict`: |
49 | | - - Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required` |
50 | | - - Value input_fields (`dict`): Contains input fields config: |
51 | | - * Key field_name (`string`): Name of a entry-point method's argument |
52 | | - * Value field_config (`tuple`): |
53 | | - + First value is a string indicate the type of field or a list for selection. |
54 | | - + Secound value is a config for type "INT", "STRING" or "FLOAT". |
55 | | - """ |
56 | | - return { |
57 | | - "required": { |
58 | | - "image": ("Image", { "tooltip": "This is an image"}), |
59 | | - "int_field": ("INT", { |
60 | | - "default": 0, |
61 | | - "min": 0, #Minimum value |
62 | | - "max": 4096, #Maximum value |
63 | | - "step": 64, #Slider's step |
64 | | - "display": "number" # Cosmetic only: display as "number" or "slider" |
65 | | - }), |
66 | | - "float_field": ("FLOAT", { |
67 | | - "default": 1.0, |
68 | | - "min": 0.0, |
69 | | - "max": 10.0, |
70 | | - "step": 0.01, |
71 | | - "round": 0.001, #The value represeting the precision to round to, will be set to the step value by default. Can be set to False to disable rounding. |
72 | | - "display": "number"}), |
73 | | - "print_to_screen": (["enable", "disable"],), |
74 | | - "string_field": ("STRING", { |
75 | | - "multiline": False, #True if you want the field to look like the one on the ClipTextEncode node |
76 | | - "default": "Hello World!" |
77 | | - }), |
78 | | - }, |
79 | | - } |
80 | | - |
81 | | - RETURN_TYPES = ("IMAGE",) |
82 | | - #RETURN_NAMES = ("image_output_name",) |
83 | | - DESCRIPTION = cleandoc(__doc__) |
84 | | - FUNCTION = "test" |
85 | | - |
86 | | - #OUTPUT_NODE = False |
87 | | - #OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node |
88 | | - |
89 | | - CATEGORY = "Example" |
90 | | - |
91 | | - def test(self, image, string_field, int_field, float_field, print_to_screen): |
92 | | - if print_to_screen == "enable": |
93 | | - print(f"""Your input contains: |
94 | | - string_field aka input text: {string_field} |
95 | | - int_field: {int_field} |
96 | | - float_field: {float_field} |
97 | | - """) |
98 | | - #do some processing on the image, in this example I just invert it |
99 | | - image = 1.0 - image |
100 | | - return (image,) |
101 | | - |
102 | | - """ |
103 | | - The node will always be re executed if any of the inputs change but |
104 | | - this method can be used to force the node to execute again even when the inputs don't change. |
105 | | - You can make this node return a number or a string. This value will be compared to the one returned the last time the node was |
106 | | - executed, if it is different the node will be executed again. |
107 | | - This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash |
108 | | - changes between executions the LoadImage node is executed again. |
109 | | - """ |
110 | | - #@classmethod |
111 | | - #def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen): |
112 | | - # return "" |
113 | | - |
114 | 6 |
|
115 | 7 | def _load_json_from_file(file_path): |
116 | 8 | try: |
@@ -150,6 +42,61 @@ def _calculate_sha256(file_path): |
150 | 42 | return sha256_hash.hexdigest() |
151 | 43 |
|
152 | 44 |
|
| 45 | +def query_lora_stack_tags( |
| 46 | + lora_stack, |
| 47 | + query_tags, |
| 48 | + print_tags, |
| 49 | + force_fetch, |
| 50 | + separator, |
| 51 | + opt_prompt=None, |
| 52 | +): |
| 53 | + if not lora_stack: |
| 54 | + output_tags = opt_prompt or "" |
| 55 | + return lora_stack, output_tags |
| 56 | + |
| 57 | + json_tags_path = os.path.join(os.path.dirname(__file__), "loras_tags.json") |
| 58 | + lora_tags = _load_json_from_file(json_tags_path) or {} |
| 59 | + |
| 60 | + all_tags = [] |
| 61 | + for lora_name, _, _ in lora_stack: |
| 62 | + if lora_name == "None": |
| 63 | + continue |
| 64 | + |
| 65 | + cached_tags = lora_tags.get(lora_name) |
| 66 | + if cached_tags is not None and cached_tags: |
| 67 | + all_tags.extend(cached_tags) |
| 68 | + continue |
| 69 | + if cached_tags is not None and not cached_tags and not (query_tags or force_fetch): |
| 70 | + continue |
| 71 | + |
| 72 | + lora_path = folder_paths.get_full_path("loras", lora_name) |
| 73 | + if not lora_path: |
| 74 | + continue |
| 75 | + |
| 76 | + if query_tags or force_fetch: |
| 77 | + print("calculating lora hash") |
| 78 | + lora_sha256 = _calculate_sha256(lora_path) |
| 79 | + print("requesting infos") |
| 80 | + model_info = _get_model_version_info(lora_sha256) |
| 81 | + if model_info is not None and "trainedWords" in model_info: |
| 82 | + print("tags found!") |
| 83 | + lora_tags[lora_name] = model_info["trainedWords"] |
| 84 | + _save_dict_to_json(lora_tags, json_tags_path) |
| 85 | + all_tags.extend(model_info["trainedWords"]) |
| 86 | + if print_tags: |
| 87 | + print("trainedWords:", ", ".join(model_info["trainedWords"])) |
| 88 | + else: |
| 89 | + print("No information found.") |
| 90 | + lora_tags[lora_name] = [] |
| 91 | + _save_dict_to_json(lora_tags, json_tags_path) |
| 92 | + |
| 93 | + output_tags = separator.join(all_tags) |
| 94 | + if opt_prompt: |
| 95 | + output_tags = f"{opt_prompt}{separator}{output_tags}" if output_tags else opt_prompt |
| 96 | + |
| 97 | + return lora_stack, output_tags |
| 98 | + |
| 99 | + |
153 | 100 | class LoraStackerTagsQuery: |
154 | 101 | """ |
155 | 102 | Fetch CivitAI trainedWords for a LoRA stack and return a CSV string. |
@@ -184,62 +131,18 @@ def query_lora_stack_tags( |
184 | 131 | separator, |
185 | 132 | opt_prompt=None, |
186 | 133 | ): |
187 | | - if not lora_stack: |
188 | | - output_tags = opt_prompt or "" |
189 | | - return (lora_stack, output_tags) |
190 | | - |
191 | | - json_tags_path = os.path.join(os.path.dirname(__file__), "loras_tags.json") |
192 | | - lora_tags = _load_json_from_file(json_tags_path) or {} |
193 | | - |
194 | | - all_tags = [] |
195 | | - for lora_name, _, _ in lora_stack: |
196 | | - if lora_name == "None": |
197 | | - continue |
198 | | - |
199 | | - cached_tags = lora_tags.get(lora_name) |
200 | | - if cached_tags is not None and cached_tags: |
201 | | - all_tags.extend(cached_tags) |
202 | | - continue |
203 | | - if cached_tags is not None and not cached_tags and not (query_tags or force_fetch): |
204 | | - continue |
205 | | - |
206 | | - lora_path = folder_paths.get_full_path("loras", lora_name) |
207 | | - if not lora_path: |
208 | | - continue |
209 | | - |
210 | | - if query_tags or force_fetch: |
211 | | - print("calculating lora hash") |
212 | | - lora_sha256 = _calculate_sha256(lora_path) |
213 | | - print("requesting infos") |
214 | | - model_info = _get_model_version_info(lora_sha256) |
215 | | - if model_info is not None and "trainedWords" in model_info: |
216 | | - print("tags found!") |
217 | | - lora_tags[lora_name] = model_info["trainedWords"] |
218 | | - _save_dict_to_json(lora_tags, json_tags_path) |
219 | | - all_tags.extend(model_info["trainedWords"]) |
220 | | - if print_tags: |
221 | | - print("trainedWords:", ", ".join(model_info["trainedWords"])) |
222 | | - else: |
223 | | - print("No informations found.") |
224 | | - lora_tags[lora_name] = [] |
225 | | - _save_dict_to_json(lora_tags, json_tags_path) |
226 | | - |
227 | | - output_tags = separator.join(all_tags) |
228 | | - if opt_prompt: |
229 | | - output_tags = f"{opt_prompt}{separator}{output_tags}" if output_tags else opt_prompt |
230 | | - |
231 | | - return (lora_stack, output_tags) |
232 | | - |
233 | | - |
234 | | -# A dictionary that contains all nodes you want to export with their names |
235 | | -# NOTE: names should be globally unique |
| 134 | + return query_lora_stack_tags( |
| 135 | + lora_stack, query_tags, print_tags, force_fetch, separator, opt_prompt |
| 136 | + ) |
| 137 | + |
| 138 | + |
| 139 | +# A dictionary that contains all nodes you want to export with their names |
| 140 | +# NOTE: names should be globally unique |
236 | 141 | NODE_CLASS_MAPPINGS = { |
237 | | - "Example": Example, |
238 | 142 | "LoraStackerTagsQuery": LoraStackerTagsQuery, |
239 | 143 | } |
240 | | - |
241 | | -# A dictionary that contains the friendly/humanly readable titles for the nodes |
| 144 | + |
| 145 | +# A dictionary that contains the friendly/humanly readable titles for the nodes |
242 | 146 | NODE_DISPLAY_NAME_MAPPINGS = { |
243 | | - "Example": "Example Node", |
244 | 147 | "LoraStackerTagsQuery": "LoRA Stacker Tags (CivitAI)", |
245 | 148 | } |
0 commit comments