Spaces:
Paused
Paused
P01yH3dr0n
commited on
Commit
•
e893ce4
1
Parent(s):
a48c731
Upload 14 files
Browse files- tagcomplete/javascript/__globals.js +129 -0
- tagcomplete/javascript/_baseParser.js +21 -0
- tagcomplete/javascript/_caretPosition.js +145 -0
- tagcomplete/javascript/_result.js +38 -0
- tagcomplete/javascript/_textAreas.js +198 -0
- tagcomplete/javascript/_utils.js +214 -0
- tagcomplete/javascript/ext_chants.js +57 -0
- tagcomplete/javascript/tagAutocomplete.js +1085 -0
- tagcomplete/tags/danbooru-0-zh.csv +0 -0
- tagcomplete/tags/danbooru.csv +0 -0
- tagcomplete/tags/demo-chants.json +32 -0
- tagcomplete/tags/e621.csv +0 -0
- tagcomplete/tags/e621_sfw.csv +0 -0
- tagcomplete/tags/extra-quality-tags.csv +6 -0
tagcomplete/javascript/__globals.js
ADDED
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Core components
|
2 |
+
var TAC_CFG = {
|
3 |
+
// Main tag file
|
4 |
+
tagFile: "danbooru.csv",
|
5 |
+
// Active in settings
|
6 |
+
activeIn: {
|
7 |
+
txt2img: true,
|
8 |
+
img2img: true,
|
9 |
+
negativePrompts: true,
|
10 |
+
thirdParty: true,
|
11 |
+
},
|
12 |
+
// Results related settings
|
13 |
+
slidingPopup: true,
|
14 |
+
maxResults: 5,
|
15 |
+
showAllResults: false,
|
16 |
+
resultStepLength: 100,
|
17 |
+
delayTime: 100,
|
18 |
+
includeEmbeddingsInNormalResults: false,
|
19 |
+
showWikiLinks: true,
|
20 |
+
showExtraNetworkPreviews: false,
|
21 |
+
// Insertion related settings
|
22 |
+
replaceUnderscores: true,
|
23 |
+
escapeParentheses: true,
|
24 |
+
appendComma: true,
|
25 |
+
appendSpace: true,
|
26 |
+
alwaysSpaceAtEnd: false,
|
27 |
+
// Alias settings
|
28 |
+
alias: {
|
29 |
+
searchByAlias: true,
|
30 |
+
onlyShowAlias: false
|
31 |
+
},
|
32 |
+
// Translation settings
|
33 |
+
translation: {
|
34 |
+
searchByTranslation: false,
|
35 |
+
},
|
36 |
+
// Extra file settings
|
37 |
+
extra: {
|
38 |
+
extraFile: "danbooru-0-zh.csv",
|
39 |
+
addMode: "Insert before"
|
40 |
+
},
|
41 |
+
// Chant file settings
|
42 |
+
chantFile: "demo-chants.json",
|
43 |
+
// Settings not from tac but still used by the script
|
44 |
+
// Custom mapping settings
|
45 |
+
keymap: {
|
46 |
+
"MoveUp": "ArrowUp",
|
47 |
+
"MoveDown": "ArrowDown",
|
48 |
+
"JumpUp": "PageUp",
|
49 |
+
"JumpDown": "PageDown",
|
50 |
+
"JumpToStart": "Home",
|
51 |
+
"JumpToEnd": "End",
|
52 |
+
"ChooseSelected": "Enter",
|
53 |
+
"ChooseFirstOrSelected": "Tab",
|
54 |
+
"Close": "Escape"
|
55 |
+
},
|
56 |
+
colorMap: {
|
57 |
+
"danbooru": {
|
58 |
+
"-1": ["red", "maroon"],
|
59 |
+
"0": ["lightblue", "dodgerblue"],
|
60 |
+
"1": ["indianred", "firebrick"],
|
61 |
+
"3": ["violet", "darkorchid"],
|
62 |
+
"4": ["lightgreen", "darkgreen"],
|
63 |
+
"5": ["orange", "darkorange"]
|
64 |
+
},
|
65 |
+
"e621": {
|
66 |
+
"-1": ["red", "maroon"],
|
67 |
+
"0": ["lightblue", "dodgerblue"],
|
68 |
+
"1": ["gold", "goldenrod"],
|
69 |
+
"3": ["violet", "darkorchid"],
|
70 |
+
"4": ["lightgreen", "darkgreen"],
|
71 |
+
"5": ["tomato", "darksalmon"],
|
72 |
+
"6": ["red", "maroon"],
|
73 |
+
"7": ["whitesmoke", "black"],
|
74 |
+
"8": ["seagreen", "darkseagreen"]
|
75 |
+
}
|
76 |
+
}
|
77 |
+
};
|
78 |
+
var tagBasePath = "tagcomplete/tags";
|
79 |
+
var modelKeywordPath = "";
|
80 |
+
var tacSelfTrigger = false;
|
81 |
+
|
82 |
+
// Tag completion data loaded from files
|
83 |
+
var allTags = [];
|
84 |
+
var translations = new Map();
|
85 |
+
var extras = [];
|
86 |
+
// Same for tag-likes
|
87 |
+
var yamlWildcards = [];
|
88 |
+
var umiWildcards = [];
|
89 |
+
var modelKeywordDict = new Map();
|
90 |
+
var chants = [];
|
91 |
+
|
92 |
+
// Current results
|
93 |
+
var results = [];
|
94 |
+
var resultCount = 0;
|
95 |
+
|
96 |
+
// Relevant for parsing
|
97 |
+
var previousTags = [];
|
98 |
+
var tagword = "";
|
99 |
+
var originalTagword = "";
|
100 |
+
let hideBlocked = false;
|
101 |
+
|
102 |
+
// Tag selection for keyboard navigation
|
103 |
+
var selectedTag = null;
|
104 |
+
var oldSelectedTag = null;
|
105 |
+
var resultCountBeforeNormalTags = 0;
|
106 |
+
|
107 |
+
// Lora keyword undo/redo history
|
108 |
+
var textBeforeKeywordInsertion = "";
|
109 |
+
var textAfterKeywordInsertion = "";
|
110 |
+
var lastEditWasKeywordInsertion = false;
|
111 |
+
var keywordInsertionUndone = false;
|
112 |
+
|
113 |
+
// UMI
|
114 |
+
var umiPreviousTags = [];
|
115 |
+
|
116 |
+
/// Extendability system:
|
117 |
+
/// Provides "queues" for other files of the script (or really any js)
|
118 |
+
/// to add functions to be called at certain points in the script.
|
119 |
+
/// Similar to a callback system, but primitive.
|
120 |
+
|
121 |
+
// Queues
|
122 |
+
const QUEUE_AFTER_INSERT = [];
|
123 |
+
const QUEUE_AFTER_SETUP = [];
|
124 |
+
const QUEUE_FILE_LOAD = [];
|
125 |
+
const QUEUE_AFTER_CONFIG_CHANGE = [];
|
126 |
+
const QUEUE_SANITIZE = [];
|
127 |
+
|
128 |
+
// List of parsers to try
|
129 |
+
const PARSERS = [];
|
tagcomplete/javascript/_baseParser.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class FunctionNotOverriddenError extends Error {
|
2 |
+
constructor(message = "", ...args) {
|
3 |
+
super(message, ...args);
|
4 |
+
this.message = message + " is an abstract base function and must be overwritten.";
|
5 |
+
}
|
6 |
+
}
|
7 |
+
|
8 |
+
class BaseTagParser {
|
9 |
+
triggerCondition = null;
|
10 |
+
|
11 |
+
constructor (triggerCondition) {
|
12 |
+
if (new.target === BaseTagParser) {
|
13 |
+
throw new TypeError("Cannot construct abstract BaseCompletionParser directly");
|
14 |
+
}
|
15 |
+
this.triggerCondition = triggerCondition;
|
16 |
+
}
|
17 |
+
|
18 |
+
parse() {
|
19 |
+
throw new FunctionNotOverriddenError("parse()");
|
20 |
+
}
|
21 |
+
}
|
tagcomplete/javascript/_caretPosition.js
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// From https://github.com/component/textarea-caret-position
|
2 |
+
|
3 |
+
// We'll copy the properties below into the mirror div.
|
4 |
+
// Note that some browsers, such as Firefox, do not concatenate properties
|
5 |
+
// into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
|
6 |
+
// so we have to list every single property explicitly.
|
7 |
+
var properties = [
|
8 |
+
'direction', // RTL support
|
9 |
+
'boxSizing',
|
10 |
+
'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
|
11 |
+
'height',
|
12 |
+
'overflowX',
|
13 |
+
'overflowY', // copy the scrollbar for IE
|
14 |
+
|
15 |
+
'borderTopWidth',
|
16 |
+
'borderRightWidth',
|
17 |
+
'borderBottomWidth',
|
18 |
+
'borderLeftWidth',
|
19 |
+
'borderStyle',
|
20 |
+
|
21 |
+
'paddingTop',
|
22 |
+
'paddingRight',
|
23 |
+
'paddingBottom',
|
24 |
+
'paddingLeft',
|
25 |
+
|
26 |
+
// https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
27 |
+
'fontStyle',
|
28 |
+
'fontVariant',
|
29 |
+
'fontWeight',
|
30 |
+
'fontStretch',
|
31 |
+
'fontSize',
|
32 |
+
'fontSizeAdjust',
|
33 |
+
'lineHeight',
|
34 |
+
'fontFamily',
|
35 |
+
|
36 |
+
'textAlign',
|
37 |
+
'textTransform',
|
38 |
+
'textIndent',
|
39 |
+
'textDecoration', // might not make a difference, but better be safe
|
40 |
+
|
41 |
+
'letterSpacing',
|
42 |
+
'wordSpacing',
|
43 |
+
|
44 |
+
'tabSize',
|
45 |
+
'MozTabSize'
|
46 |
+
|
47 |
+
];
|
48 |
+
|
49 |
+
var isBrowser = (typeof window !== 'undefined');
|
50 |
+
var isFirefox = (isBrowser && window.mozInnerScreenX != null);
|
51 |
+
|
52 |
+
function getCaretCoordinates(element, position, options) {
|
53 |
+
if (!isBrowser) {
|
54 |
+
throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
|
55 |
+
}
|
56 |
+
|
57 |
+
var debug = options && options.debug || false;
|
58 |
+
if (debug) {
|
59 |
+
var el = document.querySelector('#input-textarea-caret-position-mirror-div');
|
60 |
+
if (el) el.parentNode.removeChild(el);
|
61 |
+
}
|
62 |
+
|
63 |
+
// The mirror div will replicate the textarea's style
|
64 |
+
var div = document.createElement('div');
|
65 |
+
div.id = 'input-textarea-caret-position-mirror-div';
|
66 |
+
document.body.appendChild(div);
|
67 |
+
|
68 |
+
var style = div.style;
|
69 |
+
var computed = window.getComputedStyle ? window.getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
|
70 |
+
var isInput = element.nodeName === 'INPUT';
|
71 |
+
|
72 |
+
// Default textarea styles
|
73 |
+
style.whiteSpace = 'pre-wrap';
|
74 |
+
if (!isInput)
|
75 |
+
style.wordWrap = 'break-word'; // only for textarea-s
|
76 |
+
|
77 |
+
// Position off-screen
|
78 |
+
style.position = 'absolute'; // required to return coordinates properly
|
79 |
+
if (!debug)
|
80 |
+
style.visibility = 'hidden'; // not 'display: none' because we want rendering
|
81 |
+
|
82 |
+
// Transfer the element's properties to the div
|
83 |
+
properties.forEach(function (prop) {
|
84 |
+
if (isInput && prop === 'lineHeight') {
|
85 |
+
// Special case for <input>s because text is rendered centered and line height may be != height
|
86 |
+
if (computed.boxSizing === "border-box") {
|
87 |
+
var height = parseInt(computed.height);
|
88 |
+
var outerHeight =
|
89 |
+
parseInt(computed.paddingTop) +
|
90 |
+
parseInt(computed.paddingBottom) +
|
91 |
+
parseInt(computed.borderTopWidth) +
|
92 |
+
parseInt(computed.borderBottomWidth);
|
93 |
+
var targetHeight = outerHeight + parseInt(computed.lineHeight);
|
94 |
+
if (height > targetHeight) {
|
95 |
+
style.lineHeight = height - outerHeight + "px";
|
96 |
+
} else if (height === targetHeight) {
|
97 |
+
style.lineHeight = computed.lineHeight;
|
98 |
+
} else {
|
99 |
+
style.lineHeight = 0;
|
100 |
+
}
|
101 |
+
} else {
|
102 |
+
style.lineHeight = computed.height;
|
103 |
+
}
|
104 |
+
} else {
|
105 |
+
style[prop] = computed[prop];
|
106 |
+
}
|
107 |
+
});
|
108 |
+
|
109 |
+
if (isFirefox) {
|
110 |
+
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
|
111 |
+
if (element.scrollHeight > parseInt(computed.height))
|
112 |
+
style.overflowY = 'scroll';
|
113 |
+
} else {
|
114 |
+
style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
|
115 |
+
}
|
116 |
+
|
117 |
+
div.textContent = element.value.substring(0, position);
|
118 |
+
// The second special handling for input type="text" vs textarea:
|
119 |
+
// spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
|
120 |
+
if (isInput)
|
121 |
+
div.textContent = div.textContent.replace(/\s/g, '\u00a0');
|
122 |
+
|
123 |
+
var span = document.createElement('span');
|
124 |
+
// Wrapping must be replicated *exactly*, including when a long word gets
|
125 |
+
// onto the next line, with whitespace at the end of the line before (#7).
|
126 |
+
// The *only* reliable way to do that is to copy the *entire* rest of the
|
127 |
+
// textarea's content into the <span> created at the caret position.
|
128 |
+
// For inputs, just '.' would be enough, but no need to bother.
|
129 |
+
span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
|
130 |
+
div.appendChild(span);
|
131 |
+
|
132 |
+
var coordinates = {
|
133 |
+
top: span.offsetTop + parseInt(computed['borderTopWidth']),
|
134 |
+
left: span.offsetLeft + parseInt(computed['borderLeftWidth']),
|
135 |
+
height: parseInt(computed['lineHeight'])
|
136 |
+
};
|
137 |
+
|
138 |
+
if (debug) {
|
139 |
+
span.style.backgroundColor = '#aaa';
|
140 |
+
} else {
|
141 |
+
document.body.removeChild(div);
|
142 |
+
}
|
143 |
+
|
144 |
+
return coordinates;
|
145 |
+
}
|
tagcomplete/javascript/_result.js
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Result data type for cleaner use of optional completion result properties
|
2 |
+
|
3 |
+
// Type enum
|
4 |
+
const ResultType = Object.freeze({
|
5 |
+
"tag": 1,
|
6 |
+
"extra": 2,
|
7 |
+
"embedding": 3,
|
8 |
+
"wildcardTag": 4,
|
9 |
+
"wildcardFile": 5,
|
10 |
+
"yamlWildcard": 6,
|
11 |
+
"umiWildcard": 7,
|
12 |
+
"hypernetwork": 8,
|
13 |
+
"lora": 9,
|
14 |
+
"lyco": 10,
|
15 |
+
"chant": 11,
|
16 |
+
"styleName": 12
|
17 |
+
});
|
18 |
+
|
19 |
+
// Class to hold result data and annotations to make it clearer to use
|
20 |
+
class AutocompleteResult {
|
21 |
+
// Main properties
|
22 |
+
text = "";
|
23 |
+
type = ResultType.tag;
|
24 |
+
|
25 |
+
// Additional info, only used in some cases
|
26 |
+
category = null;
|
27 |
+
count = null;
|
28 |
+
aliases = null;
|
29 |
+
meta = null;
|
30 |
+
hash = null;
|
31 |
+
sortKey = null;
|
32 |
+
|
33 |
+
// Constructor
|
34 |
+
constructor(text, type) {
|
35 |
+
this.text = text;
|
36 |
+
this.type = type;
|
37 |
+
}
|
38 |
+
}
|
tagcomplete/javascript/_textAreas.js
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Utility functions to select text areas the script should work on,
|
2 |
+
// including third party options.
|
3 |
+
// Supported third party options so far:
|
4 |
+
// - Dataset Tag Editor
|
5 |
+
|
6 |
+
// Core text area selectors
|
7 |
+
const core = [
|
8 |
+
"#txt2img_prompt > label > textarea",
|
9 |
+
"#img2img_prompt > label > textarea",
|
10 |
+
"#txt2img_qua_prompt > label > textarea",
|
11 |
+
"#txt2img_neg_prompt > label > textarea",
|
12 |
+
"#img2img_neg_prompt > label > textarea",
|
13 |
+
".prompt > label > textarea",
|
14 |
+
"#txt2img_edit_style_prompt > label > textarea",
|
15 |
+
"#txt2img_edit_style_neg_prompt > label > textarea",
|
16 |
+
"#img2img_edit_style_prompt > label > textarea",
|
17 |
+
"#img2img_edit_style_neg_prompt > label > textarea"
|
18 |
+
];
|
19 |
+
|
20 |
+
// Third party text area selectors
|
21 |
+
const thirdParty = {
|
22 |
+
"dataset-tag-editor": {
|
23 |
+
"base": "#tab_dataset_tag_editor_interface",
|
24 |
+
"hasIds": false,
|
25 |
+
"selectors": [
|
26 |
+
"Caption of Selected Image",
|
27 |
+
"Interrogate Result",
|
28 |
+
"Edit Caption",
|
29 |
+
"Edit Tags"
|
30 |
+
]
|
31 |
+
},
|
32 |
+
"image browser": {
|
33 |
+
"base": "#tab_image_browser",
|
34 |
+
"hasIds": false,
|
35 |
+
"selectors": [
|
36 |
+
"Filename keyword search",
|
37 |
+
"EXIF keyword search"
|
38 |
+
]
|
39 |
+
},
|
40 |
+
"tab_tagger": {
|
41 |
+
"base": "#tab_tagger",
|
42 |
+
"hasIds": false,
|
43 |
+
"selectors": [
|
44 |
+
"Additional tags (split by comma)",
|
45 |
+
"Exclude tags (split by comma)"
|
46 |
+
]
|
47 |
+
},
|
48 |
+
"tiled-diffusion-t2i": {
|
49 |
+
"base": "#txt2img_script_container",
|
50 |
+
"hasIds": true,
|
51 |
+
"onDemand": true,
|
52 |
+
"selectors": [
|
53 |
+
"[id^=MD-t2i][id$=prompt] textarea",
|
54 |
+
"[id^=MD-t2i][id$=prompt] input[type='text']"
|
55 |
+
]
|
56 |
+
},
|
57 |
+
"tiled-diffusion-i2i": {
|
58 |
+
"base": "#img2img_script_container",
|
59 |
+
"hasIds": true,
|
60 |
+
"onDemand": true,
|
61 |
+
"selectors": [
|
62 |
+
"[id^=MD-i2i][id$=prompt] textarea",
|
63 |
+
"[id^=MD-i2i][id$=prompt] input[type='text']"
|
64 |
+
]
|
65 |
+
},
|
66 |
+
"adetailer-t2i": {
|
67 |
+
"base": "#txt2img_script_container",
|
68 |
+
"hasIds": true,
|
69 |
+
"onDemand": true,
|
70 |
+
"selectors": [
|
71 |
+
"[id^=script_txt2img_adetailer_ad_prompt] textarea",
|
72 |
+
"[id^=script_txt2img_adetailer_ad_negative_prompt] textarea"
|
73 |
+
]
|
74 |
+
},
|
75 |
+
"adetailer-i2i": {
|
76 |
+
"base": "#img2img_script_container",
|
77 |
+
"hasIds": true,
|
78 |
+
"onDemand": true,
|
79 |
+
"selectors": [
|
80 |
+
"[id^=script_img2img_adetailer_ad_prompt] textarea",
|
81 |
+
"[id^=script_img2img_adetailer_ad_negative_prompt] textarea"
|
82 |
+
]
|
83 |
+
},
|
84 |
+
"deepdanbooru-object-recognition": {
|
85 |
+
"base": "#tab_deepdanboru_object_recg_tab",
|
86 |
+
"hasIds": false,
|
87 |
+
"selectors": [
|
88 |
+
"Found tags",
|
89 |
+
]
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
function getTextAreas() {
|
94 |
+
// First get all core text areas
|
95 |
+
let textAreas = [...gradioApp().querySelectorAll(core.join(", "))];
|
96 |
+
|
97 |
+
for (const [key, entry] of Object.entries(thirdParty)) {
|
98 |
+
if (entry.hasIds) { // If the entry has proper ids, we can just select them
|
99 |
+
textAreas = textAreas.concat([...gradioApp().querySelectorAll(entry.selectors.join(", "))]);
|
100 |
+
} else { // Otherwise, we have to find the text areas by their adjacent labels
|
101 |
+
let base = gradioApp().querySelector(entry.base);
|
102 |
+
|
103 |
+
// Safety check
|
104 |
+
if (!base) continue;
|
105 |
+
|
106 |
+
let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")];
|
107 |
+
|
108 |
+
// Filter the text areas where the adjacent label matches one of the selectors
|
109 |
+
let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText)));
|
110 |
+
textAreas = textAreas.concat(matchingTextAreas);
|
111 |
+
}
|
112 |
+
};
|
113 |
+
|
114 |
+
return textAreas;
|
115 |
+
}
|
116 |
+
|
117 |
+
function addOnDemandObservers(setupFunction) {
|
118 |
+
for (const [key, entry] of Object.entries(thirdParty)) {
|
119 |
+
if (!entry.onDemand) continue;
|
120 |
+
|
121 |
+
let base = gradioApp().querySelector(entry.base);
|
122 |
+
if (!base) continue;
|
123 |
+
|
124 |
+
let accordions = [...base?.querySelectorAll(".gradio-accordion")];
|
125 |
+
if (!accordions) continue;
|
126 |
+
|
127 |
+
accordions.forEach(acc => {
|
128 |
+
let accObserver = new MutationObserver((mutationList, observer) => {
|
129 |
+
for (const mutation of mutationList) {
|
130 |
+
if (mutation.type === "childList") {
|
131 |
+
let newChildren = mutation.addedNodes;
|
132 |
+
if (!newChildren) {
|
133 |
+
accObserver.disconnect();
|
134 |
+
continue;
|
135 |
+
}
|
136 |
+
|
137 |
+
newChildren.forEach(child => {
|
138 |
+
if (child.classList.contains("gradio-accordion") || child.querySelector(".gradio-accordion")) {
|
139 |
+
let newAccordions = [...child.querySelectorAll(".gradio-accordion")];
|
140 |
+
newAccordions.forEach(nAcc => accObserver.observe(nAcc, { childList: true }));
|
141 |
+
}
|
142 |
+
});
|
143 |
+
|
144 |
+
if (entry.hasIds) { // If the entry has proper ids, we can just select them
|
145 |
+
[...gradioApp().querySelectorAll(entry.selectors.join(", "))].forEach(x => setupFunction(x));
|
146 |
+
} else { // Otherwise, we have to find the text areas by their adjacent labels
|
147 |
+
let base = gradioApp().querySelector(entry.base);
|
148 |
+
|
149 |
+
// Safety check
|
150 |
+
if (!base) continue;
|
151 |
+
|
152 |
+
let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")];
|
153 |
+
|
154 |
+
// Filter the text areas where the adjacent label matches one of the selectors
|
155 |
+
let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText)));
|
156 |
+
matchingTextAreas.forEach(x => setupFunction(x));
|
157 |
+
}
|
158 |
+
}
|
159 |
+
}
|
160 |
+
});
|
161 |
+
accObserver.observe(acc, { childList: true });
|
162 |
+
});
|
163 |
+
};
|
164 |
+
}
|
165 |
+
|
166 |
+
const thirdPartyIdSet = new Set();
|
167 |
+
// Get the identifier for the text area to differentiate between positive and negative
|
168 |
+
function getTextAreaIdentifier(textArea) {
|
169 |
+
let txt2img_p = gradioApp().querySelector('#txt2img_prompt > label > textarea');
|
170 |
+
let txt2img_n = gradioApp().querySelector('#txt2img_neg_prompt > label > textarea');
|
171 |
+
let img2img_p = gradioApp().querySelector('#img2img_prompt > label > textarea');
|
172 |
+
let img2img_n = gradioApp().querySelector('#img2img_neg_prompt > label > textarea');
|
173 |
+
|
174 |
+
let modifier = "";
|
175 |
+
switch (textArea) {
|
176 |
+
case txt2img_p:
|
177 |
+
modifier = ".txt2img.p";
|
178 |
+
break;
|
179 |
+
case txt2img_n:
|
180 |
+
modifier = ".txt2img.n";
|
181 |
+
break;
|
182 |
+
case img2img_p:
|
183 |
+
modifier = ".img2img.p";
|
184 |
+
break;
|
185 |
+
case img2img_n:
|
186 |
+
modifier = ".img2img.n";
|
187 |
+
break;
|
188 |
+
default:
|
189 |
+
// If the text area is not a core text area, it must be a third party text area
|
190 |
+
// Add it to the set of third party text areas and get its index as a unique identifier
|
191 |
+
if (!thirdPartyIdSet.has(textArea))
|
192 |
+
thirdPartyIdSet.add(textArea);
|
193 |
+
|
194 |
+
modifier = `.thirdParty.ta${[...thirdPartyIdSet].indexOf(textArea)}`;
|
195 |
+
break;
|
196 |
+
}
|
197 |
+
return modifier;
|
198 |
+
}
|
tagcomplete/javascript/_utils.js
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Utility functions for tag autocomplete
|
2 |
+
|
3 |
+
// Parse the CSV file into a 2D array. Doesn't use regex, so it is very lightweight.
|
4 |
+
// We are ignoring newlines in quote fields since we expect one-line entries and parsing would break for unclosed quotes otherwise
|
5 |
+
function parseCSV(str) {
|
6 |
+
const arr = [];
|
7 |
+
let quote = false; // 'true' means we're inside a quoted field
|
8 |
+
|
9 |
+
// Iterate over each character, keep track of current row and column (of the returned array)
|
10 |
+
for (let row = 0, col = 0, c = 0; c < str.length; c++) {
|
11 |
+
let cc = str[c], nc = str[c+1]; // Current character, next character
|
12 |
+
arr[row] = arr[row] || []; // Create a new row if necessary
|
13 |
+
arr[row][col] = arr[row][col] || ''; // Create a new column (start with empty string) if necessary
|
14 |
+
|
15 |
+
// If the current character is a quotation mark, and we're inside a
|
16 |
+
// quoted field, and the next character is also a quotation mark,
|
17 |
+
// add a quotation mark to the current column and skip the next character
|
18 |
+
if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
|
19 |
+
|
20 |
+
// If it's just one quotation mark, begin/end quoted field
|
21 |
+
if (cc == '"') { quote = !quote; continue; }
|
22 |
+
|
23 |
+
// If it's a comma and we're not in a quoted field, move on to the next column
|
24 |
+
if (cc == ',' && !quote) { ++col; continue; }
|
25 |
+
|
26 |
+
// If it's a newline (CRLF), skip the next character and move on to the next row and move to column 0 of that new row
|
27 |
+
if (cc == '\r' && nc == '\n') { ++row; col = 0; ++c; quote = false; continue; }
|
28 |
+
|
29 |
+
// If it's a newline (LF or CR) move on to the next row and move to column 0 of that new row
|
30 |
+
if (cc == '\n') { ++row; col = 0; quote = false; continue; }
|
31 |
+
if (cc == '\r') { ++row; col = 0; quote = false; continue; }
|
32 |
+
|
33 |
+
// Otherwise, append the current character to the current column
|
34 |
+
arr[row][col] += cc;
|
35 |
+
}
|
36 |
+
return arr;
|
37 |
+
}
|
38 |
+
|
39 |
+
// Load file
|
40 |
+
async function readFile(filePath, json = false, cache = false) {
|
41 |
+
if (!cache)
|
42 |
+
filePath += `?${new Date().getTime()}`;
|
43 |
+
|
44 |
+
let response = await fetch(`file=${filePath}`);
|
45 |
+
|
46 |
+
if (response.status != 200) {
|
47 |
+
console.error(`Error loading file "${filePath}": ` + response.status, response.statusText);
|
48 |
+
return null;
|
49 |
+
}
|
50 |
+
|
51 |
+
if (json)
|
52 |
+
return await response.json();
|
53 |
+
else
|
54 |
+
return await response.text();
|
55 |
+
}
|
56 |
+
|
57 |
+
// Load CSV
|
58 |
+
async function loadCSV(path) {
|
59 |
+
let text = await readFile(path);
|
60 |
+
return parseCSV(text);
|
61 |
+
}
|
62 |
+
|
63 |
+
// Fetch API
|
64 |
+
async function fetchAPI(url, json = true, cache = false) {
|
65 |
+
if (!cache) {
|
66 |
+
const appendChar = url.includes("?") ? "&" : "?";
|
67 |
+
url += `${appendChar}${new Date().getTime()}`
|
68 |
+
}
|
69 |
+
|
70 |
+
let response = await fetch(url);
|
71 |
+
|
72 |
+
if (response.status != 200) {
|
73 |
+
console.error(`Error fetching API endpoint "${url}": ` + response.status, response.statusText);
|
74 |
+
return null;
|
75 |
+
}
|
76 |
+
|
77 |
+
if (json)
|
78 |
+
return await response.json();
|
79 |
+
else
|
80 |
+
return await response.text();
|
81 |
+
}
|
82 |
+
|
83 |
+
// Extra network preview thumbnails
|
84 |
+
async function getExtraNetworkPreviewURL(filename, type) {
|
85 |
+
const previewJSON = await fetchAPI(`tacapi/v1/thumb-preview/${filename}?type=${type}`, true, true);
|
86 |
+
if (previewJSON?.url) {
|
87 |
+
const properURL = `sd_extra_networks/thumb?filename=${previewJSON.url}`;
|
88 |
+
if ((await fetch(properURL)).status == 200) {
|
89 |
+
return properURL;
|
90 |
+
} else {
|
91 |
+
// create blob url
|
92 |
+
const blob = await (await fetch(`tacapi/v1/thumb-preview-blob/${filename}?type=${type}`)).blob();
|
93 |
+
return URL.createObjectURL(blob);
|
94 |
+
}
|
95 |
+
} else {
|
96 |
+
return null;
|
97 |
+
}
|
98 |
+
}
|
99 |
+
|
100 |
+
// Debounce function to prevent spamming the autocomplete function
|
101 |
+
var dbTimeOut;
|
102 |
+
const debounce = (func, wait = 300) => {
|
103 |
+
return function (...args) {
|
104 |
+
if (dbTimeOut) {
|
105 |
+
clearTimeout(dbTimeOut);
|
106 |
+
}
|
107 |
+
|
108 |
+
dbTimeOut = setTimeout(() => {
|
109 |
+
func.apply(this, args);
|
110 |
+
}, wait);
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
+
// Difference function to fix duplicates not being seen as changes in normal filter
|
115 |
+
function difference(a, b) {
|
116 |
+
if (a.length == 0) {
|
117 |
+
return b;
|
118 |
+
}
|
119 |
+
if (b.length == 0) {
|
120 |
+
return a;
|
121 |
+
}
|
122 |
+
|
123 |
+
return [...b.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) - 1),
|
124 |
+
a.reduce((acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map())
|
125 |
+
)].reduce((acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), []);
|
126 |
+
}
|
127 |
+
|
128 |
+
// Sliding window function to get possible combination groups of an array
|
129 |
+
function toNgrams(inputArray, size) {
|
130 |
+
return Array.from(
|
131 |
+
{ length: inputArray.length - (size - 1) }, //get the appropriate length
|
132 |
+
(_, index) => inputArray.slice(index, index + size) //create the windows
|
133 |
+
);
|
134 |
+
}
|
135 |
+
|
136 |
+
function escapeRegExp(string, wildcardMatching = false) {
|
137 |
+
if (wildcardMatching) {
|
138 |
+
// Escape all characters except asterisks and ?, which should be treated separately as placeholders.
|
139 |
+
return string.replace(/[-[\]{}()+.,\\^$|#\s]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
|
140 |
+
}
|
141 |
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
142 |
+
}
|
143 |
+
function escapeHTML(unsafeText) {
|
144 |
+
let div = document.createElement('div');
|
145 |
+
div.textContent = unsafeText;
|
146 |
+
return div.innerHTML;
|
147 |
+
}
|
148 |
+
|
149 |
+
// Sort functions
|
150 |
+
function getSortFunction() {
|
151 |
+
let criterion = TAC_CFG.modelSortOrder || "Name";
|
152 |
+
|
153 |
+
const textSort = (a, b, reverse = false) => {
|
154 |
+
const textHolderA = a.type === ResultType.chant ? a.aliases : a.text;
|
155 |
+
const textHolderB = b.type === ResultType.chant ? b.aliases : b.text;
|
156 |
+
|
157 |
+
const aKey = a.sortKey || textHolderA;
|
158 |
+
const bKey = b.sortKey || textHolderB;
|
159 |
+
return reverse ? bKey.localeCompare(aKey) : aKey.localeCompare(bKey);
|
160 |
+
}
|
161 |
+
const numericSort = (a, b, reverse = false) => {
|
162 |
+
const noKey = reverse ? "-1" : Number.MAX_SAFE_INTEGER;
|
163 |
+
let aParsed = parseFloat(a.sortKey || noKey);
|
164 |
+
let bParsed = parseFloat(b.sortKey || noKey);
|
165 |
+
|
166 |
+
if (aParsed === bParsed) {
|
167 |
+
return textSort(a, b, false);
|
168 |
+
}
|
169 |
+
|
170 |
+
return reverse ? bParsed - aParsed : aParsed - bParsed;
|
171 |
+
}
|
172 |
+
|
173 |
+
return (a, b) => {
|
174 |
+
switch (criterion) {
|
175 |
+
case "Date Modified (newest first)":
|
176 |
+
return numericSort(a, b, true);
|
177 |
+
case "Date Modified (oldest first)":
|
178 |
+
return numericSort(a, b, false);
|
179 |
+
default:
|
180 |
+
return textSort(a, b);
|
181 |
+
}
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
// Queue calling function to process global queues
|
186 |
+
async function processQueue(queue, context, ...args) {
|
187 |
+
for (let i = 0; i < queue.length; i++) {
|
188 |
+
await queue[i].call(context, ...args);
|
189 |
+
}
|
190 |
+
}
|
191 |
+
// The same but with return values
|
192 |
+
async function processQueueReturn(queue, context, ...args)
|
193 |
+
{
|
194 |
+
let qeueueReturns = [];
|
195 |
+
for (let i = 0; i < queue.length; i++) {
|
196 |
+
let returnValue = await queue[i].call(context, ...args);
|
197 |
+
if (returnValue)
|
198 |
+
qeueueReturns.push(returnValue);
|
199 |
+
}
|
200 |
+
return qeueueReturns;
|
201 |
+
}
|
202 |
+
// Specific to tag completion parsers
|
203 |
+
async function processParsers(textArea, prompt) {
|
204 |
+
// Get all parsers that have a successful trigger condition
|
205 |
+
let matchingParsers = PARSERS.filter(parser => parser.triggerCondition());
|
206 |
+
// Guard condition
|
207 |
+
if (matchingParsers.length === 0) {
|
208 |
+
return null;
|
209 |
+
}
|
210 |
+
|
211 |
+
let parseFunctions = matchingParsers.map(parser => parser.parse);
|
212 |
+
// Process them and return the results
|
213 |
+
return await processQueueReturn(parseFunctions, null, textArea, prompt);
|
214 |
+
}
|
tagcomplete/javascript/ext_chants.js
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const CHANT_REGEX = /<(?!e:|h:|l:)[^,> ]*>?/g;
|
2 |
+
const CHANT_TRIGGER = () => TAC_CFG.chantFile && TAC_CFG.chantFile !== "None" && tagword.match(CHANT_REGEX);
|
3 |
+
|
4 |
+
class ChantParser extends BaseTagParser {
|
5 |
+
parse() {
|
6 |
+
// Show Chant
|
7 |
+
let tempResults = [];
|
8 |
+
if (tagword !== "<" && tagword !== "<c:") {
|
9 |
+
let searchTerm = tagword.replace("<chant:", "").replace("<c:", "").replace("<", "");
|
10 |
+
let filterCondition = x => {
|
11 |
+
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
12 |
+
return regex.test(x.terms.toLowerCase()) || regex.test(x.name.toLowerCase());
|
13 |
+
};
|
14 |
+
tempResults = chants.filter(x => filterCondition(x)); // Filter by tagword
|
15 |
+
} else {
|
16 |
+
tempResults = chants;
|
17 |
+
}
|
18 |
+
|
19 |
+
// Add final results
|
20 |
+
let finalResults = [];
|
21 |
+
tempResults.forEach(t => {
|
22 |
+
let result = new AutocompleteResult(t.content.trim(), ResultType.chant)
|
23 |
+
result.meta = "Chant";
|
24 |
+
result.aliases = t.name;
|
25 |
+
result.category = t.color;
|
26 |
+
finalResults.push(result);
|
27 |
+
});
|
28 |
+
|
29 |
+
return finalResults;
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
async function load() {
|
34 |
+
if (TAC_CFG.chantFile && TAC_CFG.chantFile !== "None") {
|
35 |
+
try {
|
36 |
+
chants = await readFile(`${tagBasePath}/${TAC_CFG.chantFile}?`, true);
|
37 |
+
} catch (e) {
|
38 |
+
console.error("Error loading chants.json: " + e);
|
39 |
+
}
|
40 |
+
} else {
|
41 |
+
chants = [];
|
42 |
+
}
|
43 |
+
}
|
44 |
+
|
45 |
+
function sanitize(tagType, text) {
|
46 |
+
if (tagType === ResultType.chant) {
|
47 |
+
return text;
|
48 |
+
}
|
49 |
+
return null;
|
50 |
+
}
|
51 |
+
|
52 |
+
PARSERS.push(new ChantParser(CHANT_TRIGGER));
|
53 |
+
|
54 |
+
// Add our utility functions to their respective queues
|
55 |
+
QUEUE_FILE_LOAD.push(load);
|
56 |
+
QUEUE_SANITIZE.push(sanitize);
|
57 |
+
QUEUE_AFTER_CONFIG_CHANGE.push(load);
|
tagcomplete/javascript/tagAutocomplete.js
ADDED
@@ -0,0 +1,1085 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const styleColors = {
|
2 |
+
"--results-neutral-text": ["#e0e0e0","black"],
|
3 |
+
"--results-bg": ["#0b0f19", "#ffffff"],
|
4 |
+
"--results-border-color": ["#4b5563", "#e5e7eb"],
|
5 |
+
"--results-border-width": ["1px", "1.5px"],
|
6 |
+
"--results-bg-odd": ["#111827", "#f9fafb"],
|
7 |
+
"--results-hover": ["#1f2937", "#f5f6f8"],
|
8 |
+
"--results-selected": ["#374151", "#e5e7eb"],
|
9 |
+
"--meta-text-color": ["#6b6f7b", "#a2a9b4"],
|
10 |
+
"--embedding-v1-color": ["lightsteelblue", "#2b5797"],
|
11 |
+
"--embedding-v2-color": ["skyblue", "#2d89ef"],
|
12 |
+
"--live-translation-rt": ["whitesmoke", "#222"],
|
13 |
+
"--live-translation-color-1": ["lightskyblue", "#2d89ef"],
|
14 |
+
"--live-translation-color-2": ["palegoldenrod", "#eb5700"],
|
15 |
+
"--live-translation-color-3": ["darkseagreen", "darkgreen"],
|
16 |
+
}
|
17 |
+
const browserVars = {
|
18 |
+
"--results-overflow-y": {
|
19 |
+
"firefox": "scroll",
|
20 |
+
"other": "auto"
|
21 |
+
}
|
22 |
+
}
|
23 |
+
// Style for new elements. Gets appended to the Gradio root.
|
24 |
+
const autocompleteCSS = `
|
25 |
+
#quicksettings [id^=setting_tac] {
|
26 |
+
background-color: transparent;
|
27 |
+
min-width: fit-content;
|
28 |
+
}
|
29 |
+
.autocompleteParent {
|
30 |
+
display: flex;
|
31 |
+
position: absolute;
|
32 |
+
z-index: 9999;
|
33 |
+
max-width: calc(100% - 1.5rem);
|
34 |
+
margin: 5px 0 0 0;
|
35 |
+
}
|
36 |
+
.autocompleteResults {
|
37 |
+
background-color: var(--results-bg) !important;
|
38 |
+
border: var(--results-border-width) solid var(--results-border-color) !important;
|
39 |
+
color: var(--results-neutral-text) !important;
|
40 |
+
border-radius: 12px !important;
|
41 |
+
height: fit-content;
|
42 |
+
flex-basis: fit-content;
|
43 |
+
flex-shrink: 0;
|
44 |
+
overflow-y: var(--results-overflow-y);
|
45 |
+
overflow-x: hidden;
|
46 |
+
word-break: break-word;
|
47 |
+
}
|
48 |
+
.sideInfo {
|
49 |
+
display: none;
|
50 |
+
position: relative;
|
51 |
+
margin-left: 10px;
|
52 |
+
height: 18rem;
|
53 |
+
max-width: 16rem;
|
54 |
+
}
|
55 |
+
.sideInfo > img {
|
56 |
+
object-fit: cover;
|
57 |
+
height: 100%;
|
58 |
+
width: 100%;
|
59 |
+
}
|
60 |
+
.autocompleteResultsList > li:nth-child(odd) {
|
61 |
+
background-color: var(--results-bg-odd);
|
62 |
+
}
|
63 |
+
.autocompleteResultsList > li {
|
64 |
+
list-style-type: none;
|
65 |
+
padding: 10px;
|
66 |
+
cursor: pointer;
|
67 |
+
}
|
68 |
+
.autocompleteResultsList > li:hover {
|
69 |
+
background-color: var(--results-hover);
|
70 |
+
}
|
71 |
+
.autocompleteResultsList > li.selected {
|
72 |
+
background-color: var(--results-selected);
|
73 |
+
}
|
74 |
+
.resultsFlexContainer {
|
75 |
+
display: flex;
|
76 |
+
}
|
77 |
+
.acListItem {
|
78 |
+
white-space: break-spaces;
|
79 |
+
min-width: 100px;
|
80 |
+
}
|
81 |
+
.acMetaText {
|
82 |
+
position: relative;
|
83 |
+
flex-grow: 1;
|
84 |
+
text-align: end;
|
85 |
+
padding: 0 0 0 15px;
|
86 |
+
white-space: nowrap;
|
87 |
+
color: var(--meta-text-color);
|
88 |
+
}
|
89 |
+
.acWikiLink {
|
90 |
+
padding: 0.5rem;
|
91 |
+
margin: -0.5rem 0 -0.5rem -0.5rem;
|
92 |
+
}
|
93 |
+
.acWikiLink:hover {
|
94 |
+
text-decoration: underline;
|
95 |
+
}
|
96 |
+
.acListItem.acEmbeddingV1 {
|
97 |
+
color: var(--embedding-v1-color);
|
98 |
+
}
|
99 |
+
.acListItem.acEmbeddingV2 {
|
100 |
+
color: var(--embedding-v2-color);
|
101 |
+
}
|
102 |
+
.acListItem .acPathPart:nth-child(3n+1) {
|
103 |
+
color: var(--live-translation-color-1);
|
104 |
+
}
|
105 |
+
.acListItem .acPathPart:nth-child(3n+2) {
|
106 |
+
color: var(--live-translation-color-2);
|
107 |
+
}
|
108 |
+
.acListItem .acPathPart:nth-child(3n+3) {
|
109 |
+
color: var(--live-translation-color-3);
|
110 |
+
}
|
111 |
+
`;
|
112 |
+
|
113 |
+
function gradioApp() {
|
114 |
+
const elems = document.getElementsByTagName('gradio-app');
|
115 |
+
const elem = elems.length == 0 ? document : elems[0];
|
116 |
+
|
117 |
+
if (elem !== document) {
|
118 |
+
elem.getElementById = function(id) {
|
119 |
+
return document.getElementById(id);
|
120 |
+
};
|
121 |
+
}
|
122 |
+
return elem.shadowRoot ? elem.shadowRoot : elem;
|
123 |
+
}
|
124 |
+
|
125 |
+
function updateInput(target) {
|
126 |
+
let e = new Event("input", {bubbles: true});
|
127 |
+
Object.defineProperty(e, "target", {value: target});
|
128 |
+
target.dispatchEvent(e);
|
129 |
+
}
|
130 |
+
|
131 |
+
async function loadTags(c) {
|
132 |
+
// Load main tags and aliases
|
133 |
+
if (allTags.length === 0 && c.tagFile && c.tagFile !== "None") {
|
134 |
+
try {
|
135 |
+
allTags = await loadCSV(`${tagBasePath}/${c.tagFile}`);
|
136 |
+
} catch (e) {
|
137 |
+
console.error("Error loading tags file: " + e);
|
138 |
+
return;
|
139 |
+
}
|
140 |
+
}
|
141 |
+
await loadExtraTags(c);
|
142 |
+
}
|
143 |
+
|
144 |
+
async function loadExtraTags(c) {
|
145 |
+
if (c.extra.extraFile && c.extra.extraFile !== "None") {
|
146 |
+
try {
|
147 |
+
extras = await loadCSV(`${tagBasePath}/${c.extra.extraFile}`);
|
148 |
+
// Add translations to the main translation map for extra tags that have them
|
149 |
+
extras.forEach(e => {
|
150 |
+
if (e[4]) translations.set(e[0], e[4]);
|
151 |
+
});
|
152 |
+
} catch (e) {
|
153 |
+
console.error("Error loading extra file: " + e);
|
154 |
+
return;
|
155 |
+
}
|
156 |
+
}
|
157 |
+
}
|
158 |
+
|
159 |
+
// Create the result list div and necessary styling
|
160 |
+
function createResultsDiv(textArea) {
|
161 |
+
let parentDiv = document.createElement("div");
|
162 |
+
let resultsDiv = document.createElement("div");
|
163 |
+
let resultsList = document.createElement("ul");
|
164 |
+
let sideDiv = document.createElement("div");
|
165 |
+
let sideDivImg = document.createElement("img");
|
166 |
+
|
167 |
+
let textAreaId = getTextAreaIdentifier(textArea);
|
168 |
+
let typeClass = textAreaId.replaceAll(".", " ");
|
169 |
+
|
170 |
+
parentDiv.setAttribute("class", `autocompleteParent${typeClass}`);
|
171 |
+
|
172 |
+
resultsDiv.style.maxHeight = `${TAC_CFG.maxResults * 50}px`;
|
173 |
+
resultsDiv.setAttribute("class", `autocompleteResults${typeClass} notranslate`);
|
174 |
+
resultsDiv.setAttribute("translate", "no");
|
175 |
+
resultsList.setAttribute("class", "autocompleteResultsList");
|
176 |
+
resultsDiv.appendChild(resultsList);
|
177 |
+
|
178 |
+
sideDiv.setAttribute("class", `autocompleteResults${typeClass} sideInfo`);
|
179 |
+
sideDiv.appendChild(sideDivImg);
|
180 |
+
|
181 |
+
parentDiv.appendChild(resultsDiv);
|
182 |
+
parentDiv.appendChild(sideDiv);
|
183 |
+
|
184 |
+
return parentDiv;
|
185 |
+
}
|
186 |
+
|
187 |
+
// Show or hide the results div
|
188 |
+
function isVisible(textArea) {
|
189 |
+
let textAreaId = getTextAreaIdentifier(textArea);
|
190 |
+
let parentDiv = gradioApp().querySelector('.autocompleteParent' + textAreaId);
|
191 |
+
return parentDiv.style.display === "flex";
|
192 |
+
}
|
193 |
+
|
194 |
+
function showResults(textArea) {
|
195 |
+
let textAreaId = getTextAreaIdentifier(textArea);
|
196 |
+
let parentDiv = gradioApp().querySelector('.autocompleteParent' + textAreaId);
|
197 |
+
parentDiv.style.display = "flex";
|
198 |
+
|
199 |
+
if (TAC_CFG.slidingPopup) {
|
200 |
+
let caretPosition = getCaretCoordinates(textArea, textArea.selectionEnd).left;
|
201 |
+
let offset = Math.min(textArea.offsetLeft - textArea.scrollLeft + caretPosition, textArea.offsetWidth - parentDiv.offsetWidth);
|
202 |
+
|
203 |
+
parentDiv.style.left = `${offset}px`;
|
204 |
+
} else {
|
205 |
+
if (parentDiv.style.left)
|
206 |
+
parentDiv.style.removeProperty("left");
|
207 |
+
}
|
208 |
+
// Reset here too to make absolutely sure the browser registers it
|
209 |
+
parentDiv.scrollTop = 0;
|
210 |
+
|
211 |
+
// Ensure preview is hidden
|
212 |
+
let previewDiv = gradioApp().querySelector(`.autocompleteParent${textAreaId} .sideInfo`);
|
213 |
+
previewDiv.style.display = "none";
|
214 |
+
}
|
215 |
+
|
216 |
+
function hideResults(textArea) {
|
217 |
+
let textAreaId = getTextAreaIdentifier(textArea);
|
218 |
+
let resultsDiv = gradioApp().querySelector('.autocompleteParent' + textAreaId);
|
219 |
+
|
220 |
+
if (!resultsDiv) return;
|
221 |
+
|
222 |
+
resultsDiv.style.display = "none";
|
223 |
+
selectedTag = null;
|
224 |
+
}
|
225 |
+
|
226 |
+
const WEIGHT_REGEX = /[([]([^()[\]:|]+)(?::(?:\d+(?:\.\d+)?|\.\d+))?[)\]]/g;
|
227 |
+
const POINTY_REGEX = /<[^\s,<](?:[^\t\n\r,<>]*>|[^\t\n\r,> ]*)/g;
|
228 |
+
const COMPLETED_WILDCARD_REGEX = /__[^\s,_][^\t\n\r,_]*[^\s,_]__[^\s,_]*/g;
|
229 |
+
const STYLE_VAR_REGEX = /\$\(?[^$|\[\],\s]*\)?/g;
|
230 |
+
const NORMAL_TAG_REGEX = /[^\s,|<>\[\]:]+_\([^\s,|<>\[\]:]*\)?|[^\s,|<>():\[\]]+|</g;
|
231 |
+
const TAG_REGEX = new RegExp(`${POINTY_REGEX.source}|${COMPLETED_WILDCARD_REGEX.source}|${STYLE_VAR_REGEX.source}|${NORMAL_TAG_REGEX.source}`, "g");
|
232 |
+
|
233 |
+
// On click, insert the tag into the prompt textbox with respect to the cursor position
|
234 |
+
async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithoutChoice = false) {
|
235 |
+
let text = result.text;
|
236 |
+
let tagType = result.type;
|
237 |
+
|
238 |
+
let cursorPos = textArea.selectionStart;
|
239 |
+
var sanitizedText = text
|
240 |
+
|
241 |
+
// Run sanitize queue and use first result as sanitized text
|
242 |
+
sanitizeResults = await processQueueReturn(QUEUE_SANITIZE, null, tagType, text);
|
243 |
+
|
244 |
+
if (sanitizeResults && sanitizeResults.length > 0) {
|
245 |
+
sanitizedText = sanitizeResults[0];
|
246 |
+
} else {
|
247 |
+
sanitizedText = TAC_CFG.replaceUnderscores ? text.replaceAll("_", " ") : text;
|
248 |
+
|
249 |
+
if (TAC_CFG.escapeParentheses && tagType === ResultType.tag) {
|
250 |
+
sanitizedText = sanitizedText
|
251 |
+
.replaceAll("(", "\\(")
|
252 |
+
.replaceAll(")", "\\)")
|
253 |
+
.replaceAll("[", "\\[")
|
254 |
+
.replaceAll("]", "\\]");
|
255 |
+
}
|
256 |
+
}
|
257 |
+
|
258 |
+
if ((tagType === ResultType.wildcardFile || tagType === ResultType.yamlWildcard)
|
259 |
+
&& tabCompletedWithoutChoice
|
260 |
+
&& TAC_CFG.wildcardCompletionMode !== "Always fully"
|
261 |
+
&& sanitizedText.includes("/")) {
|
262 |
+
if (TAC_CFG.wildcardCompletionMode === "To next folder level") {
|
263 |
+
let regexMatch = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}([^/]*\\/?)`, "i"));
|
264 |
+
if (regexMatch) {
|
265 |
+
let pathPart = regexMatch[0];
|
266 |
+
// In case the completion would have just added a slash, try again one level deeper
|
267 |
+
if (pathPart === `${tagword}/`) {
|
268 |
+
pathPart = sanitizedText.match(new RegExp(`${escapeRegExp(tagword)}\\/([^/]*\\/?)`, "i"))[0];
|
269 |
+
}
|
270 |
+
sanitizedText = pathPart;
|
271 |
+
}
|
272 |
+
} else if (TAC_CFG.wildcardCompletionMode === "To first difference") {
|
273 |
+
let firstDifference = 0;
|
274 |
+
let longestResult = results.map(x => x.text.length).reduce((a, b) => Math.max(a, b));
|
275 |
+
// Compare the results to each other to find the first point where they differ
|
276 |
+
for (let i = 0; i < longestResult; i++) {
|
277 |
+
let char = results[0].text[i];
|
278 |
+
if (results.every(x => x.text[i] === char)) {
|
279 |
+
firstDifference++;
|
280 |
+
} else {
|
281 |
+
break;
|
282 |
+
}
|
283 |
+
}
|
284 |
+
// Don't cut off the __ at the end if it is already the full path
|
285 |
+
if (firstDifference > 0 && firstDifference < longestResult) {
|
286 |
+
// +2 because the sanitized text already has the __ at the start but the matched text doesn't
|
287 |
+
sanitizedText = sanitizedText.substring(0, firstDifference + 2);
|
288 |
+
} else if (firstDifference === 0) {
|
289 |
+
sanitizedText = tagword;
|
290 |
+
}
|
291 |
+
}
|
292 |
+
}
|
293 |
+
|
294 |
+
var prompt = textArea.value;
|
295 |
+
|
296 |
+
// Edit prompt text
|
297 |
+
let editStart = Math.max(cursorPos - tagword.length, 0);
|
298 |
+
let editEnd = Math.min(cursorPos + tagword.length, prompt.length);
|
299 |
+
let surrounding = prompt.substring(editStart, editEnd);
|
300 |
+
let match = surrounding.match(new RegExp(escapeRegExp(`${tagword}`), "i"));
|
301 |
+
let afterInsertCursorPos = editStart + match.index + sanitizedText.length;
|
302 |
+
|
303 |
+
var optionalSeparator = "";
|
304 |
+
let extraNetworkTypes = [ResultType.hypernetwork, ResultType.lora];
|
305 |
+
let noCommaTypes = [ResultType.wildcardFile, ResultType.yamlWildcard, ResultType.umiWildcard].concat(extraNetworkTypes);
|
306 |
+
if (!noCommaTypes.includes(tagType)) {
|
307 |
+
// Append comma if enabled and not already present
|
308 |
+
let beforeComma = surrounding.match(new RegExp(`${escapeRegExp(tagword)}[,:]`, "i")) !== null;
|
309 |
+
if (TAC_CFG.appendComma)
|
310 |
+
optionalSeparator = beforeComma ? "" : ",";
|
311 |
+
// Add space if enabled
|
312 |
+
if (TAC_CFG.appendSpace && !beforeComma)
|
313 |
+
optionalSeparator += " ";
|
314 |
+
// If at end of prompt and enabled, override the normal setting if not already added
|
315 |
+
if (!TAC_CFG.appendSpace && TAC_CFG.alwaysSpaceAtEnd)
|
316 |
+
optionalSeparator += surrounding.match(new RegExp(`${escapeRegExp(tagword)}$`, "im")) !== null ? " " : "";
|
317 |
+
} else if (extraNetworkTypes.includes(tagType)) {
|
318 |
+
// Use the dedicated separator for extra networks if it's defined, otherwise fall back to space
|
319 |
+
optionalSeparator = TAC_CFG.extraNetworksSeparator || " ";
|
320 |
+
}
|
321 |
+
|
322 |
+
// Escape $ signs since they are special chars for the replace function
|
323 |
+
// We need four since we're also escaping them in replaceAll in the first place
|
324 |
+
sanitizedText = sanitizedText.replaceAll("$", "$$$$");
|
325 |
+
|
326 |
+
// Replace partial tag word with new text, add comma if needed
|
327 |
+
let insert = surrounding.replace(match, sanitizedText + optionalSeparator);
|
328 |
+
|
329 |
+
// Add back start
|
330 |
+
var newPrompt = prompt.substring(0, editStart) + insert + prompt.substring(editEnd);
|
331 |
+
|
332 |
+
// Add lora/lyco keywords if enabled and found
|
333 |
+
let keywordsLength = 0;
|
334 |
+
|
335 |
+
if (TAC_CFG.modelKeywordCompletion !== "Never" && (tagType === ResultType.lora || tagType === ResultType.lyco)) {
|
336 |
+
let keywords = null;
|
337 |
+
// Check built-in activation words first
|
338 |
+
if (tagType === ResultType.lora || tagType === ResultType.lyco) {
|
339 |
+
let info = await fetchAPI(`tacapi/v1/lora-info/${result.text}`)
|
340 |
+
if (info && info["activation text"]) {
|
341 |
+
keywords = info["activation text"];
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
if (!keywords && modelKeywordPath.length > 0 && result.hash && result.hash !== "NOFILE" && result.hash.length > 0) {
|
346 |
+
let nameDict = modelKeywordDict.get(result.hash);
|
347 |
+
let names = [result.text + ".safetensors", result.text + ".pt", result.text + ".ckpt"];
|
348 |
+
|
349 |
+
// No match, try to find a sha256 match from the cache file
|
350 |
+
if (!nameDict) {
|
351 |
+
const sha256 = await fetchAPI(`/tacapi/v1/lora-cached-hash/${result.text}`)
|
352 |
+
if (sha256) {
|
353 |
+
nameDict = modelKeywordDict.get(sha256);
|
354 |
+
}
|
355 |
+
}
|
356 |
+
|
357 |
+
if (nameDict) {
|
358 |
+
let found = false;
|
359 |
+
names.forEach(name => {
|
360 |
+
if (!found && nameDict.has(name)) {
|
361 |
+
found = true;
|
362 |
+
keywords = nameDict.get(name);
|
363 |
+
}
|
364 |
+
});
|
365 |
+
|
366 |
+
if (!found)
|
367 |
+
keywords = nameDict.get("none");
|
368 |
+
}
|
369 |
+
}
|
370 |
+
|
371 |
+
if (keywords && keywords.length > 0) {
|
372 |
+
textBeforeKeywordInsertion = newPrompt;
|
373 |
+
|
374 |
+
if (TAC_CFG.modelKeywordLocation === "Start of prompt")
|
375 |
+
newPrompt = `${keywords}, ${newPrompt}`; // Insert keywords
|
376 |
+
else if (TAC_CFG.modelKeywordLocation === "End of prompt")
|
377 |
+
newPrompt = `${newPrompt}, ${keywords}`; // Insert keywords
|
378 |
+
else {
|
379 |
+
let keywordStart = prompt[editStart - 1] === " " ? editStart - 1 : editStart;
|
380 |
+
newPrompt = prompt.substring(0, keywordStart) + `, ${keywords} ${insert}` + prompt.substring(editEnd);
|
381 |
+
}
|
382 |
+
|
383 |
+
|
384 |
+
textAfterKeywordInsertion = newPrompt;
|
385 |
+
keywordInsertionUndone = false;
|
386 |
+
setTimeout(() => lastEditWasKeywordInsertion = true, 200)
|
387 |
+
|
388 |
+
keywordsLength = keywords.length + 2; // +2 for the comma and space
|
389 |
+
}
|
390 |
+
}
|
391 |
+
|
392 |
+
// Insert into prompt textbox and reposition cursor
|
393 |
+
textArea.value = newPrompt;
|
394 |
+
textArea.selectionStart = afterInsertCursorPos + optionalSeparator.length + keywordsLength;
|
395 |
+
textArea.selectionEnd = textArea.selectionStart
|
396 |
+
|
397 |
+
// Set self trigger flag to show wildcard contents after the filename was inserted
|
398 |
+
if ([ResultType.wildcardFile, ResultType.yamlWildcard, ResultType.umiWildcard].includes(result.type))
|
399 |
+
tacSelfTrigger = true;
|
400 |
+
// Since we've modified a Gradio Textbox component manually, we need to simulate an `input` DOM event to ensure it's propagated back to python.
|
401 |
+
// Uses a built-in method from the webui's ui.js which also already accounts for event target
|
402 |
+
updateInput(textArea);
|
403 |
+
|
404 |
+
// Update previous tags with the edited prompt to prevent re-searching the same term
|
405 |
+
let weightedTags = [...newPrompt.matchAll(WEIGHT_REGEX)]
|
406 |
+
.map(match => match[1]);
|
407 |
+
let tags = newPrompt.match(TAG_REGEX)
|
408 |
+
if (weightedTags !== null) {
|
409 |
+
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted)))
|
410 |
+
.concat(weightedTags);
|
411 |
+
}
|
412 |
+
previousTags = tags;
|
413 |
+
|
414 |
+
// Callback
|
415 |
+
let returns = await processQueueReturn(QUEUE_AFTER_INSERT, null, tagType, sanitizedText, newPrompt, textArea);
|
416 |
+
// Return if any queue function returned true (has handled hide/show already)
|
417 |
+
if (returns.some(x => x === true))
|
418 |
+
return;
|
419 |
+
|
420 |
+
// Hide results after inserting, if it hasn't been hidden already by a queue function
|
421 |
+
if (!hideBlocked && isVisible(textArea)) {
|
422 |
+
hideResults(textArea);
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
function addResultsToList(textArea, results, tagword, resetList) {
|
427 |
+
let textAreaId = getTextAreaIdentifier(textArea);
|
428 |
+
let resultDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
|
429 |
+
let resultsList = resultDiv.querySelector('ul');
|
430 |
+
|
431 |
+
// Reset list, selection and scrollTop since the list changed
|
432 |
+
if (resetList) {
|
433 |
+
resultsList.innerHTML = "";
|
434 |
+
selectedTag = null;
|
435 |
+
oldSelectedTag = null;
|
436 |
+
resultDiv.scrollTop = 0;
|
437 |
+
resultCount = 0;
|
438 |
+
}
|
439 |
+
|
440 |
+
// Find right colors from config
|
441 |
+
let tagFileName = TAC_CFG.tagFile.split(".")[0];
|
442 |
+
let tagColors = TAC_CFG.colorMap;
|
443 |
+
let mode = (document.querySelector(".dark") || gradioApp().querySelector(".dark")) ? 0 : 1;
|
444 |
+
let nextLength = Math.min(results.length, resultCount + TAC_CFG.resultStepLength);
|
445 |
+
|
446 |
+
for (let i = resultCount; i < nextLength; i++) {
|
447 |
+
let result = results[i];
|
448 |
+
|
449 |
+
// Skip if the result is null or undefined
|
450 |
+
if (!result)
|
451 |
+
continue;
|
452 |
+
|
453 |
+
let li = document.createElement("li");
|
454 |
+
|
455 |
+
let flexDiv = document.createElement("div");
|
456 |
+
flexDiv.classList.add("resultsFlexContainer");
|
457 |
+
li.appendChild(flexDiv);
|
458 |
+
|
459 |
+
let itemText = document.createElement("div");
|
460 |
+
itemText.classList.add("acListItem");
|
461 |
+
|
462 |
+
let displayText = "";
|
463 |
+
// If the tag matches the tagword, we don't need to display the alias
|
464 |
+
if(result.type === ResultType.chant) {
|
465 |
+
displayText = escapeHTML(result.aliases);
|
466 |
+
} else if (result.aliases && !result.text.includes(tagword)) { // Alias
|
467 |
+
let splitAliases = result.aliases.split(",");
|
468 |
+
let bestAlias = splitAliases.find(a => a.toLowerCase().includes(tagword));
|
469 |
+
|
470 |
+
// search in translations if no alias matches
|
471 |
+
if (!bestAlias) {
|
472 |
+
let tagOrAlias = pair => pair[0] === result.text || splitAliases.includes(pair[0]);
|
473 |
+
var tArray = [...translations];
|
474 |
+
if (tArray) {
|
475 |
+
var translationKey = [...translations].find(pair => tagOrAlias(pair) && pair[1].includes(tagword));
|
476 |
+
if (translationKey)
|
477 |
+
bestAlias = translationKey[0];
|
478 |
+
}
|
479 |
+
}
|
480 |
+
|
481 |
+
displayText = escapeHTML(bestAlias);
|
482 |
+
|
483 |
+
// Append translation for alias if it exists and is not what the user typed
|
484 |
+
if (translations.has(bestAlias) && translations.get(bestAlias) !== bestAlias && bestAlias !== result.text)
|
485 |
+
displayText += `[${translations.get(bestAlias)}]`;
|
486 |
+
|
487 |
+
if (!TAC_CFG.alias.onlyShowAlias && result.text !== bestAlias)
|
488 |
+
displayText += " ➝ " + result.text;
|
489 |
+
} else { // No alias
|
490 |
+
displayText = escapeHTML(result.text);
|
491 |
+
}
|
492 |
+
|
493 |
+
// Append translation for result if it exists
|
494 |
+
if (translations.has(result.text))
|
495 |
+
displayText += `[${translations.get(result.text)}]`;
|
496 |
+
|
497 |
+
// Print search term bolded in result
|
498 |
+
itemText.innerHTML = displayText.replace(tagword, `<b>${tagword}</b>`);
|
499 |
+
|
500 |
+
const splitTypes = [ResultType.wildcardFile, ResultType.yamlWildcard]
|
501 |
+
if (splitTypes.includes(result.type) && itemText.innerHTML.includes("/")) {
|
502 |
+
let parts = itemText.innerHTML.split("/");
|
503 |
+
let lastPart = parts[parts.length - 1];
|
504 |
+
parts = parts.slice(0, parts.length - 1);
|
505 |
+
|
506 |
+
itemText.innerHTML = "<span class='acPathPart'>" + parts.join("</span><span class='acPathPart'>/") + "</span>" + "/" + lastPart;
|
507 |
+
}
|
508 |
+
|
509 |
+
// Add wiki link if the setting is enabled and a supported tag set loaded
|
510 |
+
if (TAC_CFG.showWikiLinks
|
511 |
+
&& (result.type === ResultType.tag)
|
512 |
+
&& (tagFileName.toLowerCase().startsWith("danbooru") || tagFileName.toLowerCase().startsWith("e621"))) {
|
513 |
+
let wikiLink = document.createElement("a");
|
514 |
+
wikiLink.classList.add("acWikiLink");
|
515 |
+
wikiLink.innerText = "?";
|
516 |
+
|
517 |
+
let linkPart = displayText;
|
518 |
+
// Only use alias result if it is one
|
519 |
+
if (displayText.includes("➝"))
|
520 |
+
linkPart = displayText.split(" ➝ ")[1];
|
521 |
+
|
522 |
+
// Remove any trailing translations
|
523 |
+
if (linkPart.includes("[")) {
|
524 |
+
linkPart = linkPart.split("[")[0]
|
525 |
+
}
|
526 |
+
|
527 |
+
linkPart = encodeURIComponent(linkPart);
|
528 |
+
|
529 |
+
// Set link based on selected file
|
530 |
+
let tagFileNameLower = tagFileName.toLowerCase();
|
531 |
+
if (tagFileNameLower.startsWith("danbooru")) {
|
532 |
+
wikiLink.href = `https://danbooru.donmai.us/wiki_pages/${linkPart}`;
|
533 |
+
} else if (tagFileNameLower.startsWith("e621")) {
|
534 |
+
wikiLink.href = `https://e621.net/wiki_pages/${linkPart}`;
|
535 |
+
}
|
536 |
+
|
537 |
+
wikiLink.target = "_blank";
|
538 |
+
flexDiv.appendChild(wikiLink);
|
539 |
+
}
|
540 |
+
|
541 |
+
flexDiv.appendChild(itemText);
|
542 |
+
|
543 |
+
// Add post count & color if it's a tag
|
544 |
+
// Wildcards & Embeds have no tag category
|
545 |
+
if (result.category) {
|
546 |
+
// Set the color of the tag
|
547 |
+
let cat = result.category;
|
548 |
+
let colorGroup = tagColors[tagFileName];
|
549 |
+
// Default to danbooru scheme if no matching one is found
|
550 |
+
if (!colorGroup)
|
551 |
+
colorGroup = tagColors["danbooru"];
|
552 |
+
|
553 |
+
// Set tag type to invalid if not found
|
554 |
+
if (!colorGroup[cat])
|
555 |
+
cat = "-1";
|
556 |
+
|
557 |
+
flexDiv.style = `color: ${colorGroup[cat][mode]};`;
|
558 |
+
}
|
559 |
+
|
560 |
+
// Post count
|
561 |
+
if (result.count && !isNaN(result.count)) {
|
562 |
+
let postCount = result.count;
|
563 |
+
let formatter;
|
564 |
+
|
565 |
+
// Danbooru formats numbers with a padded fraction for 1M or 1k, but not for 10/100k
|
566 |
+
if (postCount >= 1000000 || (postCount >= 1000 && postCount < 10000))
|
567 |
+
formatter = Intl.NumberFormat("en", { notation: "compact", minimumFractionDigits: 1, maximumFractionDigits: 1 });
|
568 |
+
else
|
569 |
+
formatter = Intl.NumberFormat("en", {notation: "compact"});
|
570 |
+
|
571 |
+
let formattedCount = formatter.format(postCount);
|
572 |
+
|
573 |
+
let countDiv = document.createElement("div");
|
574 |
+
countDiv.textContent = formattedCount;
|
575 |
+
countDiv.classList.add("acMetaText");
|
576 |
+
flexDiv.appendChild(countDiv);
|
577 |
+
} else if (result.meta) { // Check if there is meta info to display
|
578 |
+
let metaDiv = document.createElement("div");
|
579 |
+
metaDiv.textContent = result.meta;
|
580 |
+
metaDiv.classList.add("acMetaText");
|
581 |
+
|
582 |
+
// Add version info classes if it is an embedding
|
583 |
+
if (result.type === ResultType.embedding) {
|
584 |
+
if (result.meta.startsWith("v1"))
|
585 |
+
itemText.classList.add("acEmbeddingV1");
|
586 |
+
else if (result.meta.startsWith("v2"))
|
587 |
+
itemText.classList.add("acEmbeddingV2");
|
588 |
+
}
|
589 |
+
|
590 |
+
flexDiv.appendChild(metaDiv);
|
591 |
+
}
|
592 |
+
|
593 |
+
// Add listener
|
594 |
+
li.addEventListener("click", function () { insertTextAtCursor(textArea, result, tagword); });
|
595 |
+
// Add element to list
|
596 |
+
resultsList.appendChild(li);
|
597 |
+
}
|
598 |
+
resultCount = nextLength;
|
599 |
+
|
600 |
+
if (resetList) {
|
601 |
+
selectedTag = null;
|
602 |
+
oldSelectedTag = null;
|
603 |
+
resultDiv.scrollTop = 0;
|
604 |
+
}
|
605 |
+
}
|
606 |
+
|
607 |
+
async function updateSelectionStyle(textArea, newIndex, oldIndex) {
|
608 |
+
let textAreaId = getTextAreaIdentifier(textArea);
|
609 |
+
let resultDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
|
610 |
+
let resultsList = resultDiv.querySelector('ul');
|
611 |
+
let items = resultsList.getElementsByTagName('li');
|
612 |
+
|
613 |
+
if (oldIndex != null) {
|
614 |
+
items[oldIndex].classList.remove('selected');
|
615 |
+
}
|
616 |
+
|
617 |
+
// make it safer
|
618 |
+
if (newIndex !== null) {
|
619 |
+
let selected = items[newIndex];
|
620 |
+
selected.classList.add('selected');
|
621 |
+
|
622 |
+
// Set scrolltop to selected item
|
623 |
+
resultDiv.scrollTop = selected.offsetTop - resultDiv.offsetTop;
|
624 |
+
}
|
625 |
+
|
626 |
+
// Show preview if enabled and the selected type supports it
|
627 |
+
if (newIndex !== null) {
|
628 |
+
let selected = items[newIndex];
|
629 |
+
let previewTypes = ["v1 Embedding", "v2 Embedding", "Hypernetwork", "Lora", "Lyco"];
|
630 |
+
let selectedType = selected.querySelector(".acMetaText").innerText;
|
631 |
+
let selectedFilename = selected.querySelector(".acListItem").innerText;
|
632 |
+
|
633 |
+
let previewDiv = gradioApp().querySelector(`.autocompleteParent${textAreaId} .sideInfo`);
|
634 |
+
|
635 |
+
if (TAC_CFG.showExtraNetworkPreviews && previewTypes.includes(selectedType)) {
|
636 |
+
let shorthandType = "";
|
637 |
+
switch (selectedType) {
|
638 |
+
case "v1 Embedding":
|
639 |
+
case "v2 Embedding":
|
640 |
+
shorthandType = "embed";
|
641 |
+
break;
|
642 |
+
case "Hypernetwork":
|
643 |
+
shorthandType = "hyper";
|
644 |
+
break;
|
645 |
+
case "Lora":
|
646 |
+
shorthandType = "lora";
|
647 |
+
break;
|
648 |
+
case "Lyco":
|
649 |
+
shorthandType = "lyco";
|
650 |
+
break;
|
651 |
+
}
|
652 |
+
|
653 |
+
let img = previewDiv.querySelector("img");
|
654 |
+
|
655 |
+
let url = await getExtraNetworkPreviewURL(selectedFilename, shorthandType);
|
656 |
+
if (url) {
|
657 |
+
img.src = url;
|
658 |
+
previewDiv.style.display = "block";
|
659 |
+
} else {
|
660 |
+
previewDiv.style.display = "none";
|
661 |
+
}
|
662 |
+
} else {
|
663 |
+
previewDiv.style.display = "none";
|
664 |
+
}
|
665 |
+
}
|
666 |
+
}
|
667 |
+
|
668 |
+
// Check if the last edit was the keyword insertion, and catch undo/redo in that case
|
669 |
+
function checkKeywordInsertionUndo(textArea, event) {
|
670 |
+
if (TAC_CFG.modelKeywordCompletion === "Never") return;
|
671 |
+
|
672 |
+
switch (event.inputType) {
|
673 |
+
case "historyUndo":
|
674 |
+
if (lastEditWasKeywordInsertion && !keywordInsertionUndone) {
|
675 |
+
keywordInsertionUndone = true;
|
676 |
+
textArea.value = textBeforeKeywordInsertion;
|
677 |
+
tacSelfTrigger = true;
|
678 |
+
updateInput(textArea);
|
679 |
+
}
|
680 |
+
break;
|
681 |
+
case "historyRedo":
|
682 |
+
if (lastEditWasKeywordInsertion && keywordInsertionUndone) {
|
683 |
+
keywordInsertionUndone = false;
|
684 |
+
textArea.value = textAfterKeywordInsertion;
|
685 |
+
tacSelfTrigger = true;
|
686 |
+
updateInput(textArea);
|
687 |
+
}
|
688 |
+
case undefined:
|
689 |
+
// undefined is caused by the updateInput event firing, so we just ignore it
|
690 |
+
break;
|
691 |
+
default:
|
692 |
+
// Everything else deactivates the keyword undo and returns to normal undo behavior
|
693 |
+
lastEditWasKeywordInsertion = false;
|
694 |
+
keywordInsertionUndone = false;
|
695 |
+
textBeforeKeywordInsertion = "";
|
696 |
+
textAfterKeywordInsertion = "";
|
697 |
+
break;
|
698 |
+
}
|
699 |
+
}
|
700 |
+
|
701 |
+
async function autocomplete(textArea, prompt, fixedTag = null) {
|
702 |
+
// Return if the function is deactivated in the UI
|
703 |
+
|
704 |
+
// Guard for empty prompt
|
705 |
+
if (prompt.length === 0) {
|
706 |
+
hideResults(textArea);
|
707 |
+
previousTags = [];
|
708 |
+
tagword = "";
|
709 |
+
return;
|
710 |
+
}
|
711 |
+
|
712 |
+
if (fixedTag === null) {
|
713 |
+
// Match tags with RegEx to get the last edited one
|
714 |
+
// We also match for the weighting format (e.g. "tag:1.0") here, and combine the two to get the full tag word set
|
715 |
+
let weightedTags = [...prompt.matchAll(WEIGHT_REGEX)]
|
716 |
+
.map(match => match[1]);
|
717 |
+
let tags = prompt.match(TAG_REGEX)
|
718 |
+
if (weightedTags !== null && tags !== null) {
|
719 |
+
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted) && !tag.startsWith("<[") && !tag.startsWith("$(")))
|
720 |
+
.concat(weightedTags);
|
721 |
+
}
|
722 |
+
|
723 |
+
// Guard for no tags
|
724 |
+
if (!tags || tags.length === 0) {
|
725 |
+
previousTags = [];
|
726 |
+
tagword = "";
|
727 |
+
hideResults(textArea);
|
728 |
+
return;
|
729 |
+
}
|
730 |
+
|
731 |
+
let tagCountChange = tags.length - previousTags.length;
|
732 |
+
let diff = difference(tags, previousTags);
|
733 |
+
previousTags = tags;
|
734 |
+
|
735 |
+
// Guard for no difference / only whitespace remaining / last edited tag was fully removed
|
736 |
+
if (diff === null || diff.length === 0 || (diff.length === 1 && tagCountChange < 0)) {
|
737 |
+
if (!hideBlocked) hideResults(textArea);
|
738 |
+
return;
|
739 |
+
}
|
740 |
+
|
741 |
+
tagword = diff[0]
|
742 |
+
|
743 |
+
// Guard for empty tagword
|
744 |
+
if (tagword === null || tagword.length === 0) {
|
745 |
+
hideResults(textArea);
|
746 |
+
return;
|
747 |
+
}
|
748 |
+
} else {
|
749 |
+
tagword = fixedTag;
|
750 |
+
}
|
751 |
+
|
752 |
+
results = [];
|
753 |
+
resultCountBeforeNormalTags = 0;
|
754 |
+
tagword = tagword.toLowerCase().replace(/[\n\r]/g, "");
|
755 |
+
|
756 |
+
// Process all parsers
|
757 |
+
let resultCandidates = (await processParsers(textArea, prompt))?.filter(x => x.length > 0);
|
758 |
+
// If one ore more result candidates match, use their results
|
759 |
+
if (resultCandidates && resultCandidates.length > 0) {
|
760 |
+
// Flatten our candidate(s)
|
761 |
+
results = resultCandidates.flat();
|
762 |
+
// Sort results, but not if it's umi tags since they are sorted by count
|
763 |
+
if (!(resultCandidates.length === 1 && results[0].type === ResultType.umiWildcard))
|
764 |
+
results = results.sort(getSortFunction());
|
765 |
+
|
766 |
+
// Since some tags are kaomoji, we have to add the normal results in some cases
|
767 |
+
if (tagword.startsWith("<") || tagword.startsWith("*<")) {
|
768 |
+
// Create escaped search regex with support for * as a start placeholder
|
769 |
+
let searchRegex;
|
770 |
+
if (tagword.startsWith("*")) {
|
771 |
+
tagword = tagword.slice(1);
|
772 |
+
searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i');
|
773 |
+
} else {
|
774 |
+
searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(tagword)}`, 'i');
|
775 |
+
}
|
776 |
+
let genericResults = allTags.filter(x => x[0].toLowerCase().search(searchRegex) > -1).slice(0, TAC_CFG.maxResults);
|
777 |
+
|
778 |
+
genericResults.forEach(g => {
|
779 |
+
let result = new AutocompleteResult(g[0].trim(), ResultType.tag)
|
780 |
+
result.category = g[1];
|
781 |
+
result.count = g[2];
|
782 |
+
result.aliases = g[3];
|
783 |
+
results.push(result);
|
784 |
+
});
|
785 |
+
}
|
786 |
+
}
|
787 |
+
// Else search the normal tag list
|
788 |
+
if (!resultCandidates || resultCandidates.length === 0
|
789 |
+
|| (TAC_CFG.includeEmbeddingsInNormalResults && !(tagword.startsWith("<") || tagword.startsWith("*<")))
|
790 |
+
) {
|
791 |
+
resultCountBeforeNormalTags = results.length;
|
792 |
+
|
793 |
+
// Create escaped search regex with support for * as a start placeholder
|
794 |
+
let searchRegex;
|
795 |
+
if (tagword.startsWith("*")) {
|
796 |
+
tagword = tagword.slice(1);
|
797 |
+
searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i');
|
798 |
+
} else {
|
799 |
+
searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(tagword)}`, 'i');
|
800 |
+
}
|
801 |
+
|
802 |
+
// Both normal tags and aliases/translations are included depending on the config
|
803 |
+
let baseFilter = (x) => x[0].toLowerCase().search(searchRegex) > -1;
|
804 |
+
let aliasFilter = (x) => x[3] && x[3].toLowerCase().search(searchRegex) > -1;
|
805 |
+
let translationFilter = (x) => (translations.has(x[0]) && translations.get(x[0]).toLowerCase().search(searchRegex) > -1)
|
806 |
+
|| x[3] && x[3].split(",").some(y => translations.has(y) && translations.get(y).toLowerCase().search(searchRegex) > -1);
|
807 |
+
|
808 |
+
let fil;
|
809 |
+
if (TAC_CFG.alias.searchByAlias && TAC_CFG.translation.searchByTranslation)
|
810 |
+
fil = (x) => baseFilter(x) || aliasFilter(x) || translationFilter(x);
|
811 |
+
else if (TAC_CFG.alias.searchByAlias && !TAC_CFG.translation.searchByTranslation)
|
812 |
+
fil = (x) => baseFilter(x) || aliasFilter(x);
|
813 |
+
else if (TAC_CFG.translation.searchByTranslation && !TAC_CFG.alias.searchByAlias)
|
814 |
+
fil = (x) => baseFilter(x) || translationFilter(x);
|
815 |
+
else
|
816 |
+
fil = (x) => baseFilter(x);
|
817 |
+
|
818 |
+
// Add final results
|
819 |
+
allTags.filter(fil).forEach(t => {
|
820 |
+
let result = new AutocompleteResult(t[0].trim(), ResultType.tag)
|
821 |
+
result.category = t[1];
|
822 |
+
result.count = t[2];
|
823 |
+
result.aliases = t[3];
|
824 |
+
results.push(result);
|
825 |
+
});
|
826 |
+
|
827 |
+
// Add extras
|
828 |
+
if (TAC_CFG.extra.extraFile) {
|
829 |
+
let extraResults = [];
|
830 |
+
|
831 |
+
extras.filter(fil).forEach(e => {
|
832 |
+
let result = new AutocompleteResult(e[0].trim(), ResultType.extra)
|
833 |
+
result.category = e[1] || 0; // If no category is given, use 0 as the default
|
834 |
+
result.meta = e[2] || "Custom tag";
|
835 |
+
result.aliases = e[3] || "";
|
836 |
+
extraResults.push(result);
|
837 |
+
});
|
838 |
+
|
839 |
+
if (TAC_CFG.extra.addMode === "Insert before") {
|
840 |
+
results = extraResults.concat(results);
|
841 |
+
} else {
|
842 |
+
results = results.concat(extraResults);
|
843 |
+
}
|
844 |
+
}
|
845 |
+
|
846 |
+
// Slice if the user has set a max result count
|
847 |
+
if (!TAC_CFG.showAllResults) {
|
848 |
+
results = results.slice(0, TAC_CFG.maxResults + resultCountBeforeNormalTags);
|
849 |
+
}
|
850 |
+
}
|
851 |
+
|
852 |
+
// Guard for empty results
|
853 |
+
if (!results || results.length === 0) {
|
854 |
+
//console.log('No results found for "' + tagword + '"');
|
855 |
+
hideResults(textArea);
|
856 |
+
return;
|
857 |
+
}
|
858 |
+
|
859 |
+
addResultsToList(textArea, results, tagword, true);
|
860 |
+
showResults(textArea);
|
861 |
+
}
|
862 |
+
|
863 |
+
function navigateInList(textArea, event) {
|
864 |
+
// Return if the function is deactivated in the UI or the current model is excluded due to white/blacklist settings
|
865 |
+
|
866 |
+
let keys = TAC_CFG.keymap;
|
867 |
+
|
868 |
+
// Close window if Home or End is pressed while not a keybinding, since it would break completion on leaving the original tag
|
869 |
+
if ((event.key === "Home" || event.key === "End") && !Object.values(keys).includes(event.key)) {
|
870 |
+
hideResults(textArea);
|
871 |
+
return;
|
872 |
+
}
|
873 |
+
|
874 |
+
// All set keys that are not None or empty are valid
|
875 |
+
// Default keys are: ArrowUp, ArrowDown, PageUp, PageDown, Home, End, Enter, Tab, Escape
|
876 |
+
validKeys = Object.values(keys).filter(x => x !== "None" && x !== "");
|
877 |
+
|
878 |
+
if (!validKeys.includes(event.key)) return;
|
879 |
+
if (!isVisible(textArea)) return
|
880 |
+
// Add modifier keys to base as text+.
|
881 |
+
let modKey = "";
|
882 |
+
if (event.ctrlKey) modKey += "Ctrl+";
|
883 |
+
if (event.altKey) modKey += "Alt+";
|
884 |
+
if (event.shiftKey) modKey += "Shift+";
|
885 |
+
if (event.metaKey) modKey += "Meta+";
|
886 |
+
modKey += event.key;
|
887 |
+
|
888 |
+
oldSelectedTag = selectedTag;
|
889 |
+
|
890 |
+
switch (modKey) {
|
891 |
+
case keys["MoveUp"]:
|
892 |
+
if (selectedTag === null) {
|
893 |
+
selectedTag = resultCount - 1;
|
894 |
+
} else {
|
895 |
+
selectedTag = (selectedTag - 1 + resultCount) % resultCount;
|
896 |
+
}
|
897 |
+
break;
|
898 |
+
case keys["MoveDown"]:
|
899 |
+
if (selectedTag === null) {
|
900 |
+
selectedTag = 0;
|
901 |
+
} else {
|
902 |
+
selectedTag = (selectedTag + 1) % resultCount;
|
903 |
+
}
|
904 |
+
break;
|
905 |
+
case keys["JumpUp"]:
|
906 |
+
if (selectedTag === null || selectedTag === 0) {
|
907 |
+
selectedTag = resultCount - 1;
|
908 |
+
} else {
|
909 |
+
selectedTag = (Math.max(selectedTag - 5, 0) + resultCount) % resultCount;
|
910 |
+
}
|
911 |
+
break;
|
912 |
+
case keys["JumpDown"]:
|
913 |
+
if (selectedTag === null || selectedTag === resultCount - 1) {
|
914 |
+
selectedTag = 0;
|
915 |
+
} else {
|
916 |
+
selectedTag = Math.min(selectedTag + 5, resultCount - 1) % resultCount;
|
917 |
+
}
|
918 |
+
break;
|
919 |
+
case keys["JumpToStart"]:
|
920 |
+
if (TAC_CFG.includeEmbeddingsInNormalResults &&
|
921 |
+
selectedTag > resultCountBeforeNormalTags &&
|
922 |
+
resultCountBeforeNormalTags > 0
|
923 |
+
) {
|
924 |
+
selectedTag = resultCountBeforeNormalTags;
|
925 |
+
} else {
|
926 |
+
selectedTag = 0;
|
927 |
+
}
|
928 |
+
break;
|
929 |
+
case keys["JumpToEnd"]:
|
930 |
+
// Jump to the end of the list, or the end of embeddings if they are included in the normal results
|
931 |
+
if (TAC_CFG.includeEmbeddingsInNormalResults &&
|
932 |
+
selectedTag < resultCountBeforeNormalTags &&
|
933 |
+
resultCountBeforeNormalTags > 0
|
934 |
+
) {
|
935 |
+
selectedTag = Math.min(resultCountBeforeNormalTags, resultCount - 1);
|
936 |
+
} else {
|
937 |
+
selectedTag = resultCount - 1;
|
938 |
+
}
|
939 |
+
break;
|
940 |
+
case keys["ChooseSelected"]:
|
941 |
+
if (selectedTag !== null) {
|
942 |
+
insertTextAtCursor(textArea, results[selectedTag], tagword);
|
943 |
+
} else {
|
944 |
+
hideResults(textArea);
|
945 |
+
return;
|
946 |
+
}
|
947 |
+
break;
|
948 |
+
case keys["ChooseFirstOrSelected"]:
|
949 |
+
let withoutChoice = false;
|
950 |
+
if (selectedTag === null) {
|
951 |
+
selectedTag = 0;
|
952 |
+
withoutChoice = true;
|
953 |
+
} else if (TAC_CFG.wildcardCompletionMode === "To next folder level") {
|
954 |
+
withoutChoice = true;
|
955 |
+
}
|
956 |
+
insertTextAtCursor(textArea, results[selectedTag], tagword, withoutChoice);
|
957 |
+
break;
|
958 |
+
case keys["Close"]:
|
959 |
+
hideResults(textArea);
|
960 |
+
break;
|
961 |
+
default:
|
962 |
+
if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) return;
|
963 |
+
}
|
964 |
+
let moveKeys = [keys["MoveUp"], keys["MoveDown"], keys["JumpUp"], keys["JumpDown"], keys["JumpToStart"], keys["JumpToEnd"]];
|
965 |
+
if (selectedTag === resultCount - 1 && moveKeys.includes(event.key)) {
|
966 |
+
addResultsToList(textArea, results, tagword, false);
|
967 |
+
}
|
968 |
+
// Update highlighting
|
969 |
+
if (selectedTag !== null)
|
970 |
+
updateSelectionStyle(textArea, selectedTag, oldSelectedTag);
|
971 |
+
|
972 |
+
// Prevent default behavior
|
973 |
+
event.preventDefault();
|
974 |
+
event.stopPropagation();
|
975 |
+
}
|
976 |
+
|
977 |
+
function addAutocompleteToArea(area) {
|
978 |
+
// Return if autocomplete is disabled for the current area type in config
|
979 |
+
let textAreaId = getTextAreaIdentifier(area);
|
980 |
+
if ((!TAC_CFG.activeIn.img2img && textAreaId.includes("img2img"))
|
981 |
+
|| (!TAC_CFG.activeIn.txt2img && textAreaId.includes("txt2img"))
|
982 |
+
|| (!TAC_CFG.activeIn.negativePrompts && textAreaId.includes("n"))
|
983 |
+
|| (!TAC_CFG.activeIn.thirdParty && textAreaId.includes("thirdParty"))) {
|
984 |
+
return;
|
985 |
+
}
|
986 |
+
|
987 |
+
// Only add listeners once
|
988 |
+
if (!area.classList.contains('autocomplete')) {
|
989 |
+
// Add our new element
|
990 |
+
var resultsDiv = createResultsDiv(area);
|
991 |
+
area.parentNode.insertBefore(resultsDiv, area.nextSibling);
|
992 |
+
// Hide by default so it doesn't show up on page load
|
993 |
+
hideResults(area);
|
994 |
+
|
995 |
+
// Add autocomplete event listener
|
996 |
+
area.addEventListener('input', (e) => {
|
997 |
+
|
998 |
+
// Cancel autocomplete itself if the event has no inputType (e.g. because it was triggered by the updateInput() function)
|
999 |
+
if (!e.inputType && !tacSelfTrigger) return;
|
1000 |
+
tacSelfTrigger = false;
|
1001 |
+
|
1002 |
+
debounce(autocomplete(area, area.value), TAC_CFG.delayTime);
|
1003 |
+
checkKeywordInsertionUndo(area, e);
|
1004 |
+
});
|
1005 |
+
// Add focusout event listener
|
1006 |
+
area.addEventListener('focusout', debounce(() => {
|
1007 |
+
if (!hideBlocked)
|
1008 |
+
hideResults(area);
|
1009 |
+
}, 400));
|
1010 |
+
// Add up and down arrow event listener
|
1011 |
+
area.addEventListener('keydown', (e) => navigateInList(area, e));
|
1012 |
+
// CompositionEnd fires after the user has finished IME composing
|
1013 |
+
// We need to block hide here to prevent the enter key from insta-closing the results
|
1014 |
+
area.addEventListener('compositionend', () => {
|
1015 |
+
hideBlocked = true;
|
1016 |
+
setTimeout(() => { hideBlocked = false; }, 100);
|
1017 |
+
});
|
1018 |
+
|
1019 |
+
// Add class so we know we've already added the listeners
|
1020 |
+
area.classList.add('autocomplete');
|
1021 |
+
}
|
1022 |
+
}
|
1023 |
+
|
1024 |
+
// One-time setup, triggered from onUiUpdate
|
1025 |
+
async function setup() {
|
1026 |
+
// Load external files needed by completion extensions
|
1027 |
+
await processQueue(QUEUE_FILE_LOAD, null);
|
1028 |
+
|
1029 |
+
// Find all textareas
|
1030 |
+
let textAreas = getTextAreas();
|
1031 |
+
|
1032 |
+
// Add mutation observer to accordions inside a base that has onDemand set to true
|
1033 |
+
addOnDemandObservers(addAutocompleteToArea);
|
1034 |
+
|
1035 |
+
// Not found, we're on a page without prompt textareas
|
1036 |
+
if (textAreas.every(v => v === null || v === undefined)) return;
|
1037 |
+
// Already added or unnecessary to add
|
1038 |
+
if (gradioApp().querySelector('.autocompleteParent.p')) {
|
1039 |
+
if (gradioApp().querySelector('.autocompleteParent.n') || !TAC_CFG.activeIn.negativePrompts) {
|
1040 |
+
return;
|
1041 |
+
}
|
1042 |
+
} else if (!TAC_CFG.activeIn.txt2img && !TAC_CFG.activeIn.img2img) {
|
1043 |
+
return;
|
1044 |
+
}
|
1045 |
+
|
1046 |
+
textAreas.forEach(area => addAutocompleteToArea(area));
|
1047 |
+
textAreas.forEach(area => area.offsetParent.style.setProperty('overflow', 'visible'));
|
1048 |
+
textAreas[0].offsetParent.parentElement.style.overflow = 'visible';
|
1049 |
+
// Add style to dom
|
1050 |
+
let acStyle = document.createElement('style');
|
1051 |
+
let mode = (document.querySelector(".dark") || gradioApp().querySelector(".dark")) ? 0 : 1;
|
1052 |
+
// Check if we are on webkit
|
1053 |
+
let browser = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 ? "firefox" : "other";
|
1054 |
+
|
1055 |
+
let css = autocompleteCSS;
|
1056 |
+
// Replace vars with actual values (can't use actual css vars because of the way we inject the css)
|
1057 |
+
Object.keys(styleColors).forEach((key) => {
|
1058 |
+
css = css.replaceAll(`var(${key})`, styleColors[key][mode]);
|
1059 |
+
})
|
1060 |
+
Object.keys(browserVars).forEach((key) => {
|
1061 |
+
css = css.replaceAll(`var(${key})`, browserVars[key][browser]);
|
1062 |
+
})
|
1063 |
+
|
1064 |
+
if (acStyle.styleSheet) {
|
1065 |
+
acStyle.styleSheet.cssText = css;
|
1066 |
+
} else {
|
1067 |
+
acStyle.appendChild(document.createTextNode(css));
|
1068 |
+
}
|
1069 |
+
gradioApp().appendChild(acStyle);
|
1070 |
+
|
1071 |
+
// Callback
|
1072 |
+
await processQueue(QUEUE_AFTER_SETUP, null);
|
1073 |
+
}
|
1074 |
+
var tacLoading = false;
|
1075 |
+
async function run() {
|
1076 |
+
if (tacLoading) return;
|
1077 |
+
if (!TAC_CFG) return;
|
1078 |
+
tacLoading = true;
|
1079 |
+
// Get our tag base path from the temp file
|
1080 |
+
// Rest of setup
|
1081 |
+
await loadTags(TAC_CFG);
|
1082 |
+
setup();
|
1083 |
+
tacLoading = false;
|
1084 |
+
}
|
1085 |
+
run();
|
tagcomplete/tags/danbooru-0-zh.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tagcomplete/tags/danbooru.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tagcomplete/tags/demo-chants.json
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"name": "Basic-NegativePrompt",
|
4 |
+
"terms": "Basic,Negative,Low,Quality",
|
5 |
+
"content": "(worst quality, low quality, normal quality)",
|
6 |
+
"color": 3
|
7 |
+
},
|
8 |
+
{
|
9 |
+
"name": "Basic-HighQuality",
|
10 |
+
"terms": "Basic,Best,High,Quality",
|
11 |
+
"content": "(masterpiece, best quality, high quality, highres, ultra-detailed)",
|
12 |
+
"color": 1
|
13 |
+
},
|
14 |
+
{
|
15 |
+
"name": "Basic-Start",
|
16 |
+
"terms": "Basic, Start, Simple, Demo",
|
17 |
+
"content": "(masterpiece, best quality, high quality, highres), 1girl, extremely beautiful detailed face, short curly hair, light smile, flower dress, outdoors, leaf, tree, best shadow",
|
18 |
+
"color": 5
|
19 |
+
},
|
20 |
+
{
|
21 |
+
"name": "Fancy-FireMagic",
|
22 |
+
"terms": "Fire, Magic, Fancy",
|
23 |
+
"content": "(extremely detailed CG unity 8k wallpaper), (masterpiece), (best quality), (ultra-detailed), (best illustration),(best shadow), (an extremely delicate and beautiful), dynamic angle, floating, fine detail, (bloom), (shine), glinting stars, classic, (painting), (sketch),\n\na girl, solo, bare shoulders, flat_chest, diamond and glaring eyes, beautiful detailed cold face, very long blue and sliver hair, floating black feathers, wavy hair, extremely delicate and beautiful girls, beautiful detailed eyes, glowing eyes,\n\npalace, the best building, ((Fire butterflies, Flying sparks, Flames))",
|
24 |
+
"color": 5
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"name": "Fancy-WaterMagic",
|
28 |
+
"terms": "Water, Magic, Fancy",
|
29 |
+
"content": "(extremely detailed CG unity 8k wallpaper), (masterpiece), (best quality), (ultra-detailed), (best illustration),(best shadow), (an extremely delicate and beautiful), classic, dynamic angle, floating, fine detail, Depth of field, classic, (painting), (sketch), (bloom), (shine), glinting stars,\n\na girl, solo, bare shoulders, flat chest, diamond and glaring eyes, beautiful detailed cold face, very long blue and sliver hair, floating black feathers, wavy hair, extremely delicate and beautiful girls, beautiful detailed eyes, glowing eyes,\n\nriver, (forest),palace, (fairyland,feather,flowers, nature),(sunlight),Hazy fog, mist",
|
30 |
+
"color": 5
|
31 |
+
}
|
32 |
+
]
|
tagcomplete/tags/e621.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tagcomplete/tags/e621_sfw.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
tagcomplete/tags/extra-quality-tags.csv
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
masterpiece,5,Quality tag,,
|
2 |
+
best_quality,5,Quality tag,,
|
3 |
+
high_quality,5,Quality tag,,
|
4 |
+
normal_quality,5,Quality tag,,
|
5 |
+
low_quality,5,Quality tag,,
|
6 |
+
worst_quality,5,Quality tag,,
|