mirror of
https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git
synced 2026-01-27 03:29:55 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a95f422f10 | ||
|
|
5640a438da | ||
|
|
3169c4c653 | ||
|
|
bba5d6b1c0 | ||
|
|
7fbfb7dd43 | ||
|
|
2e93691305 | ||
|
|
eaba97dc3a | ||
|
|
5e453efc2b | ||
|
|
13c7f31bba | ||
|
|
c4c588c1d4 | ||
|
|
1d40449942 | ||
|
|
495feb026c | ||
|
|
b59123f6e6 | ||
|
|
79b670eaea | ||
|
|
6b51dc806b | ||
|
|
5b9af499c3 | ||
|
|
aed449c882 | ||
|
|
a724da362c | ||
|
|
cc67adf82f | ||
|
|
c1d610e390 | ||
|
|
7fabc84a1e | ||
|
|
5d5db7bafe | ||
|
|
40edb89974 | ||
|
|
f9f7732c69 | ||
|
|
e7af9dbfba | ||
|
|
64cf9b2159 | ||
|
|
ba38d1b893 | ||
|
|
4442cb78ec | ||
|
|
6b42efaa40 | ||
|
|
47e0c15835 | ||
|
|
68b4224f37 | ||
|
|
fc6c1ff579 | ||
|
|
50b33b987a | ||
|
|
ea24f7657a | ||
|
|
1e9431faba | ||
|
|
664ae50c1a | ||
|
|
43243a9bf1 | ||
|
|
8912957a26 | ||
|
|
5fe5398b94 | ||
|
|
52f92e4d42 | ||
|
|
0e177d0945 | ||
|
|
94365630c7 | ||
|
|
d4941c7b73 | ||
|
|
91fb1cba38 | ||
|
|
3169420fd3 | ||
|
|
84b6a0394e | ||
|
|
38fd2523e6 | ||
|
|
85db4a61df | ||
|
|
b18823e88f | ||
|
|
83461e2f54 | ||
|
|
a2e7b6bf6c | ||
|
|
f4572469c1 | ||
|
|
672d409e46 | ||
|
|
3b51035c26 | ||
|
|
dcc6602056 | ||
|
|
d1357cddc1 | ||
|
|
11d94e11f9 | ||
|
|
5fbc18ed1d | ||
|
|
223abf5420 | ||
|
|
4331bdccda |
41
README.md
41
README.md
@@ -78,6 +78,46 @@ For example:
|
||||
|
||||

|
||||
|
||||
### Chants
|
||||
Chants are longer prompt presets. The name is inspired by some early prompt collections from Chinese users, which often were called along the lines of "Spellbook", "Codex", etc. The prompt snippets from such documents were fittingly called spells or chants for this reason.
|
||||
|
||||
Similar to embeddings and loras, this feature is triggered by typing the `<`, `<c:` or `<chant:` commands. For instance, when you enter `<c:HighQuality` in the prompt box and select it, the following prompt text will be inserted:
|
||||
```
|
||||
(masterpiece, best quality, high quality, highres, ultra-detailed),
|
||||
```
|
||||
|
||||
|
||||
Chants can be added in JSON files following this format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "Basic-NegativePrompt",
|
||||
"terms": "Negative,Low,Quality",
|
||||
"content": "(worst quality, low quality, normal quality)",
|
||||
"color": 3
|
||||
},
|
||||
{
|
||||
"name": "Basic-HighQuality",
|
||||
"terms": "Best,High,Quality",
|
||||
"content": "(masterpiece, best quality, high quality, highres, ultra-detailed)",
|
||||
"color": 1
|
||||
},
|
||||
{
|
||||
"name": "Basic-Start",
|
||||
"terms": "Basic, Start, Simple, Demo",
|
||||
"content": "(masterpiece, best quality, high quality, highres), 1girl, extremely beautiful detailed face, ...",
|
||||
"color": 5
|
||||
}
|
||||
]
|
||||
```
|
||||
The file can then be selected using the "Chant file" settings dropdown if it is located inside the extension's `tags` folder.
|
||||
|
||||
A chant object has four fields:
|
||||
- `name` - Display name
|
||||
- `terms` - Search terms
|
||||
- `content` - The actual prompt content
|
||||
- `color` - Color, using the same category color system as normal tags
|
||||
|
||||
### Umi AI tags
|
||||
https://github.com/Klokinator/Umi-AI is a feature-rich wildcard extension similar to Unprompted or Dynamic Wildcards.
|
||||
In recent releases, it uses YAML-based wildcard tags to enable a complex chaining system,for example `<[preset][--female][sfw][species]>` will choose the preset category, exclude female related tags, further narrow it down with the following categories, and then choose one random fill-in matching all these criteria at runtime. Completion is triggered by `<[` and then each following new unclosed bracket, e.g. `<[xyz][`, until closed by `>`.
|
||||
@@ -109,6 +149,7 @@ The extension has a large amount of configuration & customizability built in:
|
||||
| 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 below. |
|
||||
| chantFile | The file to use for chants (longer prompt presets / shortcuts). |
|
||||
| keymap | Customizable hotkeys. |
|
||||
| colors | Customizable tag colors. More info below. |
|
||||
### Colors
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Core components
|
||||
var CFG = null;
|
||||
var TAC_CFG = null;
|
||||
var tagBasePath = "";
|
||||
|
||||
// Tag completion data loaded from files
|
||||
@@ -13,6 +13,8 @@ var yamlWildcards = [];
|
||||
var embeddings = [];
|
||||
var hypernetworks = [];
|
||||
var loras = [];
|
||||
var lycos = [];
|
||||
var chants = [];
|
||||
|
||||
// Selected model info for black/whitelisting
|
||||
var currentModelHash = "";
|
||||
|
||||
@@ -9,7 +9,9 @@ const ResultType = Object.freeze({
|
||||
"wildcardFile": 5,
|
||||
"yamlWildcard": 6,
|
||||
"hypernetwork": 7,
|
||||
"lora": 8
|
||||
"lora": 8,
|
||||
"lyco": 9,
|
||||
"chant": 10
|
||||
});
|
||||
|
||||
// Class to hold result data and annotations to make it clearer to use
|
||||
|
||||
@@ -22,24 +22,58 @@ const thirdParty = {
|
||||
"Edit Caption",
|
||||
"Edit Tags"
|
||||
]
|
||||
},
|
||||
"image browser": {
|
||||
"base": "#tab_image_browser",
|
||||
"hasIds": false,
|
||||
"selectors": [
|
||||
"Filename keyword search",
|
||||
"EXIF keyword search"
|
||||
]
|
||||
},
|
||||
"tab_tagger": {
|
||||
"base": "#tab_tagger",
|
||||
"hasIds": false,
|
||||
"selectors": [
|
||||
"Additional tags (split by comma)",
|
||||
"Exclude tags (split by comma)"
|
||||
]
|
||||
},
|
||||
"tiled-diffusion-t2i": {
|
||||
"base": "#txt2img_script_container",
|
||||
"hasIds": true,
|
||||
"onDemand": true,
|
||||
"selectors": [
|
||||
"[id^=MD-t2i][id$=prompt] textarea",
|
||||
"[id^=MD-t2i][id$=prompt] input[type='text']"
|
||||
]
|
||||
},
|
||||
"tiled-diffusion-i2i": {
|
||||
"base": "#img2img_script_container",
|
||||
"hasIds": true,
|
||||
"onDemand": true,
|
||||
"selectors": [
|
||||
"[id^=MD-i2i][id$=prompt] textarea",
|
||||
"[id^=MD-i2i][id$=prompt] input[type='text']"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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")];
|
||||
|
||||
|
||||
let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")];
|
||||
|
||||
// Filter the text areas where the adjacent label matches one of the selectors
|
||||
let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText)));
|
||||
textAreas = textAreas.concat(matchingTextAreas);
|
||||
@@ -49,6 +83,55 @@ function getTextAreas() {
|
||||
return textAreas;
|
||||
}
|
||||
|
||||
function addOnDemandObservers(setupFunction) {
|
||||
for (const [key, entry] of Object.entries(thirdParty)) {
|
||||
if (!entry.onDemand) continue;
|
||||
|
||||
let base = gradioApp().querySelector(entry.base);
|
||||
if (!base) continue;
|
||||
|
||||
let accordions = [...base?.querySelectorAll(".gradio-accordion")];
|
||||
if (!accordions) continue;
|
||||
|
||||
accordions.forEach(acc => {
|
||||
let accObserver = new MutationObserver((mutationList, observer) => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "childList") {
|
||||
let newChildren = mutation.addedNodes;
|
||||
if (!newChildren) {
|
||||
accObserver.disconnect();
|
||||
continue;
|
||||
}
|
||||
|
||||
newChildren.forEach(child => {
|
||||
if (child.classList.contains("gradio-accordion") || child.querySelector(".gradio-accordion")) {
|
||||
let newAccordions = [...child.querySelectorAll(".gradio-accordion")];
|
||||
newAccordions.forEach(nAcc => accObserver.observe(nAcc, { childList: true }));
|
||||
}
|
||||
});
|
||||
|
||||
if (entry.hasIds) { // If the entry has proper ids, we can just select them
|
||||
[...gradioApp().querySelectorAll(entry.selectors.join(", "))].forEach(x => setupFunction(x));
|
||||
} else { // Otherwise, we have to find the text areas by their adjacent labels
|
||||
let base = gradioApp().querySelector(entry.base);
|
||||
|
||||
// Safety check
|
||||
if (!base) continue;
|
||||
|
||||
let allTextAreas = [...base.querySelectorAll("textarea, input[type='text']")];
|
||||
|
||||
// Filter the text areas where the adjacent label matches one of the selectors
|
||||
let matchingTextAreas = allTextAreas.filter(ta => [...ta.parentElement.childNodes].some(x => entry.selectors.includes(x.innerText)));
|
||||
matchingTextAreas.forEach(x => setupFunction(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
accObserver.observe(acc, { childList: true });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const thirdPartyIdSet = new Set();
|
||||
// Get the identifier for the text area to differentiate between positive and negative
|
||||
function getTextAreaIdentifier(textArea) {
|
||||
|
||||
@@ -89,6 +89,14 @@ function difference(a, b) {
|
||||
)].reduce((acc, [v, count]) => acc.concat(Array(Math.abs(count)).fill(v)), []);
|
||||
}
|
||||
|
||||
// Sliding window function to get possible combination groups of an array
|
||||
function toNgrams(inputArray, size) {
|
||||
return Array.from(
|
||||
{ length: inputArray.length - (size - 1) }, //get the appropriate length
|
||||
(_, index) => inputArray.slice(index, index + size) //create the windows
|
||||
);
|
||||
}
|
||||
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
@@ -98,6 +106,41 @@ function escapeHTML(unsafeText) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// For black/whitelisting
|
||||
function updateModelName() {
|
||||
let sdm = gradioApp().querySelector("#setting_sd_model_checkpoint");
|
||||
let modelDropdown = sdm?.querySelector("input") || sdm?.querySelector("select");
|
||||
if (modelDropdown) {
|
||||
currentModelName = modelDropdown.value;
|
||||
} else {
|
||||
// Fallback for intermediate versions
|
||||
modelDropdown = sdm?.querySelector("span.single-select");
|
||||
currentModelName = modelDropdown?.textContent || "";
|
||||
}
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/61975440, how to detect JS value changes
|
||||
function observeElement(element, property, callback, delay = 0) {
|
||||
let elementPrototype = Object.getPrototypeOf(element);
|
||||
if (elementPrototype.hasOwnProperty(property)) {
|
||||
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
|
||||
Object.defineProperty(element, property, {
|
||||
get: function() {
|
||||
return descriptor.get.apply(this, arguments);
|
||||
},
|
||||
set: function () {
|
||||
let oldValue = this[property];
|
||||
descriptor.set.apply(this, arguments);
|
||||
let newValue = this[property];
|
||||
if (typeof callback == "function") {
|
||||
setTimeout(callback.bind(this, oldValue, newValue), delay);
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Queue calling function to process global queues
|
||||
async function processQueue(queue, context, ...args) {
|
||||
for (let i = 0; i < queue.length; i++) {
|
||||
|
||||
54
javascript/ext_chants.js
Normal file
54
javascript/ext_chants.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const CHANT_REGEX = /<(?!e:|h:|l:)[^,> ]*>?/g;
|
||||
const CHANT_TRIGGER = () => TAC_CFG.chantFile && TAC_CFG.chantFile !== "None" && tagword.match(CHANT_REGEX);
|
||||
|
||||
class ChantParser extends BaseTagParser {
|
||||
parse() {
|
||||
// Show Chant
|
||||
let tempResults = [];
|
||||
if (tagword !== "<" && tagword !== "<c:") {
|
||||
let searchTerm = tagword.replace("<chant:", "").replace("<c:", "").replace("<", "");
|
||||
let filterCondition = x => x.terms.toLowerCase().includes(searchTerm) || x.name.toLowerCase().includes(searchTerm);
|
||||
tempResults = chants.filter(x => filterCondition(x)); // Filter by tagword
|
||||
} else {
|
||||
tempResults = chants;
|
||||
}
|
||||
|
||||
// Add final results
|
||||
let finalResults = [];
|
||||
tempResults.forEach(t => {
|
||||
let result = new AutocompleteResult(t.content.trim(), ResultType.chant)
|
||||
result.meta = "Chant";
|
||||
result.aliases = t.name;
|
||||
result.category = t.color;
|
||||
finalResults.push(result);
|
||||
});
|
||||
|
||||
return finalResults;
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (TAC_CFG.chantFile && TAC_CFG.chantFile !== "None") {
|
||||
try {
|
||||
chants = await readFile(`${tagBasePath}/${TAC_CFG.chantFile}?`, true);
|
||||
} catch (e) {
|
||||
console.error("Error loading chants.json: " + e);
|
||||
}
|
||||
} else {
|
||||
chants = [];
|
||||
}
|
||||
}
|
||||
|
||||
function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.chant) {
|
||||
return text.replace(/^.*?: /g, "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
PARSERS.push(new ChantParser(CHANT_TRIGGER));
|
||||
|
||||
// Add our utility functions to their respective queues
|
||||
QUEUE_FILE_LOAD.push(load);
|
||||
QUEUE_SANITIZE.push(sanitize);
|
||||
QUEUE_AFTER_CONFIG_CHANGE.push(load);
|
||||
@@ -1,5 +1,5 @@
|
||||
const EMB_REGEX = /<(?!l:|h:)[^,> ]*>?/g;
|
||||
const EMB_TRIGGER = () => CFG.useEmbeddings && tagword.match(EMB_REGEX);
|
||||
const EMB_REGEX = /<(?!l:|h:|c:)[^,> ]*>?/g;
|
||||
const EMB_TRIGGER = () => TAC_CFG.useEmbeddings && tagword.match(EMB_REGEX);
|
||||
|
||||
class EmbeddingParser extends BaseTagParser {
|
||||
parse() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const HYP_REGEX = /<(?!e:|l:)[^,> ]*>?/g;
|
||||
const HYP_TRIGGER = () => CFG.useHypernetworks && tagword.match(HYP_REGEX);
|
||||
const HYP_REGEX = /<(?!e:|l:|c:)[^,> ]*>?/g;
|
||||
const HYP_TRIGGER = () => TAC_CFG.useHypernetworks && tagword.match(HYP_REGEX);
|
||||
|
||||
class HypernetParser extends BaseTagParser {
|
||||
parse() {
|
||||
@@ -39,7 +39,7 @@ async function load() {
|
||||
|
||||
function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.hypernetwork) {
|
||||
return `<hypernet:${text}:${CFG.extraNetworksDefaultMultiplier}>`;
|
||||
return `<hypernet:${text}:${TAC_CFG.extraNetworksDefaultMultiplier}>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const LORA_REGEX = /<(?!e:|h:)[^,> ]*>?/g;
|
||||
const LORA_TRIGGER = () => CFG.useLoras && tagword.match(LORA_REGEX);
|
||||
const LORA_REGEX = /<(?!e:|h:|c:)[^,> ]*>?/g;
|
||||
const LORA_TRIGGER = () => TAC_CFG.useLoras && tagword.match(LORA_REGEX);
|
||||
|
||||
class LoraParser extends BaseTagParser {
|
||||
parse() {
|
||||
@@ -39,7 +39,7 @@ async function load() {
|
||||
|
||||
function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.lora) {
|
||||
return `<lora:${text}:${CFG.extraNetworksDefaultMultiplier}>`;
|
||||
return `<lora:${text}:${TAC_CFG.extraNetworksDefaultMultiplier}>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
51
javascript/ext_lycos.js
Normal file
51
javascript/ext_lycos.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const LYCO_REGEX = /<(?!e:|h:|c:)[^,> ]*>?/g;
|
||||
const LYCO_TRIGGER = () => TAC_CFG.useLycos && tagword.match(LYCO_REGEX);
|
||||
|
||||
class LycoParser extends BaseTagParser {
|
||||
parse() {
|
||||
// Show lyco
|
||||
let tempResults = [];
|
||||
if (tagword !== "<" && tagword !== "<l:" && tagword !== "<lyco:") {
|
||||
let searchTerm = tagword.replace("<lyco:", "").replace("<l:", "").replace("<", "");
|
||||
let filterCondition = x => x.toLowerCase().includes(searchTerm) || x.toLowerCase().replaceAll(" ", "_").includes(searchTerm);
|
||||
tempResults = lycos.filter(x => filterCondition(x)); // Filter by tagword
|
||||
} else {
|
||||
tempResults = lycos;
|
||||
}
|
||||
|
||||
// Add final results
|
||||
let finalResults = [];
|
||||
tempResults.forEach(t => {
|
||||
let result = new AutocompleteResult(t.trim(), ResultType.lyco)
|
||||
result.meta = "Lyco";
|
||||
finalResults.push(result);
|
||||
});
|
||||
|
||||
return finalResults;
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (lycos.length === 0) {
|
||||
try {
|
||||
lycos = (await readFile(`${tagBasePath}/temp/lyco.txt`)).split("\n")
|
||||
.filter(x => x.trim().length > 0) // Remove empty lines
|
||||
.map(x => x.trim()); // Remove carriage returns and padding if it exists
|
||||
} catch (e) {
|
||||
console.error("Error loading lyco.txt: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.lyco) {
|
||||
return `<lyco:${text}:${TAC_CFG.extraNetworksDefaultMultiplier}>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
PARSERS.push(new LycoParser(LYCO_TRIGGER));
|
||||
|
||||
// Add our utility functions to their respective queues
|
||||
QUEUE_FILE_LOAD.push(load);
|
||||
QUEUE_SANITIZE.push(sanitize);
|
||||
@@ -1,7 +1,7 @@
|
||||
const UMI_PROMPT_REGEX = /<[^\s]*?\[[^,<>]*[\]|]?>?/gi;
|
||||
const UMI_TAG_REGEX = /(?:\[|\||--)([^<>\[\]\-|]+)/gi;
|
||||
|
||||
const UMI_TRIGGER = () => CFG.useWildcards && [...tagword.matchAll(UMI_PROMPT_REGEX)].length > 0;
|
||||
const UMI_TRIGGER = () => TAC_CFG.useWildcards && [...tagword.matchAll(UMI_PROMPT_REGEX)].length > 0;
|
||||
|
||||
class UmiParser extends BaseTagParser {
|
||||
parse(textArea, prompt) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
const WC_REGEX = /\b__([^,]+)__([^, ]*)\b/g;
|
||||
|
||||
// Trigger conditions
|
||||
const WC_TRIGGER = () => CFG.useWildcards && [...tagword.matchAll(WC_REGEX)].length > 0;
|
||||
const WC_FILE_TRIGGER = () => CFG.useWildcards && (tagword.startsWith("__") && !tagword.endsWith("__") || tagword === "__");
|
||||
const WC_TRIGGER = () => TAC_CFG.useWildcards && [...tagword.matchAll(WC_REGEX)].length > 0;
|
||||
const WC_FILE_TRIGGER = () => TAC_CFG.useWildcards && (tagword.startsWith("__") && !tagword.endsWith("__") || tagword === "__");
|
||||
|
||||
class WildcardParser extends BaseTagParser {
|
||||
async parse() {
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"--meta-text-color": ["#6b6f7b", "#a2a9b4"],
|
||||
"--embedding-v1-color": ["lightsteelblue", "#2b5797"],
|
||||
"--embedding-v2-color": ["skyblue", "#2d89ef"],
|
||||
"--live-translation-rt": ["whitesmoke", "#222"],
|
||||
"--live-translation-color-1": ["lightskyblue", "#2d89ef"],
|
||||
"--live-translation-color-2": ["palegoldenrod", "#eb5700"],
|
||||
"--live-translation-color-3": ["darkseagreen", "darkgreen"],
|
||||
}
|
||||
const browserVars = {
|
||||
"--results-overflow-y": {
|
||||
@@ -20,10 +24,6 @@ const autocompleteCSS = `
|
||||
#quicksettings [id^=setting_tac] {
|
||||
background-color: transparent;
|
||||
min-width: fit-content;
|
||||
align-self: center;
|
||||
}
|
||||
#quicksettings [id^=setting_tac] > label > span {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.autocompleteResults {
|
||||
position: absolute;
|
||||
@@ -78,6 +78,39 @@ const autocompleteCSS = `
|
||||
.acListItem.acEmbeddingV2 {
|
||||
color: var(--embedding-v2-color);
|
||||
}
|
||||
.acRuby {
|
||||
padding: var(--input-padding);
|
||||
color: #888;
|
||||
font-size: 0.8rem;
|
||||
user-select: none;
|
||||
}
|
||||
.acRuby > ruby {
|
||||
display: inline-flex;
|
||||
flex-direction: column-reverse;
|
||||
margin-top: 0.5rem;
|
||||
vertical-align: bottom;
|
||||
cursor: pointer;
|
||||
}
|
||||
.acRuby > ruby::hover {
|
||||
text-decoration: underline;
|
||||
text-shadow: 0 0 10px var(--live-translation-color-1);
|
||||
}
|
||||
.acRuby > :nth-child(3n+1) {
|
||||
color: var(--live-translation-color-1);
|
||||
}
|
||||
.acRuby > :nth-child(3n+2) {
|
||||
color: var(--live-translation-color-2);
|
||||
}
|
||||
.acRuby > :nth-child(3n+3) {
|
||||
color: var(--live-translation-color-3);
|
||||
}
|
||||
.acRuby > ruby > rt {
|
||||
line-height: 1rem;
|
||||
padding: 0px 5px 0px 0px;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
color: var(--live-translation-rt);
|
||||
}
|
||||
`;
|
||||
|
||||
async function loadTags(c) {
|
||||
@@ -90,9 +123,17 @@ async function loadTags(c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await loadExtraTags(c);
|
||||
}
|
||||
|
||||
async function loadExtraTags(c) {
|
||||
if (c.extra.extraFile && c.extra.extraFile !== "None") {
|
||||
try {
|
||||
extras = await loadCSV(`${tagBasePath}/${c.extra.extraFile}`);
|
||||
// Add translations to the main translation map for extra tags that have them
|
||||
extras.forEach(e => {
|
||||
if (e[4]) translations.set(e[0], e[4]);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Error loading extra file: " + e);
|
||||
return;
|
||||
@@ -141,6 +182,7 @@ async function syncOptions() {
|
||||
useEmbeddings: opts["tac_useEmbeddings"],
|
||||
useHypernetworks: opts["tac_useHypernetworks"],
|
||||
useLoras: opts["tac_useLoras"],
|
||||
useLycos: opts["tac_useLycos"],
|
||||
showWikiLinks: opts["tac_showWikiLinks"],
|
||||
// Insertion related settings
|
||||
replaceUnderscores: opts["tac_replaceUnderscores"],
|
||||
@@ -156,42 +198,55 @@ async function syncOptions() {
|
||||
translationFile: opts["tac_translation.translationFile"],
|
||||
oldFormat: opts["tac_translation.oldFormat"],
|
||||
searchByTranslation: opts["tac_translation.searchByTranslation"],
|
||||
liveTranslation: opts["tac_translation.liveTranslation"],
|
||||
},
|
||||
// Extra file settings
|
||||
extra: {
|
||||
extraFile: opts["tac_extra.extraFile"],
|
||||
addMode: opts["tac_extra.addMode"]
|
||||
},
|
||||
// Chant file settings
|
||||
chantFile: opts["tac_chantFile"],
|
||||
// Settings not from tac but still used by the script
|
||||
extraNetworksDefaultMultiplier: opts["extra_networks_default_multiplier"],
|
||||
extraNetworksSeparator: opts["extra_networks_add_text_separator"],
|
||||
// Custom mapping settings
|
||||
keymap: JSON.parse(opts["tac_keymap"]),
|
||||
colorMap: JSON.parse(opts["tac_colormap"])
|
||||
}
|
||||
|
||||
if (newCFG.alias.onlyShowAlias) {
|
||||
newCFG.alias.searchByAlias = true; // if only show translation, enable search by translation is necessary
|
||||
}
|
||||
|
||||
// Reload tags if the tag file changed
|
||||
if (!CFG || newCFG.tagFile !== CFG.tagFile || newCFG.extra.extraFile !== CFG.extra.extraFile) {
|
||||
// Reload translations if the translation file changed
|
||||
if (!TAC_CFG || newCFG.translation.translationFile !== TAC_CFG.translation.translationFile) {
|
||||
translations.clear();
|
||||
await loadTranslations(newCFG);
|
||||
await loadExtraTags(newCFG);
|
||||
}
|
||||
// Reload tags if the tag file changed (after translations so extra tag translations get re-added)
|
||||
if (!TAC_CFG || newCFG.tagFile !== TAC_CFG.tagFile || newCFG.extra.extraFile !== TAC_CFG.extra.extraFile) {
|
||||
allTags = [];
|
||||
await loadTags(newCFG);
|
||||
}
|
||||
// Reload translations if the translation file changed
|
||||
if (!CFG || newCFG.translation.translationFile !== CFG.translation.translationFile) {
|
||||
translations.clear();
|
||||
await loadTranslations(newCFG);
|
||||
}
|
||||
|
||||
// Update CSS if maxResults changed
|
||||
if (CFG && newCFG.maxResults !== CFG.maxResults) {
|
||||
if (TAC_CFG && newCFG.maxResults !== TAC_CFG.maxResults) {
|
||||
gradioApp().querySelectorAll(".autocompleteResults").forEach(r => {
|
||||
r.style.maxHeight = `${newCFG.maxResults * 50}px`;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove ruby div if live preview was disabled
|
||||
if (newCFG.translation.liveTranslation === false) {
|
||||
[...gradioApp().querySelectorAll('.acRuby')].forEach(r => {
|
||||
r.remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
CFG = newCFG;
|
||||
TAC_CFG = newCFG;
|
||||
|
||||
// Callback
|
||||
await processQueue(QUEUE_AFTER_CONFIG_CHANGE, null);
|
||||
@@ -205,7 +260,7 @@ function createResultsDiv(textArea) {
|
||||
let textAreaId = getTextAreaIdentifier(textArea);
|
||||
let typeClass = textAreaId.replaceAll(".", " ");
|
||||
|
||||
resultsDiv.style.maxHeight = `${CFG.maxResults * 50}px`;
|
||||
resultsDiv.style.maxHeight = `${TAC_CFG.maxResults * 50}px`;
|
||||
resultsDiv.setAttribute("class", `autocompleteResults ${typeClass} notranslate`);
|
||||
resultsDiv.setAttribute("translate", "no");
|
||||
resultsList.setAttribute("class", "autocompleteResultsList");
|
||||
@@ -225,7 +280,7 @@ function showResults(textArea) {
|
||||
let resultsDiv = gradioApp().querySelector('.autocompleteResults' + textAreaId);
|
||||
resultsDiv.style.display = "block";
|
||||
|
||||
if (CFG.slidingPopup) {
|
||||
if (TAC_CFG.slidingPopup) {
|
||||
let caretPosition = getCaretCoordinates(textArea, textArea.selectionEnd).left;
|
||||
let offset = Math.min(textArea.offsetLeft - textArea.scrollLeft + caretPosition, textArea.offsetWidth - resultsDiv.offsetWidth);
|
||||
|
||||
@@ -234,6 +289,8 @@ function showResults(textArea) {
|
||||
if (resultsDiv.style.left)
|
||||
resultsDiv.style.removeProperty("left");
|
||||
}
|
||||
// Reset here too to make absolutely sure the browser registers it
|
||||
resultsDiv.scrollTop = 0;
|
||||
}
|
||||
function hideResults(textArea) {
|
||||
let textAreaId = getTextAreaIdentifier(textArea);
|
||||
@@ -247,18 +304,18 @@ function hideResults(textArea) {
|
||||
|
||||
// Function to check activation criteria
|
||||
function isEnabled() {
|
||||
if (CFG.activeIn.global) {
|
||||
if (TAC_CFG.activeIn.global) {
|
||||
// Skip check if the current model was not correctly detected, since it could wrongly disable the script otherwise
|
||||
if (!currentModelName || !currentModelHash) return true;
|
||||
|
||||
let modelList = CFG.activeIn.modelList
|
||||
let modelList = TAC_CFG.activeIn.modelList
|
||||
.split(",")
|
||||
.map(x => x.trim())
|
||||
.filter(x => x.length > 0);
|
||||
|
||||
let shortHash = currentModelHash.substring(0, 10);
|
||||
let modelNameWithoutHash = currentModelName.replace(/\[.*\]$/g, "").trim();
|
||||
if (CFG.activeIn.modelListMode.toLowerCase() === "blacklist") {
|
||||
if (TAC_CFG.activeIn.modelListMode.toLowerCase() === "blacklist") {
|
||||
// If the current model is in the blacklist, disable
|
||||
return modelList.filter(x => x === currentModelName || x === modelNameWithoutHash || x === currentModelHash || x === shortHash).length === 0;
|
||||
} else {
|
||||
@@ -275,6 +332,7 @@ const WEIGHT_REGEX = /[([]([^()[\]:|]+)(?::(?:\d+(?:\.\d+)?|\.\d+))?[)\]]/g;
|
||||
const POINTY_REGEX = /<[^\s,<](?:[^\t\n\r,<>]*>|[^\t\n\r,> ]*)/g;
|
||||
const COMPLETED_WILDCARD_REGEX = /__[^\s,_][^\t\n\r,_]*[^\s,_]__[^\s,_]*/g;
|
||||
const NORMAL_TAG_REGEX = /[^\s,|<>)\]]+|</g;
|
||||
const RUBY_TAG_REGEX = /[\w\d<][\w\d' \-?!/$%]{2,}>?/g;
|
||||
const TAG_REGEX = new RegExp(`${POINTY_REGEX.source}|${COMPLETED_WILDCARD_REGEX.source}|${NORMAL_TAG_REGEX.source}`, "g");
|
||||
|
||||
// On click, insert the tag into the prompt textbox with respect to the cursor position
|
||||
@@ -291,9 +349,9 @@ async function insertTextAtCursor(textArea, result, tagword) {
|
||||
if (sanitizeResults && sanitizeResults.length > 0) {
|
||||
sanitizedText = sanitizeResults[0];
|
||||
} else {
|
||||
sanitizedText = CFG.replaceUnderscores ? text.replaceAll("_", " ") : text;
|
||||
sanitizedText = TAC_CFG.replaceUnderscores ? text.replaceAll("_", " ") : text;
|
||||
|
||||
if (CFG.escapeParentheses && tagType === ResultType.tag) {
|
||||
if (TAC_CFG.escapeParentheses && tagType === ResultType.tag) {
|
||||
sanitizedText = sanitizedText
|
||||
.replaceAll("(", "\\(")
|
||||
.replaceAll(")", "\\)")
|
||||
@@ -311,18 +369,23 @@ async function insertTextAtCursor(textArea, result, tagword) {
|
||||
let match = surrounding.match(new RegExp(escapeRegExp(`${tagword}`), "i"));
|
||||
let afterInsertCursorPos = editStart + match.index + sanitizedText.length;
|
||||
|
||||
var optionalComma = "";
|
||||
if (CFG.appendComma && ![ResultType.wildcardFile, ResultType.yamlWildcard].includes(tagType)) {
|
||||
optionalComma = surrounding.match(new RegExp(`${escapeRegExp(tagword)}[,:]`, "i")) !== null ? "" : ", ";
|
||||
var optionalSeparator = "";
|
||||
let extraNetworkTypes = [ResultType.hypernetwork, ResultType.lora];
|
||||
let noCommaTypes = [ResultType.wildcardFile, ResultType.yamlWildcard].concat(extraNetworkTypes);
|
||||
if (TAC_CFG.appendComma && !noCommaTypes.includes(tagType)) {
|
||||
optionalSeparator = surrounding.match(new RegExp(`${escapeRegExp(tagword)}[,:]`, "i")) !== null ? "" : ", ";
|
||||
} else if (extraNetworkTypes.includes(tagType)) {
|
||||
// Use the dedicated separator for extra networks if it's defined, otherwise fall back to space
|
||||
optionalSeparator = TAC_CFG.extraNetworksSeparator || " ";
|
||||
}
|
||||
|
||||
// Replace partial tag word with new text, add comma if needed
|
||||
let insert = surrounding.replace(match, sanitizedText + optionalComma);
|
||||
let insert = surrounding.replace(match, sanitizedText + optionalSeparator);
|
||||
|
||||
// Add back start
|
||||
var newPrompt = prompt.substring(0, editStart) + insert + prompt.substring(editEnd);
|
||||
textArea.value = newPrompt;
|
||||
textArea.selectionStart = afterInsertCursorPos + optionalComma.length;
|
||||
textArea.selectionStart = afterInsertCursorPos + optionalSeparator.length;
|
||||
textArea.selectionEnd = textArea.selectionStart
|
||||
|
||||
// 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.
|
||||
@@ -360,15 +423,16 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
if (resetList) {
|
||||
resultsList.innerHTML = "";
|
||||
selectedTag = null;
|
||||
oldSelectedTag = null;
|
||||
resultDiv.scrollTop = 0;
|
||||
resultCount = 0;
|
||||
}
|
||||
|
||||
// Find right colors from config
|
||||
let tagFileName = CFG.tagFile.split(".")[0];
|
||||
let tagColors = CFG.colorMap;
|
||||
let mode = gradioApp().querySelector('.dark') ? 0 : 1;
|
||||
let nextLength = Math.min(results.length, resultCount + CFG.resultStepLength);
|
||||
let tagFileName = TAC_CFG.tagFile.split(".")[0];
|
||||
let tagColors = TAC_CFG.colorMap;
|
||||
let mode = (document.querySelector(".dark") || gradioApp().querySelector(".dark")) ? 0 : 1;
|
||||
let nextLength = Math.min(results.length, resultCount + TAC_CFG.resultStepLength);
|
||||
|
||||
for (let i = resultCount; i < nextLength; i++) {
|
||||
let result = results[i];
|
||||
@@ -388,7 +452,9 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
|
||||
let displayText = "";
|
||||
// If the tag matches the tagword, we don't need to display the alias
|
||||
if (result.aliases && !result.text.includes(tagword)) { // Alias
|
||||
if(result.type === ResultType.chant) {
|
||||
displayText = escapeHTML(result.aliases);
|
||||
} else if (result.aliases && !result.text.includes(tagword)) { // Alias
|
||||
let splitAliases = result.aliases.split(",");
|
||||
let bestAlias = splitAliases.find(a => a.toLowerCase().includes(tagword));
|
||||
|
||||
@@ -409,7 +475,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
if (translations.has(bestAlias) && translations.get(bestAlias) !== bestAlias && bestAlias !== result.text)
|
||||
displayText += `[${translations.get(bestAlias)}]`;
|
||||
|
||||
if (!CFG.alias.onlyShowAlias && result.text !== bestAlias)
|
||||
if (!TAC_CFG.alias.onlyShowAlias && result.text !== bestAlias)
|
||||
displayText += " ➝ " + result.text;
|
||||
} else { // No alias
|
||||
displayText = escapeHTML(result.text);
|
||||
@@ -423,7 +489,7 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
itemText.innerHTML = displayText.replace(tagword, `<b>${tagword}</b>`);
|
||||
|
||||
// Add wiki link if the setting is enabled and a supported tag set loaded
|
||||
if (CFG.showWikiLinks
|
||||
if (TAC_CFG.showWikiLinks
|
||||
&& (result.type === ResultType.tag)
|
||||
&& (tagFileName.toLowerCase().startsWith("danbooru") || tagFileName.toLowerCase().startsWith("e621"))) {
|
||||
let wikiLink = document.createElement("a");
|
||||
@@ -506,8 +572,11 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
}
|
||||
resultCount = nextLength;
|
||||
|
||||
if (resetList)
|
||||
if (resetList) {
|
||||
selectedTag = null;
|
||||
oldSelectedTag = null;
|
||||
resultDiv.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelectionStyle(textArea, newIndex, oldIndex) {
|
||||
@@ -526,12 +595,117 @@ function updateSelectionStyle(textArea, newIndex, oldIndex) {
|
||||
}
|
||||
|
||||
// Set scrolltop to selected item if we are showing more than max results
|
||||
if (items.length > CFG.maxResults) {
|
||||
if (items.length > TAC_CFG.maxResults) {
|
||||
let selected = items[newIndex];
|
||||
resultDiv.scrollTop = selected.offsetTop - resultDiv.offsetTop;
|
||||
}
|
||||
}
|
||||
|
||||
function updateRuby(textArea, prompt) {
|
||||
if (!TAC_CFG.translation.liveTranslation) return;
|
||||
if (!TAC_CFG.translation.translationFile || TAC_CFG.translation.translationFile === "None") return;
|
||||
|
||||
let ruby = gradioApp().querySelector('.acRuby' + getTextAreaIdentifier(textArea));
|
||||
if (!ruby) {
|
||||
let textAreaId = getTextAreaIdentifier(textArea);
|
||||
let typeClass = textAreaId.replaceAll(".", " ");
|
||||
ruby = document.createElement("div");
|
||||
ruby.setAttribute("class", `acRuby${typeClass} notranslate`);
|
||||
textArea.parentNode.appendChild(ruby);
|
||||
}
|
||||
|
||||
ruby.innerText = prompt;
|
||||
|
||||
let bracketEscapedPrompt = prompt.replaceAll("\\(", "$").replaceAll("\\)", "%");
|
||||
|
||||
let rubyTags = bracketEscapedPrompt.match(RUBY_TAG_REGEX);
|
||||
if (!rubyTags) return;
|
||||
|
||||
rubyTags.sort((a, b) => b.length - a.length);
|
||||
rubyTags = new Set(rubyTags);
|
||||
|
||||
const prepareTag = (tag) => {
|
||||
tag = tag.replaceAll("$", "\\(").replaceAll("%", "\\)");
|
||||
|
||||
let unsanitizedTag = tag
|
||||
.replaceAll(" ", "_")
|
||||
.replaceAll("\\(", "(")
|
||||
.replaceAll("\\)", ")");
|
||||
|
||||
const translation = translations?.get(tag) || translations?.get(unsanitizedTag);
|
||||
|
||||
let escapedTag = escapeRegExp(tag);
|
||||
return { tag, escapedTag, translation };
|
||||
}
|
||||
|
||||
const replaceOccurences = (text, tuple) => {
|
||||
let { tag, escapedTag, translation } = tuple;
|
||||
let searchRegex = new RegExp(`(?<!<ruby>)(?:\\b)${escapedTag}(?:\\b|$|(?=[,|: \\t\\n\\r]))(?!<rt>)`, "g");
|
||||
return text.replaceAll(searchRegex, `<ruby>${escapeHTML(tag)}<rt>${translation}</rt></ruby>`);
|
||||
}
|
||||
|
||||
let html = escapeHTML(prompt);
|
||||
|
||||
// First try to find direct matches
|
||||
[...rubyTags].forEach(tag => {
|
||||
let tuple = prepareTag(tag);
|
||||
|
||||
if (tuple.translation) {
|
||||
html = replaceOccurences(html, tuple);
|
||||
} else {
|
||||
let subTags = tuple.tag.split(" ").filter(x => x.trim().length > 0);
|
||||
// Return if there is only one word
|
||||
if (subTags.length === 1) return;
|
||||
|
||||
let subHtml = tag.replaceAll("$", "\\(").replaceAll("%", "\\)");
|
||||
|
||||
let translateNgram = (windows) => {
|
||||
windows.forEach(window => {
|
||||
let combinedTag = window.join(" ");
|
||||
let subTuple = prepareTag(combinedTag);
|
||||
|
||||
if (subTuple.tag.length <= 2) return;
|
||||
|
||||
if (subTuple.translation) {
|
||||
subHtml = replaceOccurences(subHtml, subTuple);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Perform n-gram sliding window search
|
||||
translateNgram(toNgrams(subTags, 3));
|
||||
translateNgram(toNgrams(subTags, 2));
|
||||
translateNgram(toNgrams(subTags, 1));
|
||||
|
||||
let escapedTag = escapeRegExp(tuple.tag);
|
||||
|
||||
let searchRegex = new RegExp(`(?<!<ruby>)(?:\\b)${escapedTag}(?:\\b|$|(?=[,|: \\t\\n\\r]))(?!<rt>)`, "g");
|
||||
html = html.replaceAll(searchRegex, subHtml);
|
||||
}
|
||||
});
|
||||
|
||||
ruby.innerHTML = html;
|
||||
|
||||
// Add listeners for auto selection
|
||||
const childNodes = [...ruby.childNodes];
|
||||
[...ruby.children].forEach(child => {
|
||||
const textBefore = childNodes.slice(0, childNodes.indexOf(child)).map(x => x.childNodes[0]?.textContent || x.textContent).join("")
|
||||
child.onclick = () => rubyTagClicked(child, textBefore, prompt, textArea);
|
||||
});
|
||||
}
|
||||
|
||||
function rubyTagClicked(node, textBefore, prompt, textArea) {
|
||||
let selectionText = node.childNodes[0].textContent;
|
||||
|
||||
// Find start and end position of the tag in the prompt
|
||||
let startPos = prompt.indexOf(textBefore) + textBefore.length;
|
||||
let endPos = startPos + selectionText.length;
|
||||
|
||||
// Select in text area
|
||||
textArea.focus();
|
||||
textArea.setSelectionRange(startPos, endPos);
|
||||
}
|
||||
|
||||
async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
// Return if the function is deactivated in the UI
|
||||
if (!isEnabled()) return;
|
||||
@@ -597,7 +771,11 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
// instead of having them added in the order of the parsers
|
||||
let shouldSort = resultCandidates.length > 1;
|
||||
if (shouldSort) {
|
||||
results = results.sort((a, b) => a.text.localeCompare(b.text));
|
||||
results = results.sort((a, b) => {
|
||||
let sortByA = a.type === ResultType.chant ? a.aliases : a.text;
|
||||
let sortByB = b.type === ResultType.chant ? b.aliases : b.text;
|
||||
return sortByA.localeCompare(sortByB);
|
||||
});
|
||||
|
||||
// Since some tags are kaomoji, we have to add the normal results in some cases
|
||||
if (tagword.startsWith("<") || tagword.startsWith("*<")) {
|
||||
@@ -609,7 +787,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
} else {
|
||||
searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(tagword)}`, 'i');
|
||||
}
|
||||
let genericResults = allTags.filter(x => x[0].toLowerCase().search(searchRegex) > -1).slice(0, CFG.maxResults);
|
||||
let genericResults = allTags.filter(x => x[0].toLowerCase().search(searchRegex) > -1).slice(0, TAC_CFG.maxResults);
|
||||
|
||||
genericResults.forEach(g => {
|
||||
let result = new AutocompleteResult(g[0].trim(), ResultType.tag)
|
||||
@@ -637,11 +815,11 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
|| x[3] && x[3].split(",").some(y => translations.has(y) && translations.get(y).toLowerCase().search(searchRegex) > -1);
|
||||
|
||||
let fil;
|
||||
if (CFG.alias.searchByAlias && CFG.translation.searchByTranslation)
|
||||
if (TAC_CFG.alias.searchByAlias && TAC_CFG.translation.searchByTranslation)
|
||||
fil = (x) => baseFilter(x) || aliasFilter(x) || translationFilter(x);
|
||||
else if (CFG.alias.searchByAlias && !CFG.translation.searchByTranslation)
|
||||
else if (TAC_CFG.alias.searchByAlias && !TAC_CFG.translation.searchByTranslation)
|
||||
fil = (x) => baseFilter(x) || aliasFilter(x);
|
||||
else if (CFG.translation.searchByTranslation && !CFG.alias.searchByAlias)
|
||||
else if (TAC_CFG.translation.searchByTranslation && !TAC_CFG.alias.searchByAlias)
|
||||
fil = (x) => baseFilter(x) || translationFilter(x);
|
||||
else
|
||||
fil = (x) => baseFilter(x);
|
||||
@@ -656,7 +834,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
});
|
||||
|
||||
// Add extras
|
||||
if (CFG.extra.extraFile) {
|
||||
if (TAC_CFG.extra.extraFile) {
|
||||
let extraResults = [];
|
||||
|
||||
extras.filter(fil).forEach(e => {
|
||||
@@ -667,7 +845,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
extraResults.push(result);
|
||||
});
|
||||
|
||||
if (CFG.extra.addMode === "Insert before") {
|
||||
if (TAC_CFG.extra.addMode === "Insert before") {
|
||||
results = extraResults.concat(results);
|
||||
} else {
|
||||
results = results.concat(extraResults);
|
||||
@@ -675,8 +853,8 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
}
|
||||
|
||||
// Slice if the user has set a max result count
|
||||
if (!CFG.showAllResults) {
|
||||
results = results.slice(0, CFG.maxResults);
|
||||
if (!TAC_CFG.showAllResults) {
|
||||
results = results.slice(0, TAC_CFG.maxResults);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +873,7 @@ function navigateInList(textArea, event) {
|
||||
// Return if the function is deactivated in the UI or the current model is excluded due to white/blacklist settings
|
||||
if (!isEnabled()) return;
|
||||
|
||||
let keys = CFG.keymap;
|
||||
let keys = TAC_CFG.keymap;
|
||||
|
||||
// Close window if Home or End is pressed while not a keybinding, since it would break completion on leaving the original tag
|
||||
if ((event.key === "Home" || event.key === "End") && !Object.values(keys).includes(event.key)) {
|
||||
@@ -767,8 +945,8 @@ function navigateInList(textArea, event) {
|
||||
hideResults(textArea);
|
||||
break;
|
||||
}
|
||||
if (selectedTag === resultCount - 1
|
||||
&& (event.key === keys["MoveUp"] || event.key === keys["MoveDown"] || event.key === keys["JumpToStart"] || event.key === keys["JumpToEnd"])) {
|
||||
let moveKeys = [keys["MoveUp"], keys["MoveDown"], keys["JumpUp"], keys["JumpDown"], keys["JumpToStart"], keys["JumpToEnd"]];
|
||||
if (selectedTag === resultCount - 1 && moveKeys.includes(event.key)) {
|
||||
addResultsToList(textArea, results, tagword, false);
|
||||
}
|
||||
// Update highlighting
|
||||
@@ -780,6 +958,45 @@ function navigateInList(textArea, event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function addAutocompleteToArea(area) {
|
||||
// Return if autocomplete is disabled for the current area type in config
|
||||
let textAreaId = getTextAreaIdentifier(area);
|
||||
if ((!TAC_CFG.activeIn.img2img && textAreaId.includes("img2img"))
|
||||
|| (!TAC_CFG.activeIn.txt2img && textAreaId.includes("txt2img"))
|
||||
|| (!TAC_CFG.activeIn.negativePrompts && textAreaId.includes("n"))
|
||||
|| (!TAC_CFG.activeIn.thirdParty && textAreaId.includes("thirdParty"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add listeners once
|
||||
if (!area.classList.contains('autocomplete')) {
|
||||
// Add our new element
|
||||
var resultsDiv = createResultsDiv(area);
|
||||
area.parentNode.insertBefore(resultsDiv, area.nextSibling);
|
||||
// Hide by default so it doesn't show up on page load
|
||||
hideResults(area);
|
||||
|
||||
// Add autocomplete event listener
|
||||
area.addEventListener('input', () => {
|
||||
debounce(autocomplete(area, area.value), TAC_CFG.delayTime);
|
||||
updateRuby(area, area.value);
|
||||
});
|
||||
// Add focusout event listener
|
||||
area.addEventListener('focusout', debounce(() => hideResults(area), 400));
|
||||
// Add up and down arrow event listener
|
||||
area.addEventListener('keydown', (e) => navigateInList(area, e));
|
||||
// CompositionEnd fires after the user has finished IME composing
|
||||
// We need to block hide here to prevent the enter key from insta-closing the results
|
||||
area.addEventListener('compositionend', () => {
|
||||
hideBlocked = true;
|
||||
setTimeout(() => { hideBlocked = false; }, 100);
|
||||
});
|
||||
|
||||
// Add class so we know we've already added the listeners
|
||||
area.classList.add('autocomplete');
|
||||
}
|
||||
}
|
||||
|
||||
// One-time setup, triggered from onUiUpdate
|
||||
async function setup() {
|
||||
// Load external files needed by completion extensions
|
||||
@@ -788,6 +1005,9 @@ async function setup() {
|
||||
// Find all textareas
|
||||
let textAreas = getTextAreas();
|
||||
|
||||
// Add mutation observer to accordions inside a base that has onDemand set to true
|
||||
addOnDemandObservers(addAutocompleteToArea);
|
||||
|
||||
// Add event listener to apply settings button so we can mirror the changes to our internal config
|
||||
let applySettingsButton = gradioApp().querySelector("#tab_settings #settings_submit") || gradioApp().querySelector("#tab_settings > div > .gr-button-primary");
|
||||
applySettingsButton?.addEventListener("click", () => {
|
||||
@@ -798,7 +1018,7 @@ async function setup() {
|
||||
});
|
||||
// Add change listener to our quicksettings to change our internal config without the apply button for them
|
||||
let quicksettings = gradioApp().querySelector('#quicksettings');
|
||||
let commonQueryPart = "[id^=setting_tac] > label >";
|
||||
let commonQueryPart = "[id^=setting_tac] > label";
|
||||
quicksettings?.querySelectorAll(`${commonQueryPart} input, ${commonQueryPart} textarea, ${commonQueryPart} select`).forEach(e => {
|
||||
e.addEventListener("change", () => {
|
||||
setTimeout(async () => {
|
||||
@@ -806,23 +1026,24 @@ async function setup() {
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
quicksettings?.querySelectorAll(`[id^=setting_tac].gradio-dropdown input`).forEach(e => {
|
||||
observeElement(e, "value", () => {
|
||||
setTimeout(async () => {
|
||||
await syncOptions();
|
||||
}, 500);
|
||||
})
|
||||
});
|
||||
|
||||
// Add mutation observer for the model hash text to also allow hash-based blacklist again
|
||||
let modelHashText = gradioApp().querySelector("#sd_checkpoint_hash");
|
||||
updateModelName();
|
||||
if (modelHashText) {
|
||||
currentModelHash = modelHashText.title
|
||||
let modelHashObserver = new MutationObserver((mutationList, observer) => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type === "attributes" && mutation.attributeName === "title") {
|
||||
currentModelHash = mutation.target.title;
|
||||
let modelDropdown = gradioApp().querySelector("#setting_sd_model_checkpoint span.single-select")
|
||||
if (modelDropdown) {
|
||||
currentModelName = modelDropdown.textContent;
|
||||
} else {
|
||||
// Fallback for older versions
|
||||
modelDropdown = gradioApp().querySelector("#setting_sd_model_checkpoint select");
|
||||
currentModelName = modelDropdown.value;
|
||||
}
|
||||
updateModelName();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -833,63 +1054,28 @@ async function setup() {
|
||||
if (textAreas.every(v => v === null || v === undefined)) return;
|
||||
// Already added or unnecessary to add
|
||||
if (gradioApp().querySelector('.autocompleteResults.p')) {
|
||||
if (gradioApp().querySelector('.autocompleteResults.n') || !CFG.activeIn.negativePrompts) {
|
||||
if (gradioApp().querySelector('.autocompleteResults.n') || !TAC_CFG.activeIn.negativePrompts) {
|
||||
return;
|
||||
}
|
||||
} else if (!CFG.activeIn.txt2img && !CFG.activeIn.img2img) {
|
||||
} else if (!TAC_CFG.activeIn.txt2img && !TAC_CFG.activeIn.img2img) {
|
||||
return;
|
||||
}
|
||||
|
||||
textAreas.forEach(area => {
|
||||
// Return if autocomplete is disabled for the current area type in config
|
||||
let textAreaId = getTextAreaIdentifier(area);
|
||||
if ((!CFG.activeIn.img2img && textAreaId.includes("img2img"))
|
||||
|| (!CFG.activeIn.txt2img && textAreaId.includes("txt2img"))
|
||||
|| (!CFG.activeIn.negativePrompts && textAreaId.includes("n"))
|
||||
|| (!CFG.activeIn.thirdParty && textAreaId.includes("thirdParty"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add listeners once
|
||||
if (!area.classList.contains('autocomplete')) {
|
||||
// Add our new element
|
||||
var resultsDiv = createResultsDiv(area);
|
||||
area.parentNode.insertBefore(resultsDiv, area.nextSibling);
|
||||
// Hide by default so it doesn't show up on page load
|
||||
hideResults(area);
|
||||
|
||||
// Add autocomplete event listener
|
||||
area.addEventListener('input', debounce(() => autocomplete(area, area.value), CFG.delayTime));
|
||||
// Add focusout event listener
|
||||
area.addEventListener('focusout', debounce(() => hideResults(area), 400));
|
||||
// Add up and down arrow event listener
|
||||
area.addEventListener('keydown', (e) => navigateInList(area, e));
|
||||
// CompositionEnd fires after the user has finished IME composing
|
||||
// We need to block hide here to prevent the enter key from insta-closing the results
|
||||
area.addEventListener('compositionend', () => {
|
||||
hideBlocked = true;
|
||||
setTimeout(() => { hideBlocked = false; }, 100);
|
||||
});
|
||||
|
||||
// Add class so we know we've already added the listeners
|
||||
area.classList.add('autocomplete');
|
||||
}
|
||||
});
|
||||
textAreas.forEach(area => addAutocompleteToArea(area));
|
||||
|
||||
// Add style to dom
|
||||
let acStyle = document.createElement('style');
|
||||
//let css = gradioApp().querySelector('.dark') ? autocompleteCSS_dark : autocompleteCSS_light;
|
||||
let mode = gradioApp().querySelector('.dark') ? 0 : 1;
|
||||
let mode = (document.querySelector(".dark") || gradioApp().querySelector(".dark")) ? 0 : 1;
|
||||
// Check if we are on webkit
|
||||
let browser = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 ? "firefox" : "other";
|
||||
|
||||
let css = autocompleteCSS;
|
||||
// Replace vars with actual values (can't use actual css vars because of the way we inject the css)
|
||||
Object.keys(styleColors).forEach((key) => {
|
||||
css = css.replace(`var(${key})`, styleColors[key][mode]);
|
||||
css = css.replaceAll(`var(${key})`, styleColors[key][mode]);
|
||||
})
|
||||
Object.keys(browserVars).forEach((key) => {
|
||||
css = css.replace(`var(${key})`, browserVars[key][browser]);
|
||||
css = css.replaceAll(`var(${key})`, browserVars[key][browser]);
|
||||
})
|
||||
|
||||
if (acStyle.styleSheet) {
|
||||
@@ -902,17 +1088,17 @@ async function setup() {
|
||||
// Callback
|
||||
await processQueue(QUEUE_AFTER_SETUP, null);
|
||||
}
|
||||
let loading = false;
|
||||
var tacLoading = false;
|
||||
onUiUpdate(async () => {
|
||||
if (loading) return;
|
||||
if (tacLoading) return;
|
||||
if (Object.keys(opts).length === 0) return;
|
||||
if (CFG) return;
|
||||
loading = true;
|
||||
if (TAC_CFG) return;
|
||||
tacLoading = true;
|
||||
// Get our tag base path from the temp file
|
||||
tagBasePath = await readFile(`tmp/tagAutocompletePath.txt`);
|
||||
// Load config from webui opts
|
||||
await syncOptions();
|
||||
// Rest of setup
|
||||
setup();
|
||||
loading = false;
|
||||
tacLoading = false;
|
||||
});
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# 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
|
||||
import glob
|
||||
from pathlib import Path
|
||||
from modules import scripts, script_callbacks, shared, sd_hijack
|
||||
|
||||
import gradio as gr
|
||||
import yaml
|
||||
from modules import script_callbacks, scripts, sd_hijack, shared
|
||||
|
||||
try:
|
||||
from modules.paths import script_path, extensions_dir
|
||||
from modules.paths import extensions_dir, script_path
|
||||
|
||||
# Webui root path
|
||||
FILE_DIR = Path(script_path)
|
||||
|
||||
@@ -31,6 +34,11 @@ try:
|
||||
LORA_PATH = Path(shared.cmd_opts.lora_dir)
|
||||
except AttributeError:
|
||||
LORA_PATH = None
|
||||
|
||||
try:
|
||||
LYCO_PATH = Path(shared.cmd_opts.lyco_dir)
|
||||
except AttributeError:
|
||||
LYCO_PATH = None
|
||||
|
||||
def find_ext_wildcard_paths():
|
||||
"""Returns the path to the extension wildcards folder"""
|
||||
@@ -154,7 +162,8 @@ 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"}]
|
||||
hyp_paths = [Path(h) for h in glob.glob(HYP_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
all_hypernetworks = [str(h.name) for h in hyp_paths if h.suffix in {".pt"}]
|
||||
# Remove file extensions
|
||||
return sorted([h[:h.rfind('.')] for h in all_hypernetworks], key=lambda x: x.lower())
|
||||
|
||||
@@ -162,10 +171,19 @@ 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"}]
|
||||
lora_paths = [Path(l) for l in glob.glob(LORA_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
all_lora = [str(l.name) for l in lora_paths if l.suffix in {".safetensors", ".ckpt", ".pt"}]
|
||||
# Remove file extensions
|
||||
return sorted([l[:l.rfind('.')] for l in all_lora], key=lambda x: x.lower())
|
||||
|
||||
def get_lyco():
|
||||
"""Write a list of all LyCORIS/LOHA from https://github.com/KohakuBlueleaf/a1111-sd-webui-lycoris"""
|
||||
|
||||
# Get a list of all LyCORIS in the folder
|
||||
lyco_paths = [Path(ly) for ly in glob.glob(LYCO_PATH.joinpath("**/*").as_posix(), recursive=True)]
|
||||
all_lyco = [str(ly.name) for ly in lyco_paths if ly.suffix in {".safetensors", ".ckpt", ".pt"}]
|
||||
# Remove file extensions
|
||||
return sorted([ly[:ly.rfind('.')] for ly in all_lyco], key=lambda x: x.lower())
|
||||
|
||||
def write_tag_base_path():
|
||||
"""Writes the tag base path to a fixed location temporary file"""
|
||||
@@ -188,6 +206,14 @@ def update_tag_files():
|
||||
csv_files = files
|
||||
csv_files_withnone = ["None"] + files
|
||||
|
||||
json_files = []
|
||||
json_files_withnone = []
|
||||
def update_json_files():
|
||||
"""Returns a list of all potential json files"""
|
||||
global json_files, json_files_withnone
|
||||
files = [str(j.relative_to(TAGS_PATH)) for j in TAGS_PATH.glob("*.json")]
|
||||
json_files = files
|
||||
json_files_withnone = ["None"] + files
|
||||
|
||||
|
||||
# Write the tag base path to a fixed location temporary file
|
||||
@@ -197,6 +223,7 @@ if not STATIC_TEMP_PATH.exists():
|
||||
|
||||
write_tag_base_path()
|
||||
update_tag_files()
|
||||
update_json_files()
|
||||
|
||||
# Check if the temp path exists and create it if not
|
||||
if not TEMP_PATH.exists():
|
||||
@@ -209,6 +236,7 @@ write_to_temp_file('wce.txt', [])
|
||||
write_to_temp_file('wcet.txt', [])
|
||||
write_to_temp_file('hyp.txt', [])
|
||||
write_to_temp_file('lora.txt', [])
|
||||
write_to_temp_file('lyco.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', [])
|
||||
@@ -244,6 +272,11 @@ if LORA_PATH is not None and LORA_PATH.exists():
|
||||
if lora:
|
||||
write_to_temp_file('lora.txt', lora)
|
||||
|
||||
if LYCO_PATH is not None and LYCO_PATH.exists():
|
||||
lyco = get_lyco()
|
||||
if lyco:
|
||||
write_to_temp_file('lyco.txt', lyco)
|
||||
|
||||
# Register autocomplete options
|
||||
def on_ui_settings():
|
||||
TAC_SECTION = ("tac", "Tag Autocomplete")
|
||||
@@ -254,7 +287,7 @@ def on_ui_settings():
|
||||
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.thirdParty", shared.OptionInfo(True, "Active in third party textboxes [Dataset Tag Editor] [Image Browser] [Tagger] [Multidiffusion Upscaler] (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
|
||||
@@ -267,6 +300,7 @@ def on_ui_settings():
|
||||
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_useLycos", shared.OptionInfo(True, "Search for LyCORIS/LoHa", 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))
|
||||
@@ -279,12 +313,15 @@ def on_ui_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))
|
||||
shared.opts.add_option("tac_translation.liveTranslation", shared.OptionInfo(False, "Show live tag translation below prompt (WIP, expect some bugs)", section=TAC_SECTION))
|
||||
# Extra file settings
|
||||
shared.opts.add_option("tac_extra.extraFile", shared.OptionInfo("extra-quality-tags.csv", "Extra filename (for small sets of custom tags)", gr.Dropdown, lambda: {"choices": csv_files_withnone}, refresh=update_tag_files, section=TAC_SECTION))
|
||||
shared.opts.add_option("tac_extra.addMode", shared.OptionInfo("Insert before", "Mode to add the extra tags to the main tag list", gr.Dropdown, lambda: {"choices": ["Insert before","Insert after"]}, section=TAC_SECTION))
|
||||
# Chant settings
|
||||
shared.opts.add_option("tac_chantFile", shared.OptionInfo("demo-chants.json", "Chant filename (Chants are longer prompt presets)", gr.Dropdown, lambda: {"choices": json_files_withnone}, refresh=update_json_files, section=TAC_SECTION))
|
||||
# Custom mappings
|
||||
shared.opts.add_option("tac_keymap", shared.OptionInfo(
|
||||
"""{
|
||||
keymapDefault = """\
|
||||
{
|
||||
"MoveUp": "ArrowUp",
|
||||
"MoveDown": "ArrowDown",
|
||||
"JumpUp": "PageUp",
|
||||
@@ -294,9 +331,10 @@ def on_ui_settings():
|
||||
"ChooseSelected": "Enter",
|
||||
"ChooseFirstOrSelected": "Tab",
|
||||
"Close": "Escape"
|
||||
}""", """Configure Hotkeys. For possible values, see https://www.w3.org/TR/uievents-key, or leave empty / set to 'None' to disable. Must be valid JSON.""", gr.Code, lambda: {"language": "json", "interactive": True}, section=TAC_SECTION))
|
||||
shared.opts.add_option("tac_colormap", shared.OptionInfo(
|
||||
"""{
|
||||
}\
|
||||
"""
|
||||
colorDefault = """\
|
||||
{
|
||||
"danbooru": {
|
||||
"-1": ["red", "maroon"],
|
||||
"0": ["lightblue", "dodgerblue"],
|
||||
@@ -316,7 +354,17 @@ def on_ui_settings():
|
||||
"7": ["whitesmoke", "black"],
|
||||
"8": ["seagreen", "darkseagreen"]
|
||||
}
|
||||
}""", "Configure colors. See https://github.com/DominikDoom/a1111-sd-webui-tagcomplete#colors for info. Must be valid JSON.", gr.Code, lambda: {"language": "json", "interactive": True}, section=TAC_SECTION))
|
||||
}\
|
||||
"""
|
||||
keymapLabel = "Configure Hotkeys. For possible values, see https://www.w3.org/TR/uievents-key, or leave empty / set to 'None' to disable. Must be valid JSON."
|
||||
colorLabel = "Configure colors. See https://github.com/DominikDoom/a1111-sd-webui-tagcomplete#colors for info. Must be valid JSON."
|
||||
|
||||
try:
|
||||
shared.opts.add_option("tac_keymap", shared.OptionInfo(keymapDefault, keymapLabel, gr.Code, lambda: {"language": "json", "interactive": True}, section=TAC_SECTION))
|
||||
shared.opts.add_option("tac_colormap", shared.OptionInfo(colorDefault, colorLabel, gr.Code, lambda: {"language": "json", "interactive": True}, section=TAC_SECTION))
|
||||
except AttributeError:
|
||||
shared.opts.add_option("tac_keymap", shared.OptionInfo(keymapDefault, keymapLabel, gr.Textbox, section=TAC_SECTION))
|
||||
shared.opts.add_option("tac_colormap", shared.OptionInfo(colorDefault, colorLabel, gr.Textbox, section=TAC_SECTION))
|
||||
|
||||
|
||||
script_callbacks.on_ui_settings(on_ui_settings)
|
||||
|
||||
32
tags/demo-chants.json
Normal file
32
tags/demo-chants.json
Normal file
@@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"name": "Basic-NegativePrompt",
|
||||
"terms": "Basic,Negative,Low,Quality",
|
||||
"content": "(worst quality, low quality, normal quality)",
|
||||
"color": 3
|
||||
},
|
||||
{
|
||||
"name": "Basic-HighQuality",
|
||||
"terms": "Basic,Best,High,Quality",
|
||||
"content": "(masterpiece, best quality, high quality, highres, ultra-detailed)",
|
||||
"color": 1
|
||||
},
|
||||
{
|
||||
"name": "Basic-Start",
|
||||
"terms": "Basic, Start, Simple, Demo",
|
||||
"content": "(masterpiece, best quality, high quality, highres), 1girl, extremely beautiful detailed face, short curly hair, light smile, flower dress, outdoors, leaf, tree, best shadow",
|
||||
"color": 5
|
||||
},
|
||||
{
|
||||
"name": "Fancy-FireMagic",
|
||||
"terms": "Fire, Magic, Fancy",
|
||||
"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))",
|
||||
"color": 5
|
||||
},
|
||||
{
|
||||
"name": "Fancy-WaterMagic",
|
||||
"terms": "Water, Magic, Fancy",
|
||||
"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",
|
||||
"color": 5
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
masterpiece,5,Quality tag,
|
||||
best_quality,5,Quality tag,
|
||||
high_quality,5,Quality tag,
|
||||
normal_quality,5,Quality tag,
|
||||
low_quality,5,Quality tag,
|
||||
worst_quality,5,Quality tag,
|
||||
masterpiece,5,Quality tag,,
|
||||
best_quality,5,Quality tag,,
|
||||
high_quality,5,Quality tag,,
|
||||
normal_quality,5,Quality tag,,
|
||||
low_quality,5,Quality tag,,
|
||||
worst_quality,5,Quality tag,,
|
||||
|
||||
|
Reference in New Issue
Block a user