Compare commits

...

161 Commits

Author SHA1 Message Date
Dominik Reh
e418a867b3 Merge branch 'hyp-lora-support' into main 2023-01-24 15:23:53 +01:00
Dominik Reh
040be35162 Don't escape parentheses for loras and hypernets 2023-01-24 15:03:56 +01:00
Dominik Reh
316d45e2fa Use extra network multiplier from settings 2023-01-24 15:03:35 +01:00
Dominik Reh
8ab0e2504b Fix meta display, add mixed results
< will show all three, while <e: <h: or <l: will limit it to that type.
2023-01-24 14:51:55 +01:00
Dominik Reh
b29b496b88 Simplify lora and hypernetwork loading 2023-01-24 14:08:11 +01:00
Dominik Reh
e144f0d388 Make script work without settings tab
Fixes #116
2023-01-24 13:08:43 +01:00
JM
ae01f41f30 add support for hypernetworks and lora 2023-01-22 19:24:59 +01:00
DominikDoom
fb27ac9187 Update README_ZH.md 2023-01-18 16:31:57 +01:00
DominikDoom
770bb495a5 Update README.md 2023-01-18 16:29:55 +01:00
Dominik Reh
7fdad1bf62 Add back ability to use hashes in black/whitelist
They are displayed in the UI after all, just not in the dropdown but at the bottom
2023-01-14 14:57:39 +01:00
Dominik Reh
a91a098243 Change blacklist to use model name instead of hash
Hotfix for recent webui changes to use proper sha256 hashes, which is currently not displayed in the UI
2023-01-14 14:24:44 +01:00
Dominik Reh
c663abcbcb Fix wiki links showing on embeddings & wildcards 2023-01-13 19:33:43 +01:00
Dominik Reh
bec222f2b3 Fix for 1-letter completion
Completion would sometimes not show if the prompt was only one letter long and identical to the previous completion
2023-01-12 15:54:57 +01:00
Dominik Reh
d4db6a7907 Option to show ? wiki links for danbooru/e621 tags
Disabled by default since the wiki pages likely contain NSFW images.
Closes #109
2023-01-12 15:49:53 +01:00
Dominik Reh
52593e6ac8 Update setting descriptions for black/whitelist 2023-01-12 14:45:16 +01:00
Dominik Reh
849e346924 Black/whitelisting options for models
Enables selective (de)activation based on model hash.
Closes #14
2023-01-12 14:35:54 +01:00
Dominik Reh
25b285bea3 Styling adjustments 2023-01-10 15:10:13 +01:00
Dominik Reh
984a7e772a File comments 2023-01-10 15:01:22 +01:00
Dominik Reh
964b4fcff3 Rework results system
Now uses object properties instead of array indices, much less confusing
2023-01-10 14:59:09 +01:00
Dominik Reh
54641ddbfc Move utility functions to their own file 2023-01-10 14:58:25 +01:00
Dominik Reh
c048684909 Load embeds recursively in fallback
Webui now supports recursive embedding loading, so we also use it here.
This shouldn't happen since the newer version uses the non-fallback, but it doesn't hurt
2023-01-06 15:52:40 +01:00
Dominik Reh
da9acfea2a Rework embedding load, now uses callback.
Should hopefully fix #100
2023-01-03 17:30:30 +01:00
Dominik Reh
552c6517b8 Make new settings id the default behavior instead of fallback 2023-01-03 11:14:16 +01:00
DominikDoom
f626eb3467 Merge pull request #101 from stysmmaker/fix/apply-settings-button-fallback
Add fallback to applySettingsButton variable
2023-01-03 10:57:53 +01:00
MMaker
2ba513bedc fix: Add fallback to applySettingsButton var
Need due to layout change in recent webui update
269f6e8676
2023-01-03 00:07:15 -05:00
Dominik Reh
89d36da47e Add fallback for embedding loading
Fixes error on outdated webuis, as mentioned in #98 and #99
2023-01-02 16:09:36 +01:00
Dominik Reh
5f2f746310 Skipped embeddings now also hold shape info
so we don't need to guess the type anymore if the model didn't load any.
2023-01-02 12:45:56 +01:00
Dominik Reh
454c13ef6d Fix for embedding search without v1/v2 prefix 2023-01-02 00:44:56 +01:00
Dominik Reh
6deefda279 Show version info for embeddings
Also allows searching by version to quickly find v1 or v2 model embeddings
Closes #97
2023-01-02 00:38:48 +01:00
Dominik Reh
b57042edd0 Remove leftover debug logs 2022-12-26 13:31:04 +01:00
DominikDoom
ceba61163e Merge pull request #93 from ctwrs/umi-suggestions
Smarter suggestion system for UMI wildcard tags that takes previous tags into account to only show possible candidates.
2022-12-26 13:28:46 +01:00
catwars
16201605d0 Merge branch 'DominikDoom:main' into umi-suggestions 2022-12-26 02:10:49 +01:00
ctwrs
0c3397aee6 Fix umi tag autocomplete filtering logic 2022-12-26 01:57:15 +01:00
DominikDoom
4f582f4528 Merge pull request #92 from ctwrs/main
Allows None as a main tag file selection if the user wants special completion like wildcards but not for normal tags.
2022-12-24 13:27:34 +01:00
ctwrs
d2b5142d7d Umi filtering - initial version 2022-12-24 01:32:45 +01:00
ctwrs
f11abe60c2 Allow for selecting an empty tagFile to disable booru suggestions 2022-12-23 21:34:00 +01:00
DominikDoom
16bf9d9a51 Merge pull request #90 from DominikDoom/feature-yaml-wildcards 2022-12-22 14:48:23 +01:00
Dominik Reh
bdd8cf68c7 Fix show all condition 2022-12-22 12:39:00 +01:00
Dominik Reh
63a0d2e73e Bugfixes for change detection & multiple umi tags
(WIP)
2022-12-20 19:45:07 +01:00
Dominik Reh
34ba08d804 Extract regexes for easier editing & testing 2022-12-20 18:25:37 +01:00
Dominik Reh
f1a437ff48 Autocompletion for UMI yaml wildcards 2022-12-20 17:08:09 +01:00
Dominik Reh
97cbada882 Sort yaml tags by count 2022-12-20 13:28:43 +01:00
DominikDoom
860a4034bb Update README.md 2022-12-19 19:30:36 +01:00
Dominik Reh
255d7420fd Merge branch 'main' of https://github.com/DominikDoom/a1111-sd-webui-tagcomplete into main 2022-12-19 19:07:32 +01:00
Dominik Reh
6b34d8ccd1 Warning about e621 as extra file 2022-12-19 19:07:29 +01:00
DominikDoom
b35ee10f8e Merge pull request #88 from ctwrs/main
Preparation for new yaml wildcard support
2022-12-19 13:05:43 +01:00
ctwrs
fc8540589a Index tags used in yaml wildcard files 2022-12-19 12:53:30 +01:00
Dominik Reh
3d1ca6893a Fix formatting 2022-12-19 10:32:47 +01:00
Dominik Reh
73c3424ab3 Fix for script load if no third party found
Added missing null check
Fixes #87
2022-12-19 10:23:34 +01:00
DominikDoom
5f8a5d468d Update README_ZH.md 2022-12-18 16:51:11 +01:00
DominikDoom
4296d8e3b7 Update README.md 2022-12-18 16:38:17 +01:00
DominikDoom
8d9c0c7bb7 Merge pull request #86 from DominikDoom/support-dataset-tag-editor 2022-12-18 14:17:27 +01:00
Dominik Reh
1c22a22abe Support third party textboxes
Base functionality for third party textboxes, specifically Dataset Tag Editor
Closes #83
2022-12-18 14:15:37 +01:00
DominikDoom
f38c5df257 Update README_ZH.md 2022-11-28 08:00:08 +01:00
DominikDoom
3332d62639 Update README.md 2022-11-28 07:55:23 +01:00
DominikDoom
b159efe74e Update README.md 2022-11-28 07:52:36 +01:00
DominikDoom
3789457702 Fix typo 2022-11-26 15:44:43 +01:00
DominikDoom
35875a07a8 Update README_ZH.md 2022-11-26 15:42:30 +01:00
DominikDoom
77c6a2b950 Update README.md for new settings 2022-11-26 15:22:44 +01:00
DominikDoom
bc80b3ea2c Merge pull request #80 from DominikDoom/feature-options
Migrates config file to webui settings
2022-11-26 15:09:46 +01:00
Dominik Reh
a4e0b69d26 Better description for translation format 2022-11-26 15:01:51 +01:00
Dominik Reh
4f68a50a25 Fix styling for tac refresh buttons
Add change listener for settings of type 'select'
2022-11-26 14:45:43 +01:00
Dominik Reh
05c11c9781 Finalize settings migration
- Dropdown selection & refresh for tag files
- Tag, Extra & Translation file reloading without restart
- Any options can now be added to the quicksettings bar
- Standalone colors.json file
2022-11-26 14:29:20 +01:00
Dominik Reh
def6ebb798 Initial changes for settings migration 2022-11-21 19:11:20 +01:00
Dominik Reh
e4a8ee7439 Move settings inline
As mentioned in #71
2022-11-20 12:28:47 +01:00
Dominik Reh
1c3e60cfb2 Use fetch instead of XmlHttpRequest
Easier to use and also seems to be faster
2022-11-19 18:43:04 +01:00
DominikDoom
fc4484ddc6 Merge pull request #77 from stysmmaker/patch/embeddings-dir 2022-11-17 18:22:20 +01:00
MMaker
d6eb751e4b fix: Use correct embeddings dir
Use the `--embeddings-dir` if specified
2022-11-17 12:13:43 -05:00
batvbs
894335f1de Improved search function (#75)
Now only searches for matches at the start of tags or sub-words in multi-word-tags.
Old behavior can still be used by typing * at the beginning of the word.
2022-11-14 12:56:59 +01:00
Dominik Reh
2d45d6c796 Dynamic css construction for less duplicate code
Inserts variables for light/dark mode instead of keeping separate css for both
Fixes scrollbar space being reserved in chrome where it's not needed.
2022-11-07 19:25:51 +01:00
DominikDoom
dba4046064 Update README_ZH.md
I updated it using machine translation, so if you are a native Chinese speaker and notice any mistakes, please tell me.
2022-11-07 10:01:29 +01:00
Dominik Reh
ca8a0c433e Fix result text cutoff in Firefox
Fixes #65
2022-11-06 14:33:11 +01:00
Dominik Reh
535c2a6753 Safety checks for translations
Should prevent list getting cut off if no translation or alias matches
2022-11-06 14:16:56 +01:00
Dominik Reh
e86c604903 Fix for translations failing to match sometimes
Fixes #62 (again)
2022-11-06 14:00:34 +01:00
Dominik Reh
4eabf00f01 Remove max width for resutls
Fixes #65
2022-11-05 18:27:53 +01:00
Dominik Reh
a39b0d0742 Include deprecated danbooru tags
Since many of the deprecated tags have large post counts, it makes sense to include them, even if better alternatives are available now.
Fixes #64
2022-11-05 17:51:34 +01:00
DominikDoom
ecc71902cd Update README.md 2022-11-05 16:46:24 +01:00
DominikDoom
2dc1dfea86 Update README.md 2022-11-05 16:21:38 +01:00
Dominik Reh
18556c6115 Rework translation to be separate from aliases
Enables two-way translation where the translation is always visible.
Closes #62.
2022-11-05 15:54:26 +01:00
DominikDoom
82355cdb60 Update README.md 2022-11-04 16:10:59 +01:00
Dominik Reh
2c6b6e7f13 Count and Alias support
The data and script have been updated to include post count and common aliases for tags.
Aliases are added the same way as translations.
Closes #60.
2022-11-04 15:50:27 +01:00
Dominik Reh
abb5625e55 Cache workaround
Force reload by querying with current time added to the URL
2022-11-01 13:56:03 +01:00
Dominik Reh
d5de786d07 Don't use unnecessary onUIUpdate 2022-11-01 13:54:38 +01:00
Dominik Reh
f8a9223c29 Add config option to hide autocomplete UI options
Implements #57
2022-11-01 13:50:34 +01:00
Dominik Reh
61a97175a7 Fix parentheses regression
Fixes #59
2022-11-01 13:03:03 +01:00
Dominik Reh
92a08205d0 Remove unnecessary path check
Simplifies getting the tag base path. Also fixes #55
2022-10-31 11:04:36 +01:00
DominikDoom
372a499615 Merge pull request #52 from Kinsmir/main 2022-10-30 17:14:10 +01:00
Dominik Reh
ca717948a4 Add config & UI option for appending commas
Closes #49
2022-10-30 17:10:31 +01:00
Dominik Reh
6c6999d5f1 Fix diff check for negative tag count changes
Now properly closes the popup if the last letter of a tag gets deleted.
2022-10-30 16:10:55 +01:00
Joris Neuteboom
f7f5101f62 Removed wildcard comments
https://github.com/Klokinator/UnivAICharGen uses # for comments within the wildcards
2022-10-30 16:10:38 +01:00
Dominik Reh
e49862d422 Fix Regex for non-ascii tags
Also separates the regex for simplification. Fixes #51.
2022-10-30 16:08:13 +01:00
Dominik Reh
524514bd46 Fix parsing for real this time
Fixes #48 (again)
2022-10-29 18:35:06 +02:00
Dominik Reh
106fa13f65 Hotfix for broken parsing
Fixes #48
2022-10-29 17:24:44 +02:00
Dominik Reh
a038664616 Fix new regex for embeddings 2022-10-29 15:55:30 +02:00
Dominik Reh
789f44d52a Support editing tags inside weighting parentheses
Fixes #47
2022-10-29 14:48:44 +02:00
Dominik Reh
59ec54b171 Fix duplicate wildcards
Would occur if the extension folder was also just "wildcards" due to recursive search
2022-10-29 10:08:20 +02:00
Dominik Reh
983da36329 Create tmp folder in root if it doesn't exist
Fixes extension installation on Linux, closes #46
2022-10-29 09:55:30 +02:00
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
DominikDoom
353cb1937e Fix inline code not separated 2022-10-17 15:40:38 +02:00
sgmklp
c8c857f2cc Add Chinese README (#20) 2022-10-17 15:32:57 +02:00
DominikDoom
0112acb820 Move showAllResults next to maxResults in config 2022-10-17 15:03:54 +02:00
DominikDoom
896533c986 Added translation info to README 2022-10-17 14:47:36 +02:00
Dominik Reh
9fc9f1ab7d Don't capture keydown if CTRL or ALT is held
Prevents the script from blocking weight editing and prompt submit shortcuts
Fixes #19
2022-10-17 13:56:15 +02:00
DominikDoom
1d12fdcc6a Merge pull request #16 from sgmklp/dev
Ability to add translations in the main csv or additional files.
2022-10-17 11:07:49 +02:00
sgmklp
1387351d4d add match by class 2022-10-17 11:07:54 +08:00
sgmklp
4593a9a4e1 Update javascript/tagAutocomplete.js
Co-authored-by: DominikDoom <34448969+DominikDoom@users.noreply.github.com>
2022-10-17 10:46:44 +08:00
sgmklp
d5636f9026 also add Tags in extra file without translation 2022-10-17 03:47:26 +08:00
sgmklp
59ccb7ac19 will add new Tag in translation to tag list 2022-10-17 03:30:22 +08:00
sgmklp
d8ec8793fa keep the old match function avaliable 2022-10-17 02:03:45 +08:00
sgmklp
b0bc2d9b0b change the match function of translation 2022-10-17 01:57:21 +08:00
DominikDoom
c67fd336fd Add note about caching issues to README 2022-10-16 14:31:30 +02:00
Dominik Reh
5bf0451432 Support nested wildcards 2022-10-16 13:42:50 +02:00
sgmklp
046e2d99fb add a config option to show all result 2022-10-16 19:02:33 +08:00
sgmklp
cb31b072b4 chang maxResults to default 2022-10-16 13:51:08 +08:00
sgmklp
b858370acf allow use extra translation file 2022-10-16 13:43:03 +08:00
sgmklp
854b1952db . 2022-10-16 01:50:07 +08:00
sgmklp
f77283342e add search by translation 2022-10-16 01:47:53 +08:00
sgmklp
ae1ed19b7d suppost only show the translation to result 2022-10-16 00:00:39 +08:00
sgmklp
5f1b8c8da3 . 2022-10-15 23:06:19 +08:00
sgmklp
db6dcc9568 Make the tranlation in csv adding to result, also support Tag without tranlation. 2022-10-15 22:57:04 +08:00
DominikDoom
53899093c8 Merge pull request #15 from sgmklp/dev 2022-10-15 16:07:18 +02:00
Dominik Reh
f9d98740f4 Fix negative prompt autocomplete after UI change 2022-10-15 16:05:49 +02:00
sgmklp
534f07225e fix write wildcard files with Chinese name with wrong coding to wc.txt 2022-10-15 21:43:57 +08:00
Dominik Reh
b8b0673e2d Formatting 2022-10-15 15:32:23 +02:00
DominikDoom
2f0d18a73f Update README.md 2022-10-15 15:15:24 +02:00
Dominik Reh
e68e7389dd Fix for empty wildcard or embed lines showing in results list 2022-10-15 15:15:13 +02:00
Dominik Reh
b5cecc4e8d Fix for wildcard & embedding file extensions 2022-10-15 14:53:34 +02:00
Dominik Reh
96828c241c Enable wildcards and embeds by default
Since we now search automatically, the script also doesn't try to load anything if none are found
2022-10-15 14:39:26 +02:00
Dominik Reh
07d7eddf66 Move script to js folder for easier install 2022-10-15 14:32:41 +02:00
Dominik Reh
08c10928f8 Automatic wildcard & embed discovery 2022-10-15 14:32:02 +02:00
Dominik Reh
a628d96a41 Added UI checkbox to turn off/on without restart
Also helps with #14
2022-10-15 13:23:03 +02:00
Dominik Reh
3a47a9b010 Insert results as textContent instead of innerHtml
Fixes #7
2022-10-14 09:38:51 +02:00
Dominik Reh
fbfc988fe5 Fix for broken RegEx with tags containing parentheses
Have I mentioned that all my homies hate RegEx?
2022-10-13 23:37:28 +02:00
Dominik Reh
a93a209e7e Update README.md 2022-10-13 22:37:23 +02:00
Dominik Reh
f5c00d8de4 Update README.md 2022-10-13 22:06:31 +02:00
Dominik Reh
0b7bb146a5 Fixed text insertion & curser position
This time for real (hopefully)
All my homies hate RegEx
2022-10-13 21:31:37 +02:00
Dominik Reh
f098b14248 Update README.md 2022-10-13 17:36:56 +02:00
Dominik Reh
9710eef4cc Added wildcard info to README 2022-10-13 17:26:17 +02:00
Dominik Reh
db29a6a84a Support completion for the wildcard script
Also some reworks for text insertion, should now work no matter where in the word your cursor is
2022-10-13 17:10:35 +02:00
Dominik Reh
4785142549 Added link wrap to badge 2022-10-13 10:58:21 +02:00
Dominik Reh
cddd9da700 Added release badge to README 2022-10-13 10:53:06 +02:00
Dominik Reh
ae02c749e9 Negative prompts now exclusive to their category
They now only autocomplete if their parent category is active in the config as well
2022-10-13 10:35:08 +02:00
Dominik Reh
fca985ba39 Update README.md 2022-10-13 10:19:47 +02:00
Dominik Reh
fff756cb86 Added functionality for img2img & negative prompts 2022-10-13 10:15:21 +02:00
Dominik Reh
7c21452560 Default to danbooru tag colors 2022-10-13 08:30:28 +02:00
13 changed files with 202157 additions and 176273 deletions

1
.gitignore vendored Normal file
View File

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

220
README.md
View File

@@ -1,104 +1,186 @@
![tag_autocomplete_light](https://user-images.githubusercontent.com/34448969/208306863-90bbd663-2cb4-47f1-a7fe-7b662a7b95e2.png)
# Booru tag autocompletion for A1111
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/DominikDoom/a1111-sd-webui-tagcomplete)](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases)
## [中文文档](./README_ZH.md)
This custom script serves as a drop-in extension for the popular [AUTOMATIC1111 web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) for Stable Diffusion.
It displays autocompletion hints for recognized tags from "image booru" boards such as Danbooru, which are primarily used for browsing Anime-style illustrations.
Since some Stable Diffusion models were trained using this information, for example [Waifu Diffusion](https://github.com/harubaru/waifu-diffusion), using exact tags in prompts can often improve composition and help to achieve a wanted look.
I created this script as a convenience tool since it reduces the need of switching back and forth between the web UI and a booru site to copy-paste tags.
You can install it using the inbuilt available extensions list, clone the files manually as described [below](#installation), or use a pre-packaged version from [Releases](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases).
You can either download the files manually as described below, or use a pre-packaged version from [Releases](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases).
### Disclaimer:
This script is definitely not optimized, and it's not very intelligent. The tags are simply recommended based on their natural order in the CSV, which is their respective image count for the default Danbooru tag list. Also, at least for now, completion for negative or img2img prompt textboxes isn't supported, and there's no way to turn the script off from the ui, but I plan to get around to those features eventually.
### Known Issues:
If `replaceUnderscores` is active, the script will currently only partly replace edited tags containing multiple words in brackets.
## Common Problems & Known Issues:
- If `replaceUnderscores` is active, the script will currently only partially replace edited tags containing multiple words in brackets.
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.
## Screenshots
Demo video (with keyboard navigation):
https://user-images.githubusercontent.com/34448969/195344430-2b5f9945-b98b-4943-9fbc-82cf633321b1.mp4
https://user-images.githubusercontent.com/34448969/200128020-10d9a8b2-cea6-4e3f-bcd2-8c40c8c73233.mp4
Wildcard script support:
https://user-images.githubusercontent.com/34448969/200128031-22dd7c33-71d1-464f-ae36-5f6c8fd49df0.mp4
Dark and Light mode supported, including tag colors:
![tagtypes](https://user-images.githubusercontent.com/34448969/195177127-f63949f8-271d-4767-bccd-f1b5e818a7f8.png)
![tagtypes_light](https://user-images.githubusercontent.com/34448969/195180061-ceebcc25-9e4c-424f-b0c9-ba8e8f4f17f4.png)
![results_dark](https://user-images.githubusercontent.com/34448969/200128214-3b6f21b4-9dda-4acf-820e-5df0285c30d6.png)
![results_light](https://user-images.githubusercontent.com/34448969/200128217-bfac8b60-6673-447b-90fd-dc6326f1618c.png)
## Installation
Simply put `tagAutocomplete.js` into the `javascript` folder of your web UI installation. It will run automatically the next time the web UI is started.
For the script to work, you also need to download the `tags` folder from this repo and paste it and its contents into the web UI root, or create them there manually.
The tags folder contains `config.json` and the tag data the script uses for autocompletion. By default, Danbooru and e621 tags are included.
### Config
The config contains the following settings and defaults:
```json
{
"tagFile": "danbooru.csv",
"maxResults": 5,
"replaceUnderscores": true,
"colors": {
"danbooru": {
"0": ["lightblue", "dodgerblue"],
"1": ["indianred", "firebrick"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["orange", "darkorange"]
},
"e621": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["gold", "goldenrod"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["tomato", "darksalmon"],
"6": ["red", "maroon"],
"7": ["whitesmoke", "black"],
"8": ["seagreen", "darkseagreen"]
}
}
}
### 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).
Or create a folder there manually and place the `javascript`, `scripts` and `tags` folders in it.
### In the root folder (legacy)
This installation method is for old webui versions pre-extension system, it will not work on current versions!
---
In both configurations, the `tags` folder contains `colors.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.
## 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 or other similar scripts/extensions. 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, .bin and .png files inside if you start typing `<`. Note that some normal tags also use < in Kaomoji (like ">_<" for example), so the results will contain both.
## Settings
The extension has a large amount of configuration & customizability built in:
![image](https://user-images.githubusercontent.com/34448969/204093162-99c6a0e7-8183-4f47-963b-1f172774f527.png)
| Setting | Description |
|---------|-------------|
| 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.|
| maxResults | How many results to show max. For the default tag set, the results are ordered by occurence count. |
| 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. |
| delayTime | Specifies how much to wait in milliseconds before triggering autocomplete. Helps prevent too frequent updates while typing. |
| showAllResults | If true, will ignore maxResults and show all results in a scrollable list. **Warning:** can lag your browser for long lists. |
| replaceUnderscores | If true, undescores are replaced with spaces on clicking a tag. Might work better for some models. |
| colors | Contains customizable colors for the tag types, you can add new ones here for custom tag files (same name as filename, without the .csv). The first value is for dark, the second for light mode. Color names and hex codes should both work.|
| escapeParentheses | If true, escapes tags containing () so they don't contribute to the web UI's prompt weighting functionality. |
| appendComma | Specifies the starting value of the "Append commas" UI switch. If UI options are disabled, this will always be used. |
| useWildcards | Used to toggle the wildcard completion functionality. |
| useEmbeddings | Used to toggle the embedding completion functionality. |
| alias | Options for aliases. More info in the section below. |
| translation | Options for translations. More info in the section below. |
| extras | Options for additional tag files / aliases / translations. More info in the section below. |
### CSV tag data
### colors.json
Additionally, tag type colors can be specified using the separate `colors.json` file in the extension's `tags` folder.
You can also add new ones here for custom tag files (same name as filename, without the .csv). The first value is for dark, the second for light mode. Color names and hex codes should both work.
```json
{
"danbooru": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["indianred", "firebrick"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["orange", "darkorange"]
},
"e621": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["gold", "goldenrod"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["tomato", "darksalmon"],
"6": ["red", "maroon"],
"7": ["whitesmoke", "black"],
"8": ["seagreen", "darkseagreen"]
}
}
```
The numbers are specifying the tag type, which is dependent on the tag source. For an example, see [CSV tag data](#csv-tag-data).
### Aliases, Translations & Extra tags
#### Aliases
Like on Booru sites, tags can have one or multiple aliases which redirect to the actual value on completion. These will be searchable / shown according to the settings in `config.json`:
- `searchByAlias` - Whether to also search for the alias or only the actual tag.
- `onlyShowAlias` - Shows only the alias instead of `alias -> actual`. Only for displaying, the inserted text at the end is still the actual tag.
#### 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 extra files that used a three column format, you can turn on `oldFormat` to use that instead.
Example with chinese translation:
![IME-input](https://user-images.githubusercontent.com/34448969/200126551-2264e9cc-abb2-4450-9afa-43f362a77ab0.png)
![english-input](https://user-images.githubusercontent.com/34448969/200126513-bf6b3940-6e22-41b0-a369-f2b4640f87d6.png)
**Important**
As of a recent update, translations added in the old Extra file way will only work as an alias and not be visible anymore if typing the English tag for that translation.
#### Extra file
Aliases can be added in multiple ways, which is where the "Extra" file comes into play.
1. As an extra file containing tag, category, optional count and the new alias. Will be matched to the English tags in the main file based on the name & type, so might be slow for large files.
2. As an extra file with `onlyAliasExtraFile` true. With this configuration, the extra file has to include *only* the alias itself. That means it is purely index based, assigning the aliases to the main tags is really fast but also needs the lines to match (including empty lines). If the order or amount in the main file changes, the translations will potentially not match anymore. Not recommended.
So your CSV values would look like this for each method:
| | 1 | 2 |
|------------|--------------------------|--------------------------|
| Main file | `tag,type,count,(alias)` | `tag,type,count,(alias)` |
| Extra file | `tag,type,(count),alias` | `alias` |
Count in the extra file is optional, since there isn't always a post count for custom tag sets.
The extra files can also be used to just add new / custom tags not included in the main set, provided `onlyAliasExtraFile` is false.
If an extra tag doesn't match any existing tag, it will be added to the list as a new tag instead. For this, it will need to include the post count and alias columns even if they don't contain anything, so it could be in the form of `tag,type,,`.
##### WARNING
Do not use e621.csv or danbooru.csv as an extra file. Alias comparison has exponential runtime, so for the combination of danbooru+e621, it will need to do 10,000,000,000 (yes, ten billion) lookups and usually take multiple minutes to load.
## CSV tag data
The script expects a CSV file with tags saved in the following way:
```csv
1girl,0
solo,0
highres,5
long_hair,0
<name>,<type>,<postCount>,"<aliases>"
```
Notably, it does not expect column names in the first row.
The first value needs to be the tag name, while the second value specifies the tag type.
Example:
```csv
1girl,0,4114588,"1girls,sole_female"
solo,0,3426446,"female_solo,solo_female"
highres,5,3008413,"high_res,high_resolution,hires"
long_hair,0,2898315,longhair
commentary_request,5,2610959,
```
Notably, it does not expect column names in the first row and both count and aliases are technically optional,
although count is always included in the default data. Multiple aliases need to be comma separated as well, but encased in string quotes to not break the CSV parsing.
The numbering system follows the [tag API docs](https://danbooru.donmai.us/wiki_pages/api%3Atags) of Danbooru:
| Value | Description |
|-------|-------------|
|0 | General |
|1 | Artist |
|3 | Copyright |
|4 | Character |
|5 | Meta |
|0 | General |
|1 | Artist |
|3 | Copyright |
|4 | Character |
|5 | Meta |
or of e621:
or similarly for e621:
| Value | Description |
|-------|-------------|
|-1 | Invalid |
|0 | General |
|1 | Artist |
|3 | Copyright |
|4 | Character |
|5 | Species |
|6 | Invalid |
|7 | Meta |
|8 | Lore |
|-1 | Invalid |
|0 | General |
|1 | Artist |
|3 | Copyright |
|4 | Character |
|5 | Species |
|6 | Invalid |
|7 | Meta |
|8 | Lore |
The tag type is used for coloring entries in the result list.

176
README_ZH.md Normal file
View File

@@ -0,0 +1,176 @@
![tag_autocomplete_light_zh](https://user-images.githubusercontent.com/34448969/208307331-430696b4-e854-4458-b9e9-f6a6594f19e1.png)
# Booru tag autocompletion for A1111
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/DominikDoom/a1111-sd-webui-tagcomplete)](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases)
## [English Document](./README.md)
## 功能概述
本脚本为 [AUTOMATIC1111 web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)的自定义脚本,能在输入Tag时提供booru风格如Danbooru的TAG自动补全。因为有一些模型是基于这种TAG风格训练的例如[Waifu Diffusion](https://github.com/harubaru/waifu-diffusion)因此使用这些Tag能获得较为精确的效果。
这个脚本的创建是为了减少因复制Tag在Web UI和 booru网站的反复切换。
你可以按照[以下方法](#installation)下载或拷贝文件,也可以使用[Releases](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases)中打包好的文件。
## 常见问题 & 已知缺陷:
-`replaceUnderscores`选项开启时, 脚本只会替换Tag的一部分如果Tag包含多个单词,比如将`atago (azur lane)`修改`atago``taihou`并使用自动补全时.会得到 `taihou (azur lane), lane)`的结果, 因为脚本没有把后面的部分认为成同一个Tag。
## 演示与截图
演示视频(使用了键盘导航):
https://user-images.githubusercontent.com/34448969/200128020-10d9a8b2-cea6-4e3f-bcd2-8c40c8c73233.mp4
Wildcard支持演示:
https://user-images.githubusercontent.com/34448969/200128031-22dd7c33-71d1-464f-ae36-5f6c8fd49df0.mp4
深浅色主题支持,包括Tag的颜色:
![results_dark](https://user-images.githubusercontent.com/34448969/200128214-3b6f21b4-9dda-4acf-820e-5df0285c30d6.png)
![results_light](https://user-images.githubusercontent.com/34448969/200128217-bfac8b60-6673-447b-90fd-dc6326f1618c.png)
## 安装
### 作为一种扩展(推荐)
要么把它克隆到你的扩展文件夹里
```bash
git clone "https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git" extensions/tag-autocomplete
```
(第二个参数指定文件夹的名称,你可以选择任何你喜欢的东西)。
或者手动创建一个文件夹,将 `javascript``scripts``tags`文件夹放在其中。
### 在根目录下(过时的方法)
这种安装方法适用于添加扩展系统之前的旧版webui在目前的版本上是行不通的。
---
在这两种配置中,标签文件夹包含`colors.json`和脚本用于自动完成的标签数据。
默认情况下Tag数据包括`Danbooru.csv``e621.csv`
在扫描过`/embeddings`和wildcards后会将列表存放在`tags/temp`文件夹下。删除该文件夹不会有任何影响,下次启动时它会重新创建。
### 注意:
本脚本的允许需要**全部的三个文件夹**。
## [Wildcard](https://github.com/jtkelm2/stable-diffusion-webui-1/blob/master/scripts/wildcards.py) & Embedding 支持
自动补全同样适用于 [Wildcard](https://github.com/jtkelm2/stable-diffusion-webui-1/blob/master/scripts/wildcards.py)中所述的通配符文件(后面有演示视频)。这将使你能够插入Wildcard脚本需要的通配符更进一步的你还可以插入通配符文件内的某个具体Tag。
当输入`__`字符时,`/scripts/wildcards`文件夹下的通配符文件会列出到自动补全,当你选择某个具体通配符文件时,会列出其中的所有的具体Tag,但如果你仅需要选择某个通配符,请按下空格。
当输入`<`字符时,`embeddings`文件夹下的`.pt``.bin`文件会列出到自动完成。需要注意的是,一些颜文字也会包含`<`(比如`>_<`),所以它们也会出现在结果中。
现在这项功能默认是启用的,并会自动扫描`/embeddings``/scripts/wildcards`文件夹,不再需要使用`tags/wildcardNames.txt`文件了,早期版本的用户可以将它删除。
## 配置文件
该扩展有大量的配置和可定制性的内建:
![image](https://user-images.githubusercontent.com/34448969/204093162-99c6a0e7-8183-4f47-963b-1f172774f527.png)
| 设置 | 描述 |
|---------|-------------|
| tagFile | 指定要使用的标记文件。您可以提供您喜欢的自定义标签数据库,但由于该脚本是在考虑 Danbooru 标签的情况下开发的,因此它可能无法与其他配置一起正常工作。|
| activeIn | 允许有选择地(取消)激活 txt2img、img2img 和两者的否定提示的脚本。|
| maxResults | 最多显示多少个结果。对于默认标记集,结果按出现次数排序。对于嵌入和通配符,它​​将在可滚动列表中显示所有结果。 |
| showAllResults | 如果为真,将忽略 maxResults 并在可滚动列表中显示所有结果。 **警告:** 对于长列表,您的浏览器可能会滞后。 |
| resultStepLength | 允许以指定大小的小批次加载结果以便在长列表中获得更好的性能或者在showAllResults为真时。 |
| delayTime | 指定在触发自动完成之前要等待多少毫秒。有助于防止打字时过于频繁的更新。 |
| replaceUnderscores | 如果为 true则在单击标签时将取消划线替换为空格。对于某些型号可能会更好。|
| escapeParentheses | 如果为 true则转义包含 () 的标签,因此它们不会对 Web UI 的提示权重功能做出贡献。 |
| useWildcards | 用于切换通配符完成功能。 |
| useEmbeddings | 用于切换嵌入完成功能。 |
| alias | 标签别名的选项。更多信息在下面的部分。 |
| translation | 用于翻译标签的选项。更多信息在下面的部分。 |
| extras | 附加标签文件/翻译的选项。更多信息在下面的部分。|
### colors.json (标签颜色)
此外,标签类型的颜色可以使用扩展的`tags`文件夹中单独的`colors.json`文件来指定。
你也可以在这里为自定义标签文件添加新的(与文件名相同,不带 .csv。第一个值是暗模式第二个值是亮模式。颜色名称和十六进制代码都被支持。
```json
{
"danbooru": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["indianred", "firebrick"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["orange", "darkorange"]
},
"e621": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["gold", "goldenrod"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["tomato", "darksalmon"],
"6": ["red", "maroon"],
"7": ["whitesmoke", "black"],
"8": ["seagreen", "darkseagreen"]
}
}
```
数字是指定标签的类型,这取决于标签的来源。例如,见[CSV tag data](#csv-tag-data)。
### 别名,翻译&新增Tag
#### 别名
像Booru网站一样标签可以有一个或多个别名完成后重定向到实际值。这些将根据`config.json`中的设置进行搜索/显示。
- `searchByAlias` - 是否也要搜索别名,或只搜索实际的标签。
- `onlyShowAlias` - 只显示别名,不显示 `别名->实际`。仅用于显示,最后的文本仍然是实际的标签。
#### 翻译
可以在翻译部分添加一个额外的文件,它将被用来翻译标签和别名,同时也可以通过翻译进行搜索。
这个文件需要是CSV格式的`<英语标签/别名>,<翻译>`,但为了向后兼容使用三栏格式的旧的额外文件,你可以打开`oldFormat`来代替它。
完整和部分中文标签集的示例:
![IME-input](https://user-images.githubusercontent.com/34448969/200126551-2264e9cc-abb2-4450-9afa-43f362a77ab0.png)
![english-input](https://user-images.githubusercontent.com/34448969/200126513-bf6b3940-6e22-41b0-a369-f2b4640f87d6.png)
**重要的是**
从最近的更新来看用旧的Extra文件方式添加的翻译只能作为一个别名使用如果输入该翻译的英文标签将不再可见。
可以通过多种方式添加别名,这就是额外文件发挥作用的地方。
1. 作为仅包含已翻译标签行的额外文件(因此仍包括英文标签名称和标签类型)。将根据名称和类型与主文件中的英文标签匹配,因此对于大型翻译文件可能会很慢。
2. 作为 `onlyAliasExtraFile` 为 true 的额外文件。使用此配置,额外文件必须包含*仅*翻译本身。这意味着它完全基于索引,将翻译分配给主要标签非常快,但也需要匹配行(包括空行)。如果主文件中的顺序或数量发生变化,则翻译可能不再匹配。
因此,对于每种方法,您的 CSV 值将如下所示:
| | 1 | 2 |
|------------|--------------------------|--------------------------|
| Main file | `tag,type,count,(alias)` | `tag,type,count,(alias)` |
| Extra file | `tag,type,(count),alias` | `alias` |
额外文件中的计数是可选的,因为自定义标签集并不总是有帖子计数。
如果额外的标签与任何现有标签都不匹配,它将作为新标签添加到列表中。
### CSV tag data
本脚本的Tag文件格式如下你可以安装这个格式制作自己的Tag文件:
```csv
1girl,0,4114588,"1girls,sole_female"
solo,0,3426446,"female_solo,solo_female"
highres,5,3008413,"high_res,high_resolution,hires"
long_hair,0,2898315,longhair
commentary_request,5,2610959,
```
值得注意的是不希望在第一行有列名而且count和aliases在技术上都是可选的。
尽管count总是包含在默认数据中。多个别名也需要用逗号分隔但要用字符串引号包裹以免破坏CSV解析。
编号系统遵循 Danbooru 的 [tag API docs](https://danbooru.donmai.us/wiki_pages/api%3Atags):
| Value | Description |
|-------|-------------|
|0 | General |
|1 | Artist |
|3 | Copyright |
|4 | Character |
|5 | Meta |
类似的还有e621
| Value | Description |
|-------|-------------|
|-1 | Invalid |
|0 | General |
|1 | Artist |
|3 | Copyright |
|4 | Character |
|5 | Species |
|6 | Invalid |
|7 | Meta |
|8 | Lore |
标记类型用于为结果列表中的条目着色.

31
javascript/_result.js Normal file
View File

@@ -0,0 +1,31 @@
// Result data type for cleaner use of optional completion result properties
// Type enum
const ResultType = Object.freeze({
"tag": 1,
"embedding": 2,
"wildcardTag": 3,
"wildcardFile": 4,
"yamlWildcard": 5,
"hypernetwork": 6,
"lora": 7
});
// Class to hold result data and annotations to make it clearer to use
class AutocompleteResult {
// Main properties
text = "";
type = ResultType.tag;
// Additional info, only used in some cases
category = null;
count = null;
aliases = null;
meta = null;
// Constructor
constructor(text, type) {
this.text = text;
this.type = type;
}
}

84
javascript/_textAreas.js Normal file
View File

@@ -0,0 +1,84 @@
// Utility functions to select text areas the script should work on,
// including third party options.
// Supported third party options so far:
// - Dataset Tag Editor
// Core text area selectors
const core = [
"#txt2img_prompt > label > textarea",
"#img2img_prompt > label > textarea",
"#txt2img_neg_prompt > label > textarea",
"#img2img_neg_prompt > label > textarea"
];
// Third party text area selectors
const thirdParty = {
"dataset-tag-editor": {
"base": "#tab_dataset_tag_editor_interface",
"hasIds": false,
"selectors": [
"Caption of Selected Image",
"Interrogate Result",
"Edit Caption",
"Edit Tags"
]
}
}
function getTextAreas() {
// First get all core text areas
let textAreas = [...gradioApp().querySelectorAll(core.join(", "))];
for (const [key, entry] of Object.entries(thirdParty)) {
if (entry.hasIds) { // If the entry has proper ids, we can just select them
textAreas = textAreas.concat([...gradioApp().querySelectorAll(entry.selectors.join(", "))]);
} 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")];
// 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)));
textAreas = textAreas.concat(matchingTextAreas);
}
};
return textAreas;
}
const thirdPartyIdSet = new Set();
// 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_p = gradioApp().querySelector('#img2img_prompt > label > textarea');
let img2img_n = gradioApp().querySelector('#img2img_neg_prompt > label > textarea');
let modifier = "";
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:
// If the text area is not a core text area, it must be a third party text area
// Add it to the set of third party text areas and get its index as a unique identifier
if (!thirdPartyIdSet.has(textArea))
thirdPartyIdSet.add(textArea);
modifier = `.thirdParty.ta${[...thirdPartyIdSet].indexOf(textArea)}`;
break;
}
return modifier;
}

96
javascript/_utils.js Normal file
View File

@@ -0,0 +1,96 @@
// Utility functions for tag autocomplete
// Parse the CSV file into a 2D array. Doesn't use regex, so it is very lightweight.
function parseCSV(str) {
var arr = [];
var quote = false; // 'true' means we're inside a quoted field
// Iterate over each character, keep track of current row and column (of the returned array)
for (var row = 0, col = 0, c = 0; c < str.length; c++) {
var cc = str[c], nc = str[c + 1]; // Current character, next character
arr[row] = arr[row] || []; // Create a new row if necessary
arr[row][col] = arr[row][col] || ''; // Create a new column (start with empty string) if necessary
// If the current character is a quotation mark, and we're inside a
// quoted field, and the next character is also a quotation mark,
// add a quotation mark to the current column and skip the next character
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
// If it's just one quotation mark, begin/end quoted field
if (cc == '"') { quote = !quote; continue; }
// If it's a comma and we're not in a quoted field, move on to the next column
if (cc == ',' && !quote) { ++col; continue; }
// If it's a newline (CRLF) and we're not in a quoted field, skip the next character
// and move on to the next row and move to column 0 of that new row
if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
// If it's a newline (LF or CR) and we're not in a quoted field,
// move on to the next row and move to column 0 of that new row
if (cc == '\n' && !quote) { ++row; col = 0; continue; }
if (cc == '\r' && !quote) { ++row; col = 0; continue; }
// Otherwise, append the current character to the current column
arr[row][col] += cc;
}
return arr;
}
// Load file
async function readFile(filePath, json = false) {
let response = await fetch(`file=${filePath}`);
if (response.status != 200) {
console.error(`Error loading file "${filePath}": ` + response.status, response.statusText);
return null;
}
if (json)
return await response.json();
else
return await response.text();
}
// Load CSV
async function loadCSV(path) {
let text = await readFile(path);
return parseCSV(text);
}
// Debounce function to prevent spamming the autocomplete function
var dbTimeOut;
const debounce = (func, wait = 300) => {
return function (...args) {
if (dbTimeOut) {
clearTimeout(dbTimeOut);
}
dbTimeOut = setTimeout(() => {
func.apply(this, args);
}, wait);
}
}
// Difference function to fix duplicates not being seen as changes in normal filter
function difference(a, b) {
if (a.length == 0) {
return b;
}
if (b.length == 0) {
return a;
}
return [...b.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) - 1),
a.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map())
)].reduce((acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), []);
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
function escapeHTML(unsafeText) {
let div = document.createElement('div');
div.textContent = unsafeText;
return div.innerHTML;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
# This helper script scans folders for wildcards and embeddings and writes them
# to a temporary file to expose it to the javascript side
import gradio as gr
from pathlib import Path
from modules import scripts, script_callbacks, shared, sd_hijack
import yaml
import time
import threading
# Webui root path
FILE_DIR = Path().absolute()
# The extension base path
EXT_PATH = FILE_DIR.joinpath('extensions')
# Tags base path
TAGS_PATH = Path(scripts.basedir()).joinpath('tags')
# The path to the folder containing the wildcards and embeddings
WILDCARD_PATH = FILE_DIR.joinpath('scripts/wildcards')
EMB_PATH = Path(shared.cmd_opts.embeddings_dir)
LORA_PATH = Path(shared.cmd_opts.lora_dir)
HYP_PATH = Path(shared.cmd_opts.hypernetwork_dir)
def find_ext_wildcard_paths():
"""Returns the path to the extension wildcards folder"""
found = list(EXT_PATH.glob('*/wildcards/'))
return found
# The path to the extension wildcards folder
WILDCARD_EXT_PATHS = find_ext_wildcard_paths()
# The path to the temporary files
STATIC_TEMP_PATH = FILE_DIR.joinpath('tmp') # In the webui root, on windows it exists by default, on linux it doesn't
TEMP_PATH = TAGS_PATH.joinpath('temp') # Extension specific temp files
def get_wildcards():
"""Returns a list of all wildcards. Works on nested folders."""
wildcard_files = list(WILDCARD_PATH.rglob("*.txt"))
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_ext_wildcard_tags():
"""Returns a list of all tags found in extension YAML files found under a Tags: key."""
wildcard_tags = {} # { tag: count }
yaml_files = []
for path in WILDCARD_EXT_PATHS:
yaml_files.extend(p for p in path.rglob("*.yml"))
yaml_files.extend(p for p in path.rglob("*.yaml"))
count = 0
for path in yaml_files:
try:
with open(path, encoding="utf8") as file:
data = yaml.safe_load(file)
for item in data:
wildcard_tags[count] = ','.join(data[item]['Tags'])
count += 1
except yaml.YAMLError as exc:
print(exc)
# Sort by count
sorted_tags = sorted(wildcard_tags.items(), key=lambda item: item[1], reverse=True)
output = []
for tag, count in sorted_tags:
output.append(f"{tag},{count}")
return output
def get_embeddings(sd_model):
"""Write a list of all embeddings with their version"""
# Version constants
V1_SHAPE = 768
V2_SHAPE = 1024
emb_v1 = []
emb_v2 = []
results = []
try:
# Get embedding dict from sd_hijack to separate v1/v2 embeddings
emb_type_a = sd_hijack.model_hijack.embedding_db.word_embeddings
emb_type_b = sd_hijack.model_hijack.embedding_db.skipped_embeddings
# Get the shape of the first item in the dict
emb_a_shape = -1
emb_b_shape = -1
if (len(emb_type_a) > 0):
emb_a_shape = next(iter(emb_type_a.items()))[1].shape
if (len(emb_type_b) > 0):
emb_b_shape = next(iter(emb_type_b.items()))[1].shape
# Add embeddings to the correct list
if (emb_a_shape == V1_SHAPE):
emb_v1 = list(emb_type_a.keys())
elif (emb_a_shape == V2_SHAPE):
emb_v2 = list(emb_type_a.keys())
if (emb_b_shape == V1_SHAPE):
emb_v1 = list(emb_type_b.keys())
elif (emb_b_shape == V2_SHAPE):
emb_v2 = list(emb_type_b.keys())
# Get shape of current model
#vec = sd_model.cond_stage_model.encode_embedding_init_text(",", 1)
#model_shape = vec.shape[1]
# Show relevant entries at the top
#if (model_shape == V1_SHAPE):
# results = [e + ",v1" for e in emb_v1] + [e + ",v2" for e in emb_v2]
#elif (model_shape == V2_SHAPE):
# results = [e + ",v2" for e in emb_v2] + [e + ",v1" for e in emb_v1]
#else:
# raise AttributeError # Fallback to old method
results = sorted([e + ",v1" for e in emb_v1] + [e + ",v2" for e in emb_v2], key=lambda x: x.lower())
except AttributeError:
print("tag_autocomplete_helper: Old webui version or unrecognized model shape, using fallback for embedding completion.")
# Get a list of all embeddings in the folder
all_embeds = [str(e.relative_to(EMB_PATH)) for e in EMB_PATH.rglob("*") if e.suffix in {".bin", ".pt", ".png",'.webp', '.jxl', '.avif'}]
# Remove files with a size of 0
all_embeds = [e for e in all_embeds if EMB_PATH.joinpath(e).stat().st_size > 0]
# Remove file extensions
all_embeds = [e[:e.rfind('.')] for e in all_embeds]
results = [e + "," for e in all_embeds]
write_to_temp_file('emb.txt', results)
def get_hypernetworks():
"""Write a list of all hypernetworks"""
# Get a list of all hypernetworks in the folder
all_hypernetworks = [str(h.name) for h in HYP_PATH.rglob("*") if h.suffix in {".pt"}]
# Remove file extensions
return [h[:h.rfind('.')] for h in all_hypernetworks]
def get_lora():
"""Write a list of all lora"""
# Get a list of all lora in the folder
all_lora = [str(l.name) for l in LORA_PATH.rglob("*") if l.suffix in {".safetensors", ".ckpt", ".pt"}]
# Remove file extensions
return [l[:l.rfind('.')] for l in all_lora]
def write_tag_base_path():
"""Writes the tag base path to a fixed location temporary file"""
with open(STATIC_TEMP_PATH.joinpath('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):
"""Writes the given data to a temporary file"""
with open(TEMP_PATH.joinpath(name), 'w', encoding="utf-8") as f:
f.write(('\n'.join(data)))
csv_files = []
csv_files_withnone = []
def update_tag_files():
"""Returns a list of all potential tag files"""
global csv_files, csv_files_withnone
files = [str(t.relative_to(TAGS_PATH)) for t in TAGS_PATH.glob("*.csv")]
csv_files = files
csv_files_withnone = ["None"] + files
# 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
if not STATIC_TEMP_PATH.exists():
STATIC_TEMP_PATH.mkdir(exist_ok=True)
write_tag_base_path()
update_tag_files()
# 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('wce.txt', [])
write_to_temp_file('wcet.txt', [])
write_to_temp_file('hyp.txt', [])
write_to_temp_file('lora.txt', [])
# Only reload embeddings if the file doesn't exist, since they are already re-written on model load
if not TEMP_PATH.joinpath("emb.txt").exists():
write_to_temp_file('emb.txt', [])
# Write wildcards to wc.txt if found
if WILDCARD_PATH.exists():
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 yaml extension wildcards to wcet.txt if found
wildcards_yaml_ext = get_ext_wildcard_tags()
if wildcards_yaml_ext:
write_to_temp_file('wcet.txt', wildcards_yaml_ext)
# Write embeddings to emb.txt if found
if EMB_PATH.exists():
# Get embeddings after the model loaded callback
script_callbacks.on_model_loaded(get_embeddings)
if HYP_PATH.exists():
hypernets = get_hypernetworks()
if hypernets:
write_to_temp_file('hyp.txt', hypernets)
if LORA_PATH.exists():
lora = get_lora()
if lora:
write_to_temp_file('lora.txt', lora)
# Register autocomplete options
def on_ui_settings():
TAC_SECTION = ("tac", "Tag Autocomplete")
# Main tag file
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))
# Active in settings
shared.opts.add_option("tac_active", shared.OptionInfo(True, "Enable Tag Autocompletion", section=TAC_SECTION))
shared.opts.add_option("tac_activeIn.txt2img", shared.OptionInfo(True, "Active in txt2img (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_activeIn.img2img", shared.OptionInfo(True, "Active in img2img (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_activeIn.negativePrompts", shared.OptionInfo(True, "Active in negative prompts (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_activeIn.thirdParty", shared.OptionInfo(True, "Active in third party textboxes [Dataset Tag Editor] (Requires restart)", section=TAC_SECTION))
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))
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))
# Results related settings
shared.opts.add_option("tac_maxResults", shared.OptionInfo(5, "Maximum results", section=TAC_SECTION))
shared.opts.add_option("tac_showAllResults", shared.OptionInfo(False, "Show all results", section=TAC_SECTION))
shared.opts.add_option("tac_resultStepLength", shared.OptionInfo(100, "How many results to load at once", section=TAC_SECTION))
shared.opts.add_option("tac_delayTime", shared.OptionInfo(100, "Time in ms to wait before triggering completion again (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_useWildcards", shared.OptionInfo(True, "Search for wildcards", section=TAC_SECTION))
shared.opts.add_option("tac_useEmbeddings", shared.OptionInfo(True, "Search for embeddings", section=TAC_SECTION))
shared.opts.add_option("tac_useHypernetworks", shared.OptionInfo(True, "Search for hypernetworks", section=TAC_SECTION))
shared.opts.add_option("tac_useLoras", shared.OptionInfo(True, "Search for Loras", section=TAC_SECTION))
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))
# Insertion related settings
shared.opts.add_option("tac_replaceUnderscores", shared.OptionInfo(True, "Replace underscores with spaces on insertion", section=TAC_SECTION))
shared.opts.add_option("tac_escapeParentheses", shared.OptionInfo(True, "Escape parentheses on insertion", section=TAC_SECTION))
shared.opts.add_option("tac_appendComma", shared.OptionInfo(True, "Append comma on tag autocompletion", section=TAC_SECTION))
# Alias settings
shared.opts.add_option("tac_alias.searchByAlias", shared.OptionInfo(True, "Search by alias", section=TAC_SECTION))
shared.opts.add_option("tac_alias.onlyShowAlias", shared.OptionInfo(False, "Only show alias", section=TAC_SECTION))
# Translation settings
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))
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))
shared.opts.add_option("tac_translation.searchByTranslation", shared.OptionInfo(True, "Search by translation", section=TAC_SECTION))
# Extra file settings
shared.opts.add_option("tac_extra.extraFile", shared.OptionInfo("None", "Extra filename (do not use e621.csv here!)", gr.Dropdown, lambda: {"choices": csv_files_withnone}, refresh=update_tag_files, section=TAC_SECTION))
shared.opts.add_option("tac_extra.onlyAliasExtraFile", shared.OptionInfo(False, "Extra file in alias only format", section=TAC_SECTION))
script_callbacks.on_ui_settings(on_ui_settings)

View File

@@ -1,363 +0,0 @@
// Style for new elements. Gets appended to the Gradio root.
const autocompleteCSS_dark = `
#autocompleteResults {
position: absolute;
z-index: 999;
margin: 5px 0 0 0;
background-color: #0b0f19 !important;
border: 1px solid #4b5563 !important;
border-radius: 12px !important;
overflow: hidden;
}
#autocompleteResultsList > li:nth-child(odd) {
background-color: #111827;
}
#autocompleteResultsList > li {
list-style-type: none;
padding: 10px;
cursor: pointer;
}
#autocompleteResultsList > li:hover {
background-color: #1f2937;
}
#autocompleteResultsList > li.selected {
background-color: #374151;
}
`;
const autocompleteCSS_light = `
#autocompleteResults {
position: absolute;
z-index: 999;
margin: 5px 0 0 0;
background-color: #ffffff !important;
border: 1.5px solid #e5e7eb !important;
border-radius: 12px !important;
overflow: hidden;
}
#autocompleteResultsList > li:nth-child(odd) {
background-color: #f9fafb;
}
#autocompleteResultsList > li {
list-style-type: none;
padding: 10px;
cursor: pointer;
}
#autocompleteResultsList > li:hover {
background-color: #f5f6f8;
}
#autocompleteResultsList > li.selected {
background-color: #e5e7eb;
}
`;
var acConfig = null;
// Parse the CSV file into a 2D array. Doesn't use regex, so it is very lightweight.
function parseCSV(str) {
var arr = [];
var quote = false; // 'true' means we're inside a quoted field
// Iterate over each character, keep track of current row and column (of the returned array)
for (var row = 0, col = 0, c = 0; c < str.length; c++) {
var cc = str[c], nc = str[c+1]; // Current character, next character
arr[row] = arr[row] || []; // Create a new row if necessary
arr[row][col] = arr[row][col] || ''; // Create a new column (start with empty string) if necessary
// If the current character is a quotation mark, and we're inside a
// quoted field, and the next character is also a quotation mark,
// add a quotation mark to the current column and skip the next character
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
// If it's just one quotation mark, begin/end quoted field
if (cc == '"') { quote = !quote; continue; }
// If it's a comma and we're not in a quoted field, move on to the next column
if (cc == ',' && !quote) { ++col; continue; }
// If it's a newline (CRLF) and we're not in a quoted field, skip the next character
// and move on to the next row and move to column 0 of that new row
if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
// If it's a newline (LF or CR) and we're not in a quoted field,
// move on to the next row and move to column 0 of that new row
if (cc == '\n' && !quote) { ++row; col = 0; continue; }
if (cc == '\r' && !quote) { ++row; col = 0; continue; }
// Otherwise, append the current character to the current column
arr[row][col] += cc;
}
return arr;
}
// Load file
function readFile(filePath) {
let request = new XMLHttpRequest();
request.open("GET", filePath, false);
request.send(null);
return request.responseText;
}
function loadCSV() {
let text = readFile(`file/tags/${acConfig.tagFile}`);
return parseCSV(text);
}
// Debounce function to prevent spamming the autocomplete function
var dbTimeOut;
const debounce = (func, wait = 300) => {
return function(...args) {
if (dbTimeOut) {
clearTimeout(dbTimeOut);
}
dbTimeOut = setTimeout(() => {
func.apply(this, args);
}, wait);
}
}
// Difference function to fix duplicates not being seen as changes in normal filter
function difference(a, b) {
if (a.length == 0) {
return b;
}
if (b.length == 0) {
return a;
}
return [...b.reduce( (acc, v) => acc.set(v, (acc.get(v) || 0) - 1),
a.reduce( (acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map() )
)].reduce( (acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), [] );
}
// Create the result list div and necessary styling
function createResultsDiv() {
let resultsDiv = document.createElement("div");
let resultsList = document.createElement('ul');
resultsDiv.setAttribute('id', 'autocompleteResults');
resultsList.setAttribute('id', 'autocompleteResultsList');
resultsDiv.appendChild(resultsList);
return resultsDiv;
}
// The selected tag index. Needs to be up here so hide can access it.
var selectedTag = null;
// Show or hide the results div
var isVisible = false;
function showResults() {
let resultsDiv = gradioApp().querySelector('#autocompleteResults');
resultsDiv.style.display = "block";
isVisible = true;
}
function hideResults() {
let resultsDiv = gradioApp().querySelector('#autocompleteResults');
resultsDiv.style.display = "none";
isVisible = false;
selectedTag = null;
}
// On click, insert the tag into the prompt textbox with respect to the cursor position
function insertTextAtCursor(text, tagword) {
let promptTextbox = gradioApp().querySelector('#txt2img_prompt > label > textarea');
let cursorPos = promptTextbox.selectionStart;
let sanitizedText = acConfig.replaceUnderscores ? text.replaceAll("_", " ") : text;
sanitizedText = acConfig.escapeParentheses ? sanitizedText.replaceAll("(", "\\(").replaceAll(")", "\\)") : sanitizedText;
var prompt = promptTextbox.value;
let optionalComma = (prompt[cursorPos] === "," || prompt[cursorPos + tagword.length] === ",") ? "" : ", ";
// Edit prompt text
let direction = prompt.substring(cursorPos, cursorPos + tagword.length) === tagword ? 1 : -1;
if (direction === 1) {
promptTextbox.value = prompt.substring(0, cursorPos) + sanitizedText + optionalComma + prompt.substring(cursorPos + tagword.length)
// Update cursor position to after the inserted text
promptTextbox.selectionStart = cursorPos + sanitizedText.length + optionalComma.length;
} else {
promptTextbox.value = prompt.substring(0, cursorPos - tagword.length) + sanitizedText + optionalComma + prompt.substring(cursorPos)
promptTextbox.selectionStart = cursorPos - tagword.length + sanitizedText.length + optionalComma.length;
}
prompt = promptTextbox.value;
promptTextbox.selectionEnd = promptTextbox.selectionStart;
// Since we've modified a Gradio Textbox component manually, we need to simulate an `input` DOM event to ensure its
// internal Svelte data binding remains in sync.
promptTextbox.dispatchEvent(new Event("input", { bubbles: true }));
// Hide results after inserting
hideResults();
// Update previous tags with the edited prompt to prevent re-searching the same term
let tags = prompt.match(/[^, ]+/g);
previousTags = tags;
}
function addResultsToList(results, tagword) {
let resultsList = gradioApp().querySelector('#autocompleteResultsList');
resultsList.innerHTML = "";
// Find right colors from config
let tagFileName = acConfig.tagFile.split(".")[0];
let tagColors = acConfig.colors;
//let colorIndex = Object.keys(tagColors).findIndex(key => key === tagFileName);
//let colorValues = Object.values(tagColors)[colorIndex];
let mode = gradioApp().querySelector('.dark') ? 0 : 1;
for (let i = 0; i < results.length; i++) {
let result = results[i];
let li = document.createElement("li");
li.innerHTML = result[0];
// Set the color of the tag
let tagType = result[1];
li.style = `color: ${tagColors[tagFileName][tagType][mode]};`;
// Add listener
li.addEventListener("click", function() { insertTextAtCursor(result[0], tagword); });
// Add element to list
resultsList.appendChild(li);
}
}
function updateSelectionStyle(num) {
let resultsList = gradioApp().querySelector('#autocompleteResultsList');
let items = resultsList.getElementsByTagName('li');
for (let i = 0; i < items.length; i++) {
items[i].classList.remove('selected');
}
items[num].classList.add('selected');
}
allTags = [];
previousTags = [];
results = [];
tagword = "";
resultCount = 0;
function autocomplete(prompt) {
// Guard for empty prompt
if (prompt.length === 0) {
hideResults();
return;
}
// Match tags with RegEx to get the last edited one
let tags = prompt.match(/[^, ]+/g);
let diff = difference(tags, previousTags)
previousTags = tags;
// Guard for no difference / only whitespace remaining
if (diff === undefined || diff.length === 0) {
hideResults();
return;
}
tagword = diff[0]
// Guard for empty tagword
if (tagword === undefined || tagword.length === 0) {
hideResults();
return;
}
results = allTags.filter(x => x[0].includes(tagword)).slice(0, acConfig.maxResults);
resultCount = results.length;
// Guard for empty results
if (resultCount === 0) {
hideResults();
return;
}
showResults();
addResultsToList(results, tagword);
}
function navigateInList(event) {
validKeys = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Enter", "Escape"];
if (!validKeys.includes(event.key)) return;
if (!isVisible) return
switch (event.key) {
case "ArrowUp":
if (selectedTag === null) {
selectedTag = resultCount - 1;
} else {
selectedTag = (selectedTag - 1 + resultCount) % resultCount;
}
break;
case "ArrowDown":
if (selectedTag === null) {
selectedTag = 0;
} else {
selectedTag = (selectedTag + 1) % resultCount;
}
break;
case "ArrowLeft":
selectedTag = 0;
break;
case "ArrowRight":
selectedTag = resultCount - 1;
break;
case "Enter":
if (selectedTag !== null) {
insertTextAtCursor(results[selectedTag][0], tagword);
}
break;
case "Escape":
hideResults();
break;
}
// Update highlighting
if (selectedTag !== null)
updateSelectionStyle(selectedTag);
// Prevent default behavior
event.preventDefault();
event.stopPropagation();
}
onUiUpdate(function(){
// One-time CSV setup
if (acConfig === null) acConfig = JSON.parse(readFile("file/tags/config.json"));
if (allTags.length === 0) allTags = loadCSV();
let promptTextbox = gradioApp().querySelector('#txt2img_prompt > label > textarea');
if (promptTextbox === null) return;
if (gradioApp().querySelector('#autocompleteResults') != null) return;
// Only add listeners once
if (!promptTextbox.classList.contains('autocomplete')) {
// Add our new element
var resultsDiv = gradioApp().querySelector('#autocompleteResults') ?? createResultsDiv();
promptTextbox.parentNode.insertBefore(resultsDiv, promptTextbox.nextSibling);
// Hide by default so it doesn't show up on page load
hideResults();
// Add autocomplete event listener
promptTextbox.addEventListener('input', debounce(() => autocomplete(promptTextbox.value), 100));
// Add focusout event listener
promptTextbox.addEventListener('focusout', debounce(() => hideResults(), 400));
// Add up and down arrow event listener
promptTextbox.addEventListener('keydown', (e) => navigateInList(e));
// Add class so we know we've already added the listeners
promptTextbox.classList.add('autocomplete');
// Add style to dom
let acStyle = document.createElement('style');
let css = gradioApp().querySelector('.dark') ? autocompleteCSS_dark : autocompleteCSS_light;
if (acStyle.styleSheet) {
acStyle.styleSheet.cssText = css;
} else {
acStyle.appendChild(document.createTextNode(css));
}
gradioApp().appendChild(acStyle);
}
});

21
tags/colors.json Normal file
View File

@@ -0,0 +1,21 @@
{
"danbooru": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["indianred", "firebrick"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["orange", "darkorange"]
},
"e621": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["gold", "goldenrod"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["tomato", "darksalmon"],
"6": ["red", "maroon"],
"7": ["whitesmoke", "black"],
"8": ["seagreen", "darkseagreen"]
}
}

View File

@@ -1,26 +0,0 @@
{
"tagFile": "danbooru.csv",
"maxResults": 5,
"replaceUnderscores": true,
"escapeParentheses": true,
"colors": {
"danbooru": {
"0": ["lightblue", "dodgerblue"],
"1": ["indianred", "firebrick"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["orange", "darkorange"]
},
"e621": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["gold", "goldenrod"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["tomato", "darksalmon"],
"6": ["red", "maroon"],
"7": ["whitesmoke", "black"],
"8": ["seagreen", "darkseagreen"]
}
}
}

File diff suppressed because it is too large Load Diff

166094
tags/e621.csv

File diff suppressed because one or more lines are too long