|
|
|
|
@@ -1,5 +1,6 @@
|
|
|
|
|
var acConfig = null;
|
|
|
|
|
var acActive = true;
|
|
|
|
|
var acAppendComma = false;
|
|
|
|
|
|
|
|
|
|
// Style for new elements. Gets appended to the Gradio root.
|
|
|
|
|
let autocompleteCSS_dark = `
|
|
|
|
|
@@ -92,15 +93,24 @@ function parseCSV(str) {
|
|
|
|
|
|
|
|
|
|
// Load file
|
|
|
|
|
function readFile(filePath) {
|
|
|
|
|
let request = new XMLHttpRequest();
|
|
|
|
|
request.open("GET", filePath, false);
|
|
|
|
|
request.send(null);
|
|
|
|
|
return request.responseText;
|
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
|
let request = new XMLHttpRequest();
|
|
|
|
|
request.open("GET", filePath, true);
|
|
|
|
|
request.onload = function () {
|
|
|
|
|
var status = request.status;
|
|
|
|
|
if (status == 200) {
|
|
|
|
|
resolve(request.responseText);
|
|
|
|
|
} else {
|
|
|
|
|
reject(status);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
request.send(null);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load CSV
|
|
|
|
|
function loadCSV(path) {
|
|
|
|
|
let text = readFile(path);
|
|
|
|
|
async function loadCSV(path) {
|
|
|
|
|
let text = await readFile(path);
|
|
|
|
|
return parseCSV(text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -176,7 +186,7 @@ function createResultsDiv(textArea) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the checkbox to enable/disable autocomplete
|
|
|
|
|
function createCheckbox() {
|
|
|
|
|
function createCheckbox(text) {
|
|
|
|
|
let label = document.createElement("label");
|
|
|
|
|
let input = document.createElement("input");
|
|
|
|
|
let span = document.createElement("span");
|
|
|
|
|
@@ -187,7 +197,7 @@ function createCheckbox() {
|
|
|
|
|
input.setAttribute('class', 'gr-check-radio gr-checkbox')
|
|
|
|
|
span.setAttribute('class', 'ml-2');
|
|
|
|
|
|
|
|
|
|
span.textContent = "Enable Autocomplete";
|
|
|
|
|
span.textContent = text;
|
|
|
|
|
|
|
|
|
|
label.appendChild(input);
|
|
|
|
|
label.appendChild(span);
|
|
|
|
|
@@ -220,6 +230,8 @@ function escapeRegExp(string) {
|
|
|
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const WEIGHT_REGEX = /[([]([^,()[\]:| ]+)(?::(?:\d+(?:\.\d+)?|\.\d+))?[)\]]/g;
|
|
|
|
|
const TAG_REGEX = /([^\s,|]+)/g
|
|
|
|
|
let hideBlocked = false;
|
|
|
|
|
|
|
|
|
|
// On click, insert the tag into the prompt textbox with respect to the cursor position
|
|
|
|
|
@@ -259,8 +271,8 @@ function insertTextAtCursor(textArea, result, tagword) {
|
|
|
|
|
let afterInsertCursorPos = editStart + match.index + sanitizedText.length;
|
|
|
|
|
|
|
|
|
|
var optionalComma = "";
|
|
|
|
|
if (tagType !== "wildcardFile") {
|
|
|
|
|
optionalComma = surrounding.match(new RegExp(escapeRegExp(`${tagword},`), "i")) !== null ? "" : ", ";
|
|
|
|
|
if (acAppendComma && tagType !== "wildcardFile") {
|
|
|
|
|
optionalComma = surrounding.match(new RegExp(`${escapeRegExp(tagword)}[,:]`, "i")) !== null ? "" : ", ";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace partial tag word with new text, add comma if needed
|
|
|
|
|
@@ -277,7 +289,13 @@ function insertTextAtCursor(textArea, result, tagword) {
|
|
|
|
|
textArea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
|
|
|
|
|
|
|
|
// Update previous tags with the edited prompt to prevent re-searching the same term
|
|
|
|
|
let tags = newPrompt.match(/[^,\n\r ]+/g);
|
|
|
|
|
let weightedTags = [...newPrompt.matchAll(WEIGHT_REGEX)]
|
|
|
|
|
.map(match => match[1]);
|
|
|
|
|
let tags = newPrompt.match(TAG_REGEX)
|
|
|
|
|
if (weightedTags !== null) {
|
|
|
|
|
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted)))
|
|
|
|
|
.concat(weightedTags);
|
|
|
|
|
}
|
|
|
|
|
previousTags = tags;
|
|
|
|
|
|
|
|
|
|
// Hide results after inserting
|
|
|
|
|
@@ -372,7 +390,7 @@ var allTags = [];
|
|
|
|
|
var results = [];
|
|
|
|
|
var tagword = "";
|
|
|
|
|
var resultCount = 0;
|
|
|
|
|
function autocomplete(textArea, prompt, fixedTag = null) {
|
|
|
|
|
async function autocomplete(textArea, prompt, fixedTag = null) {
|
|
|
|
|
// Return if the function is deactivated in the UI
|
|
|
|
|
if (!acActive) return;
|
|
|
|
|
|
|
|
|
|
@@ -384,12 +402,21 @@ function autocomplete(textArea, prompt, fixedTag = null) {
|
|
|
|
|
|
|
|
|
|
if (fixedTag === null) {
|
|
|
|
|
// Match tags with RegEx to get the last edited one
|
|
|
|
|
let tags = prompt.match(/[^,\n\r ]+/g);
|
|
|
|
|
let diff = difference(tags, previousTags)
|
|
|
|
|
// We also match for the weighting format (e.g. "tag:1.0") here, and combine the two to get the full tag word set
|
|
|
|
|
let weightedTags = [...prompt.matchAll(WEIGHT_REGEX)]
|
|
|
|
|
.map(match => match[1]);
|
|
|
|
|
let tags = prompt.match(TAG_REGEX)
|
|
|
|
|
if (weightedTags !== null) {
|
|
|
|
|
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted)))
|
|
|
|
|
.concat(weightedTags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let tagCountChange = tags.length - previousTags.length;
|
|
|
|
|
let diff = difference(tags, previousTags);
|
|
|
|
|
previousTags = tags;
|
|
|
|
|
|
|
|
|
|
// Guard for no difference / only whitespace remaining
|
|
|
|
|
if (diff === null || diff.length === 0) {
|
|
|
|
|
// Guard for no difference / only whitespace remaining / last edited tag was fully removed
|
|
|
|
|
if (diff === null || diff.length === 0 || (diff.length === 1 && tagCountChange < 0)) {
|
|
|
|
|
if (!hideBlocked) hideResults(textArea);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
@@ -405,7 +432,7 @@ function autocomplete(textArea, prompt, fixedTag = null) {
|
|
|
|
|
tagword = fixedTag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tagword = tagword.toLowerCase().trim();
|
|
|
|
|
tagword = tagword.toLowerCase().replace(/[\n\r]/g, "");
|
|
|
|
|
|
|
|
|
|
if (acConfig.useWildcards && [...tagword.matchAll(/\b__([^, ]+)__([^, ]*)\b/g)].length > 0) {
|
|
|
|
|
// Show wildcards from a file with that name
|
|
|
|
|
@@ -421,8 +448,8 @@ function autocomplete(textArea, prompt, fixedTag = null) {
|
|
|
|
|
else // Look in extensions wildcard files
|
|
|
|
|
wcPair = wildcardExtFiles.find(x => x[1].toLowerCase() === wcFile);
|
|
|
|
|
|
|
|
|
|
let wildcards = readFile(`file/${wcPair[0]}/${wcPair[1]}.txt`).split("\n")
|
|
|
|
|
.filter(x => x.trim().length > 0); // Remove empty lines
|
|
|
|
|
let wildcards = (await readFile(`file/${wcPair[0]}/${wcPair[1]}.txt?${new Date().getTime()}`)).split("\n")
|
|
|
|
|
.filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments
|
|
|
|
|
|
|
|
|
|
results = wildcards.filter(x => (wcWord !== null && wcWord.length > 0) ? x.toLowerCase().includes(wcWord) : x) // Filter by tagword
|
|
|
|
|
.map(x => [wcFile + ": " + x.trim(), "wildcardTag"]); // Mark as wildcard
|
|
|
|
|
@@ -559,15 +586,15 @@ function navigateInList(textArea, event) {
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var styleAdded = false;
|
|
|
|
|
onUiUpdate(function () {
|
|
|
|
|
// One-time setup
|
|
|
|
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
|
|
|
// Get our tag base path from the temp file
|
|
|
|
|
let tagBasePath = readFile("file/tmp/tagAutocompletePath.txt");
|
|
|
|
|
let tagBasePath = await readFile(`file/tmp/tagAutocompletePath.txt?${new Date().getTime()}`);
|
|
|
|
|
|
|
|
|
|
// Load config
|
|
|
|
|
if (acConfig === null) {
|
|
|
|
|
try {
|
|
|
|
|
acConfig = JSON.parse(readFile(`file/${tagBasePath}/config.json`));
|
|
|
|
|
acConfig = JSON.parse(await readFile(`file/${tagBasePath}/config.json?${new Date().getTime()}`));
|
|
|
|
|
if (acConfig.translation.onlyShowTranslation) {
|
|
|
|
|
acConfig.translation.searchByTranslation = true; // if only show translation, enable search by translation is necessary
|
|
|
|
|
}
|
|
|
|
|
@@ -579,14 +606,14 @@ onUiUpdate(function () {
|
|
|
|
|
// Load main tags and translations
|
|
|
|
|
if (allTags.length === 0) {
|
|
|
|
|
try {
|
|
|
|
|
allTags = loadCSV(`file/${tagBasePath}/${acConfig.tagFile}`);
|
|
|
|
|
allTags = await loadCSV(`file/${tagBasePath}/${acConfig.tagFile}?${new Date().getTime()}`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("Error loading tags file: " + e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (acConfig.extra.extraFile) {
|
|
|
|
|
try {
|
|
|
|
|
extras = loadCSV(`file/${tagBasePath}/${acConfig.extra.extraFile}`);
|
|
|
|
|
extras = await loadCSV(`file/${tagBasePath}/${acConfig.extra.extraFile}`);
|
|
|
|
|
if (acConfig.extra.onlyTranslationExtraFile) {
|
|
|
|
|
// This works purely on index, so it's not very robust. But a lot faster.
|
|
|
|
|
for (let i = 0, n = extras.length; i < n; i++) {
|
|
|
|
|
@@ -613,16 +640,16 @@ onUiUpdate(function () {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Load wildcards
|
|
|
|
|
if (wildcardFiles.length === 0 && acConfig.useWildcards) {
|
|
|
|
|
if (acConfig.useWildcards && wildcardFiles.length === 0) {
|
|
|
|
|
try {
|
|
|
|
|
let wcFileArr = readFile(`file/${tagBasePath}/temp/wc.txt`).split("\n");
|
|
|
|
|
let wcFileArr = (await readFile(`file/${tagBasePath}/temp/wc.txt?${new Date().getTime()}`)).split("\n");
|
|
|
|
|
let wcBasePath = wcFileArr[0].trim(); // First line should be the base path
|
|
|
|
|
wildcardFiles = wcFileArr.slice(1)
|
|
|
|
|
.filter(x => x.trim().length > 0) // Remove empty lines
|
|
|
|
|
.map(x => [wcBasePath, x.trim().replace(".txt", "")]); // Remove file extension & newlines
|
|
|
|
|
|
|
|
|
|
// To support multiple sources, we need to separate them using the provided "-----" strings
|
|
|
|
|
let wcExtFileArr = readFile(`file/${tagBasePath}/temp/wce.txt`).split("\n");
|
|
|
|
|
let wcExtFileArr = (await readFile(`file/${tagBasePath}/temp/wce.txt?${new Date().getTime()}`)).split("\n");
|
|
|
|
|
let splitIndices = [];
|
|
|
|
|
for (let index = 0; index < wcExtFileArr.length; index++) {
|
|
|
|
|
if (wcExtFileArr[index].trim() === "-----") {
|
|
|
|
|
@@ -649,9 +676,9 @@ onUiUpdate(function () {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Load embeddings
|
|
|
|
|
if (embeddings.length === 0 && acConfig.useEmbeddings) {
|
|
|
|
|
if (acConfig.useEmbeddings && embeddings.length === 0) {
|
|
|
|
|
try {
|
|
|
|
|
embeddings = readFile(`file/${tagBasePath}/temp/emb.txt`).split("\n")
|
|
|
|
|
embeddings = (await readFile(`file/${tagBasePath}/temp/emb.txt?${new Date().getTime()}`)).split("\n")
|
|
|
|
|
.filter(x => x.trim().length > 0) // Remove empty lines
|
|
|
|
|
.map(x => x.replace(".bin", "").replace(".pt", "").replace(".png", "")); // Remove file extensions
|
|
|
|
|
} catch (e) {
|
|
|
|
|
@@ -680,7 +707,6 @@ onUiUpdate(function () {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
textAreas.forEach(area => {
|
|
|
|
|
|
|
|
|
|
// Return if autocomplete is disabled for the current area type in config
|
|
|
|
|
let textAreaId = getTextAreaIdentifier(area);
|
|
|
|
|
if ((!acConfig.activeIn.img2img && textAreaId.includes("img2img"))
|
|
|
|
|
@@ -709,17 +735,42 @@ onUiUpdate(function () {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (gradioApp().querySelector("#acActiveCheckbox") === null) {
|
|
|
|
|
acAppendComma = acConfig.appendComma;
|
|
|
|
|
// Add our custom options elements
|
|
|
|
|
if (!acConfig.hideUIOptions && gradioApp().querySelector("#tagAutocompleteOptions") === null) {
|
|
|
|
|
let optionsDiv = document.createElement("div");
|
|
|
|
|
optionsDiv.id = "tagAutocompleteOptions";
|
|
|
|
|
optionsDiv.classList.add("flex", "flex-col", "p-1", "px-1", "relative", "text-sm");
|
|
|
|
|
|
|
|
|
|
let optionsInner = document.createElement("div");
|
|
|
|
|
optionsInner.classList.add("flex", "flex-row", "p-1", "gap-4", "text-gray-700");
|
|
|
|
|
|
|
|
|
|
// Add label
|
|
|
|
|
let title = document.createElement("p");
|
|
|
|
|
title.textContent = "Autocomplete options";
|
|
|
|
|
optionsDiv.appendChild(title);
|
|
|
|
|
|
|
|
|
|
// Add toggle switch
|
|
|
|
|
let cb = createCheckbox();
|
|
|
|
|
cb.querySelector("input").checked = acActive;
|
|
|
|
|
cb.querySelector("input").addEventListener("change", (e) => {
|
|
|
|
|
let cbActive = createCheckbox("Enable Autocomplete");
|
|
|
|
|
cbActive.querySelector("input").checked = acActive;
|
|
|
|
|
cbActive.querySelector("input").addEventListener("change", (e) => {
|
|
|
|
|
acActive = e.target.checked;
|
|
|
|
|
});
|
|
|
|
|
quicksettings.parentNode.insertBefore(cb, quicksettings.nextSibling);
|
|
|
|
|
}
|
|
|
|
|
// Add comma switch
|
|
|
|
|
let cbComma = createCheckbox("Append commas");
|
|
|
|
|
cbComma.querySelector("input").checked = acAppendComma;
|
|
|
|
|
cbComma.querySelector("input").addEventListener("change", (e) => {
|
|
|
|
|
acAppendComma = e.target.checked;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (styleAdded) return;
|
|
|
|
|
// Add options to optionsDiv
|
|
|
|
|
optionsInner.appendChild(cbActive);
|
|
|
|
|
optionsInner.appendChild(cbComma);
|
|
|
|
|
optionsDiv.appendChild(optionsInner);
|
|
|
|
|
|
|
|
|
|
// Add options div to DOM
|
|
|
|
|
quicksettings.parentNode.insertBefore(optionsDiv, quicksettings.nextSibling);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add style to dom
|
|
|
|
|
let acStyle = document.createElement('style');
|
|
|
|
|
@@ -731,4 +782,4 @@ onUiUpdate(function () {
|
|
|
|
|
}
|
|
|
|
|
gradioApp().appendChild(acStyle);
|
|
|
|
|
styleAdded = true;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|