├── .github └── workflows │ └── publish.yml ├── README.md ├── __init__.py ├── load_lora_with_tags.py └── pyproject.toml /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "pyproject.toml" 9 | 10 | permissions: 11 | issues: write 12 | 13 | jobs: 14 | publish-node: 15 | name: Publish Custom Node to registry 16 | runs-on: ubuntu-latest 17 | if: ${{ github.repository_owner == 'Extraltodeus' }} 18 | steps: 19 | - name: Check out code 20 | uses: actions/checkout@v4 21 | - name: Publish Custom Node 22 | uses: Comfy-Org/publish-node-action@v1 23 | with: 24 | ## Add your own personal access token to your Github Repository secrets and reference it here. 25 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/Extraltodeus/LoadLoraWithTags/assets/15731540/150f926f-6c9e-44d0-801f-7de6df9d6993) 2 | 3 | # LoadLoraWithTags 4 | - Save/Load trigger words for loras from a json and auto fetch them on civitai if they are missing. 5 | - Optional prompt input to auto append them (togglable, simply returns the prompt if disabled). 6 | - Actual alphabetical order. 7 | - Print trigger words to terminal. 8 | - Bypass toggle to disable without aiming the sliders at 0. 9 | - Force fetch (not in the screenshot) if you want to force a refresh. 10 | - It uses the sha256 hash so you can rename the file as you like. 11 | 12 | I'm talking about these words right there: 13 | 14 | ![image](https://github.com/Extraltodeus/LoadLoraWithTags/assets/15731540/f4685bd4-5575-4055-a589-89e77eee1365) 15 | 16 | Note: I don't intend to update this node any further. 17 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .load_lora_with_tags import NODE_CLASS_MAPPINGS 2 | __all__ = ["NODE_CLASS_MAPPINGS"] 3 | -------------------------------------------------------------------------------- /load_lora_with_tags.py: -------------------------------------------------------------------------------- 1 | import folder_paths 2 | from comfy.sd import load_lora_for_models 3 | from comfy.utils import load_torch_file 4 | import hashlib 5 | import requests 6 | import json 7 | 8 | def load_json_from_file(file_path): 9 | try: 10 | with open(file_path, 'r') as json_file: 11 | data = json.load(json_file) 12 | return data 13 | except FileNotFoundError: 14 | print(f"File not found: {file_path}") 15 | return None 16 | except json.JSONDecodeError: 17 | print(f"Error decoding JSON in file: {file_path}") 18 | return None 19 | 20 | def save_dict_to_json(data_dict, file_path): 21 | try: 22 | with open(file_path, 'w') as json_file: 23 | json.dump(data_dict, json_file, indent=4) 24 | print(f"Data saved to {file_path}") 25 | except Exception as e: 26 | print(f"Error saving JSON to file: {e}") 27 | 28 | def get_model_version_info(hash_value): 29 | api_url = f"https://civitai.com/api/v1/model-versions/by-hash/{hash_value}" 30 | response = requests.get(api_url) 31 | 32 | if response.status_code == 200: 33 | return response.json() 34 | else: 35 | return None 36 | 37 | def calculate_sha256(file_path): 38 | sha256_hash = hashlib.sha256() 39 | with open(file_path, "rb") as f: 40 | for chunk in iter(lambda: f.read(4096), b""): 41 | sha256_hash.update(chunk) 42 | return sha256_hash.hexdigest() 43 | 44 | class LoraLoaderTagsQuery: 45 | def __init__(self): 46 | self.loaded_lora = None 47 | 48 | @classmethod 49 | def INPUT_TYPES(s): 50 | LORA_LIST = sorted(folder_paths.get_filename_list("loras"), key=str.lower) 51 | return {"required": { "model": ("MODEL",), 52 | "clip": ("CLIP", ), 53 | "lora_name": (LORA_LIST, ), 54 | "strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.1}), 55 | "strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.1}), 56 | "query_tags": ("BOOLEAN", {"default": True}), 57 | "tags_out": ("BOOLEAN", {"default": True}), 58 | "print_tags": ("BOOLEAN", {"default": False}), 59 | "bypass": ("BOOLEAN", {"default": False}), 60 | "force_fetch": ("BOOLEAN", {"default": False}), 61 | }, 62 | "optional": 63 | { 64 | "opt_prompt": ("STRING", {"forceInput": True}), 65 | } 66 | } 67 | 68 | RETURN_TYPES = ("MODEL", "CLIP", "STRING") 69 | FUNCTION = "load_lora" 70 | CATEGORY = "loaders" 71 | 72 | def load_lora(self, model, clip, lora_name, strength_model, strength_clip, query_tags, tags_out, print_tags, bypass, force_fetch, opt_prompt=None): 73 | if strength_model == 0 and strength_clip == 0 or bypass: 74 | if opt_prompt is not None: 75 | out_string = opt_prompt 76 | else: 77 | out_string = "" 78 | return (model, clip, out_string,) 79 | 80 | json_tags_path = "./loras_tags.json" 81 | lora_tags = load_json_from_file(json_tags_path) 82 | output_tags = lora_tags.get(lora_name, None) if lora_tags is not None else None 83 | if output_tags is not None: 84 | output_tags = ", ".join(output_tags) 85 | if print_tags: 86 | print("trainedWords:",output_tags) 87 | else: 88 | output_tags = "" 89 | 90 | lora_path = folder_paths.get_full_path("loras", lora_name) 91 | if (query_tags and output_tags == "") or force_fetch: 92 | print("calculating lora hash") 93 | LORAsha256 = calculate_sha256(lora_path) 94 | print("requesting infos") 95 | model_info = get_model_version_info(LORAsha256) 96 | if model_info is not None: 97 | if "trainedWords" in model_info: 98 | print("tags found!") 99 | if lora_tags is None: 100 | lora_tags = {} 101 | lora_tags[lora_name] = model_info["trainedWords"] 102 | save_dict_to_json(lora_tags,json_tags_path) 103 | output_tags = ", ".join(model_info["trainedWords"]) 104 | if print_tags: 105 | print("trainedWords:",output_tags) 106 | else: 107 | print("No informations found.") 108 | if lora_tags is None: 109 | lora_tags = {} 110 | lora_tags[lora_name] = [] 111 | save_dict_to_json(lora_tags,json_tags_path) 112 | 113 | lora = None 114 | if self.loaded_lora is not None: 115 | if self.loaded_lora[0] == lora_path: 116 | lora = self.loaded_lora[1] 117 | else: 118 | temp = self.loaded_lora 119 | self.loaded_lora = None 120 | del temp 121 | 122 | if lora is None: 123 | lora = load_torch_file(lora_path, safe_load=True) 124 | self.loaded_lora = (lora_path, lora) 125 | 126 | model_lora, clip_lora = load_lora_for_models(model, clip, lora, strength_model, strength_clip) 127 | if opt_prompt is not None: 128 | if tags_out: 129 | output_tags = opt_prompt+", "+output_tags 130 | else: 131 | output_tags = opt_prompt 132 | return (model_lora, clip_lora, output_tags,) 133 | 134 | NODE_CLASS_MAPPINGS = { 135 | "LoraLoaderTagsQuery": LoraLoaderTagsQuery, 136 | } 137 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "loadlorawithtags" 3 | description = "Nodes:LoadLoraWithTags. Save/Load trigger words for loras from a json and auto fetch them on civitai if they are missing." 4 | version = "1.0.0" 5 | license = "LICENSE" 6 | 7 | [project.urls] 8 | Repository = "https://github.com/Extraltodeus/LoadLoraWithTags" 9 | # Used by Comfy Registry https://comfyregistry.org 10 | 11 | [tool.comfy] 12 | PublisherId = "extraltodeus" 13 | DisplayName = "LoadLoraWithTags" 14 | Icon = "" 15 | --------------------------------------------------------------------------------