mirror of
https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git
synced 2026-01-26 19:19:57 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19a30beed4 | ||
|
|
89fee277e3 | ||
|
|
c4510663ca | ||
|
|
8766965a30 | ||
|
|
34e68e1628 | ||
|
|
41d185b616 | ||
|
|
e0baa58ace | ||
|
|
c1ef12d887 | ||
|
|
4fc122de4b | ||
|
|
c341ccccb6 | ||
|
|
bda8701734 | ||
|
|
63fca457a7 |
@@ -86,6 +86,13 @@ const thirdParty = {
|
||||
"selectors": [
|
||||
"Found tags",
|
||||
]
|
||||
},
|
||||
"TIPO": {
|
||||
"base": "#tab_txt2img",
|
||||
"hasIds": false,
|
||||
"selectors": [
|
||||
"Tag Prompt"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ const autocompleteCSS = `
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
max-width: calc(100% - 1.5rem);
|
||||
margin: 5px 0 0 0;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.autocompleteResults {
|
||||
background-color: var(--results-bg) !important;
|
||||
@@ -44,11 +45,11 @@ const autocompleteCSS = `
|
||||
overflow-y: var(--results-overflow-y);
|
||||
overflow-x: hidden;
|
||||
word-break: break-word;
|
||||
margin-top: 10px; /* Margin to create space below the cursor */
|
||||
}
|
||||
.sideInfo {
|
||||
display: none;
|
||||
position: relative;
|
||||
margin-left: 10px;
|
||||
height: 18rem;
|
||||
max-width: 16rem;
|
||||
}
|
||||
@@ -90,6 +91,10 @@ const autocompleteCSS = `
|
||||
content: "✨";
|
||||
margin-right: 2px;
|
||||
}
|
||||
.acMetaText span.used::after {
|
||||
content: "🔁";
|
||||
margin-right: 2px;
|
||||
}
|
||||
.acWikiLink {
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem 0 -0.5rem -0.5rem;
|
||||
@@ -358,10 +363,13 @@ function showResults(textArea) {
|
||||
parentDiv.style.display = "flex";
|
||||
|
||||
if (TAC_CFG.slidingPopup) {
|
||||
let caretPosition = getCaretCoordinates(textArea, textArea.selectionEnd).left;
|
||||
let offset = Math.min(textArea.offsetLeft - textArea.scrollLeft + caretPosition, textArea.offsetWidth - parentDiv.offsetWidth);
|
||||
let caretPosition = getCaretCoordinates(textArea, textArea.selectionEnd);
|
||||
// Top cursor offset fix for SDNext modern UI, based on code by https://github.com/Nyx01
|
||||
let offsetTop = textArea.offsetTop + caretPosition.top - textArea.scrollTop + 10; // Adjust this value for desired distance below cursor
|
||||
let offsetLeft = Math.min(textArea.offsetLeft - textArea.scrollLeft + caretPosition.left, textArea.offsetWidth - parentDiv.offsetWidth);
|
||||
|
||||
parentDiv.style.left = `${offset}px`;
|
||||
parentDiv.style.top = `${offsetTop}px`; // Position below the cursor
|
||||
parentDiv.style.left = `${offsetLeft}px`;
|
||||
} else {
|
||||
if (parentDiv.style.left)
|
||||
parentDiv.style.removeProperty("left");
|
||||
@@ -626,12 +634,30 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
updateInput(textArea);
|
||||
|
||||
// Update previous tags with the edited prompt to prevent re-searching the same term
|
||||
let weightedTags = [...newPrompt.matchAll(WEIGHT_REGEX)]
|
||||
.map(match => match[1]);
|
||||
let tags = newPrompt.match(TAG_REGEX())
|
||||
if (weightedTags !== null) {
|
||||
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted)))
|
||||
.concat(weightedTags);
|
||||
let weightedTags = [...prompt.matchAll(WEIGHT_REGEX)]
|
||||
.map(match => match[1])
|
||||
.sort((a, b) => a.length - b.length);
|
||||
let tags = [...prompt.match(TAG_REGEX())].sort((a, b) => a.length - b.length);
|
||||
|
||||
if (weightedTags !== null && tags !== null) {
|
||||
// Create a working copy of the normal tags
|
||||
let workingTags = [...tags];
|
||||
|
||||
// For each weighted tag
|
||||
for (const weightedTag of weightedTags) {
|
||||
// Find first matching tag and remove it from working set
|
||||
const matchIndex = workingTags.findIndex(tag =>
|
||||
tag === weightedTag && !tag.startsWith("<[") && !tag.startsWith("$(")
|
||||
);
|
||||
|
||||
if (matchIndex !== -1) {
|
||||
// Remove the matched tag from the working set
|
||||
workingTags.splice(matchIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Combine filtered normal tags with weighted tags
|
||||
tags = workingTags.concat(weightedTags);
|
||||
}
|
||||
previousTags = tags;
|
||||
|
||||
@@ -666,6 +692,30 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
let tagColors = TAC_CFG.colorMap;
|
||||
let mode = (document.querySelector(".dark") || gradioApp().querySelector(".dark")) ? 0 : 1;
|
||||
let nextLength = Math.min(results.length, resultCount + TAC_CFG.resultStepLength);
|
||||
const IS_DAN_OR_E621_TAG_FILE = (tagFileName.toLowerCase().startsWith("danbooru") || tagFileName.toLowerCase().startsWith("e621"));
|
||||
|
||||
const tagCount = {};
|
||||
|
||||
// Indicate if tag was used before
|
||||
if (IS_DAN_OR_E621_TAG_FILE) {
|
||||
const prompt = textArea.value.trim();
|
||||
const tags = prompt.replaceAll('\n', ',').split(',').map(tag => tag.trim()).filter(tag => tag);
|
||||
|
||||
const unsanitizedTags = tags.map(tag => {
|
||||
const weightedTags = [...tag.matchAll(WEIGHT_REGEX)].flat();
|
||||
if (weightedTags.length === 2) {
|
||||
return weightedTags[1];
|
||||
} else {
|
||||
// normal tags
|
||||
return tag;
|
||||
}
|
||||
}).map(tag => tag.replaceAll(" ", "_").replaceAll("\\(", "(").replaceAll("\\)", ")"));
|
||||
|
||||
// Split tags by `,` and count tag
|
||||
for (const tag of unsanitizedTags) {
|
||||
tagCount[tag] = tagCount[tag] ? tagCount[tag] + 1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = resultCount; i < nextLength; i++) {
|
||||
let result = results[i];
|
||||
@@ -734,8 +784,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
if (
|
||||
TAC_CFG.showWikiLinks &&
|
||||
result.type === ResultType.tag &&
|
||||
(tagFileName.toLowerCase().startsWith("danbooru") ||
|
||||
tagFileName.toLowerCase().startsWith("e621"))
|
||||
IS_DAN_OR_E621_TAG_FILE
|
||||
) {
|
||||
let wikiLink = document.createElement("a");
|
||||
wikiLink.classList.add("acWikiLink");
|
||||
@@ -828,7 +877,19 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
// Add small ✨ marker to indicate usage sorting
|
||||
if (result.usageBias) {
|
||||
flexDiv.querySelector(".acMetaText").classList.add("biased");
|
||||
flexDiv.title = "✨ Frequent tag. Ctrl/Cmd + click to reset usage count."
|
||||
flexDiv.title = "✨ Frequent tag. Ctrl/Cmd + click to reset usage count.";
|
||||
}
|
||||
|
||||
// Add 🔁 to indicate if tag was used before
|
||||
if (IS_DAN_OR_E621_TAG_FILE && tagCount[result.text]) {
|
||||
// Fix PR#313#issuecomment-2592551794
|
||||
if (!(result.text === tagword && tagCount[result.text] === 1)) {
|
||||
const textNode = flexDiv.querySelector(".acMetaText");
|
||||
const span = document.createElement("span");
|
||||
textNode.insertBefore(span, textNode.firstChild);
|
||||
span.classList.add("used");
|
||||
span.title = "🔁 The prompt already contains this tag";
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a negative prompt
|
||||
@@ -1087,11 +1148,29 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
// Match tags with RegEx to get the last edited one
|
||||
// We also match for the weighting format (e.g. "tag:1.0") here, and combine the two to get the full tag word set
|
||||
let weightedTags = [...prompt.matchAll(WEIGHT_REGEX)]
|
||||
.map(match => match[1]);
|
||||
let tags = prompt.match(TAG_REGEX())
|
||||
.map(match => match[1])
|
||||
.sort((a, b) => a.length - b.length);
|
||||
let tags = [...prompt.match(TAG_REGEX())].sort((a, b) => a.length - b.length);
|
||||
|
||||
if (weightedTags !== null && tags !== null) {
|
||||
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted) && !tag.startsWith("<[") && !tag.startsWith("$(")))
|
||||
.concat(weightedTags);
|
||||
// Create a working copy of the normal tags
|
||||
let workingTags = [...tags];
|
||||
|
||||
// For each weighted tag
|
||||
for (const weightedTag of weightedTags) {
|
||||
// Find first matching tag and remove it from working set
|
||||
const matchIndex = workingTags.findIndex(tag =>
|
||||
tag === weightedTag && !tag.startsWith("<[") && !tag.startsWith("$(")
|
||||
);
|
||||
|
||||
if (matchIndex !== -1) {
|
||||
// Remove the matched tag from the working set
|
||||
workingTags.splice(matchIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Combine filtered normal tags with weighted tags
|
||||
tags = workingTags.concat(weightedTags);
|
||||
}
|
||||
|
||||
// Guard for no tags
|
||||
@@ -1436,6 +1515,12 @@ function addAutocompleteToArea(area) {
|
||||
if (!e.inputType && !tacSelfTrigger) return;
|
||||
tacSelfTrigger = false;
|
||||
|
||||
// Block hide we are composing (IME), so enter doesn't close the results
|
||||
if (e.isComposing) {
|
||||
hideBlocked = true;
|
||||
setTimeout(() => { hideBlocked = false; }, 100);
|
||||
}
|
||||
|
||||
debounce(autocomplete(area, area.value), TAC_CFG.delayTime);
|
||||
checkKeywordInsertionUndo(area, e);
|
||||
});
|
||||
@@ -1561,7 +1646,7 @@ async function setup() {
|
||||
} else {
|
||||
acStyle.appendChild(document.createTextNode(css));
|
||||
}
|
||||
gradioApp().appendChild(acStyle);
|
||||
document.head.appendChild(acStyle);
|
||||
|
||||
// Callback
|
||||
await processQueue(QUEUE_AFTER_SETUP, null);
|
||||
|
||||
@@ -20,9 +20,27 @@ except ImportError:
|
||||
TAGS_PATH = Path(scripts.basedir()).joinpath("tags").absolute()
|
||||
|
||||
# The path to the folder containing the wildcards and embeddings
|
||||
WILDCARD_PATH = FILE_DIR.joinpath("scripts/wildcards").absolute()
|
||||
try: # SD.Next
|
||||
WILDCARD_PATH = Path(shared.opts.wildcards_dir).absolute()
|
||||
except Exception: # A1111
|
||||
WILDCARD_PATH = FILE_DIR.joinpath("scripts/wildcards").absolute()
|
||||
EMB_PATH = Path(shared.cmd_opts.embeddings_dir).absolute()
|
||||
HYP_PATH = Path(shared.cmd_opts.hypernetwork_dir).absolute()
|
||||
|
||||
# Forge Classic detection
|
||||
try:
|
||||
from modules_forge.forge_version import version as forge_version
|
||||
IS_FORGE_CLASSIC = forge_version == "classic"
|
||||
except ImportError:
|
||||
IS_FORGE_CLASSIC = False
|
||||
|
||||
# Forge Classic skips it
|
||||
if not IS_FORGE_CLASSIC:
|
||||
try:
|
||||
HYP_PATH = Path(shared.cmd_opts.hypernetwork_dir).absolute()
|
||||
except AttributeError:
|
||||
HYP_PATH = None
|
||||
else:
|
||||
HYP_PATH = None
|
||||
|
||||
try:
|
||||
LORA_PATH = Path(shared.cmd_opts.lora_dir).absolute()
|
||||
|
||||
@@ -98,9 +98,9 @@ def sort_models(model_list, sort_method = None, name_has_subpath = False):
|
||||
# During merging on the JS side we need to re-sort anyway, so here only the sort criteria are calculated.
|
||||
# The list itself doesn't need to get sorted at this point.
|
||||
if len(model_list[0]) > 2:
|
||||
results = [f'{name},"{sorter(path, name, name_has_subpath)}",{meta}' for path, name, meta in model_list]
|
||||
results = [f'"{name}","{sorter(path, name, name_has_subpath)}",{meta}' for path, name, meta in model_list]
|
||||
else:
|
||||
results = [f'{name},"{sorter(path, name, name_has_subpath)}"' for path, name in model_list]
|
||||
results = [f'"{name}","{sorter(path, name, name_has_subpath)}"' for path, name in model_list]
|
||||
return results
|
||||
|
||||
|
||||
@@ -503,7 +503,14 @@ def write_style_names(*args, **kwargs):
|
||||
def write_temp_files(skip_wildcard_refresh = False):
|
||||
# Write wildcards to wc.txt if found
|
||||
if WILDCARD_PATH.exists() and not skip_wildcard_refresh:
|
||||
wildcards = [WILDCARD_PATH.relative_to(FILE_DIR).as_posix()] + get_wildcards()
|
||||
try:
|
||||
# Attempt to create a relative path, but fall back to an absolute path if not possible
|
||||
relative_wildcard_path = WILDCARD_PATH.relative_to(FILE_DIR).as_posix()
|
||||
except ValueError:
|
||||
# If the paths are not relative, use the absolute path
|
||||
relative_wildcard_path = WILDCARD_PATH.as_posix()
|
||||
|
||||
wildcards = [relative_wildcard_path] + get_wildcards()
|
||||
if wildcards:
|
||||
write_to_temp_file('wc.txt', wildcards)
|
||||
|
||||
@@ -515,7 +522,7 @@ def write_temp_files(skip_wildcard_refresh = False):
|
||||
# Write yaml extension wildcards to umi_tags.txt and wc_yaml.json if found
|
||||
get_yaml_wildcards()
|
||||
|
||||
if HYP_PATH.exists():
|
||||
if HYP_PATH is not None and HYP_PATH.exists():
|
||||
hypernets = get_hypernetworks()
|
||||
if hypernets:
|
||||
write_to_temp_file('hyp.txt', hypernets)
|
||||
@@ -741,7 +748,7 @@ def api_tac(_: gr.Blocks, app: FastAPI):
|
||||
return Response(status_code=404)
|
||||
|
||||
try:
|
||||
json_candidates = glob.glob(base_path.as_posix() + f"/**/{filename}.json", recursive=True)
|
||||
json_candidates = glob.glob(base_path.as_posix() + f"/**/{glob.escape(filename)}.json", recursive=True)
|
||||
if json_candidates is not None and len(json_candidates) > 0 and Path(json_candidates[0]).is_file():
|
||||
return FileResponse(json_candidates[0])
|
||||
except Exception as e:
|
||||
@@ -752,7 +759,7 @@ def api_tac(_: gr.Blocks, app: FastAPI):
|
||||
return Response(status_code=404)
|
||||
|
||||
try:
|
||||
img_glob = glob.glob(base_path.as_posix() + f"/**/{filename}.*", recursive=True)
|
||||
img_glob = glob.glob(base_path.as_posix() + f"/**/{glob.escape(filename)}.*", recursive=True)
|
||||
img_candidates = [img for img in img_glob if Path(img).suffix in [".png", ".jpg", ".jpeg", ".webp", ".gif"] and Path(img).is_file()]
|
||||
if img_candidates is not None and len(img_candidates) > 0:
|
||||
if blob:
|
||||
@@ -781,7 +788,7 @@ def api_tac(_: gr.Blocks, app: FastAPI):
|
||||
|
||||
@app.get("/tacapi/v1/lora-cached-hash/{lora_name}")
|
||||
async def get_lora_cached_hash(lora_name: str):
|
||||
path_glob = glob.glob(LORA_PATH.as_posix() + f"/**/{lora_name}.*", recursive=True)
|
||||
path_glob = glob.glob(LORA_PATH.as_posix() + f"/**/{glob.escape(lora_name)}.*", recursive=True)
|
||||
paths = [lora for lora in path_glob if Path(lora).suffix in [".safetensors", ".ckpt", ".pt"] and Path(lora).is_file()]
|
||||
if paths is not None and len(paths) > 0:
|
||||
path = paths[0]
|
||||
|
||||
160178
tags/noob_characters-chants.json
Normal file
160178
tags/noob_characters-chants.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user