mirror of
https://github.com/DominikDoom/a1111-sd-webui-tagcomplete.git
synced 2026-01-26 19:19:57 +00:00
Compare commits
23 Commits
3.0.0
...
feature-fu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ec1bc3424 | ||
|
|
fe32ad739d | ||
|
|
ade67e30a6 | ||
|
|
e9a21e7a55 | ||
|
|
3ef2a7d206 | ||
|
|
29b5bf0701 | ||
|
|
cfe026f366 | ||
|
|
3eef536b64 | ||
|
|
c594156d30 | ||
|
|
2126a5d217 | ||
|
|
a4a656ae23 | ||
|
|
c59ffed049 | ||
|
|
625298bed6 | ||
|
|
8c0c308a6f | ||
|
|
fa4a3bc036 | ||
|
|
a8f175925f | ||
|
|
0d24e697d2 | ||
|
|
a27633da55 | ||
|
|
4cd6174a22 | ||
|
|
9155e4d42c | ||
|
|
700642a400 | ||
|
|
1b592dbf56 | ||
|
|
d1eea880f3 |
@@ -20,18 +20,15 @@ Booru style tag autocompletion for the AUTOMATIC1111 Stable Diffusion WebUI
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
#### ⚠️ Notice:
|
||||
I am currently looking for feedback on a new feature I'm working on and want to release soon.<br/>
|
||||
Please check [the announcement post](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/discussions/270) for more info if you are interested to help.
|
||||
|
||||
# 📄 Description
|
||||
|
||||
Tag Autocomplete is an extension for the popular [AUTOMATIC1111 web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) for Stable Diffusion.
|
||||
You can install it using the inbuilt available extensions list, clone the files manually as described [below](#-installation), or use a pre-packaged version from [Releases](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases).
|
||||
|
||||
It displays autocompletion hints for recognized tags from "image booru" boards such as Danbooru, which are primarily used for browsing Anime-style illustrations.
|
||||
Since some Stable Diffusion models were trained using this information, for example [Waifu Diffusion](https://github.com/harubaru/waifu-diffusion) and many of the NAI-descendant models or merges, using exact tags in prompts can often improve composition and consistency.
|
||||
Since most custom Stable Diffusion models were trained using this information or merged with ones that did, using exact tags in prompts can often improve composition and consistency, even if the model itself has a photorealistic style.
|
||||
|
||||
You can install it using the inbuilt available extensions list, clone the files manually as described [below](#-installation), or use a pre-packaged version from [Releases](https://github.com/DominikDoom/a1111-sd-webui-tagcomplete/releases).
|
||||
Disclaimer: The default tag lists contain NSFW terms, please use them responsibly.
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
@@ -20,6 +20,15 @@ var lycos = [];
|
||||
var modelKeywordDict = new Map();
|
||||
var chants = [];
|
||||
var styleNames = [];
|
||||
// uFuzzy haystacks
|
||||
var tacHaystacks = {
|
||||
"tag": [],
|
||||
"extra": [],
|
||||
"tagAlias": [],
|
||||
"extraAlias": [],
|
||||
"translationKeys": [],
|
||||
"translationValues": []
|
||||
}
|
||||
|
||||
// Selected model info for black/whitelisting
|
||||
var currentModelHash = "";
|
||||
|
||||
@@ -30,6 +30,9 @@ class AutocompleteResult {
|
||||
meta = null;
|
||||
hash = null;
|
||||
sortKey = null;
|
||||
// uFuzzy specific
|
||||
highlightedText = null;
|
||||
matchSource = null;
|
||||
|
||||
// Constructor
|
||||
constructor(text, type) {
|
||||
|
||||
250
javascript/_uFuzzy.js
Normal file
250
javascript/_uFuzzy.js
Normal file
File diff suppressed because one or more lines are too long
@@ -61,7 +61,7 @@ async function loadCSV(path) {
|
||||
}
|
||||
|
||||
// Fetch API
|
||||
async function fetchAPI(url, json = true, cache = false) {
|
||||
async function fetchTacAPI(url, json = true, cache = false) {
|
||||
if (!cache) {
|
||||
const appendChar = url.includes("?") ? "&" : "?";
|
||||
url += `${appendChar}${new Date().getTime()}`
|
||||
@@ -80,7 +80,7 @@ async function fetchAPI(url, json = true, cache = false) {
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
async function postAPI(url, body = null) {
|
||||
async function postTacAPI(url, body = null) {
|
||||
let response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
@@ -95,7 +95,7 @@ async function postAPI(url, body = null) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function putAPI(url, body = null) {
|
||||
async function putTacAPI(url, body = null) {
|
||||
let response = await fetch(url, { method: "PUT", body: body });
|
||||
|
||||
if (response.status != 200) {
|
||||
@@ -107,8 +107,8 @@ async function putAPI(url, body = null) {
|
||||
}
|
||||
|
||||
// Extra network preview thumbnails
|
||||
async function getExtraNetworkPreviewURL(filename, type) {
|
||||
const previewJSON = await fetchAPI(`tacapi/v1/thumb-preview/${filename}?type=${type}`, true, true);
|
||||
async function getTacExtraNetworkPreviewURL(filename, type) {
|
||||
const previewJSON = await fetchTacAPI(`tacapi/v1/thumb-preview/${filename}?type=${type}`, true, true);
|
||||
if (previewJSON?.url) {
|
||||
const properURL = `sd_extra_networks/thumb?filename=${previewJSON.url}`;
|
||||
if ((await fetch(properURL)).status == 200) {
|
||||
@@ -237,24 +237,34 @@ function mapUseCountArray(useCounts, posAndNeg = false) {
|
||||
}
|
||||
// Call API endpoint to increase bias of tag in the database
|
||||
function increaseUseCount(tagName, type, negative = false) {
|
||||
postAPI(`tacapi/v1/increase-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`);
|
||||
postTacAPI(`tacapi/v1/increase-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`);
|
||||
}
|
||||
// Get use count of tag from the database
|
||||
async function getUseCount(tagName, type, negative = false) {
|
||||
return (await fetchAPI(`tacapi/v1/get-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`, true, false))["result"];
|
||||
const response = await fetchTacAPI(`tacapi/v1/get-use-count?tagname=${tagName}&ttype=${type}&neg=${negative}`, true, false);
|
||||
// Guard for no db
|
||||
if (response == null) return null;
|
||||
// Result
|
||||
return response["result"];
|
||||
}
|
||||
async function getUseCounts(tagNames, types, negative = false) {
|
||||
// While semantically weird, we have to use POST here for the body, as urls are limited in length
|
||||
const body = JSON.stringify({"tagNames": tagNames, "tagTypes": types, "neg": negative});
|
||||
const rawArray = (await postAPI(`tacapi/v1/get-use-count-list`, body))["result"]
|
||||
return mapUseCountArray(rawArray);
|
||||
const response = await postTacAPI(`tacapi/v1/get-use-count-list`, body)
|
||||
// Guard for no db
|
||||
if (response == null) return null;
|
||||
// Results
|
||||
return mapUseCountArray(response["result"]);
|
||||
}
|
||||
async function getAllUseCounts() {
|
||||
const rawArray = (await fetchAPI(`tacapi/v1/get-all-use-counts`))["result"];
|
||||
return mapUseCountArray(rawArray, true);
|
||||
const response = await fetchTacAPI(`tacapi/v1/get-all-use-counts`);
|
||||
// Guard for no db
|
||||
if (response == null) return null;
|
||||
// Results
|
||||
return mapUseCountArray(response["result"], true);
|
||||
}
|
||||
async function resetUseCount(tagName, type, resetPosCount, resetNegCount) {
|
||||
await putAPI(`tacapi/v1/reset-use-count?tagname=${tagName}&ttype=${type}&pos=${resetPosCount}&neg=${resetNegCount}`);
|
||||
await putTacAPI(`tacapi/v1/reset-use-count?tagname=${tagName}&ttype=${type}&pos=${resetPosCount}&neg=${resetNegCount}`);
|
||||
}
|
||||
|
||||
function createTagUsageTable(tagCounts) {
|
||||
|
||||
@@ -5,14 +5,17 @@ class LoraParser extends BaseTagParser {
|
||||
parse() {
|
||||
// Show lora
|
||||
let tempResults = [];
|
||||
let searchTerm = tagword;
|
||||
if (tagword !== "<" && tagword !== "<l:" && tagword !== "<lora:") {
|
||||
let searchTerm = tagword.replace("<lora:", "").replace("<l:", "").replace("<", "");
|
||||
searchTerm = tagword.replace("<lora:", "").replace("<l:", "").replace("<", "");
|
||||
let filterCondition = x => {
|
||||
let regex = new RegExp(escapeRegExp(searchTerm, true), 'i');
|
||||
return regex.test(x.toLowerCase()) || regex.test(x.toLowerCase().replaceAll(" ", "_"));
|
||||
};
|
||||
filterCondition = (x) => TacFuzzy.check(x, searchTerm);
|
||||
tempResults = loras.filter(x => filterCondition(x[0])); // Filter by tagword
|
||||
} else {
|
||||
searchTerm = null;
|
||||
tempResults = loras;
|
||||
}
|
||||
|
||||
@@ -25,9 +28,12 @@ class LoraParser extends BaseTagParser {
|
||||
let name = text.substring(lastSlash + 1, lastDot);
|
||||
|
||||
let result = new AutocompleteResult(name, ResultType.lora)
|
||||
result.highlightedText = TacFuzzy.manualHighlight(name, searchTerm);
|
||||
result.matchSource = "base";
|
||||
result.meta = "Lora";
|
||||
result.sortKey = t[1];
|
||||
result.hash = t[2];
|
||||
|
||||
finalResults.push(result);
|
||||
});
|
||||
|
||||
@@ -50,7 +56,7 @@ async function load() {
|
||||
async function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.lora) {
|
||||
let multiplier = TAC_CFG.extraNetworksDefaultMultiplier;
|
||||
let info = await fetchAPI(`tacapi/v1/lora-info/${text}`)
|
||||
let info = await fetchTacAPI(`tacapi/v1/lora-info/${text}`)
|
||||
if (info && info["preferred weight"]) {
|
||||
multiplier = info["preferred weight"];
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ async function load() {
|
||||
async function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.lyco) {
|
||||
let multiplier = TAC_CFG.extraNetworksDefaultMultiplier;
|
||||
let info = await fetchAPI(`tacapi/v1/lyco-info/${text}`)
|
||||
let info = await fetchTacAPI(`tacapi/v1/lyco-info/${text}`)
|
||||
if (info && info["preferred weight"]) {
|
||||
multiplier = info["preferred weight"];
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Regex
|
||||
const WC_REGEX = /\b__([^,]+)__([^, ]*)\b/g;
|
||||
const WC_REGEX = new RegExp(/__([^,]+)__([^, ]*)/g);
|
||||
|
||||
// Trigger conditions
|
||||
const WC_TRIGGER = () => TAC_CFG.useWildcards && [...tagword.matchAll(WC_REGEX)].length > 0;
|
||||
const WC_FILE_TRIGGER = () => TAC_CFG.useWildcards && (tagword.startsWith("__") && !tagword.endsWith("__") || tagword === "__");
|
||||
const WC_TRIGGER = () => TAC_CFG.useWildcards && [...tagword.matchAll(new RegExp(WC_REGEX.source.replaceAll("__", escapeRegExp(TAC_CFG.wcWrap)), "g"))].length > 0;
|
||||
const WC_FILE_TRIGGER = () => TAC_CFG.useWildcards && (tagword.startsWith(TAC_CFG.wcWrap) && !tagword.endsWith(TAC_CFG.wcWrap) || tagword === TAC_CFG.wcWrap);
|
||||
|
||||
class WildcardParser extends BaseTagParser {
|
||||
async parse() {
|
||||
// Show wildcards from a file with that name
|
||||
let wcMatch = [...tagword.matchAll(WC_REGEX)]
|
||||
let wcMatch = [...tagword.matchAll(new RegExp(WC_REGEX.source.replaceAll("__", escapeRegExp(TAC_CFG.wcWrap)), "g"))];
|
||||
let wcFile = wcMatch[0][1];
|
||||
let wcWord = wcMatch[0][2];
|
||||
|
||||
@@ -38,7 +38,7 @@ class WildcardParser extends BaseTagParser {
|
||||
}
|
||||
wildcards = wildcards.concat(getDescendantProp(yamlWildcards[basePath], fileName));
|
||||
} else {
|
||||
const fileContent = (await fetchAPI(`tacapi/v1/wildcard-contents?basepath=${basePath}&filename=${fileName}.txt`, false))
|
||||
const fileContent = (await fetchTacAPI(`tacapi/v1/wildcard-contents?basepath=${basePath}&filename=${fileName}.txt`, false))
|
||||
.split("\n")
|
||||
.filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments
|
||||
wildcards = wildcards.concat(fileContent);
|
||||
@@ -64,8 +64,8 @@ class WildcardFileParser extends BaseTagParser {
|
||||
parse() {
|
||||
// Show available wildcard files
|
||||
let tempResults = [];
|
||||
if (tagword !== "__") {
|
||||
let lmb = (x) => x[1].toLowerCase().includes(tagword.replace("__", ""))
|
||||
if (tagword !== TAC_CFG.wcWrap) {
|
||||
let lmb = (x) => x[1].toLowerCase().includes(tagword.replace(TAC_CFG.wcWrap, ""))
|
||||
tempResults = wildcardFiles.filter(lmb).concat(wildcardExtFiles.filter(lmb)) // Filter by tagword
|
||||
} else {
|
||||
tempResults = wildcardFiles.concat(wildcardExtFiles);
|
||||
@@ -151,7 +151,7 @@ async function load() {
|
||||
|
||||
function sanitize(tagType, text) {
|
||||
if (tagType === ResultType.wildcardFile || tagType === ResultType.yamlWildcard) {
|
||||
return `__${text}__`;
|
||||
return `${TAC_CFG.wcWrap}${text}${TAC_CFG.wcWrap}`;
|
||||
} else if (tagType === ResultType.wildcardTag) {
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"--live-translation-color-1": ["lightskyblue", "#2d89ef"],
|
||||
"--live-translation-color-2": ["palegoldenrod", "#eb5700"],
|
||||
"--live-translation-color-3": ["darkseagreen", "darkgreen"],
|
||||
"--match-filter": ["brightness(1.2) drop-shadow(1px 1px 6px black)", "brightness(0.8)"]
|
||||
}
|
||||
const browserVars = {
|
||||
"--results-overflow-y": {
|
||||
@@ -90,6 +91,9 @@ const autocompleteCSS = `
|
||||
content: "✨";
|
||||
margin-right: 2px;
|
||||
}
|
||||
.acMatchHighlight {
|
||||
filter: var(--match-filter);
|
||||
}
|
||||
.acWikiLink {
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem 0 -0.5rem -0.5rem;
|
||||
@@ -158,6 +162,14 @@ async function loadTags(c) {
|
||||
}
|
||||
}
|
||||
await loadExtraTags(c);
|
||||
|
||||
// Assign uFuzzy haystacks
|
||||
tacHaystacks.tag = allTags.map(x => x[0]);
|
||||
tacHaystacks.extra = extras.map(x => x[0]);
|
||||
tacHaystacks.tagAlias = allTags.map(x => x[3] || "");
|
||||
tacHaystacks.extraAlias = extras.map(e => e[3] || "");
|
||||
tacHaystacks.translationKeys = Array.from(translations.keys());
|
||||
tacHaystacks.translationValues = Array.from(translations.values());
|
||||
}
|
||||
|
||||
async function loadExtraTags(c) {
|
||||
@@ -241,6 +253,7 @@ async function syncOptions() {
|
||||
wildcardCompletionMode: opts["tac_wildcardCompletionMode"],
|
||||
modelKeywordCompletion: opts["tac_modelKeywordCompletion"],
|
||||
modelKeywordLocation: opts["tac_modelKeywordLocation"],
|
||||
wcWrap: opts["dp_parser_wildcard_wrap"] || "__", // to support custom wrapper chars set by dp_parser
|
||||
// Alias settings
|
||||
alias: {
|
||||
searchByAlias: opts["tac_alias.searchByAlias"],
|
||||
@@ -413,7 +426,7 @@ const COMPLETED_WILDCARD_REGEX = /__[^\s,_][^\t\n\r,_]*[^\s,_]__[^\s,_]*/g;
|
||||
const STYLE_VAR_REGEX = /\$\(?[^$|\[\],\s]*\)?/g;
|
||||
const NORMAL_TAG_REGEX = /[^\s,|<>\[\]:]+_\([^\s,|<>\[\]:]*\)?|[^\s,|<>():\[\]]+|</g;
|
||||
const RUBY_TAG_REGEX = /[\w\d<][\w\d' \-?!/$%]{2,}>?/g;
|
||||
const TAG_REGEX = new RegExp(`${POINTY_REGEX.source}|${COMPLETED_WILDCARD_REGEX.source}|${STYLE_VAR_REGEX.source}|${NORMAL_TAG_REGEX.source}`, "g");
|
||||
const TAG_REGEX = () => { return new RegExp(`${POINTY_REGEX.source}|${COMPLETED_WILDCARD_REGEX.source.replaceAll("__", escapeRegExp(TAC_CFG.wcWrap))}|${STYLE_VAR_REGEX.source}|${NORMAL_TAG_REGEX.source}`, "g"); }
|
||||
|
||||
// On click, insert the tag into the prompt textbox with respect to the cursor position
|
||||
async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithoutChoice = false) {
|
||||
@@ -469,7 +482,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
// Don't cut off the __ at the end if it is already the full path
|
||||
if (firstDifference > 0 && firstDifference < longestResult) {
|
||||
// +2 because the sanitized text already has the __ at the start but the matched text doesn't
|
||||
sanitizedText = sanitizedText.substring(0, firstDifference + 2);
|
||||
sanitizedText = sanitizedText.substring(0, firstDifference + TAC_CFG.wcWrap.length);
|
||||
} else if (firstDifference === 0) {
|
||||
sanitizedText = tagword;
|
||||
}
|
||||
@@ -484,7 +497,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
case ResultType.wildcardFile:
|
||||
case ResultType.yamlWildcard:
|
||||
// We only want to update the frequency for a full wildcard, not partial paths
|
||||
if (sanitizedText.endsWith("__"))
|
||||
if (sanitizedText.endsWith(TAC_CFG.wcWrap))
|
||||
name = text
|
||||
break;
|
||||
case ResultType.chant:
|
||||
@@ -552,7 +565,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
let keywords = null;
|
||||
// Check built-in activation words first
|
||||
if (tagType === ResultType.lora || tagType === ResultType.lyco) {
|
||||
let info = await fetchAPI(`tacapi/v1/lora-info/${result.text}`)
|
||||
let info = await fetchTacAPI(`tacapi/v1/lora-info/${result.text}`)
|
||||
if (info && info["activation text"]) {
|
||||
keywords = info["activation text"];
|
||||
}
|
||||
@@ -564,7 +577,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
|
||||
// No match, try to find a sha256 match from the cache file
|
||||
if (!nameDict) {
|
||||
const sha256 = await fetchAPI(`/tacapi/v1/lora-cached-hash/${result.text}`)
|
||||
const sha256 = await fetchTacAPI(`/tacapi/v1/lora-cached-hash/${result.text}`)
|
||||
if (sha256) {
|
||||
nameDict = modelKeywordDict.get(sha256);
|
||||
}
|
||||
@@ -622,7 +635,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout
|
||||
// Update previous tags with the edited prompt to prevent re-searching the same term
|
||||
let weightedTags = [...newPrompt.matchAll(WEIGHT_REGEX)]
|
||||
.map(match => match[1]);
|
||||
let tags = newPrompt.match(TAG_REGEX)
|
||||
let tags = newPrompt.match(TAG_REGEX())
|
||||
if (weightedTags !== null) {
|
||||
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted)))
|
||||
.concat(weightedTags);
|
||||
@@ -713,7 +726,31 @@ function addResultsToList(textArea, results, tagword, resetList) {
|
||||
displayText += `[${translations.get(result.text)}]`;
|
||||
|
||||
// Print search term bolded in result
|
||||
itemText.innerHTML = displayText.replace(tagword, `<b>${tagword}</b>`);
|
||||
if (result.highlightedText) {
|
||||
switch (result.matchSource) {
|
||||
case "base":
|
||||
itemText.innerHTML = result.highlightedText;
|
||||
break;
|
||||
case "alias":
|
||||
let aliases = result.highlightedText.split(",");
|
||||
let matchingAlias = aliases.find(a => a.includes("<b class=\"acMatchHighlight\">"));
|
||||
itemText.innerHTML = matchingAlias + " ➝ " + result.text;
|
||||
break;
|
||||
case "translatedBase":
|
||||
itemText.innerHTML = `${result.text}[${result.highlightedText}]`
|
||||
break;
|
||||
case "translatedAlias":
|
||||
let tAliases = result.aliases.split(",");
|
||||
let tMatchingAlias = tAliases.find(a => a.includes("<b class=\"acMatchHighlight\">"));
|
||||
let baseTranslation = `[${translations.get(result.text)}];` || "";
|
||||
itemText.innerHTML = `${tMatchingAlias}[${result.highlightedText}] ➝ ${result.text}${baseTranslation}`;
|
||||
default:
|
||||
itemText.innerHTML = displayText;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
itemText.innerHTML = displayText;
|
||||
}
|
||||
|
||||
const splitTypes = [ResultType.wildcardFile, ResultType.yamlWildcard]
|
||||
if (splitTypes.includes(result.type) && itemText.innerHTML.includes("/")) {
|
||||
@@ -860,34 +897,19 @@ async function updateSelectionStyle(textArea, newIndex, oldIndex) {
|
||||
|
||||
// Show preview if enabled and the selected type supports it
|
||||
if (newIndex !== null) {
|
||||
let selected = items[newIndex];
|
||||
let previewTypes = ["v1 Embedding", "v2 Embedding", "Hypernetwork", "Lora", "Lyco"];
|
||||
let selectedType = selected.querySelector(".acMetaText").innerText;
|
||||
let selectedFilename = selected.querySelector(".acListItem").innerText;
|
||||
let selectedResult = results[newIndex];
|
||||
let selectedType = selectedResult.type;
|
||||
// These types support previews (others could technically too, but are not native to the webui gallery)
|
||||
let previewTypes = [ResultType.embedding, ResultType.hypernetwork, ResultType.lora, ResultType.lyco];
|
||||
|
||||
let previewDiv = gradioApp().querySelector(`.autocompleteParent${textAreaId} .sideInfo`);
|
||||
|
||||
if (TAC_CFG.showExtraNetworkPreviews && previewTypes.includes(selectedType)) {
|
||||
let shorthandType = "";
|
||||
switch (selectedType) {
|
||||
case "v1 Embedding":
|
||||
case "v2 Embedding":
|
||||
shorthandType = "embed";
|
||||
break;
|
||||
case "Hypernetwork":
|
||||
shorthandType = "hyper";
|
||||
break;
|
||||
case "Lora":
|
||||
shorthandType = "lora";
|
||||
break;
|
||||
case "Lyco":
|
||||
shorthandType = "lyco";
|
||||
break;
|
||||
}
|
||||
|
||||
let img = previewDiv.querySelector("img");
|
||||
|
||||
let url = await getExtraNetworkPreviewURL(selectedFilename, shorthandType);
|
||||
// String representation of our type enum
|
||||
const typeString = Object.keys(ResultType)[selectedType - 1].toLowerCase();
|
||||
// Get image from API
|
||||
let url = await getTacExtraNetworkPreviewURL(selectedResult.text, typeString);
|
||||
if (url) {
|
||||
img.src = url;
|
||||
previewDiv.style.display = "block";
|
||||
@@ -1055,7 +1077,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
// 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)
|
||||
let tags = prompt.match(TAG_REGEX())
|
||||
if (weightedTags !== null && tags !== null) {
|
||||
tags = tags.filter(tag => !weightedTags.some(weighted => tag.includes(weighted) && !tag.startsWith("<[") && !tag.startsWith("$(")))
|
||||
.concat(weightedTags);
|
||||
@@ -1139,6 +1161,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
else
|
||||
fil = (x) => baseFilter(x);
|
||||
|
||||
/*
|
||||
// Add final results
|
||||
allTags.filter(fil).forEach(t => {
|
||||
let result = new AutocompleteResult(t[0].trim(), ResultType.tag)
|
||||
@@ -1147,25 +1170,78 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
result.aliases = t[3];
|
||||
results.push(result);
|
||||
});
|
||||
*/
|
||||
|
||||
// Add extras
|
||||
if (TAC_CFG.extra.extraFile) {
|
||||
let extraResults = [];
|
||||
let tagIndexSet = new Set()
|
||||
let extraIndexSet = new Set();
|
||||
// Here we store the values instead of the index, as the same translation can apply to different keys
|
||||
// For searching, only the first result is relevant (assuming it is also the best match)
|
||||
let translatedValueSet = new Set();
|
||||
|
||||
extras.filter(fil).forEach(e => {
|
||||
let result = new AutocompleteResult(e[0].trim(), ResultType.extra)
|
||||
result.category = e[1] || 0; // If no category is given, use 0 as the default
|
||||
result.meta = e[2] || "Custom tag";
|
||||
result.aliases = e[3] || "";
|
||||
extraResults.push(result);
|
||||
});
|
||||
|
||||
if (TAC_CFG.extra.addMode === "Insert before") {
|
||||
results = extraResults.concat(results);
|
||||
} else {
|
||||
results = results.concat(extraResults);
|
||||
}
|
||||
let tagOut = [];
|
||||
let extraOut = [];
|
||||
// Base text search
|
||||
TacFuzzy.assignResults(TacFuzzy.search(tacHaystacks.tag, tagword),"base", "tag", tagIndexSet, tagOut)
|
||||
TacFuzzy.assignResults(TacFuzzy.search(tacHaystacks.extra, tagword), "base", "extra", extraIndexSet, extraOut)
|
||||
// Alias search
|
||||
if (TAC_CFG.alias.searchByAlias) {
|
||||
TacFuzzy.assignResults(TacFuzzy.search(tacHaystacks.tagAlias, tagword), "alias", "tag", tagIndexSet, tagOut)
|
||||
TacFuzzy.assignResults(TacFuzzy.search(tacHaystacks.extraAlias, tagword), "alias", "extra", extraIndexSet, extraOut)
|
||||
}
|
||||
// Translation search
|
||||
if (TAC_CFG.translation.searchByTranslation) {
|
||||
// Translations need special treatment as they can belong to both tags and extras and need lookup based on their keys.
|
||||
// We also use unicode search here, slower but needed for non-latin translations.
|
||||
TacFuzzy.search(tacHaystacks.translationValues, tagword, true).forEach(pair => {
|
||||
const idx = pair[0];
|
||||
const orderIdx = pair[1];
|
||||
const translationKey = tacHaystacks.translationKeys[idx];
|
||||
|
||||
// Placeholder to make sure we never access an index of null if no matching key was found
|
||||
const notFound = [null, null, null, null];
|
||||
|
||||
// Check if the translation belongs to a tag or its alias. Only search alias if no base text found.
|
||||
const translatedTagBase = allTags.find(t => t[0] === translationKey);
|
||||
const translatedTagAlias = !translatedTagBase
|
||||
? allTags.find(t => t[3]?.split(",").some(a => a === translationKey))
|
||||
: null;
|
||||
const translatedTag = translatedTagBase || translatedTagAlias || notFound; // Combined result for easier checks later
|
||||
// Check if the translation belongs to an extra or its alias. Only search alias if no base text found.
|
||||
const translatedExtraBase = extras.find(e => e[0] === translationKey);
|
||||
const translatedExtraAlias = !translatedExtraBase
|
||||
? extras.find(e => e[3]?.split(",").some(a => a === translationKey))
|
||||
: null;
|
||||
const translatedExtra = translatedExtraBase || translatedExtraAlias || notFound; // Combined result for easier checks later
|
||||
|
||||
// For translations, we can sadly only exit early after making sure we don't have a duplicate (which is most of the work).
|
||||
// This is a side effect of translations mapping to multiple keys for the search direction, eg. different aliases.
|
||||
if (translatedValueSet.has(translatedTag[0] || translatedExtra[0])) return;
|
||||
|
||||
const resultType = translatedTag[0] ? ResultType.tag : ResultType.extra;
|
||||
const result = new AutocompleteResult(translatedTag[0] || translatedExtra[0], resultType);
|
||||
result.highlightedText = TacFuzzy.toStr(orderIdx);
|
||||
|
||||
result.matchSource = (translatedTagBase || translatedExtraBase) ? "translatedBase" : "translatedAlias";
|
||||
result.category = translatedTag[1] || translatedExtra[1] || 0;
|
||||
|
||||
if (translatedTag[0])
|
||||
result.count = translatedTag[2] || 0;
|
||||
else if (translatedExtra[0])
|
||||
result.meta = translatedExtra[2] || "Custom tag";
|
||||
|
||||
result.aliases = translatedTag[3] || translatedExtra[3] || "";
|
||||
|
||||
if (translatedTag[0]) {
|
||||
tagOut.push(result);
|
||||
} else if (translatedExtra[0]) {
|
||||
extraOut.push(result);
|
||||
}
|
||||
translatedValueSet.add(translatedTag[0] || translatedExtra[0]);
|
||||
});
|
||||
}
|
||||
|
||||
// Append results for each set
|
||||
results = results.concat([...extraOut]).concat([...tagOut]);
|
||||
}
|
||||
|
||||
// Guard for empty results
|
||||
@@ -1187,7 +1263,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
const name = r.type === ResultType.chant ? r.aliases : r.text;
|
||||
// Add to alias list or tag list depending on if the name includes the tagword
|
||||
// (the same criteria is used in the filter in calculateUsageBias)
|
||||
if (aliasTypes.includes(r.type) && !name.includes(tagword)) {
|
||||
if (aliasTypes.includes(r.type) && r.matchSource === "alias") {
|
||||
aliasNames.push(name);
|
||||
} else {
|
||||
tagNames.push(name);
|
||||
@@ -1201,7 +1277,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) {
|
||||
|
||||
// Request use counts from the DB
|
||||
const names = TAC_CFG.frequencyIncludeAlias ? tagNames.concat(aliasNames) : tagNames;
|
||||
const counts = await getUseCounts(names, types, isNegative);
|
||||
const counts = await getUseCounts(names, types, isNegative) || [];
|
||||
|
||||
// Pre-calculate weights to prevent duplicate work
|
||||
const resultBiasMap = new Map();
|
||||
@@ -1361,7 +1437,7 @@ async function refreshTacTempFiles(api = false) {
|
||||
}
|
||||
|
||||
if (api) {
|
||||
await postAPI("tacapi/v1/refresh-temp-files");
|
||||
await postTacAPI("tacapi/v1/refresh-temp-files");
|
||||
await reload();
|
||||
} else {
|
||||
setTimeout(async () => {
|
||||
@@ -1371,7 +1447,7 @@ async function refreshTacTempFiles(api = false) {
|
||||
}
|
||||
|
||||
async function refreshEmbeddings() {
|
||||
await postAPI("tacapi/v1/refresh-embeddings", null);
|
||||
await postTacAPI("tacapi/v1/refresh-embeddings", null);
|
||||
embeddings = [];
|
||||
await processQueue(QUEUE_FILE_LOAD, null);
|
||||
console.log("TAC: Refreshed embeddings");
|
||||
|
||||
@@ -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 sys
|
||||
import glob
|
||||
import importlib
|
||||
import json
|
||||
@@ -21,7 +22,14 @@ from scripts.model_keyword_support import (get_lora_simple_hash,
|
||||
from scripts.shared_paths import *
|
||||
|
||||
try:
|
||||
import scripts.tag_frequency_db as tdb
|
||||
try:
|
||||
from scripts import tag_frequency_db as tdb
|
||||
except ModuleNotFoundError:
|
||||
from inspect import getframeinfo, currentframe
|
||||
filename = getframeinfo(currentframe()).filename
|
||||
parent = Path(filename).resolve().parent
|
||||
sys.path.append(str(parent))
|
||||
import tag_frequency_db as tdb
|
||||
|
||||
# Ensure the db dependency is reloaded on script reload
|
||||
importlib.reload(tdb)
|
||||
@@ -197,7 +205,7 @@ def get_embeddings(sd_model):
|
||||
skipped = skipped | skipped_sdnext
|
||||
|
||||
# Add embeddings to the correct list
|
||||
for key, emb in (loaded | skipped).items():
|
||||
for key, emb in (skipped | loaded).items():
|
||||
if emb.filename is None:
|
||||
continue
|
||||
|
||||
@@ -719,9 +727,9 @@ def api_tac(_: gr.Blocks, app: FastAPI):
|
||||
return LORA_PATH
|
||||
elif type == "lyco":
|
||||
return LYCO_PATH
|
||||
elif type == "hyper":
|
||||
elif type == "hypernetwork":
|
||||
return HYP_PATH
|
||||
elif type == "embed":
|
||||
elif type == "embedding":
|
||||
return EMB_PATH
|
||||
else:
|
||||
return None
|
||||
@@ -802,7 +810,10 @@ def api_tac(_: gr.Blocks, app: FastAPI):
|
||||
date_limit = getattr(shared.opts, "tac_frequencyMaxAge", 30)
|
||||
date_limit = date_limit if date_limit > 0 else None
|
||||
|
||||
count_list = list(db.get_tag_counts(body.tagNames, body.tagTypes, body.neg, date_limit))
|
||||
if db:
|
||||
count_list = list(db.get_tag_counts(body.tagNames, body.tagTypes, body.neg, date_limit))
|
||||
else:
|
||||
count_list = None
|
||||
|
||||
# If a limit is set, return at max the top n results by count
|
||||
if count_list and len(count_list):
|
||||
|
||||
@@ -78,6 +78,7 @@ class TagFrequencyDb:
|
||||
)
|
||||
|
||||
def __get_version(self):
|
||||
db_version = None
|
||||
with transaction() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
|
||||
@@ -28,5 +28,17 @@
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "Pony-Positive",
|
||||
"terms": "Pony,Score,Positive,Quality",
|
||||
"content": "score_9, score_8_up, score_7_up, score_6_up, source_anime, source_furry, source_pony, source_cartoon",
|
||||
"color": 1
|
||||
},
|
||||
{
|
||||
"name": "Pony-Negative",
|
||||
"terms": "Pony,Score,Negative,Quality",
|
||||
"content": "score_1, score_2, score_3, score_4, score_5, source_anime, source_furry, source_pony, source_cartoon",
|
||||
"color": 3
|
||||
}
|
||||
]
|
||||
141146
tags/derpibooru.csv
141146
tags/derpibooru.csv
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user