Compare commits

...

10 Commits

Author SHA1 Message Date
DominikDoom
4ec1bc3424 Merge branch 'main' into feature-fuzzysearch 2024-07-05 17:16:52 +02:00
DominikDoom
cfe026f366 Merge branch 'main' into feature-fuzzysearch 2024-05-04 14:12:58 +02:00
DominikDoom
c594156d30 Fix duplicates & displaying for translations 2024-04-28 19:13:40 +02:00
DominikDoom
2126a5d217 Some refactoring, WIP translation search
Translation are still a bit buggy, e.g. listing duplicates & no english tag shown yet.
2024-04-28 17:14:30 +02:00
DominikDoom
a4a656ae23 Fix frequency sorting not matching fuzzy results 2024-04-28 15:47:27 +02:00
DominikDoom
c59ffed049 Extremely WIP and unperformant experiments for fuzzy lora matching 2024-04-25 23:33:34 +02:00
DominikDoom
625298bed6 Fix alias display & cross-field searching (not correct with translations yet)
- Now stays isolated to tag/alias/translation and also only matches inside one alias at a time
- Limited intraIns to 10, should still be enough for most cases while fixing extreme outliers
- Added unicode search variant in preparation for translations
2024-04-25 18:39:29 +02:00
DominikDoom
8c0c308a6f Extremely messy temp hack / experiment to search in different fields (alias etc)
Needs a better data structure later that isn't reliant on the source/index, current state is only useful for testing
2024-04-24 22:50:11 +02:00
DominikDoom
fa4a3bc036 Use μFuzzy instead as it is faster and more configurable 2024-04-21 17:58:14 +02:00
DominikDoom
a8f175925f Fuzzysort experiment WIP 2024-04-21 12:11:06 +02:00
5 changed files with 377 additions and 19 deletions

View File

@@ -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 = "";

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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);
});

View File

@@ -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) {
@@ -714,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("/")) {
@@ -1125,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)
@@ -1133,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
@@ -1173,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);