Compare commits

..

16 Commits

Author SHA1 Message Date
DominikDoom
e23bb6d4ea Add support for --wildcards-dir cmd argument
Refactor PR #229 a bit to share code with this
2023-09-02 17:59:27 +02:00
DominikDoom
d4cca00575 Merge pull request #229 from azmodii/main 2023-09-02 17:27:00 +02:00
DominikDoom
86ea94a565 Merge pull request #228 from re-unknown/main 2023-09-02 17:08:13 +02:00
Joel Clark
53f46c91a2 feat: Allow support for custom wildcard directory in sd-dynamic-prompts 2023-09-02 21:30:01 +10:00
ReUnknown
e5f93188c3 Support for updated style editor 2023-09-02 16:51:41 +09:00
DominikDoom
3e57842ac6 Remove unnecessary autocomplete call in wildcards
which would result in duplicate file requests
2023-08-29 10:23:13 +02:00
DominikDoom
32c4589df3 Rework wildcards to use own API endpoint
Maybe fixes #226
2023-08-29 09:39:32 +02:00
DominikDoom
5bbd97588c Remove duplicate slash from wildcard files
(should be cosmetic only)
2023-08-28 19:15:34 +02:00
DominikDoom
b2a663f7a7 Merge pull request #223 from Symbiomatrix/embload 2023-08-20 19:08:40 +02:00
Symbiomatrix
6f93d19a2b Edit error message. 2023-08-20 20:02:57 +03:00
Symbiomatrix
79bab04fd2 Typo. 2023-08-20 18:59:12 +03:00
Symbiomatrix
5b69d1e622 Embedding forced reload. 2023-08-20 18:51:37 +03:00
DominikDoom
651cf5fb46 Add metaKey and Shift to non-captured modifiers
Fixes #222
2023-08-19 11:59:41 +02:00
DominikDoom
5deb72cddf Add clearer README description for legacy translations
As suggested in #221
2023-08-16 10:50:05 +02:00
DominikDoom
97ebe78205 !After Detailer (adetailer) support 2023-08-15 14:44:38 +02:00
DominikDoom
b937e853c9 Fix booru wiki links with translations 2023-08-08 19:23:13 +02:00
6 changed files with 91 additions and 20 deletions

View File

@@ -474,7 +474,9 @@ You can also add this to your quicksettings bar to have the refresh button avail
# Translations
An additional file can be added in the translation section, which will be used to translate both tags and aliases and also enables searching by translation.
This file needs to be a CSV in the format `<English tag/alias>,<Translation>`, but for backwards compatibility with older files that used a three column format, you can turn on `Translation file uses old 3-column translation format instead of the new 2-column one` to support them. In that case, the second column will be unused and skipped during parsing.
This file needs to be a CSV in the format `<English tag/alias>,<Translation>`. Some older files use a three column format, which requires a compatibility setting to be activated.
You can find it under `Settings > Tag autocomplete > Translation filename > Translation file uses old 3-column translation format instead of the new 2-column one`.
With it on, the second column will be unused and skipped during parsing.
Example with Chinese translation:

View File

@@ -9,7 +9,11 @@ const core = [
"#img2img_prompt > label > textarea",
"#txt2img_neg_prompt > label > textarea",
"#img2img_neg_prompt > label > textarea",
".prompt > label > textarea"
".prompt > label > textarea",
"#txt2img_edit_style_prompt > label > textarea",
"#txt2img_edit_style_neg_prompt > label > textarea",
"#img2img_edit_style_prompt > label > textarea",
"#img2img_edit_style_neg_prompt > label > textarea"
];
// Third party text area selectors
@@ -57,6 +61,24 @@ const thirdParty = {
"[id^=MD-i2i][id$=prompt] textarea",
"[id^=MD-i2i][id$=prompt] input[type='text']"
]
},
"adetailer-t2i": {
"base": "#txt2img_script_container",
"hasIds": true,
"onDemand": true,
"selectors": [
"[id^=script_txt2img_adetailer_ad_prompt] textarea",
"[id^=script_txt2img_adetailer_ad_negative_prompt] textarea"
]
},
"adetailer-i2i": {
"base": "#img2img_script_container",
"hasIds": true,
"onDemand": true,
"selectors": [
"[id^=script_img2img_adetailer_ad_prompt] textarea",
"[id^=script_img2img_adetailer_ad_negative_prompt] textarea"
]
}
}
@@ -90,7 +112,7 @@ function addOnDemandObservers(setupFunction) {
let base = gradioApp().querySelector(entry.base);
if (!base) continue;
let accordions = [...base?.querySelectorAll(".gradio-accordion")];
if (!accordions) continue;
@@ -115,12 +137,12 @@ function addOnDemandObservers(setupFunction) {
[...gradioApp().querySelectorAll(entry.selectors.join(", "))].forEach(x => setupFunction(x));
} else { // Otherwise, we have to find the text areas by their adjacent labels
let base = gradioApp().querySelector(entry.base);
// Safety check
if (!base) continue;
let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")];
// Filter the text areas where the adjacent label matches one of the selectors
let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText)));
matchingTextAreas.forEach(x => setupFunction(x));

View File

@@ -22,10 +22,13 @@ class WildcardParser extends BaseTagParser {
let wildcards = [];
for (let i = 0; i < wcPairs.length; i++) {
const wcPair = wcPairs[i];
if (!wcPair[0] || !wcPair[1]) continue;
const basePath = wcPairs[i][0];
const fileName = wcPairs[i][1];
if (!basePath || !fileName) return;
if (wcPair[0].endsWith(".yaml")) {
// YAML wildcards are already loaded as json, so we can get the values directly.
// basePath is the name of the file in this case, and fileName the key
if (basePath.endsWith(".yaml")) {
const getDescendantProp = (obj, desc) => {
const arr = desc.split("/");
while (arr.length) {
@@ -33,10 +36,11 @@ class WildcardParser extends BaseTagParser {
}
return obj;
}
wildcards = wildcards.concat(getDescendantProp(yamlWildcards[wcPair[0]], wcPair[1]));
wildcards = wildcards.concat(getDescendantProp(yamlWildcards[basePath], fileName));
} else {
const fileContent = (await readFile(`${wcPair[0]}/${wcPair[1]}.txt`)).split("\n")
.filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments
const fileContent = (await fetchAPI(`tacapi/v1/wildcard-contents?basepath=${basePath}&filename=${fileName}.txt`, false))
.split("\n")
.filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments
wildcards = wildcards.concat(fileContent);
}
}
@@ -155,7 +159,6 @@ function keepOpenIfWildcard(tagType, sanitizedText, newPrompt, textArea) {
// If it's a wildcard, we want to keep the results open so the user can select another wildcard
if (tagType === ResultType.wildcardFile || tagType === ResultType.yamlWildcard) {
hideBlocked = true;
autocomplete(textArea, newPrompt, sanitizedText);
setTimeout(() => { hideBlocked = false; }, 450);
return true;
}

View File

@@ -651,6 +651,11 @@ function addResultsToList(textArea, results, tagword, resetList) {
// Only use alias result if it is one
if (displayText.includes("➝"))
linkPart = displayText.split(" ➝ ")[1];
// Remove any trailing translations
if (linkPart.includes("[")) {
linkPart = linkPart.split("[")[0]
}
// Set link based on selected file
let tagFileNameLower = tagFileName.toLowerCase();
@@ -1109,7 +1114,7 @@ function navigateInList(textArea, event) {
if (!validKeys.includes(event.key)) return;
if (!isVisible(textArea)) return
// Return if ctrl key is pressed to not interfere with weight editing shortcut
if (event.ctrlKey || event.altKey) return;
if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) return;
oldSelectedTag = selectedTag;

View File

@@ -1,4 +1,5 @@
from pathlib import Path
from modules import scripts, shared
try:
@@ -37,6 +38,21 @@ except AttributeError:
def find_ext_wildcard_paths():
"""Returns the path to the extension wildcards folder"""
found = list(EXT_PATH.glob("*/wildcards/"))
# Try to find the wildcard path from the shared opts
try:
from modules.shared import opts
except ImportError: # likely not in an a1111 context
opts = None
# Append custom wildcard paths
custom_paths = [
getattr(shared.cmd_opts, "wildcards_dir", None), # Cmd arg from the wildcard extension
getattr(opts, "wildcard_dir", None), # Custom path from sd-dynamic-prompts
]
for path in [Path(p) for p in custom_paths if p is not None]:
if path.exists():
found.append(path)
return found

View File

@@ -17,6 +17,12 @@ from scripts.model_keyword_support import (get_lora_simple_hash,
write_model_keyword_path)
from scripts.shared_paths import *
# Attempt to get embedding load function, using the same call as api.
try:
load_textual_inversion_embeddings = sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings
except Exception as e: # Not supported.
load_textual_inversion_embeddings = lambda *args, **kwargs: None
print("Tag Autocomplete: Cannot reload embeddings instantly:", e)
def get_wildcards():
"""Returns a list of all wildcards. Works on nested folders."""
@@ -280,6 +286,7 @@ if EMB_PATH.exists():
def refresh_temp_files():
global WILDCARD_EXT_PATHS
WILDCARD_EXT_PATHS = find_ext_wildcard_paths()
load_textual_inversion_embeddings(force_reload = True) # Instant embedding reload.
write_temp_files()
get_embeddings(shared.sd_model)
@@ -450,18 +457,18 @@ script_callbacks.on_ui_settings(on_ui_settings)
def api_tac(_: gr.Blocks, app: FastAPI):
async def get_json_info(base_path: Path, filename: str = None):
if base_path is None or (not base_path.exists()):
return json.dumps({})
return JSONResponse({}, status_code=404)
try:
json_candidates = glob.glob(base_path.as_posix() + f"/**/{filename}.json", recursive=True)
if json_candidates is not None and len(json_candidates) > 0:
return FileResponse(json_candidates[0])
except Exception as e:
return json.dumps({"error": e})
return JSONResponse({"error": e}, status_code=500)
async def get_preview_thumbnail(base_path: Path, filename: str = None, blob: bool = False):
if base_path is None or (not base_path.exists()):
return json.dumps({})
return JSONResponse({}, status_code=404)
try:
img_glob = glob.glob(base_path.as_posix() + f"/**/{filename}.*", recursive=True)
@@ -472,7 +479,7 @@ def api_tac(_: gr.Blocks, app: FastAPI):
else:
return JSONResponse({"url": urllib.parse.quote(img_candidates[0])})
except Exception as e:
return json.dumps({"error": e})
return JSONResponse({"error": e}, status_code=500)
@app.get("/tacapi/v1/lora-info/{lora_name}")
async def get_lora_info(lora_name):
@@ -502,7 +509,23 @@ def api_tac(_: gr.Blocks, app: FastAPI):
async def get_thumb_preview_blob(filename, type):
return await get_preview_thumbnail(get_path_for_type(type), filename, True)
@app.get("/tacapi/v1/wildcard-contents")
async def get_wildcard_contents(basepath: str, filename: str):
if basepath is None or basepath == "":
return JSONResponse({}, status_code=404)
base = Path(basepath)
if base is None or (not base.exists()):
return JSONResponse({}, status_code=404)
try:
wildcard_path = base.joinpath(filename)
if wildcard_path.exists():
return FileResponse(wildcard_path)
else:
return JSONResponse({}, status_code=404)
except Exception as e:
return JSONResponse({"error": e}, status_code=500)
script_callbacks.on_app_started(api_tac)
script_callbacks.on_app_started(api_tac)