Upload tag_autocomplete_helper.py
Browse files- tag_autocomplete_helper.py +284 -0
tag_autocomplete_helper.py
ADDED
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This helper script scans folders for wildcards and embeddings and writes them
|
2 |
+
# to a temporary file to expose it to the javascript side
|
3 |
+
|
4 |
+
import gradio as gr
|
5 |
+
from pathlib import Path
|
6 |
+
from modules import scripts, script_callbacks, shared, sd_hijack
|
7 |
+
#from modules.paths import script_path, extensions_dir
|
8 |
+
import yaml
|
9 |
+
|
10 |
+
# Webui root path
|
11 |
+
FILE_DIR = Path().absolute()
|
12 |
+
#FILE_DIR = Path(script_path)
|
13 |
+
|
14 |
+
|
15 |
+
# The extension base path
|
16 |
+
EXT_PATH = FILE_DIR.joinpath('extensions')
|
17 |
+
#EXT_PATH = Path(extensions_dir)
|
18 |
+
|
19 |
+
|
20 |
+
# Tags base path
|
21 |
+
TAGS_PATH = Path(scripts.basedir()).joinpath('tags')
|
22 |
+
|
23 |
+
# The path to the folder containing the wildcards and embeddings
|
24 |
+
WILDCARD_PATH = FILE_DIR.joinpath('scripts/wildcards')
|
25 |
+
EMB_PATH = Path(shared.cmd_opts.embeddings_dir)
|
26 |
+
HYP_PATH = Path(shared.cmd_opts.hypernetwork_dir)
|
27 |
+
|
28 |
+
try:
|
29 |
+
LORA_PATH = Path(shared.cmd_opts.lora_dir)
|
30 |
+
except AttributeError:
|
31 |
+
LORA_PATH = None
|
32 |
+
|
33 |
+
def find_ext_wildcard_paths():
|
34 |
+
"""Returns the path to the extension wildcards folder"""
|
35 |
+
found = list(EXT_PATH.glob('*/wildcards/'))
|
36 |
+
return found
|
37 |
+
|
38 |
+
|
39 |
+
# The path to the extension wildcards folder
|
40 |
+
WILDCARD_EXT_PATHS = find_ext_wildcard_paths()
|
41 |
+
|
42 |
+
# The path to the temporary files
|
43 |
+
STATIC_TEMP_PATH = FILE_DIR.joinpath('tmp') # In the webui root, on windows it exists by default, on linux it doesn't
|
44 |
+
TEMP_PATH = TAGS_PATH.joinpath('temp') # Extension specific temp files
|
45 |
+
|
46 |
+
|
47 |
+
def get_wildcards():
|
48 |
+
"""Returns a list of all wildcards. Works on nested folders."""
|
49 |
+
wildcard_files = list(WILDCARD_PATH.rglob("*.txt"))
|
50 |
+
resolved = [w.relative_to(WILDCARD_PATH).as_posix(
|
51 |
+
) for w in wildcard_files if w.name != "put wildcards here.txt"]
|
52 |
+
return resolved
|
53 |
+
|
54 |
+
|
55 |
+
def get_ext_wildcards():
|
56 |
+
"""Returns a list of all extension wildcards. Works on nested folders."""
|
57 |
+
wildcard_files = []
|
58 |
+
|
59 |
+
for path in WILDCARD_EXT_PATHS:
|
60 |
+
wildcard_files.append(path.relative_to(FILE_DIR).as_posix())
|
61 |
+
wildcard_files.extend(p.relative_to(path).as_posix() for p in path.rglob("*.txt") if p.name != "put wildcards here.txt")
|
62 |
+
wildcard_files.append("-----")
|
63 |
+
|
64 |
+
return wildcard_files
|
65 |
+
|
66 |
+
|
67 |
+
def get_ext_wildcard_tags():
|
68 |
+
"""Returns a list of all tags found in extension YAML files found under a Tags: key."""
|
69 |
+
wildcard_tags = {} # { tag: count }
|
70 |
+
yaml_files = []
|
71 |
+
for path in WILDCARD_EXT_PATHS:
|
72 |
+
yaml_files.extend(p for p in path.rglob("*.yml"))
|
73 |
+
yaml_files.extend(p for p in path.rglob("*.yaml"))
|
74 |
+
count = 0
|
75 |
+
for path in yaml_files:
|
76 |
+
try:
|
77 |
+
with open(path, encoding="utf8") as file:
|
78 |
+
data = yaml.safe_load(file)
|
79 |
+
for item in data:
|
80 |
+
if data[item] and 'Tags' in data[item]:
|
81 |
+
wildcard_tags[count] = ','.join(data[item]['Tags'])
|
82 |
+
count += 1
|
83 |
+
else:
|
84 |
+
print('Issue with tags found in ' + path.name + ' at item ' + item)
|
85 |
+
except yaml.YAMLError as exc:
|
86 |
+
print(exc)
|
87 |
+
# Sort by count
|
88 |
+
sorted_tags = sorted(wildcard_tags.items(), key=lambda item: item[1], reverse=True)
|
89 |
+
output = []
|
90 |
+
for tag, count in sorted_tags:
|
91 |
+
output.append(f"{tag},{count}")
|
92 |
+
return output
|
93 |
+
|
94 |
+
|
95 |
+
def get_embeddings(sd_model):
|
96 |
+
"""Write a list of all embeddings with their version"""
|
97 |
+
|
98 |
+
# Version constants
|
99 |
+
V1_SHAPE = 768
|
100 |
+
V2_SHAPE = 1024
|
101 |
+
emb_v1 = []
|
102 |
+
emb_v2 = []
|
103 |
+
results = []
|
104 |
+
|
105 |
+
try:
|
106 |
+
# Get embedding dict from sd_hijack to separate v1/v2 embeddings
|
107 |
+
emb_type_a = sd_hijack.model_hijack.embedding_db.word_embeddings
|
108 |
+
emb_type_b = sd_hijack.model_hijack.embedding_db.skipped_embeddings
|
109 |
+
# Get the shape of the first item in the dict
|
110 |
+
emb_a_shape = -1
|
111 |
+
emb_b_shape = -1
|
112 |
+
if (len(emb_type_a) > 0):
|
113 |
+
emb_a_shape = next(iter(emb_type_a.items()))[1].shape
|
114 |
+
if (len(emb_type_b) > 0):
|
115 |
+
emb_b_shape = next(iter(emb_type_b.items()))[1].shape
|
116 |
+
|
117 |
+
# Add embeddings to the correct list
|
118 |
+
if (emb_a_shape == V1_SHAPE):
|
119 |
+
emb_v1 = list(emb_type_a.keys())
|
120 |
+
elif (emb_a_shape == V2_SHAPE):
|
121 |
+
emb_v2 = list(emb_type_a.keys())
|
122 |
+
|
123 |
+
if (emb_b_shape == V1_SHAPE):
|
124 |
+
emb_v1 = list(emb_type_b.keys())
|
125 |
+
elif (emb_b_shape == V2_SHAPE):
|
126 |
+
emb_v2 = list(emb_type_b.keys())
|
127 |
+
|
128 |
+
# Get shape of current model
|
129 |
+
#vec = sd_model.cond_stage_model.encode_embedding_init_text(",", 1)
|
130 |
+
#model_shape = vec.shape[1]
|
131 |
+
# Show relevant entries at the top
|
132 |
+
#if (model_shape == V1_SHAPE):
|
133 |
+
# results = [e + ",v1" for e in emb_v1] + [e + ",v2" for e in emb_v2]
|
134 |
+
#elif (model_shape == V2_SHAPE):
|
135 |
+
# results = [e + ",v2" for e in emb_v2] + [e + ",v1" for e in emb_v1]
|
136 |
+
#else:
|
137 |
+
# raise AttributeError # Fallback to old method
|
138 |
+
results = sorted([e + ",v1" for e in emb_v1] + [e + ",v2" for e in emb_v2], key=lambda x: x.lower())
|
139 |
+
except AttributeError:
|
140 |
+
print("tag_autocomplete_helper: Old webui version or unrecognized model shape, using fallback for embedding completion.")
|
141 |
+
# Get a list of all embeddings in the folder
|
142 |
+
all_embeds = [str(e.relative_to(EMB_PATH)) for e in EMB_PATH.rglob("*") if e.suffix in {".bin", ".pt", ".png",'.webp', '.jxl', '.avif'}]
|
143 |
+
# Remove files with a size of 0
|
144 |
+
all_embeds = [e for e in all_embeds if EMB_PATH.joinpath(e).stat().st_size > 0]
|
145 |
+
# Remove file extensions
|
146 |
+
all_embeds = [e[:e.rfind('.')] for e in all_embeds]
|
147 |
+
results = [e + "," for e in all_embeds]
|
148 |
+
|
149 |
+
write_to_temp_file('emb.txt', results)
|
150 |
+
|
151 |
+
def get_hypernetworks():
|
152 |
+
"""Write a list of all hypernetworks"""
|
153 |
+
|
154 |
+
# Get a list of all hypernetworks in the folder
|
155 |
+
all_hypernetworks = [str(h.name) for h in HYP_PATH.rglob("*") if h.suffix in {".pt"}]
|
156 |
+
# Remove file extensions
|
157 |
+
return sorted([h[:h.rfind('.')] for h in all_hypernetworks], key=lambda x: x.lower())
|
158 |
+
|
159 |
+
def get_lora():
|
160 |
+
"""Write a list of all lora"""
|
161 |
+
|
162 |
+
# Get a list of all lora in the folder
|
163 |
+
all_lora = [str(l.name) for l in LORA_PATH.rglob("*") if l.suffix in {".safetensors", ".ckpt", ".pt"}]
|
164 |
+
# Remove file extensions
|
165 |
+
return sorted([l[:l.rfind('.')] for l in all_lora], key=lambda x: x.lower())
|
166 |
+
|
167 |
+
|
168 |
+
def write_tag_base_path():
|
169 |
+
"""Writes the tag base path to a fixed location temporary file"""
|
170 |
+
with open(STATIC_TEMP_PATH.joinpath('tagAutocompletePath.txt'), 'w', encoding="utf-8") as f:
|
171 |
+
f.write(TAGS_PATH.relative_to(FILE_DIR).as_posix())
|
172 |
+
|
173 |
+
|
174 |
+
def write_to_temp_file(name, data):
|
175 |
+
"""Writes the given data to a temporary file"""
|
176 |
+
with open(TEMP_PATH.joinpath(name), 'w', encoding="utf-8") as f:
|
177 |
+
f.write(('\n'.join(data)))
|
178 |
+
|
179 |
+
|
180 |
+
csv_files = []
|
181 |
+
csv_files_withnone = []
|
182 |
+
def update_tag_files():
|
183 |
+
"""Returns a list of all potential tag files"""
|
184 |
+
global csv_files, csv_files_withnone
|
185 |
+
files = [str(t.relative_to(TAGS_PATH)) for t in TAGS_PATH.glob("*.csv")]
|
186 |
+
csv_files = files
|
187 |
+
csv_files_withnone = ["None"] + files
|
188 |
+
|
189 |
+
|
190 |
+
|
191 |
+
# Write the tag base path to a fixed location temporary file
|
192 |
+
# to enable the javascript side to find our files regardless of extension folder name
|
193 |
+
if not STATIC_TEMP_PATH.exists():
|
194 |
+
STATIC_TEMP_PATH.mkdir(exist_ok=True)
|
195 |
+
|
196 |
+
write_tag_base_path()
|
197 |
+
update_tag_files()
|
198 |
+
|
199 |
+
# Check if the temp path exists and create it if not
|
200 |
+
if not TEMP_PATH.exists():
|
201 |
+
TEMP_PATH.mkdir(parents=True, exist_ok=True)
|
202 |
+
|
203 |
+
# Set up files to ensure the script doesn't fail to load them
|
204 |
+
# even if no wildcards or embeddings are found
|
205 |
+
write_to_temp_file('wc.txt', [])
|
206 |
+
write_to_temp_file('wce.txt', [])
|
207 |
+
write_to_temp_file('wcet.txt', [])
|
208 |
+
write_to_temp_file('hyp.txt', [])
|
209 |
+
write_to_temp_file('lora.txt', [])
|
210 |
+
# Only reload embeddings if the file doesn't exist, since they are already re-written on model load
|
211 |
+
if not TEMP_PATH.joinpath("emb.txt").exists():
|
212 |
+
write_to_temp_file('emb.txt', [])
|
213 |
+
|
214 |
+
# Write wildcards to wc.txt if found
|
215 |
+
if WILDCARD_PATH.exists():
|
216 |
+
wildcards = [WILDCARD_PATH.relative_to(FILE_DIR).as_posix()] + get_wildcards()
|
217 |
+
if wildcards:
|
218 |
+
write_to_temp_file('wc.txt', wildcards)
|
219 |
+
|
220 |
+
# Write extension wildcards to wce.txt if found
|
221 |
+
if WILDCARD_EXT_PATHS is not None:
|
222 |
+
wildcards_ext = get_ext_wildcards()
|
223 |
+
if wildcards_ext:
|
224 |
+
write_to_temp_file('wce.txt', wildcards_ext)
|
225 |
+
# Write yaml extension wildcards to wcet.txt if found
|
226 |
+
wildcards_yaml_ext = get_ext_wildcard_tags()
|
227 |
+
if wildcards_yaml_ext:
|
228 |
+
write_to_temp_file('wcet.txt', wildcards_yaml_ext)
|
229 |
+
|
230 |
+
# Write embeddings to emb.txt if found
|
231 |
+
if EMB_PATH.exists():
|
232 |
+
# Get embeddings after the model loaded callback
|
233 |
+
script_callbacks.on_model_loaded(get_embeddings)
|
234 |
+
|
235 |
+
if HYP_PATH.exists():
|
236 |
+
hypernets = get_hypernetworks()
|
237 |
+
if hypernets:
|
238 |
+
write_to_temp_file('hyp.txt', hypernets)
|
239 |
+
|
240 |
+
if LORA_PATH is not None and LORA_PATH.exists():
|
241 |
+
lora = get_lora()
|
242 |
+
if lora:
|
243 |
+
write_to_temp_file('lora.txt', lora)
|
244 |
+
|
245 |
+
# Register autocomplete options
|
246 |
+
def on_ui_settings():
|
247 |
+
TAC_SECTION = ("tac", "Tag Autocomplete")
|
248 |
+
# Main tag file
|
249 |
+
shared.opts.add_option("tac_tagFile", shared.OptionInfo("danbooru.csv", "Tag filename", gr.Dropdown, lambda: {"choices": csv_files_withnone}, refresh=update_tag_files, section=TAC_SECTION))
|
250 |
+
# Active in settings
|
251 |
+
shared.opts.add_option("tac_active", shared.OptionInfo(True, "Enable Tag Autocompletion", section=TAC_SECTION))
|
252 |
+
shared.opts.add_option("tac_activeIn.txt2img", shared.OptionInfo(True, "Active in txt2img (Requires restart)", section=TAC_SECTION))
|
253 |
+
shared.opts.add_option("tac_activeIn.img2img", shared.OptionInfo(True, "Active in img2img (Requires restart)", section=TAC_SECTION))
|
254 |
+
shared.opts.add_option("tac_activeIn.negativePrompts", shared.OptionInfo(True, "Active in negative prompts (Requires restart)", section=TAC_SECTION))
|
255 |
+
shared.opts.add_option("tac_activeIn.thirdParty", shared.OptionInfo(True, "Active in third party textboxes [Dataset Tag Editor] (Requires restart)", section=TAC_SECTION))
|
256 |
+
shared.opts.add_option("tac_activeIn.modelList", shared.OptionInfo("", "List of model names (with file extension) or their hashes to use as black/whitelist, separated by commas.", section=TAC_SECTION))
|
257 |
+
shared.opts.add_option("tac_activeIn.modelListMode", shared.OptionInfo("Blacklist", "Mode to use for model list", gr.Dropdown, lambda: {"choices": ["Blacklist","Whitelist"]}, section=TAC_SECTION))
|
258 |
+
# Results related settings
|
259 |
+
shared.opts.add_option("tac_slidingPopup", shared.OptionInfo(True, "Move completion popup together with text cursor", section=TAC_SECTION))
|
260 |
+
shared.opts.add_option("tac_maxResults", shared.OptionInfo(5, "Maximum results", section=TAC_SECTION))
|
261 |
+
shared.opts.add_option("tac_showAllResults", shared.OptionInfo(False, "Show all results", section=TAC_SECTION))
|
262 |
+
shared.opts.add_option("tac_resultStepLength", shared.OptionInfo(100, "How many results to load at once", section=TAC_SECTION))
|
263 |
+
shared.opts.add_option("tac_delayTime", shared.OptionInfo(100, "Time in ms to wait before triggering completion again (Requires restart)", section=TAC_SECTION))
|
264 |
+
shared.opts.add_option("tac_useWildcards", shared.OptionInfo(True, "Search for wildcards", section=TAC_SECTION))
|
265 |
+
shared.opts.add_option("tac_useEmbeddings", shared.OptionInfo(True, "Search for embeddings", section=TAC_SECTION))
|
266 |
+
shared.opts.add_option("tac_useHypernetworks", shared.OptionInfo(True, "Search for hypernetworks", section=TAC_SECTION))
|
267 |
+
shared.opts.add_option("tac_useLoras", shared.OptionInfo(True, "Search for Loras", section=TAC_SECTION))
|
268 |
+
shared.opts.add_option("tac_showWikiLinks", shared.OptionInfo(False, "Show '?' next to tags, linking to its Danbooru or e621 wiki page (Warning: This is an external site and very likely contains NSFW examples!)", section=TAC_SECTION))
|
269 |
+
# Insertion related settings
|
270 |
+
shared.opts.add_option("tac_replaceUnderscores", shared.OptionInfo(True, "Replace underscores with spaces on insertion", section=TAC_SECTION))
|
271 |
+
shared.opts.add_option("tac_escapeParentheses", shared.OptionInfo(True, "Escape parentheses on insertion", section=TAC_SECTION))
|
272 |
+
shared.opts.add_option("tac_appendComma", shared.OptionInfo(True, "Append comma on tag autocompletion", section=TAC_SECTION))
|
273 |
+
# Alias settings
|
274 |
+
shared.opts.add_option("tac_alias.searchByAlias", shared.OptionInfo(True, "Search by alias", section=TAC_SECTION))
|
275 |
+
shared.opts.add_option("tac_alias.onlyShowAlias", shared.OptionInfo(False, "Only show alias", section=TAC_SECTION))
|
276 |
+
# Translation settings
|
277 |
+
shared.opts.add_option("tac_translation.translationFile", shared.OptionInfo("None", "Translation filename", gr.Dropdown, lambda: {"choices": csv_files_withnone}, refresh=update_tag_files, section=TAC_SECTION))
|
278 |
+
shared.opts.add_option("tac_translation.oldFormat", shared.OptionInfo(False, "Translation file uses old 3-column translation format instead of the new 2-column one", section=TAC_SECTION))
|
279 |
+
shared.opts.add_option("tac_translation.searchByTranslation", shared.OptionInfo(True, "Search by translation", section=TAC_SECTION))
|
280 |
+
# Extra file settings
|
281 |
+
shared.opts.add_option("tac_extra.extraFile", shared.OptionInfo("extra-quality-tags.csv", "Extra filename (for small sets of custom tags)", gr.Dropdown, lambda: {"choices": csv_files_withnone}, refresh=update_tag_files, section=TAC_SECTION))
|
282 |
+
shared.opts.add_option("tac_extra.addMode", shared.OptionInfo("Insert before", "Mode to add the extra tags to the main tag list", gr.Dropdown, lambda: {"choices": ["Insert before","Insert after"]}, section=TAC_SECTION))
|
283 |
+
|
284 |
+
script_callbacks.on_ui_settings(on_ui_settings)
|