From 8e1422173942f29fc60e54d22e41dba31abd43a3 Mon Sep 17 00:00:00 2001 From: Dominik Reh Date: Sun, 29 Jan 2023 01:00:02 +0100 Subject: [PATCH] Extract wildcard completion --- javascript/ext_wildcards.js | 67 ++++ javascript/tagAutocomplete.js | 686 ++++++++++++++++------------------ 2 files changed, 395 insertions(+), 358 deletions(-) create mode 100644 javascript/ext_wildcards.js diff --git a/javascript/ext_wildcards.js b/javascript/ext_wildcards.js new file mode 100644 index 0000000..e2edf84 --- /dev/null +++ b/javascript/ext_wildcards.js @@ -0,0 +1,67 @@ +// Regex +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 === "__"); + +class WildcardParser extends BaseTagParser { + constructor(TRIGGER) { + super(TRIGGER); + } + + async parse() { + // Show wildcards from a file with that name + let wcMatch = [...tagword.matchAll(WC_REGEX)] + let wcFile = wcMatch[0][1]; + let wcWord = wcMatch[0][2]; + + // Look in normal wildcard files + let wcFound = wildcardFiles.find(x => x[1].toLowerCase() === wcFile); + // Use found wildcard file or look in external wildcard files + let wcPair = wcFound || wildcardExtFiles.find(x => x[1].toLowerCase() === wcFile); + + let wildcards = (await readFile(`${wcPair[0]}/${wcPair[1]}.txt?${new Date().getTime()}`)).split("\n") + .filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments + + let tempResults = wildcards.filter(x => (wcWord !== null && wcWord.length > 0) ? x.toLowerCase().includes(wcWord) : x) // Filter by tagword + .map(t => { + let result = new AutocompleteResult(t.trim(), ResultType.wildcardTag); + result.meta = wcFile; + return result; + }); + + return tempResults; + } +} + +class WildcardFileParser extends BaseTagParser { + constructor(TRIGGER) { + super(TRIGGER); + } + + parse() { + // Show available wildcard files + let tempResults = []; + if (tagword !== "__") { + let lmb = (x) => x[1].toLowerCase().includes(tagword.replace("__", "")) + tempResults = wildcardFiles.filter(lmb).concat(wildcardExtFiles.filter(lmb)) // Filter by tagword + } else { + tempResults = wildcardFiles.concat(wildcardExtFiles); + } + + let finalResults = []; + // Get final results + tempResults.forEach(wcFile => { + let result = new AutocompleteResult(wcFile[1].trim(), ResultType.wildcardFile); + result.meta = "Wildcard file"; + finalResults.push(result); + }); + + return finalResults; + } +} + +// Register the parsers +parsers.push(new WildcardParser(WC_TRIGGER)); +parsers.push(new WildcardFileParser(WC_FILE_TRIGGER)); \ No newline at end of file diff --git a/javascript/tagAutocomplete.js b/javascript/tagAutocomplete.js index 6f9e755..7efd0cb 100644 --- a/javascript/tagAutocomplete.js +++ b/javascript/tagAutocomplete.js @@ -287,7 +287,6 @@ function isEnabled() { const WEIGHT_REGEX = /[([]([^,()[\]:| ]+)(?::(?:\d+(?:\.\d+)?|\.\d+))?[)\]]/g; const TAG_REGEX = /(<[^\t\n\r,>]+>?|[^\s,|<>]+|<)/g -const WC_REGEX = /\b__([^, ]+)__([^, ]*)\b/g; const UMI_PROMPT_REGEX = /<[^\s]*?\[[^,<>]*[\]|]?>?/gi; const UMI_TAG_REGEX = /(?:\[|\||--)([^<>\[\]\-|]+)/gi; let hideBlocked = false; @@ -618,382 +617,353 @@ async function autocomplete(textArea, prompt, fixedTag = null) { results = []; tagword = tagword.toLowerCase().replace(/[\n\r]/g, ""); - let resultCandidates = processParsers(textArea, prompt); - - if (CFG.useWildcards && [...tagword.matchAll(WC_REGEX)].length > 0) { - // Show wildcards from a file with that name - wcMatch = [...tagword.matchAll(WC_REGEX)] - let wcFile = wcMatch[0][1]; - let wcWord = wcMatch[0][2]; - - var wcPair; - - // Look in normal wildcard files - if (wcFound = wildcardFiles.find(x => x[1].toLowerCase() === wcFile)) - wcPair = wcFound; - else // Look in extensions wildcard files - wcPair = wildcardExtFiles.find(x => x[1].toLowerCase() === wcFile); - - let wildcards = (await readFile(`${wcPair[0]}/${wcPair[1]}.txt?${new Date().getTime()}`)).split("\n") - .filter(x => x.trim().length > 0 && !x.startsWith('#')); // Remove empty lines and comments - - - let tempResults = wildcards.filter(x => (wcWord !== null && wcWord.length > 0) ? x.toLowerCase().includes(wcWord) : x) // Filter by tagword - tempResults.forEach(t => { - let result = new AutocompleteResult(t.trim(), ResultType.wildcardTag); - result.meta = wcFile; - results.push(result); - }); - } else if (CFG.useWildcards && (tagword.startsWith("__") && !tagword.endsWith("__") || tagword === "__")) { - // Show available wildcard files - let tempResults = []; - if (tagword !== "__") { - let lmb = (x) => x[1].toLowerCase().includes(tagword.replace("__", "")) - tempResults = wildcardFiles.filter(lmb).concat(wildcardExtFiles.filter(lmb)) // Filter by tagword - } else { - tempResults = wildcardFiles.concat(wildcardExtFiles); + // Process all parsers + let resultCandidates = await processParsers(textArea, prompt); + if (resultCandidates && resultCandidates.length > 0) { + // Flatten our candidate(s) + results = resultCandidates.flat(); + // If there was more than one candidate, sort the results by text to mix them + // 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)); } - - // Add final results - tempResults.forEach(wcFile => { - let result = new AutocompleteResult(wcFile[1].trim(), ResultType.wildcardFile); - result.meta = "Wildcard file"; - results.push(result); - }) - } else if (CFG.useWildcards && [...tagword.matchAll(UMI_PROMPT_REGEX)].length > 0) { - // We are in a UMI yaml tag definition, parse further - let umiSubPrompts = [...prompt.matchAll(UMI_PROMPT_REGEX)]; - - let umiTags = []; - let umiTagsWithOperators = [] - - const insertAt = (str,char,pos) => str.slice(0,pos) + char + str.slice(pos); - - umiSubPrompts.forEach(umiSubPrompt => { - umiTags = umiTags.concat([...umiSubPrompt[0].matchAll(UMI_TAG_REGEX)].map(x => x[1].toLowerCase())); + } else { + if (CFG.useWildcards && [...tagword.matchAll(UMI_PROMPT_REGEX)].length > 0) { + // We are in a UMI yaml tag definition, parse further + let umiSubPrompts = [...prompt.matchAll(UMI_PROMPT_REGEX)]; - const start = umiSubPrompt.index; - const end = umiSubPrompt.index + umiSubPrompt[0].length; - if (textArea.selectionStart >= start && textArea.selectionStart <= end) { - umiTagsWithOperators = insertAt(umiSubPrompt[0], '###', textArea.selectionStart - start); - } - }); + let umiTags = []; + let umiTagsWithOperators = [] - const promptSplitToTags = umiTagsWithOperators.replace(']###[', '][').split("]["); + const insertAt = (str,char,pos) => str.slice(0,pos) + char + str.slice(pos); - const clean = (str) => str - .replaceAll('>', '') - .replaceAll('<', '') - .replaceAll('[', '') - .replaceAll(']', '') - .trim(); - - const matches = promptSplitToTags.reduce((acc, curr) => { - isOptional = curr.includes("|"); - isNegative = curr.startsWith("--"); - let out; - if (isOptional) { - out = { - hasCursor: curr.includes("###"), - tags: clean(curr).split('|').map(x => ({ - hasCursor: x.includes("###"), - isNegative: x.startsWith("--"), - tag: clean(x).replaceAll("###", '').replaceAll("--", '') - })) - }; - acc.optional.push(out); - acc.all.push(...out.tags.map(x => x.tag)); - } else if (isNegative) { - out = { - hasCursor: curr.includes("###"), - tags: clean(curr).replaceAll("###", '').split('|'), - }; - out.tags = out.tags.map(x => x.startsWith("--") ? x.substring(2) : x); - acc.negative.push(out); - acc.all.push(...out.tags); - } else { - out = { - hasCursor: curr.includes("###"), - tags: clean(curr).replaceAll("###", '').split('|'), - }; - acc.positive.push(out); - acc.all.push(...out.tags); - } - return acc; - }, { positive: [], negative: [], optional: [], all: [] }); - - //console.log({ matches }) - - const filteredWildcards = (tagword) => { - const wildcards = yamlWildcards.filter(x => { - let tags = x[1]; - const matchesNeg = - matches.negative.length === 0 - || matches.negative.every(x => - x.hasCursor - || x.tags.every(t => !tags[t]) - ); - if (!matchesNeg) return false; - const matchesPos = - matches.positive.length === 0 - || matches.positive.every(x => - x.hasCursor - || x.tags.every(t => tags[t]) - ); - if (!matchesPos) return false; - const matchesOpt = - matches.optional.length === 0 - || matches.optional.some(x => - x.tags.some(t => - t.hasCursor - || t.isNegative - ? !tags[t.tag] - : tags[t.tag] - )); - if (!matchesOpt) return false; - return true; - }).reduce((acc, val) => { - Object.keys(val[1]).forEach(tag => acc[tag] = acc[tag] + 1 || 1); - return acc; - }, {}); - - return Object.entries(wildcards) - .sort((a, b) => b[1] - a[1]) - .filter(x => - x[0] === tagword - || !matches.all.includes(x[0]) - ); - } - - if (umiTags.length > 0) { - // Get difference for subprompt - let tagCountChange = umiTags.length - umiPreviousTags.length; - let diff = difference(umiTags, umiPreviousTags); - umiPreviousTags = umiTags; - - // Show all condition - let showAll = tagword.endsWith("[") || tagword.endsWith("[--") || tagword.endsWith("|"); - - // Exit early if the user closed the bracket manually - if ((!diff || diff.length === 0 || (diff.length === 1 && tagCountChange < 0)) && !showAll) { - if (!hideBlocked) hideResults(textArea); - return; - } - - let umiTagword = diff[0] || ''; - let tempResults = []; - if (umiTagword && umiTagword.length > 0) { - umiTagword = umiTagword.toLowerCase().replace(/[\n\r]/g, ""); - originalTagword = tagword; - tagword = umiTagword; - let filteredWildcardsSorted = filteredWildcards(umiTagword); - let searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(umiTagword)}`, 'i') - let baseFilter = x => x[0].toLowerCase().search(searchRegex) > -1; - let spaceIncludeFilter = x => x[0].toLowerCase().replaceAll(" ", "_").search(searchRegex) > -1; - tempResults = filteredWildcardsSorted.filter(x => baseFilter(x) || spaceIncludeFilter(x)) // Filter by tagword - - // Add final results - tempResults.forEach(t => { - let result = new AutocompleteResult(t[0].trim(), ResultType.yamlWildcard) - result.count = t[1]; - results.push(result); - }); - } else if (showAll) { - let filteredWildcardsSorted = filteredWildcards(""); + umiSubPrompts.forEach(umiSubPrompt => { + umiTags = umiTags.concat([...umiSubPrompt[0].matchAll(UMI_TAG_REGEX)].map(x => x[1].toLowerCase())); + const start = umiSubPrompt.index; + const end = umiSubPrompt.index + umiSubPrompt[0].length; + if (textArea.selectionStart >= start && textArea.selectionStart <= end) { + umiTagsWithOperators = insertAt(umiSubPrompt[0], '###', textArea.selectionStart - start); + } + }); + + const promptSplitToTags = umiTagsWithOperators.replace(']###[', '][').split("]["); + + const clean = (str) => str + .replaceAll('>', '') + .replaceAll('<', '') + .replaceAll('[', '') + .replaceAll(']', '') + .trim(); + + const matches = promptSplitToTags.reduce((acc, curr) => { + isOptional = curr.includes("|"); + isNegative = curr.startsWith("--"); + let out; + if (isOptional) { + out = { + hasCursor: curr.includes("###"), + tags: clean(curr).split('|').map(x => ({ + hasCursor: x.includes("###"), + isNegative: x.startsWith("--"), + tag: clean(x).replaceAll("###", '').replaceAll("--", '') + })) + }; + acc.optional.push(out); + acc.all.push(...out.tags.map(x => x.tag)); + } else if (isNegative) { + out = { + hasCursor: curr.includes("###"), + tags: clean(curr).replaceAll("###", '').split('|'), + }; + out.tags = out.tags.map(x => x.startsWith("--") ? x.substring(2) : x); + acc.negative.push(out); + acc.all.push(...out.tags); + } else { + out = { + hasCursor: curr.includes("###"), + tags: clean(curr).replaceAll("###", '').split('|'), + }; + acc.positive.push(out); + acc.all.push(...out.tags); + } + return acc; + }, { positive: [], negative: [], optional: [], all: [] }); + + //console.log({ matches }) + + const filteredWildcards = (tagword) => { + const wildcards = yamlWildcards.filter(x => { + let tags = x[1]; + const matchesNeg = + matches.negative.length === 0 + || matches.negative.every(x => + x.hasCursor + || x.tags.every(t => !tags[t]) + ); + if (!matchesNeg) return false; + const matchesPos = + matches.positive.length === 0 + || matches.positive.every(x => + x.hasCursor + || x.tags.every(t => tags[t]) + ); + if (!matchesPos) return false; + const matchesOpt = + matches.optional.length === 0 + || matches.optional.some(x => + x.tags.some(t => + t.hasCursor + || t.isNegative + ? !tags[t.tag] + : tags[t.tag] + )); + if (!matchesOpt) return false; + return true; + }).reduce((acc, val) => { + Object.keys(val[1]).forEach(tag => acc[tag] = acc[tag] + 1 || 1); + return acc; + }, {}); + + return Object.entries(wildcards) + .sort((a, b) => b[1] - a[1]) + .filter(x => + x[0] === tagword + || !matches.all.includes(x[0]) + ); + } + + if (umiTags.length > 0) { + // Get difference for subprompt + let tagCountChange = umiTags.length - umiPreviousTags.length; + let diff = difference(umiTags, umiPreviousTags); + umiPreviousTags = umiTags; + + // Show all condition + let showAll = tagword.endsWith("[") || tagword.endsWith("[--") || tagword.endsWith("|"); + + // Exit early if the user closed the bracket manually + if ((!diff || diff.length === 0 || (diff.length === 1 && tagCountChange < 0)) && !showAll) { + if (!hideBlocked) hideResults(textArea); + return; + } + + let umiTagword = diff[0] || ''; + let tempResults = []; + if (umiTagword && umiTagword.length > 0) { + umiTagword = umiTagword.toLowerCase().replace(/[\n\r]/g, ""); + originalTagword = tagword; + tagword = umiTagword; + let filteredWildcardsSorted = filteredWildcards(umiTagword); + let searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(umiTagword)}`, 'i') + let baseFilter = x => x[0].toLowerCase().search(searchRegex) > -1; + let spaceIncludeFilter = x => x[0].toLowerCase().replaceAll(" ", "_").search(searchRegex) > -1; + tempResults = filteredWildcardsSorted.filter(x => baseFilter(x) || spaceIncludeFilter(x)) // Filter by tagword + + // Add final results + tempResults.forEach(t => { + let result = new AutocompleteResult(t[0].trim(), ResultType.yamlWildcard) + result.count = t[1]; + results.push(result); + }); + } else if (showAll) { + let filteredWildcardsSorted = filteredWildcards(""); + + // Add final results + filteredWildcardsSorted.forEach(t => { + let result = new AutocompleteResult(t[0].trim(), ResultType.yamlWildcard) + result.count = t[1]; + results.push(result); + }); + + originalTagword = tagword; + tagword = ""; + } + } else { + let filteredWildcardsSorted = filteredWildcards(""); + // Add final results filteredWildcardsSorted.forEach(t => { let result = new AutocompleteResult(t[0].trim(), ResultType.yamlWildcard) result.count = t[1]; results.push(result); }); - + originalTagword = tagword; tagword = ""; } - } else { - let filteredWildcardsSorted = filteredWildcards(""); - - // Add final results - filteredWildcardsSorted.forEach(t => { - let result = new AutocompleteResult(t[0].trim(), ResultType.yamlWildcard) - result.count = t[1]; - results.push(result); - }); - - originalTagword = tagword; - tagword = ""; - } - } else if (CFG.useEmbeddings && tagword.match(/ ]*>?/g)) { - // Show embeddings - let tempResults = []; - if (tagword !== " x[0].toLowerCase().includes(searchTerm) && x[1] && x[1] === versionString); // Filter by tagword - else - tempResults = embeddings.filter(x => x[0].toLowerCase().includes(searchTerm)); // Filter by tagword - } else { - tempResults = embeddings; - } - - // Add final results - tempResults.forEach(t => { - let result = new AutocompleteResult(t[0].trim(), ResultType.embedding) - result.meta = t[1] + " Embedding"; - results.push(result); - }); - } else if(CFG.useHypernetworks && tagword.match(/ ]*>?/g)) { - // Show hypernetworks - let tempResults = []; - if (tagword !== " x.toLowerCase().includes(searchTerm)); // Filter by tagword - } else { - tempResults = hypernetworks; - } - - // Add final results - tempResults.forEach(t => { - let result = new AutocompleteResult(t.trim(), ResultType.hypernetwork) - result.meta = "Hypernetwork"; - results.push(result); - }); - } else if(CFG.useLoras && tagword.match(/ ]*>?/g)){ - // Show lora - let tempResults = []; - if (tagword !== " x.toLowerCase().includes(searchTerm)); // Filter by tagword - } else { - tempResults = loras; - } - - // Add final results - tempResults.forEach(t => { - let result = new AutocompleteResult(t.trim(), ResultType.lora) - result.meta = "Lora"; - results.push(result); - }); - } else if ((CFG.useEmbeddings || CFG.useHypernetworks || CFG.useLoras) && tagword.match(/<[^,> ]*>?/g)) { - // Embeddings, lora, wildcards all together with generic options - let tempEmbResults = []; - let tempHypResults = []; - let tempLoraResults = []; - if (tagword !== "<") { - let searchTerm = tagword.replace("<", "") - - let versionString; - if (searchTerm.startsWith("v1") || searchTerm.startsWith("v2")) { - versionString = searchTerm.slice(0, 2); - searchTerm = searchTerm.slice(2); - } - - if (versionString && CFG.useEmbeddings) { - // Version string is only for embeddings atm, so we don't search the other lists here. - tempEmbResults = embeddings.filter(x => x[0].toLowerCase().includes(searchTerm) && x[1] && x[1] === versionString); // Filter by tagword + } else if (CFG.useEmbeddings && tagword.match(/ ]*>?/g)) { + // Show embeddings + let tempResults = []; + if (tagword !== " x[0].toLowerCase().includes(searchTerm) && x[1] && x[1] === versionString); // Filter by tagword + else + tempResults = embeddings.filter(x => x[0].toLowerCase().includes(searchTerm)); // Filter by tagword } else { - tempEmbResults = embeddings.filter(x => x[0].toLowerCase().includes(searchTerm)); // Filter by tagword - tempHypResults = hypernetworks.filter(x => x.toLowerCase().includes(searchTerm)); // Filter by tagword - tempLoraResults = loras.filter(x => x.toLowerCase().includes(searchTerm)); // Filter by tagword + tempResults = embeddings; } - } else { - tempEmbResults = embeddings; - tempHypResults = hypernetworks; - tempLoraResults = loras; - } - // Since some tags are kaomoji, we have to still get the normal results first. - // Create escaped search regex with support for * as a start placeholder - let searchRegex; - if (tagword.startsWith("*")) { - tagword = tagword.slice(1); - searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i'); - } 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); - - // Add final results - let mixedResults = []; - if (CFG.useEmbeddings) { - tempEmbResults.forEach(t => { + // Add final results + tempResults.forEach(t => { let result = new AutocompleteResult(t[0].trim(), ResultType.embedding) result.meta = t[1] + " Embedding"; - mixedResults.push(result); - }); - } - if (CFG.useHypernetworks) { - tempHypResults.forEach(t => { - let result = new AutocompleteResult(t.trim(), ResultType.hypernetwork) - result.meta = "Hypernetwork"; - mixedResults.push(result); - }); - } - if (CFG.useLoras) { - tempLoraResults.forEach(t => { - let result = new AutocompleteResult(t.trim(), ResultType.lora) - result.meta = "Lora"; - mixedResults.push(result); - }); - } - - // Add all mixed results to the final results, sorted by name so that they aren't after one another. - results = mixedResults.sort((a, b) => a.text.localeCompare(b.text)); - - genericResults.forEach(g => { - let result = new AutocompleteResult(g[0].trim(), ResultType.tag) - result.category = g[1]; - result.count = g[2]; - result.aliases = g[3]; - results.push(result); - }); - } else { - // Create escaped search regex with support for * as a start placeholder - let searchRegex; - if (tagword.startsWith("*")) { - tagword = tagword.slice(1); - searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i'); - } else { - searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(tagword)}`, 'i'); - } - // If onlyShowAlias is enabled, we don't need to include normal results - if (CFG.alias.onlyShowAlias) { - results = allTags.filter(x => x[3] && x[3].toLowerCase().search(searchRegex) > -1); - } else { - // Else both normal tags and aliases/translations are included depending on the config - let baseFilter = (x) => x[0].toLowerCase().search(searchRegex) > -1; - let aliasFilter = (x) => x[3] && x[3].toLowerCase().search(searchRegex) > -1; - let translationFilter = (x) => (translations.has(x[0]) && translations.get(x[0]).toLowerCase().search(searchRegex) > -1) - || 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) - fil = (x) => baseFilter(x) || aliasFilter(x) || translationFilter(x); - else if (CFG.alias.searchByAlias && !CFG.translation.searchByTranslation) - fil = (x) => baseFilter(x) || aliasFilter(x); - else if (CFG.translation.searchByTranslation && !CFG.alias.searchByAlias) - fil = (x) => baseFilter(x) || translationFilter(x); - else - fil = (x) => baseFilter(x); - - // Add final results - allTags.filter(fil).forEach(t => { - let result = new AutocompleteResult(t[0].trim(), ResultType.tag) - result.category = t[1]; - result.count = t[2]; - result.aliases = t[3]; results.push(result); }); - } - // Slice if the user has set a max result count - if (!CFG.showAllResults) { - results = results.slice(0, CFG.maxResults); + } else if(CFG.useHypernetworks && tagword.match(/ ]*>?/g)) { + // Show hypernetworks + let tempResults = []; + if (tagword !== " x.toLowerCase().includes(searchTerm)); // Filter by tagword + } else { + tempResults = hypernetworks; + } + + // Add final results + tempResults.forEach(t => { + let result = new AutocompleteResult(t.trim(), ResultType.hypernetwork) + result.meta = "Hypernetwork"; + results.push(result); + }); + } else if(CFG.useLoras && tagword.match(/ ]*>?/g)){ + // Show lora + let tempResults = []; + if (tagword !== " x.toLowerCase().includes(searchTerm)); // Filter by tagword + } else { + tempResults = loras; + } + + // Add final results + tempResults.forEach(t => { + let result = new AutocompleteResult(t.trim(), ResultType.lora) + result.meta = "Lora"; + results.push(result); + }); + } else if ((CFG.useEmbeddings || CFG.useHypernetworks || CFG.useLoras) && tagword.match(/<[^,> ]*>?/g)) { + // Embeddings, lora, wildcards all together with generic options + let tempEmbResults = []; + let tempHypResults = []; + let tempLoraResults = []; + if (tagword !== "<") { + let searchTerm = tagword.replace("<", "") + + let versionString; + if (searchTerm.startsWith("v1") || searchTerm.startsWith("v2")) { + versionString = searchTerm.slice(0, 2); + searchTerm = searchTerm.slice(2); + } + + if (versionString && CFG.useEmbeddings) { + // Version string is only for embeddings atm, so we don't search the other lists here. + tempEmbResults = embeddings.filter(x => x[0].toLowerCase().includes(searchTerm) && x[1] && x[1] === versionString); // Filter by tagword + } else { + tempEmbResults = embeddings.filter(x => x[0].toLowerCase().includes(searchTerm)); // Filter by tagword + tempHypResults = hypernetworks.filter(x => x.toLowerCase().includes(searchTerm)); // Filter by tagword + tempLoraResults = loras.filter(x => x.toLowerCase().includes(searchTerm)); // Filter by tagword + } + } else { + tempEmbResults = embeddings; + tempHypResults = hypernetworks; + tempLoraResults = loras; + } + + // Since some tags are kaomoji, we have to still get the normal results first. + // Create escaped search regex with support for * as a start placeholder + let searchRegex; + if (tagword.startsWith("*")) { + tagword = tagword.slice(1); + searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i'); + } 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); + + // Add final results + let mixedResults = []; + if (CFG.useEmbeddings) { + tempEmbResults.forEach(t => { + let result = new AutocompleteResult(t[0].trim(), ResultType.embedding) + result.meta = t[1] + " Embedding"; + mixedResults.push(result); + }); + } + if (CFG.useHypernetworks) { + tempHypResults.forEach(t => { + let result = new AutocompleteResult(t.trim(), ResultType.hypernetwork) + result.meta = "Hypernetwork"; + mixedResults.push(result); + }); + } + if (CFG.useLoras) { + tempLoraResults.forEach(t => { + let result = new AutocompleteResult(t.trim(), ResultType.lora) + result.meta = "Lora"; + mixedResults.push(result); + }); + } + + // Add all mixed results to the final results, sorted by name so that they aren't after one another. + results = mixedResults.sort((a, b) => a.text.localeCompare(b.text)); + + genericResults.forEach(g => { + let result = new AutocompleteResult(g[0].trim(), ResultType.tag) + result.category = g[1]; + result.count = g[2]; + result.aliases = g[3]; + results.push(result); + }); + } else { + // Create escaped search regex with support for * as a start placeholder + let searchRegex; + if (tagword.startsWith("*")) { + tagword = tagword.slice(1); + searchRegex = new RegExp(`${escapeRegExp(tagword)}`, 'i'); + } else { + searchRegex = new RegExp(`(^|[^a-zA-Z])${escapeRegExp(tagword)}`, 'i'); + } + // If onlyShowAlias is enabled, we don't need to include normal results + if (CFG.alias.onlyShowAlias) { + results = allTags.filter(x => x[3] && x[3].toLowerCase().search(searchRegex) > -1); + } else { + // Else both normal tags and aliases/translations are included depending on the config + let baseFilter = (x) => x[0].toLowerCase().search(searchRegex) > -1; + let aliasFilter = (x) => x[3] && x[3].toLowerCase().search(searchRegex) > -1; + let translationFilter = (x) => (translations.has(x[0]) && translations.get(x[0]).toLowerCase().search(searchRegex) > -1) + || 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) + fil = (x) => baseFilter(x) || aliasFilter(x) || translationFilter(x); + else if (CFG.alias.searchByAlias && !CFG.translation.searchByTranslation) + fil = (x) => baseFilter(x) || aliasFilter(x); + else if (CFG.translation.searchByTranslation && !CFG.alias.searchByAlias) + fil = (x) => baseFilter(x) || translationFilter(x); + else + fil = (x) => baseFilter(x); + + // Add final results + allTags.filter(fil).forEach(t => { + let result = new AutocompleteResult(t[0].trim(), ResultType.tag) + result.category = t[1]; + result.count = t[2]; + result.aliases = t[3]; + results.push(result); + }); + } + // Slice if the user has set a max result count + if (!CFG.showAllResults) { + results = results.slice(0, CFG.maxResults); + } } } @@ -1004,8 +974,8 @@ async function autocomplete(textArea, prompt, fixedTag = null) { return; } - showResults(textArea); addResultsToList(textArea, results, tagword, true); + showResults(textArea); } function navigateInList(textArea, event) { @@ -1096,7 +1066,7 @@ async function setup() { CFG["colors"] = (await readFile(`${tagBasePath}/colors.json?${new Date().getTime()}`, true)); // Load wildcards - if (wildcardFiles.length === 0) { + if (wildcardFiles.length === 0 && wildcardExtFiles.length === 0) { try { let wcFileArr = (await readFile(`${tagBasePath}/temp/wc.txt?${new Date().getTime()}`)).split("\n"); let wcBasePath = wcFileArr[0].trim(); // First line should be the base path