diff --git a/javascript/_result.js b/javascript/_result.js index 823f26d..7c32f08 100644 --- a/javascript/_result.js +++ b/javascript/_result.js @@ -24,6 +24,7 @@ class AutocompleteResult { // Additional info, only used in some cases category = null; count = null; + usageBias = null; aliases = null; meta = null; hash = null; diff --git a/javascript/_utils.js b/javascript/_utils.js index 554e4f0..09f7220 100644 --- a/javascript/_utils.js +++ b/javascript/_utils.js @@ -170,25 +170,31 @@ function flatten(obj, roots = [], sep = ".") { } // Calculate biased tag score based on post count and frequent usage -function tagBias(count, uses) { - return Math.log(count) + Math.log(uses); +function calculateUsageBias(count, uses) { + return Math.log(1 + count) + Math.log(1 + uses); +} +// Beautify return type for easier parsing +function mapUseCountArray(useCounts) { + return useCounts.map(useCount => {return {"name": useCount[0], "type": useCount[1], "count": useCount[2]}}); } // Call API endpoint to increase bias of tag in the database -function increaseUseCount(tagName, type) { - postAPI(`tacapi/v1/increase-use-count?tagname=${tagName}&ttype=${type}`, null); +async function increaseUseCount(tagName, type) { + await postAPI(`tacapi/v1/increase-use-count?tagname=${tagName}&ttype=${type}`, null); } // Get use count of tag from the database async function getUseCount(tagName, type) { return (await fetchAPI(`tacapi/v1/get-use-count?tagname=${tagName}&ttype=${type}`, true, false))["result"]; } async function getUseCounts(tagNames, types) { - return (await fetchAPI(`tacapi/v1/get-use-count-list?tags=${tagNames.join("&tags=")}&ttypes=${types.join("&ttypes=")}`))["result"]; + const rawArray = (await fetchAPI(`tacapi/v1/get-use-count-list?tags=${tagNames.join("&tags=")}&ttypes=${types.join("&ttypes=")}`))["result"] + return mapUseCountArray(rawArray); } async function getAllUseCounts() { - return (await fetchAPI(`tacapi/v1/get-all-use-counts`))["result"]; + const rawArray = (await fetchAPI(`tacapi/v1/get-all-use-counts`))["result"]; + return mapUseCountArray(rawArray); } async function resetUseCount(tagName, type) { - putAPI(`tacapi/v1/reset-use-count?tagname=${tagName}&ttype=${type}`, null); + await putAPI(`tacapi/v1/reset-use-count?tagname=${tagName}&ttype=${type}`, null); } // Sliding window function to get possible combination groups of an array diff --git a/javascript/tagAutocomplete.js b/javascript/tagAutocomplete.js index c101831..eacbdcd 100644 --- a/javascript/tagAutocomplete.js +++ b/javascript/tagAutocomplete.js @@ -84,6 +84,10 @@ const autocompleteCSS = ` white-space: nowrap; color: var(--meta-text-color); } + .acMetaText.biased::before { + content: "✨"; + margin-right: 2px; + } .acWikiLink { padding: 0.5rem; margin: -0.5rem 0 -0.5rem -0.5rem; @@ -486,7 +490,7 @@ async function insertTextAtCursor(textArea, result, tagword, tabCompletedWithout // Sanitize name for API call name = encodeURIComponent(name) // Call API & update db - increaseUseCount(name, tagType) + await increaseUseCount(name, tagType) } } @@ -773,6 +777,11 @@ function addResultsToList(textArea, results, tagword, resetList) { flexDiv.appendChild(metaDiv); } + // Add small ✨ marker to indicate usage sorting + if (result.usageBias) { + flexDiv.querySelector(".acMetaText").classList.add("biased"); + } + // Add listener li.addEventListener("click", function () { insertTextAtCursor(textArea, result, tagword); }); // Add element to list @@ -1042,6 +1051,9 @@ async function autocomplete(textArea, prompt, fixedTag = null) { resultCountBeforeNormalTags = 0; tagword = tagword.toLowerCase().replace(/[\n\r]/g, ""); + // Needed for slicing check later + let normalTags = false; + // Process all parsers let resultCandidates = (await processParsers(textArea, prompt))?.filter(x => x.length > 0); // If one ore more result candidates match, use their results @@ -1077,6 +1089,7 @@ async function autocomplete(textArea, prompt, fixedTag = null) { if (!resultCandidates || resultCandidates.length === 0 || (TAC_CFG.includeEmbeddingsInNormalResults && !(tagword.startsWith("<") || tagword.startsWith("*<"))) ) { + normalTags = true; resultCountBeforeNormalTags = results.length; // Create escaped search regex with support for * as a start placeholder @@ -1131,11 +1144,6 @@ async function autocomplete(textArea, prompt, fixedTag = null) { results = results.concat(extraResults); } } - - // Slice if the user has set a max result count - if (!TAC_CFG.showAllResults) { - results = results.slice(0, TAC_CFG.maxResults + resultCountBeforeNormalTags); - } } // Guard for empty results @@ -1145,6 +1153,49 @@ async function autocomplete(textArea, prompt, fixedTag = null) { return; } + // Sort again with frequency / usage count if enabled + if (TAC_CFG.frequencySort) { + // Split our results into a list of names and types + let names = []; + let types = []; + // We need to limit size for the request url + results.slice(0, 100).forEach(r => { + const name = r.type === ResultType.chant ? r.aliases : r.text; + names.push(name); + types.push(r.type); + }); + + // Request use counts from the DB + const counts = await getUseCounts(names, types); + const usedResults = counts.filter(c => c.count > 0).map(c => c.name); + + // Sort all + results = results.sort((a, b) => { + const aName = a.type === ResultType.chant ? a.aliases : a.text; + const bName = b.type === ResultType.chant ? b.aliases : b.text; + + const aUseStats = counts.find(c => c.name === aName && c.type === a.type); + const bUseStats = counts.find(c => c.name === bName && c.type === b.type); + + const aWeight = calculateUsageBias(a.count || 0, aUseStats ? aUseStats.count : 0); + const bWeight = calculateUsageBias(b.count || 0, bUseStats ? bUseStats.count : 0); + + return bWeight - aWeight; + }); + + // Mark results + results.forEach(r => { + const name = r.type === ResultType.chant ? r.aliases : r.text; + if (usedResults.includes(name)) + r.usageBias = true; + }); + } + + // Slice if the user has set a max result count and we are not in a extra networks / wildcard list + if (!TAC_CFG.showAllResults && normalTags) { + results = results.slice(0, TAC_CFG.maxResults + resultCountBeforeNormalTags); + } + addResultsToList(textArea, results, tagword, true); showResults(textArea); }