Finalize settings migration

- Dropdown selection & refresh for tag files
- Tag, Extra & Translation file reloading without restart
- Any options can now be added to the quicksettings bar
- Standalone colors.json file
This commit is contained in:
Dominik Reh
2022-11-26 14:29:20 +01:00
parent def6ebb798
commit 05c11c9781
4 changed files with 195 additions and 213 deletions

View File

@@ -1,57 +1,5 @@
var CFG = null;
function syncOptions() {
let newCFG = {
// Main tag file
tagFile: opts["tac_tagFile"],
// Active in settings
activeIn: {
global: opts["tac_active"],
txt2img: opts["tac_activeIn.txt2img"],
img2img: opts["tac_activeIn.img2img"],
negativePrompts: opts["tac_activeIn.negativePrompts"]
},
// Results related settings
maxResults: opts["tac_maxResults"],
showAllResults: opts["tac_showAllResults"],
resultStepLength: opts["tac_resultStepLength"],
delayTime: opts["tac_delayTime"],
useWildcards: opts["tac_useWildcards"],
useEmbeddings: opts["tac_useEmbeddings"],
useLeftRightArrowKeys: opts["tac_useLeftRightArrowKeys"],
// Insertion related settings
replaceUnderscores: opts["tac_replaceUnderscores"],
escapeParentheses: opts["tac_escapeParentheses"],
appendComma: opts["tac_appendComma"],
// Alias settings
alias: {
searchByAlias: opts["tac_alias.searchByAlias"],
onlyShowAlias: opts["tac_alias.onlyShowAlias"]
},
// Translation settings
translation: {
translationFile: opts["tac_translation.translationFile"],
oldFormat: opts["tac_translation.oldFormat"],
searchByTranslation: opts["tac_translation.searchByTranslation"],
},
// Extra file settings
extra: {
extraFile: opts["tac_extra.extraFile"],
onlyAliasExtraFile: opts["tac_extra.onlyAliasExtraFile"]
}
}
if (CFG && CFG.colors) {
newCFG["colors"] = CFG.colors;
}
if (newCFG.alias.onlyShowAlias) {
newCFG.alias.searchByAlias = true; // if only show translation, enable search by translation is necessary
}
// Apply changes
CFG = newCFG;
}
const styleColors = {
"--results-bg": ["#0b0f19", "#ffffff"],
"--results-border-color": ["#4b5563", "#e5e7eb"],
@@ -173,6 +121,145 @@ async function loadCSV(path) {
return parseCSV(text);
}
var tagBasePath = "";
var allTags = [];
var translations = new Map();
async function loadTags(c) {
// Load main tags and aliases
if (allTags.length === 0) {
try {
allTags = await loadCSV(`${tagBasePath}/${c.tagFile}?${new Date().getTime()}`);
} catch (e) {
console.error("Error loading tags file: " + e);
return;
}
if (c.extra.extraFile && c.extra.extraFile !== "None") {
try {
extras = await loadCSV(`${tagBasePath}/${c.extra.extraFile}?${new Date().getTime()}`);
if (c.extra.onlyAliasExtraFile) {
// This works purely on index, so it's not very robust. But a lot faster.
for (let i = 0, n = extras.length; i < n; i++) {
if (extras[i][0]) {
let aliasStr = allTags[i][3] || "";
let optComma = aliasStr.length > 0 ? "," : "";
allTags[i][3] = aliasStr + optComma + extras[i][0];
}
}
} else {
extras.forEach(e => {
let hasCount = e[2] && e[3] || (!isNaN(e[2]) && !e[3]);
// Check if a tag in allTags has the same name & category as the extra tag
if (tag = allTags.find(t => t[0] === e[0] && t[1] == e[1])) {
if (hasCount && e[3] || isNaN(e[2])) { // If the extra tag has a translation / alias, add it to the normal tag
let aliasStr = tag[3] || "";
let optComma = aliasStr.length > 0 ? "," : "";
let alias = hasCount && e[3] || isNaN(e[2]) ? e[2] : e[3];
tag[3] = aliasStr + optComma + alias;
}
} else {
let count = hasCount ? e[2] : null;
let aliases = hasCount && e[3] ? e[3] : e[2];
// If the tag doesn't exist, add it to allTags
let newTag = [e[0], e[1], count, aliases];
allTags.push(newTag);
}
});
}
} catch (e) {
console.error("Error loading extra file: " + e);
return;
}
}
}
}
async function loadTranslations(c) {
if (c.translation.translationFile && c.translation.translationFile !== "None") {
try {
let tArray = await loadCSV(`${tagBasePath}/${c.translation.translationFile}?${new Date().getTime()}`);
tArray.forEach(t => {
if (c.translation.oldFormat)
translations.set(t[0], t[2]);
else
translations.set(t[0], t[1]);
});
} catch (e) {
console.error("Error loading translations file: " + e);
return;
}
}
}
async function syncOptions() {
let newCFG = {
// Main tag file
tagFile: opts["tac_tagFile"],
// Active in settings
activeIn: {
global: opts["tac_active"],
txt2img: opts["tac_activeIn.txt2img"],
img2img: opts["tac_activeIn.img2img"],
negativePrompts: opts["tac_activeIn.negativePrompts"]
},
// Results related settings
maxResults: opts["tac_maxResults"],
showAllResults: opts["tac_showAllResults"],
resultStepLength: opts["tac_resultStepLength"],
delayTime: opts["tac_delayTime"],
useWildcards: opts["tac_useWildcards"],
useEmbeddings: opts["tac_useEmbeddings"],
// Insertion related settings
replaceUnderscores: opts["tac_replaceUnderscores"],
escapeParentheses: opts["tac_escapeParentheses"],
appendComma: opts["tac_appendComma"],
// Alias settings
alias: {
searchByAlias: opts["tac_alias.searchByAlias"],
onlyShowAlias: opts["tac_alias.onlyShowAlias"]
},
// Translation settings
translation: {
translationFile: opts["tac_translation.translationFile"],
oldFormat: opts["tac_translation.oldFormat"],
searchByTranslation: opts["tac_translation.searchByTranslation"],
},
// Extra file settings
extra: {
extraFile: opts["tac_extra.extraFile"],
onlyAliasExtraFile: opts["tac_extra.onlyAliasExtraFile"]
}
}
if (CFG && CFG.colors) {
newCFG["colors"] = CFG.colors;
}
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) {
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) {
gradioApp().querySelectorAll(".autocompleteResults").forEach(r => {
r.style.maxHeight = `${newCFG.maxResults * 50}px`;
});
}
// Apply changes
CFG = newCFG;
}
// Debounce function to prevent spamming the autocomplete function
var dbTimeOut;
const debounce = (func, wait = 300) => {
@@ -236,7 +323,7 @@ function createResultsDiv(textArea) {
let textAreaId = getTextAreaIdentifier(textArea);
let typeClass = textAreaId.replaceAll(".", " ");
resultsDiv.style.setProperty("max-height", CFG.maxResults * 50 + "px");
resultsDiv.style.maxHeight = `${CFG.maxResults * 50}px`;
resultsDiv.setAttribute('class', `autocompleteResults ${typeClass}`);
resultsList.setAttribute('class', 'autocompleteResultsList');
resultsDiv.appendChild(resultsList);
@@ -244,25 +331,6 @@ function createResultsDiv(textArea) {
return resultsDiv;
}
// Create the checkbox to enable/disable autocomplete
function createCheckbox(text) {
let label = document.createElement("label");
let input = document.createElement("input");
let span = document.createElement("span");
label.setAttribute('id', 'acActiveCheckbox');
label.setAttribute('class', '"flex items-center text-gray-700 text-sm rounded-lg cursor-pointer dark:bg-transparent');
input.setAttribute('type', 'checkbox');
input.setAttribute('class', 'gr-check-radio gr-checkbox')
span.setAttribute('class', 'ml-2');
span.textContent = text;
label.appendChild(input);
label.appendChild(span);
return label;
}
// The selected tag index. Needs to be up here so hide can access it.
var selectedTag = null;
var previousTags = [];
@@ -509,8 +577,6 @@ function updateSelectionStyle(textArea, newIndex, oldIndex) {
var wildcardFiles = [];
var wildcardExtFiles = [];
var embeddings = [];
var allTags = [];
var translations = new Map();
var results = [];
var tagword = "";
var resultCount = 0;
@@ -659,8 +725,6 @@ function navigateInList(textArea, event) {
if (!CFG.activeIn.global) return;
validKeys = ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End", "Enter", "Tab", "Escape"];
if (CFG.useLeftRightArrowKeys)
validKeys.push("ArrowLeft", "ArrowRight");
if (!validKeys.includes(event.key)) return;
if (!isVisible(textArea)) return
@@ -738,85 +802,11 @@ function navigateInList(textArea, event) {
event.stopPropagation();
}
onUiUpdate(() => {
if (Object.keys(opts).length === 0) return;
if (CFG) return;
syncOptions();
// Rest of setup
setup();
});
// One-time setup
// One-time setup, triggered from onUiUpdate
async function setup() {
// Get our tag base path from the temp file
let tagBasePath = await readFile(`tmp/tagAutocompletePath.txt?${new Date().getTime()}`);
// Load colors
CFG["colors"] = (await readFile(`${tagBasePath}/config.json?${new Date().getTime()}`, true)).colors;
CFG["colors"] = (await readFile(`${tagBasePath}/colors.json?${new Date().getTime()}`, true));
// Load main tags and aliases
if (allTags.length === 0) {
try {
allTags = await loadCSV(`${tagBasePath}/${CFG.tagFile}?${new Date().getTime()}`);
} catch (e) {
console.error("Error loading tags file: " + e);
return;
}
if (CFG.extra.extraFile) {
try {
extras = await loadCSV(`${tagBasePath}/${CFG.extra.extraFile}?${new Date().getTime()}`);
if (CFG.extra.onlyAliasExtraFile) {
// This works purely on index, so it's not very robust. But a lot faster.
for (let i = 0, n = extras.length; i < n; i++) {
if (extras[i][0]) {
let aliasStr = allTags[i][3] || "";
let optComma = aliasStr.length > 0 ? "," : "";
allTags[i][3] = aliasStr + optComma + extras[i][0];
}
}
} else {
extras.forEach(e => {
let hasCount = e[2] && e[3] || (!isNaN(e[2]) && !e[3]);
// Check if a tag in allTags has the same name & category as the extra tag
if (tag = allTags.find(t => t[0] === e[0] && t[1] == e[1])) {
if (hasCount && e[3] || isNaN(e[2])) { // If the extra tag has a translation / alias, add it to the normal tag
let aliasStr = tag[3] || "";
let optComma = aliasStr.length > 0 ? "," : "";
let alias = hasCount && e[3] || isNaN(e[2]) ? e[2] : e[3];
tag[3] = aliasStr + optComma + alias;
}
} else {
let count = hasCount ? e[2] : null;
let aliases = hasCount && e[3] ? e[3] : e[2];
// If the tag doesn't exist, add it to allTags
let newTag = [e[0], e[1], count, aliases];
allTags.push(newTag);
}
});
}
} catch (e) {
console.error("Error loading extra file: " + e);
return;
}
}
}
// Load translations
if (CFG.translation.translationFile) {
try {
let tArray = await loadCSV(`${tagBasePath}/${CFG.translation.translationFile}?${new Date().getTime()}`);
tArray.forEach(t => {
if (CFG.translation.oldFormat)
translations.set(t[0], t[2]);
else
translations.set(t[0], t[1]);
});
} catch (e) {
console.error("Error loading translations file: " + e);
return;
}
}
// Load wildcards
if (wildcardFiles.length === 0) {
try {
@@ -876,7 +866,7 @@ async function setup() {
applySettingsButton.addEventListener("click", () => {
// Wait 500ms to make sure the settings have been applied to the webui opts object
setTimeout(async () => {
syncOptions();
await syncOptions();
}, 500);
});
// Add change listener to our quicksettings to change our internal config without the apply button for them
@@ -884,7 +874,7 @@ async function setup() {
quicksettings.querySelectorAll("[id^=setting_tac] > label > input, [id^=setting_tac] > label > textarea").forEach(e => {
e.addEventListener("change", () => {
setTimeout(async () => {
syncOptions();
await syncOptions();
}, 500);
});
});
@@ -958,3 +948,15 @@ async function setup() {
}
gradioApp().appendChild(acStyle);
}
onUiUpdate(async () => {
if (Object.keys(opts).length === 0) return;
if (CFG) return;
// Get our tag base path from the temp file
tagBasePath = await readFile(`tmp/tagAutocompletePath.txt?${new Date().getTime()}`);
// Load config from webui opts
await syncOptions();
// Rest of setup
setup();
});

View File

@@ -1,6 +1,7 @@
# This helper script scans folders for wildcards and embeddings and writes them
# to a temporary file to expose it to the javascript side
import gradio as gr
from pathlib import Path
from modules import scripts, script_callbacks, shared
@@ -69,12 +70,24 @@ def write_to_temp_file(name, data):
f.write(('\n'.join(data)))
csv_files = []
csv_files_withnone = []
def update_tag_files():
"""Returns a list of all potential tag files"""
global csv_files, csv_files_withnone
files = [str(t.relative_to(TAGS_PATH)) for t in TAGS_PATH.glob("*.csv")]
csv_files = files
csv_files_withnone = ["None"] + files
# Write the tag base path to a fixed location temporary file
# to enable the javascript side to find our files regardless of extension folder name
if not STATIC_TEMP_PATH.exists():
STATIC_TEMP_PATH.mkdir(exist_ok=True)
write_tag_base_path()
update_tag_files()
# Check if the temp path exists and create it if not
if not TEMP_PATH.exists():
@@ -108,7 +121,7 @@ if EMB_PATH.exists():
def on_ui_settings():
TAC_SECTION = ("tac", "Tag Autocomplete")
# Main tag file
shared.opts.add_option("tac_tagFile", shared.OptionInfo("danbooru.csv", "Tag file name (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_tagFile", shared.OptionInfo("danbooru.csv", "Tag filename", gr.Dropdown, lambda: {"choices": csv_files}, refresh=update_tag_files, section=TAC_SECTION))
# Active in settings
shared.opts.add_option("tac_active", shared.OptionInfo(True, "Enable Tag Autocompletion", section=TAC_SECTION))
shared.opts.add_option("tac_activeIn.txt2img", shared.OptionInfo(True, "Active in txt2img (Requires restart)", section=TAC_SECTION))
@@ -121,7 +134,6 @@ def on_ui_settings():
shared.opts.add_option("tac_delayTime", shared.OptionInfo(100, "Time in ms to wait before triggering completion again (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_useWildcards", shared.OptionInfo(True, "Search for wildcards", section=TAC_SECTION))
shared.opts.add_option("tac_useEmbeddings", shared.OptionInfo(True, "Search for embeddings", section=TAC_SECTION))
shared.opts.add_option("tac_useLeftRightArrowKeys", shared.OptionInfo(False, "Use left/rigt arrows to jump to the start/end of the list", section=TAC_SECTION))
# Insertion related settings
shared.opts.add_option("tac_replaceUnderscores", shared.OptionInfo(True, "Replace underscores with spaces on insertion", section=TAC_SECTION))
shared.opts.add_option("tac_escapeParentheses", shared.OptionInfo(True, "Escape parentheses on insertion", section=TAC_SECTION))
@@ -130,11 +142,11 @@ def on_ui_settings():
shared.opts.add_option("tac_alias.searchByAlias", shared.OptionInfo(True, "Search by alias", section=TAC_SECTION))
shared.opts.add_option("tac_alias.onlyShowAlias", shared.OptionInfo(False, "Only show alias", section=TAC_SECTION))
# Translation settings
shared.opts.add_option("tac_translation.translationFile", shared.OptionInfo("", "Translation file name (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_translation.oldFormat", shared.OptionInfo(False, "File uses the old 3-column translation format instead of the new 2-column one (Requires restart)", section=TAC_SECTION))
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, "File uses the old 3-column translation format instead of the new 2-column one", section=TAC_SECTION))
shared.opts.add_option("tac_translation.searchByTranslation", shared.OptionInfo(True, "Search by translation", section=TAC_SECTION))
# Extra file settings
shared.opts.add_option("tac_extra.extraFile", shared.OptionInfo("", "Extra file name (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_extra.onlyAliasExtraFile", shared.OptionInfo(False, "Extra file in alias only format (Requires restart)", section=TAC_SECTION))
shared.opts.add_option("tac_extra.extraFile", shared.OptionInfo("None", "Extra filename", gr.Dropdown, lambda: {"choices": csv_files_withnone}, refresh=update_tag_files, section=TAC_SECTION))
shared.opts.add_option("tac_extra.onlyAliasExtraFile", shared.OptionInfo(False, "Extra file in alias only format", section=TAC_SECTION))
script_callbacks.on_ui_settings(on_ui_settings)

21
tags/colors.json Normal file
View File

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

View File

@@ -1,53 +0,0 @@
{
"tagFile": "danbooru.csv",
"activeIn": {
"txt2img": true,
"img2img": true,
"negativePrompts": true
},
"hideUIOptions": false,
"maxResults": 5,
"resultStepLength": 500,
"delayTime": 100,
"showAllResults": false,
"useLeftRightArrowKeys": false,
"replaceUnderscores": true,
"escapeParentheses": true,
"appendComma": true,
"useWildcards": true,
"useEmbeddings": true,
"alias": {
"searchByAlias": true,
"onlyShowAlias": false
},
"translation": {
"translationFile": "",
"oldFormat": false,
"searchByTranslation": true
},
"extra": {
"extraFile": "",
"onlyAliasExtraFile": false
},
"colors": {
"danbooru": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["indianred", "firebrick"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["orange", "darkorange"]
},
"e621": {
"-1": ["red", "maroon"],
"0": ["lightblue", "dodgerblue"],
"1": ["gold", "goldenrod"],
"3": ["violet", "darkorchid"],
"4": ["lightgreen", "darkgreen"],
"5": ["tomato", "darksalmon"],
"6": ["red", "maroon"],
"7": ["whitesmoke", "black"],
"8": ["seagreen", "darkseagreen"]
}
}
}