Compare commits

..

17 Commits
1.6.0 ... 1.8.1

Author SHA1 Message Date
Dominik Reh
48bd3d7b51 Support multiline prompts
Fixes #44
2022-10-28 19:04:41 +02:00
Dominik Reh
c6c9e01410 Formatting 2022-10-28 18:08:02 +02:00
DominikDoom
bf5bb34605 Update README.md 2022-10-28 17:58:59 +02:00
Dominik Reh
860fd34fb4 Support for wildcards from different extensions
Now scans all extensions for a wildcards folder and will combine them
Implements the feature discussed in #37
2022-10-28 17:47:23 +02:00
Dominik Reh
886de4df29 Support installing the script as an extension
Closes #41
2022-10-28 15:46:16 +02:00
Dominik Reh
3e71890489 Support wildcard extension in arbitrary folder
Now no longer only looks in "extensions/wildcards/widlcards".
This enables the user to call the folder whatever they like.
Fixes #37
2022-10-28 15:08:28 +02:00
Dominik Reh
dc77b3f17f Configurable debounce delay
Workaround for the problem in #40
2022-10-26 15:20:06 +02:00
Dominik Reh
40d53d89d1 Hotfix for old scripts/wildcards path loading 2022-10-25 00:45:35 +02:00
Dominik Reh
c733b836e8 Load wildcards on demand
Fixes performance issues with many wildcard files, fixes #38
2022-10-25 00:39:41 +02:00
Dominik Reh
b537ca3938 Add support for PageUp/Down and Home/End scrolling
Closes #34
2022-10-24 17:01:43 +02:00
Dominik Reh
cb08b8467f Support for new wildcards extension folder
Only works for "extensions\wildcards\wildcards\" and its subfolders at the moment.
Closes #35.
2022-10-24 16:07:58 +02:00
DominikDoom
522989da8a Update README.md 2022-10-24 14:06:22 +02:00
Dominik Reh
e8cf50cdaa Support png embeddings
Fixes #33
2022-10-24 14:01:44 +02:00
Dominik Reh
4af8d5285d Make left/right arrow key navigation optional
Fixes #31
2022-10-23 14:50:04 +02:00
Dominik Reh
3759ec055a Allow autocomplete with Tab key
Closes #29
2022-10-22 13:23:16 +02:00
DominikDoom
ced6676aa6 Update README.md 2022-10-18 22:24:01 +02:00
sgmklp
6b3b8ccf45 Some performance optimizations (#21)
Also includes code readability improvements and ability to load results in steps.
Useful for long lists or if showAllResults is true.
2022-10-18 22:21:33 +02:00
5 changed files with 254 additions and 95 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
tags/temp/

View File

@@ -19,12 +19,10 @@ You can either clone / download the files manually as described [below](#install
For example, editing `atago (azur lane)`, it would be replaced with e.g. `taihou (azur lane), lane)`, since the script currently doesn't see the second part of the bracket as the same tag. So in those cases you should delete the old tag beforehand.
### Wildcard & Embedding support
Autocompletion also works with wildcard files used by [this script](https://github.com/jtkelm2/stable-diffusion-webui-1/blob/master/scripts/wildcards.py) of the same name (demo video further down). This enables you to either insert categories to be replaced by the script, or even replace them with the actual wildcard file content in the same step.
Autocompletion also works with wildcard files used by [this script](https://github.com/jtkelm2/stable-diffusion-webui-1/blob/master/scripts/wildcards.py) of the same name (demo video further down). This enables you to either insert categories to be replaced by the script, or even replace them with the actual wildcard file content in the same step. Wildcards are searched for in every extension folder as well as the `scripts/wildcards` folder to support legacy versions. This means that you can combine wildcards from multiple extensions. Nested folders are also supported if you have grouped your wildcards in that way.
It also scans the embeddings folder and displays completion hints for the names of all .pt and .bin files inside if you start typing `<`. Note that some normal tags also use < in Kaomoji (like ">_<" for example), so the results will contain both.
Both are now enabled by default and scan the `/embeddings` and `/scripts/wildcards` folders automatically.
## Screenshots
Demo video (with keyboard navigation):
@@ -40,14 +38,27 @@ Dark and Light mode supported, including tag colors:
![tagtypes_light](https://user-images.githubusercontent.com/34448969/195180061-ceebcc25-9e4c-424f-b0c9-ba8e8f4f17f4.png)
## Installation
Simply copy the `javascript`, `scripts` and `tags` folder into your web UI installation root. It will run automatically the next time the web UI is started.
### As an extension (recommended)
Either clone the repo into your extensions folder:
```bash
git clone "https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git" extensions/tag-autocomplete
```
(The second argument specifies the name of the folder, you can choose whatever you like).
The tags folder contains `config.json` and the tag data the script uses for autocompletion. By default, Danbooru and e621 tags are included.
Or create a folder there manually and place the `javascript`, `scripts` and `tags` folders in it.
### In the root folder (old)
Copy the `javascript`, `scripts` and `tags` folder into your web UI installation root. It will run automatically the next time the web UI is started.
---
In both configurations, the tags folder contains `config.json` and the tag data the script uses for autocompletion. By default, Danbooru and e621 tags are included.
After scanning for embeddings and wildcards, the script will also create a `temp` directory here which lists the found files so they can be accessed in the browser side of the script. You can delete the temp folder without consequences as it will be recreated on the next startup.
### Important:
The script needs **all three folders** to work properly.
### Config
## Config
The config contains the following settings and defaults:
```json
{
@@ -58,7 +69,9 @@ The config contains the following settings and defaults:
"negativePrompts": true
},
"maxResults": 5,
"resultStepLength": 500,
"showAllResults": false,
"useLeftRightArrowKeys": false,
"replaceUnderscores": true,
"escapeParentheses": true,
"useWildcards": true,
@@ -98,7 +111,9 @@ The config contains the following settings and defaults:
| tagFile | Specifies the tag file to use. You can provide a custom tag database of your liking, but since the script was developed with Danbooru tags in mind, it might not work properly with other configurations.|
| activeIn | Allows to selectively (de)activate the script for txt2img, img2img, and the negative prompts for both. |
| maxResults | How many results to show max. For the default tag set, the results are ordered by occurence count. For embeddings and wildcards it will show all results in a scrollable list. |
| resultStepLength | Allows to load results in smaller batches of the specified size for better performance in long lists or if showAllResults is true. |
| showAllResults | If true, will ignore maxResults and show all results in a scrollable list. **Warning:** can lag your browser for long lists. |
| useLeftRightArrowKeys | If true, left and right arrows will select the first/last result in the popup instead of moving the cursor in the textbox. |
| replaceUnderscores | If true, undescores are replaced with spaces on clicking a tag. Might work better for some models. |
| escapeParentheses | If true, escapes tags containing () so they don't contribute to the web UI's prompt weighting functionality. |
| useWildcards | Used to toggle the wildcard completion functionality. |
@@ -133,7 +148,7 @@ Methods 1 & 2 can also be mixed, in which case translations in the extra file wi
The extra files can also be used to just add new / custom tags not included in the main set, provided `onlyTranslationExtraFile` is false.
If an extra tag doesn't match any existing tag, it will be added to the list as a new tag instead.
### CSV tag data
## CSV tag data
The script expects a CSV file with tags saved in the following way:
```csv
1girl,0
@@ -165,4 +180,4 @@ or of e621:
|7 | Meta |
|8 | Lore |
The tag type is used for coloring entries in the result list.
The tag type is used for coloring entries in the result list.

View File

@@ -134,19 +134,27 @@ function difference(a, b) {
// Get the identifier for the text area to differentiate between positive and negative
function getTextAreaIdentifier(textArea) {
let txt2img_p = gradioApp().querySelector('#txt2img_prompt > label > textarea');
let txt2img_n = gradioApp().querySelector('#txt2img_neg_prompt > label > textarea');
let img2img = gradioApp().querySelector('#tab_img2img');
let img2img_p = img2img.querySelector('#img2img_prompt > label > textarea');
let img2img_n = img2img.querySelector('#img2img_neg_prompt > label > textarea');
let img2img_p = gradioApp().querySelector('#img2img_prompt > label > textarea');
let img2img_n = gradioApp().querySelector('#img2img_neg_prompt > label > textarea');
let modifier = "";
if (textArea === img2img_p || textArea === img2img_n) {
modifier += ".img2img";
}
if (textArea === txt2img_n || textArea === img2img_n) {
modifier += ".n";
} else {
modifier += ".p";
switch (textArea) {
case txt2img_p:
modifier = ".txt2img.p";
break;
case txt2img_n:
modifier = ".txt2img.n";
break;
case img2img_p:
modifier = ".img2img.p";
break;
case img2img_n:
modifier = ".img2img.n";
break;
default:
break;
}
return modifier;
}
@@ -198,13 +206,11 @@ function isVisible(textArea) {
}
function showResults(textArea) {
let textAreaId = getTextAreaIdentifier(textArea);
let resultsDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
resultsDiv.style.display = "block";
}
function hideResults(textArea) {
let textAreaId = getTextAreaIdentifier(textArea);
let resultsDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
resultsDiv.style.display = "none";
selectedTag = null;
@@ -249,16 +255,16 @@ function insertTextAtCursor(textArea, result, tagword) {
let editStart = Math.max(cursorPos - tagword.length, 0);
let editEnd = Math.min(cursorPos + tagword.length, prompt.length);
let surrounding = prompt.substring(editStart, editEnd);
let match = surrounding.match(new RegExp(escapeRegExp(`${tagword}`)));
let match = surrounding.match(new RegExp(escapeRegExp(`${tagword}`), "i"));
let afterInsertCursorPos = editStart + match.index + sanitizedText.length;
var optionalComma = "";
if (tagType !== "wildcardFile") {
optionalComma = surrounding.match(new RegExp(escapeRegExp(`${tagword},`))) !== null ? "" : ", ";
optionalComma = surrounding.match(new RegExp(escapeRegExp(`${tagword},`), "i")) !== null ? "" : ", ";
}
// Replace partial tag word with new text, add comma if needed
let insert = surrounding.replace(tagword, sanitizedText + optionalComma);
let insert = surrounding.replace(match, sanitizedText + optionalComma);
// Add back start
var newPrompt = prompt.substring(0, editStart) + insert + prompt.substring(editEnd);
@@ -271,7 +277,7 @@ function insertTextAtCursor(textArea, result, tagword) {
textArea.dispatchEvent(new Event("input", { bubbles: true }));
// Update previous tags with the edited prompt to prevent re-searching the same term
let tags = newPrompt.match(/[^, ]+/g);
let tags = newPrompt.match(/[^,\n\r ]+/g);
previousTags = tags;
// Hide results after inserting
@@ -285,23 +291,26 @@ function insertTextAtCursor(textArea, result, tagword) {
}
}
function addResultsToList(textArea, results, tagword) {
function addResultsToList(textArea, results, tagword, resetList) {
let textAreaId = getTextAreaIdentifier(textArea);
let resultDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
let resultsList = resultDiv.querySelector('ul');
// Reset list, selection and scrollTop since the list changed
resultsList.innerHTML = "";
selectedTag = null;
resultDiv.scrollTop = 0;
if (resetList) {
resultsList.innerHTML = "";
selectedTag = null;
resultDiv.scrollTop = 0;
resultCount = 0;
}
// Find right colors from config
let tagFileName = acConfig.tagFile.split(".")[0];
let tagColors = acConfig.colors;
let mode = gradioApp().querySelector('.dark') ? 0 : 1;
let nextLength = Math.min(results.length, resultCount + acConfig.resultStepLength);
for (let i = 0; i < results.length; i++) {
for (let i = resultCount; i < nextLength; i++) {
let result = results[i];
let li = document.createElement("li");
@@ -331,34 +340,38 @@ function addResultsToList(textArea, results, tagword) {
// Add element to list
resultsList.appendChild(li);
}
resultCount = nextLength;
}
function updateSelectionStyle(textArea, num) {
function updateSelectionStyle(textArea, newIndex, oldIndex) {
let textAreaId = getTextAreaIdentifier(textArea);
let resultDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
let resultsList = resultDiv.querySelector('ul');
let items = resultsList.getElementsByTagName('li');
for (let i = 0; i < items.length; i++) {
items[i].classList.remove('selected');
if (oldIndex != null) {
items[oldIndex].classList.remove('selected');
}
items[num].classList.add('selected');
// make it safer
if (newIndex !== null) {
items[newIndex].classList.add('selected');
}
// Set scrolltop to selected item if we are showing more than max results
if (items.length > acConfig.maxResults) {
let selected = items[num];
let selected = items[newIndex];
resultDiv.scrollTop = selected.offsetTop - resultDiv.offsetTop;
}
}
wildcardFiles = [];
wildcards = {};
embeddings = [];
allTags = [];
results = [];
tagword = "";
resultCount = 0;
var wildcardFiles = [];
var wildcardExtFiles = [];
var embeddings = [];
var allTags = [];
var results = [];
var tagword = "";
var resultCount = 0;
function autocomplete(textArea, prompt, fixedTag = null) {
// Return if the function is deactivated in the UI
if (!acActive) return;
@@ -371,7 +384,7 @@ function autocomplete(textArea, prompt, fixedTag = null) {
if (fixedTag === null) {
// Match tags with RegEx to get the last edited one
let tags = prompt.match(/[^, ]+/g);
let tags = prompt.match(/[^,\n\r ]+/g);
let diff = difference(tags, previousTags)
previousTags = tags;
@@ -392,24 +405,37 @@ function autocomplete(textArea, prompt, fixedTag = null) {
tagword = fixedTag;
}
tagword = tagword.toLowerCase();
tagword = tagword.toLowerCase().trim();
if (acConfig.useWildcards && [...tagword.matchAll(/\b__([^,_ ]+)__([^, ]*)\b/g)].length > 0) {
if (acConfig.useWildcards && [...tagword.matchAll(/\b__([^, ]+)__([^, ]*)\b/g)].length > 0) {
// Show wildcards from a file with that name
wcMatch = [...tagword.matchAll(/\b__([^,_ ]+)__([^, ]*)\b/g)]
wcMatch = [...tagword.matchAll(/\b__([^, ]+)__([^, ]*)\b/g)]
let wcFile = wcMatch[0][1];
let wcWord = wcMatch[0][2];
results = wildcards[wcFile].filter(x => (wcWord !== null) ? x.toLowerCase().includes(wcWord) : x) // Filter by tagword
var wcPair;
// Look in normal wildcard files
if (wcFound = wildcardFiles.find(x => x[1].toLowerCase() === wcFile))
wcPair = wcFound;
else // Look in extensions wildcard files
wcPair = wildcardExtFiles.find(x => x[1].toLowerCase() === wcFile);
let wildcards = readFile(`file/${wcPair[0]}/${wcPair[1]}.txt`).split("\n")
.filter(x => x.trim().length > 0); // Remove empty lines
results = wildcards.filter(x => (wcWord !== null && wcWord.length > 0) ? x.toLowerCase().includes(wcWord) : x) // Filter by tagword
.map(x => [wcFile + ": " + x.trim(), "wildcardTag"]); // Mark as wildcard
} else if (acConfig.useWildcards && (tagword.startsWith("__") && !tagword.endsWith("__") || tagword === "__")) {
// Show available wildcard files
let tempResults = [];
if (tagword !== "__") {
tempResults = wildcardFiles.filter(x => x.toLowerCase().includes(tagword.replace("__", ""))) // Filter by tagword
let lmb = (x) => x[1].toLowerCase().includes(tagword.replace("__", ""))
tempResults = wildcardFiles.filter(lmb).concat(wildcardExtFiles.filter(lmb)) // Filter by tagword
} else {
tempResults = wildcardFiles;
tempResults = wildcardFiles.concat(wildcardExtFiles);
}
results = tempResults.map(x => ["Wildcards: " + x.trim(), "wildcardFile"]); // Mark as wildcard
results = tempResults.map(x => ["Wildcards: " + x[1].trim(), "wildcardFile"]); // Mark as wildcard
} else if (acConfig.useEmbeddings && tagword.match(/<[^,> ]*>?/g)) {
// Show embeddings
let tempResults = [];
@@ -438,29 +464,32 @@ function autocomplete(textArea, prompt, fixedTag = null) {
}
}
resultCount = results.length;
// Guard for empty results
if (resultCount === 0) {
if (!results.length) {
hideResults(textArea);
return;
}
showResults(textArea);
addResultsToList(textArea, results, tagword);
addResultsToList(textArea, results, tagword, true);
}
var oldSelectedTag = null;
function navigateInList(textArea, event) {
// Return if the function is deactivated in the UI
if (!acActive) return;
validKeys = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Enter", "Escape"];
validKeys = ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", "Enter", "Tab", "Escape"];
if (acConfig.useLeftRightArrowKeys)
validKeys.push("ArrowLeft", "ArrowRight");
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;
oldSelectedTag = selectedTag;
switch (event.key) {
case "ArrowUp":
if (selectedTag === null) {
@@ -476,6 +505,26 @@ function navigateInList(textArea, event) {
selectedTag = (selectedTag + 1) % resultCount;
}
break;
case "PageUp":
if (selectedTag === null || selectedTag === 0) {
selectedTag = resultCount - 1;
} else {
selectedTag = (Math.max(selectedTag - 5, 0) + resultCount) % resultCount;
}
break;
case "PageDown":
if (selectedTag === null || selectedTag === resultCount - 1) {
selectedTag = 0;
} else {
selectedTag = Math.min(selectedTag + 5, resultCount - 1) % resultCount;
}
break;
case "Home":
selectedTag = 0;
break;
case "End":
selectedTag = resultCount - 1;
break;
case "ArrowLeft":
selectedTag = 0;
break;
@@ -487,25 +536,38 @@ function navigateInList(textArea, event) {
insertTextAtCursor(textArea, results[selectedTag], tagword);
}
break;
case "Tab":
if (selectedTag === null) {
selectedTag = 0;
}
insertTextAtCursor(textArea, results[selectedTag], tagword);
break;
case "Escape":
hideResults(textArea);
break;
}
if (selectedTag === resultCount - 1
&& (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "ArrowLeft" || event.key === "ArrowRight")) {
addResultsToList(textArea, results, tagword, false);
}
// Update highlighting
if (selectedTag !== null)
updateSelectionStyle(textArea, selectedTag);
updateSelectionStyle(textArea, selectedTag, oldSelectedTag);
// Prevent default behavior
event.preventDefault();
event.stopPropagation();
}
styleAdded = false;
var styleAdded = false;
onUiUpdate(function () {
// Get our tag base path from the temp file
let tagBasePath = readFile("file/tmp/tagAutocompletePath.txt");
// Load config
if (acConfig === null) {
try {
acConfig = JSON.parse(readFile("file/tags/config.json"));
acConfig = JSON.parse(readFile(`file/${tagBasePath}/config.json`));
if (acConfig.translation.onlyShowTranslation) {
acConfig.translation.searchByTranslation = true; // if only show translation, enable search by translation is necessary
}
@@ -517,14 +579,14 @@ onUiUpdate(function () {
// Load main tags and translations
if (allTags.length === 0) {
try {
allTags = loadCSV(`file/tags/${acConfig.tagFile}`);
allTags = loadCSV(`file/${tagBasePath}/${acConfig.tagFile}`);
} catch (e) {
console.error("Error loading tags file: " + e);
return;
}
if (acConfig.extra.extraFile) {
try {
extras = loadCSV(`file/tags/${acConfig.extra.extraFile}`);
extras = loadCSV(`file/${tagBasePath}/${acConfig.extra.extraFile}`);
if (acConfig.extra.onlyTranslationExtraFile) {
// This works purely on index, so it's not very robust. But a lot faster.
for (let i = 0, n = extras.length; i < n; i++) {
@@ -553,28 +615,45 @@ onUiUpdate(function () {
// Load wildcards
if (wildcardFiles.length === 0 && acConfig.useWildcards) {
try {
wildcardFiles = readFile("file/tags/temp/wc.txt").split("\n")
let wcFileArr = readFile(`file/${tagBasePath}/temp/wc.txt`).split("\n");
let wcBasePath = wcFileArr[0].trim(); // First line should be the base path
wildcardFiles = wcFileArr.slice(1)
.filter(x => x.trim().length > 0) // Remove empty lines
.map(x => x.trim().replace(".txt", "")); // Remove file extension & newlines
.map(x => [wcBasePath, x.trim().replace(".txt", "")]); // Remove file extension & newlines
wildcardFiles.forEach(fName => {
try {
wildcards[fName] = readFile(`file/scripts/wildcards/${fName}.txt`).split("\n")
.filter(x => x.trim().length > 0); // Remove empty lines
} catch (e) {
console.log(`Could not load wildcards for ${fName}`);
// To support multiple sources, we need to separate them using the provided "-----" strings
let wcExtFileArr = readFile(`file/${tagBasePath}/temp/wce.txt`).split("\n");
let splitIndices = [];
for (let index = 0; index < wcExtFileArr.length; index++) {
if (wcExtFileArr[index].trim() === "-----") {
splitIndices.push(index);
}
});
}
// For each group, add them to the wildcardFiles array with the base path as the first element
for (let i = 0; i < splitIndices.length; i++) {
let start = splitIndices[i - 1] || 0;
if (i > 0) start++; // Skip the "-----" line
let end = splitIndices[i];
let wcExtFile = wcExtFileArr.slice(start, end);
let base = wcExtFile[0].trim() + "/";
wcExtFile = wcExtFile.slice(1)
.filter(x => x.trim().length > 0) // Remove empty lines
.map(x => x.trim().replace(base, "").replace(".txt", "")); // Remove file extension & newlines;
wcExtFile = wcExtFile.map(x => [base, x]);
wildcardExtFiles.push(...wcExtFile);
}
} catch (e) {
console.error("Error loading wildcardNames.txt: " + e);
console.error("Error loading wildcards: " + e);
}
}
// Load embeddings
if (embeddings.length === 0 && acConfig.useEmbeddings) {
try {
embeddings = readFile("file/tags/temp/emb.txt").split("\n")
embeddings = readFile(`file/${tagBasePath}/temp/emb.txt`).split("\n")
.filter(x => x.trim().length > 0) // Remove empty lines
.map(x => x.replace(".bin", "").replace(".pt", "")); // Remove file extensions
.map(x => x.replace(".bin", "").replace(".pt", "").replace(".png", "")); // Remove file extensions
} catch (e) {
console.error("Error loading embeddings.txt: " + e);
}
@@ -591,25 +670,23 @@ onUiUpdate(function () {
// Not found, we're on a page without prompt textareas
if (textAreas.every(v => v === null || v === undefined)) return;
// Already added?
if (gradioApp().querySelector('.autocompleteResults.p') !== null
&& (gradioApp().querySelector('.autocompleteResults.n') === null
&& !acConfig.activeIn.negativePrompts)) {
// Already added or unnecessary to add
if (gradioApp().querySelector('.autocompleteResults.p')) {
if (gradioApp().querySelector('.autocompleteResults.n') || !acConfig.activeIn.negativePrompts) {
return;
}
} else if (!acConfig.activeIn.txt2img && !acConfig.activeIn.img2img) {
return;
}
textAreas.forEach(area => {
// Skip directly if not found on the page
if (area === null || area === undefined) return;
// Return if autocomplete is disabled for the current area type in config
let textAreaId = getTextAreaIdentifier(area);
if (textAreaId.includes("p") || (textAreaId.includes("n") && acConfig.activeIn.negativePrompts)) {
if (textAreaId.includes("img2img")) {
if (!acConfig.activeIn.img2img) return;
} else {
if (!acConfig.activeIn.txt2img) return;
}
if ((!acConfig.activeIn.img2img && textAreaId.includes("img2img"))
|| (!acConfig.activeIn.txt2img && textAreaId.includes("txt2img"))
|| (!acConfig.activeIn.negativePrompts && textAreaId.includes("n"))) {
return;
}
// Only add listeners once
@@ -621,7 +698,7 @@ onUiUpdate(function () {
hideResults(area);
// Add autocomplete event listener
area.addEventListener('input', debounce(() => autocomplete(area, area.value), 100));
area.addEventListener('input', debounce(() => autocomplete(area, area.value), acConfig.delayTime));
// Add focusout event listener
area.addEventListener('focusout', debounce(() => hideResults(area), 400));
// Add up and down arrow event listener

View File

@@ -2,25 +2,75 @@
# to a temporary file to expose it to the javascript side
from pathlib import Path
from modules import scripts
# Webui root path
FILE_DIR = Path().absolute()
# The extension base path
EXT_PATH = FILE_DIR.joinpath('extensions')
# Tags base path
def get_tags_base_path():
script_path = Path(scripts.basedir())
if (script_path.is_relative_to(EXT_PATH)):
return script_path.joinpath('tags')
else:
return FILE_DIR.joinpath('tags')
TAGS_PATH = get_tags_base_path()
# The path to the folder containing the wildcards and embeddings
FILE_DIR = Path().absolute()
WILDCARD_PATH = FILE_DIR.joinpath('scripts/wildcards')
EMB_PATH = FILE_DIR.joinpath('embeddings')
# The path to the temporary file
TEMP_PATH = FILE_DIR.joinpath('tags/temp')
def find_ext_wildcard_paths():
"""Returns the path to the extension wildcards folder"""
found = list(EXT_PATH.rglob('**/wildcards/'))
return found
# The path to the extension wildcards folder
WILDCARD_EXT_PATHS = find_ext_wildcard_paths()
# The path to the temporary files
TEMP_PATH = TAGS_PATH.joinpath('temp')
def get_wildcards():
"""Returns a list of all wildcards. Works on nested folders."""
wildcard_files = list(WILDCARD_PATH.rglob("*.txt"))
resolved = [str(w.relative_to(WILDCARD_PATH)) for w in wildcard_files]
resolved = [w.relative_to(WILDCARD_PATH).as_posix(
) for w in wildcard_files if w.name != "put wildcards here.txt"]
return resolved
def get_ext_wildcards():
"""Returns a list of all extension wildcards. Works on nested folders."""
wildcard_files = []
for path in WILDCARD_EXT_PATHS:
wildcard_files.append(path.relative_to(FILE_DIR).as_posix())
wildcard_files.extend(p.relative_to(path).as_posix() for p in path.rglob(
"*.txt") if p.name != "put wildcards here.txt")
wildcard_files.append("-----")
return wildcard_files
def get_embeddings():
"""Returns a list of all embeddings"""
return [str(e.relative_to(EMB_PATH)) for e in EMB_PATH.glob("**/*") if e.suffix in {".bin", ".pt"}]
return [str(e.relative_to(EMB_PATH)) for e in EMB_PATH.glob("**/*") if e.suffix in {".bin", ".pt", ".png"}]
def write_tag_base_path():
"""Writes the tag base path to a fixed location temporary file"""
with open(FILE_DIR.joinpath('tmp/tagAutocompletePath.txt'), 'w', encoding="utf-8") as f:
f.write(TAGS_PATH.relative_to(FILE_DIR).as_posix())
def write_to_temp_file(name, data):
@@ -29,20 +79,33 @@ def write_to_temp_file(name, data):
f.write(('\n'.join(data)))
# Write the tag base path to a fixed location temporary file
# to enable the javascript side to find our files regardless of extension folder name
write_tag_base_path()
# Check if the temp path exists and create it if not
if not TEMP_PATH.exists():
TEMP_PATH.mkdir(parents=True, exist_ok=True)
# Set up files to ensure the script doesn't fail to load them
# even if no wildcards or embeddings are found
write_to_temp_file('wc.txt', [])
write_to_temp_file('emb.txt', [])
# Set up files to ensure the script doesn't fail to load them
# even if no wildcards or embeddings are found
write_to_temp_file('wc.txt', [])
write_to_temp_file('wce.txt', [])
write_to_temp_file('emb.txt', [])
# Write wildcards to wc.txt if found
if WILDCARD_PATH.exists():
wildcards = get_wildcards()
wildcards = [WILDCARD_PATH.relative_to(
FILE_DIR).as_posix()] + get_wildcards()
if wildcards:
write_to_temp_file('wc.txt', wildcards)
# Write extension wildcards to wce.txt if found
if WILDCARD_EXT_PATHS is not None:
wildcards_ext = get_ext_wildcards()
if wildcards_ext:
write_to_temp_file('wce.txt', wildcards_ext)
# Write embeddings to emb.txt if found
if EMB_PATH.exists():
embeddings = get_embeddings()

View File

@@ -6,7 +6,10 @@
"negativePrompts": true
},
"maxResults": 5,
"resultStepLength": 500,
"delayTime": 100,
"showAllResults": false,
"useLeftRightArrowKeys": false,
"replaceUnderscores": true,
"escapeParentheses": true,
"useWildcards": true,