Merge pull request #3106 from joenunezb/optimize/improve-search perf(search): improve fuzzy character search performance by ~13x (4.5s → 350ms)
Signed| @@ -1621,7 +1621,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { | |||
| 1621 | subEntities = filterByTagState(entities, { subForEntity: entity }); | 1621 | subEntities = filterByTagState(entities, { subForEntity: entity }); |
| 1622 | if (doFilter) { | 1622 | if (doFilter) { |
| 1623 | // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up | 1623 | // sub entities filter "hacked" because folder filter should not be applied there, so even in "only folders" mode characters show up |
| 1624 | subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } }); | 1624 | subEntities = entitiesFilter.applyFilters(subEntities, { clearScoreCache: false, tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false }); |
| 1625 | } | 1625 | } |
| 1626 | if (doSort) { | 1626 | if (doSort) { |
| 1627 | sortEntitiesList(subEntities); | 1627 | sortEntitiesList(subEntities); |
| @@ -1634,11 +1634,11 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { | |||
| 1634 | // Second run filters, hiding whatever should be filtered later | 1634 | // Second run filters, hiding whatever should be filtered later |
| 1635 | if (doFilter) { | 1635 | if (doFilter) { |
| 1636 | const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true }); | 1636 | const beforeFinalEntities = filterByTagState(entities, { globalDisplayFilters: true }); |
| 1637 | entities = entitiesFilter.applyFilters(beforeFinalEntities); | 1637 | entities = entitiesFilter.applyFilters(beforeFinalEntities, { clearFuzzySearchCaches: false }); |
| 1638 | 1638 | ||
| 1639 | // Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters. | 1639 | // Magic for folder filter. If that one is enabled, and no folders are display anymore, we remove that filter to actually show the characters. |
| 1640 | if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) { | 1640 | if (isFilterState(entitiesFilter.getFilterData(FILTER_TYPES.FOLDER), FILTER_STATES.SELECTED) && entities.filter(x => x.type == 'tag').length == 0) { |
| 1641 | entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED } }); | 1641 | entities = entitiesFilter.applyFilters(beforeFinalEntities, { tempOverrides: { [FILTER_TYPES.FOLDER]: FILTER_STATES.UNDEFINED }, clearFuzzySearchCaches: false }); |
| 1642 | } | 1642 | } |
| 1643 | } | 1643 | } |
| 1644 | 1644 | ||
| @@ -1654,6 +1654,7 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { | |||
| 1654 | if (doSort) { | 1654 | if (doSort) { |
| 1655 | sortEntitiesList(entities); | 1655 | sortEntitiesList(entities); |
| 1656 | } | 1656 | } |
| 1657 | entitiesFilter.clearFuzzySearchCaches(); | ||
| 1657 | return entities; | 1658 | return entities; |
| 1658 | } | 1659 | } |
| 1659 | 1660 | ||
| @@ -56,6 +56,19 @@ export function isFilterState(a, b) { | |||
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | /** | 58 | /** |
| 59 | * The fuzzy search categories | ||
| 60 | * @type {{ characters: string, worldInfo: string, personas: string, tags: string, groups: string }} | ||
| 61 | */ | ||
| 62 | export const fuzzySearchCategories = Object.freeze({ | ||
| 63 | characters: 'characters', | ||
| 64 | worldInfo: 'worldInfo', | ||
| 65 | personas: 'personas', | ||
| 66 | tags: 'tags', | ||
| 67 | groups: 'groups', | ||
| 68 | }); | ||
| 69 | |||
| 70 | |||
| 71 | /** | ||
| 59 | * Helper class for filtering data. | 72 | * Helper class for filtering data. |
| 60 | * @example | 73 | * @example |
| 61 | * const filterHelper = new FilterHelper(() => console.log('data changed')); | 74 | * const filterHelper = new FilterHelper(() => console.log('data changed')); |
| @@ -73,12 +86,25 @@ export class FilterHelper { | |||
| 73 | scoreCache; | 86 | scoreCache; |
| 74 | 87 | ||
| 75 | /** | 88 | /** |
| 89 | * Cache for fuzzy search results per category. | ||
| 90 | * @type {Object.<string, { resultMap: Map<string, any> }>} | ||
| 91 | */ | ||
| 92 | fuzzySearchCaches; | ||
| 93 | |||
| 94 | /** | ||
| 76 | * Creates a new FilterHelper | 95 | * Creates a new FilterHelper |
| 77 | * @param {Function} onDataChanged Callback to trigger when the filter data changes | 96 | * @param {Function} onDataChanged Callback to trigger when the filter data changes |
| 78 | */ | 97 | */ |
| 79 | constructor(onDataChanged) { | 98 | constructor(onDataChanged) { |
| 80 | this.onDataChanged = onDataChanged; | 99 | this.onDataChanged = onDataChanged; |
| 81 | this.scoreCache = new Map(); | 100 | this.scoreCache = new Map(); |
| 101 | this.fuzzySearchCaches = { | ||
| 102 | [fuzzySearchCategories.characters]: { resultMap: new Map() }, | ||
| 103 | [fuzzySearchCategories.worldInfo]: { resultMap: new Map() }, | ||
| 104 | [fuzzySearchCategories.personas]: { resultMap: new Map() }, | ||
| 105 | [fuzzySearchCategories.tags]: { resultMap: new Map() }, | ||
| 106 | [fuzzySearchCategories.groups]: { resultMap: new Map() }, | ||
| 107 | }; | ||
| 82 | } | 108 | } |
| 83 | 109 | ||
| 84 | /** | 110 | /** |
| @@ -151,7 +177,7 @@ export class FilterHelper { | |||
| 151 | return data; | 177 | return data; |
| 152 | } | 178 | } |
| 153 | 179 | ||
| 154 | const fuzzySearchResults = fuzzySearchWorldInfo(data, term); | 180 | const fuzzySearchResults = fuzzySearchWorldInfo(data, term, this.fuzzySearchCaches); |
| 155 | this.cacheScores(FILTER_TYPES.WORLD_INFO_SEARCH, new Map(fuzzySearchResults.map(i => [i.item?.uid, i.score]))); | 181 | this.cacheScores(FILTER_TYPES.WORLD_INFO_SEARCH, new Map(fuzzySearchResults.map(i => [i.item?.uid, i.score]))); |
| 156 | 182 | ||
| 157 | const filteredData = data.filter(entity => fuzzySearchResults.find(x => x.item === entity)); | 183 | const filteredData = data.filter(entity => fuzzySearchResults.find(x => x.item === entity)); |
| @@ -170,7 +196,7 @@ export class FilterHelper { | |||
| 170 | return data; | 196 | return data; |
| 171 | } | 197 | } |
| 172 | 198 | ||
| 173 | const fuzzySearchResults = fuzzySearchPersonas(data, term); | 199 | const fuzzySearchResults = fuzzySearchPersonas(data, term, this.fuzzySearchCaches); |
| 174 | this.cacheScores(FILTER_TYPES.PERSONA_SEARCH, new Map(fuzzySearchResults.map(i => [i.item.key, i.score]))); | 200 | this.cacheScores(FILTER_TYPES.PERSONA_SEARCH, new Map(fuzzySearchResults.map(i => [i.item.key, i.score]))); |
| 175 | 201 | ||
| 176 | const filteredData = data.filter(name => fuzzySearchResults.find(x => x.item.key === name)); | 202 | const filteredData = data.filter(name => fuzzySearchResults.find(x => x.item.key === name)); |
| @@ -289,9 +315,9 @@ export class FilterHelper { | |||
| 289 | 315 | ||
| 290 | // Save fuzzy search results and scores if enabled | 316 | // Save fuzzy search results and scores if enabled |
| 291 | if (power_user.fuzzy_search) { | 317 | if (power_user.fuzzy_search) { |
| 292 | const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue); | 318 | const fuzzySearchCharactersResults = fuzzySearchCharacters(searchValue, this.fuzzySearchCaches); |
| 293 | const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue); | 319 | const fuzzySearchGroupsResults = fuzzySearchGroups(searchValue, this.fuzzySearchCaches); |
| 294 | const fuzzySearchTagsResult = fuzzySearchTags(searchValue); | 320 | const fuzzySearchTagsResult = fuzzySearchTags(searchValue, this.fuzzySearchCaches); |
| 295 | this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchCharactersResults.map(i => [`character.${i.refIndex}`, i.score]))); | 321 | this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchCharactersResults.map(i => [`character.${i.refIndex}`, i.score]))); |
| 296 | this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchGroupsResults.map(i => [`group.${i.item.id}`, i.score]))); | 322 | this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchGroupsResults.map(i => [`group.${i.item.id}`, i.score]))); |
| 297 | this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchTagsResult.map(i => [`tag.${i.item.id}`, i.score]))); | 323 | this.cacheScores(FILTER_TYPES.SEARCH, new Map(fuzzySearchTagsResult.map(i => [`tag.${i.item.id}`, i.score]))); |
| @@ -343,11 +369,14 @@ export class FilterHelper { | |||
| 343 | * @param {object} options - Optional call parameters | 369 | * @param {object} options - Optional call parameters |
| 344 | * @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared. | 370 | * @param {boolean} [options.clearScoreCache=true] - Whether the score cache should be cleared. |
| 345 | * @param {Object.<FilterType, any>} [options.tempOverrides={}] - Temporarily override specific filters for this filter application | 371 | * @param {Object.<FilterType, any>} [options.tempOverrides={}] - Temporarily override specific filters for this filter application |
| 372 | * @param {boolean} [options.clearFuzzySearchCaches=true] - Whether the fuzzy search caches should be cleared. | ||
| 346 | * @returns {any[]} The filtered data. | 373 | * @returns {any[]} The filtered data. |
| 347 | */ | 374 | */ |
| 348 | applyFilters(data, { clearScoreCache = true, tempOverrides = {} } = {}) { | 375 | applyFilters(data, { clearScoreCache = true, tempOverrides = {}, clearFuzzySearchCaches = true } = {}) { |
| 349 | if (clearScoreCache) this.clearScoreCache(); | 376 | if (clearScoreCache) this.clearScoreCache(); |
| 350 | 377 | ||
| 378 | if (clearFuzzySearchCaches) this.clearFuzzySearchCaches(); | ||
| 379 | |||
| 351 | // Save original filter states | 380 | // Save original filter states |
| 352 | const originalStates = {}; | 381 | const originalStates = {}; |
| 353 | for (const key in tempOverrides) { | 382 | for (const key in tempOverrides) { |
| @@ -411,4 +440,14 @@ export class FilterHelper { | |||
| 411 | this.scoreCache = new Map(); | 440 | this.scoreCache = new Map(); |
| 412 | } | 441 | } |
| 413 | } | 442 | } |
| 443 | |||
| 444 | /** | ||
| 445 | * Clears fuzzy search caches | ||
| 446 | */ | ||
| 447 | clearFuzzySearchCaches() { | ||
| 448 | for (const cache of Object.values(this.fuzzySearchCaches)) { | ||
| 449 | cache.resultMap.clear(); | ||
| 450 | } | ||
| 451 | console.log('All fuzzy search caches cleared'); | ||
| 452 | } | ||
| 414 | } | 453 | } |
| @@ -53,6 +53,7 @@ import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandE | |||
| 53 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 53 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 54 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; | 54 | import { POPUP_TYPE, callGenericPopup } from './popup.js'; |
| 55 | import { loadSystemPrompts } from './sysprompt.js'; | 55 | import { loadSystemPrompts } from './sysprompt.js'; |
| 56 | import { fuzzySearchCategories } from './filters.js'; | ||
| 56 | 57 | ||
| 57 | export { | 58 | export { |
| 58 | loadPowerUserSettings, | 59 | loadPowerUserSettings, |
| @@ -1829,27 +1830,28 @@ async function loadContextSettings() { | |||
| 1829 | }); | 1830 | }); |
| 1830 | } | 1831 | } |
| 1831 | 1832 | ||
| 1833 | |||
| 1832 | /** | 1834 | /** |
| 1833 | * Fuzzy search characters by a search term | 1835 | * Common function to perform fuzzy search with optional caching |
| 1836 | * @param {string} type - Type of search from fuzzySearchCategories | ||
| 1837 | * @param {any[]} data - Data array to search in | ||
| 1838 | * @param {Array<{name: string, weight: number, getFn?: Function}>} keys - Fuse.js keys configuration | ||
| 1834 | * @param {string} searchValue - The search term | 1839 | * @param {string} searchValue - The search term |
| 1840 | * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | ||
| 1835 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | 1841 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score |
| 1836 | */ | 1842 | */ |
| 1837 | export function fuzzySearchCharacters(searchValue) { | 1843 | function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) { |
| 1844 | // Check cache if provided | ||
| 1845 | if (fuzzySearchCaches) { | ||
| 1846 | const cache = fuzzySearchCaches[type]; | ||
| 1847 | if (cache?.resultMap.has(searchValue)) { | ||
| 1848 | return cache.resultMap.get(searchValue); | ||
| 1849 | } | ||
| 1850 | } | ||
| 1851 | |||
| 1838 | // @ts-ignore | 1852 | // @ts-ignore |
| 1839 | const fuse = new Fuse(characters, { | 1853 | const fuse = new Fuse(data, { |
| 1840 | keys: [ | 1854 | keys: keys, |
| 1841 | { name: 'data.name', weight: 20 }, | ||
| 1842 | { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') }, | ||
| 1843 | { name: 'data.description', weight: 3 }, | ||
| 1844 | { name: 'data.mes_example', weight: 3 }, | ||
| 1845 | { name: 'data.scenario', weight: 2 }, | ||
| 1846 | { name: 'data.personality', weight: 2 }, | ||
| 1847 | { name: 'data.first_mes', weight: 2 }, | ||
| 1848 | { name: 'data.creator_notes', weight: 2 }, | ||
| 1849 | { name: 'data.creator', weight: 1 }, | ||
| 1850 | { name: 'data.tags', weight: 1 }, | ||
| 1851 | { name: 'data.alternate_greetings', weight: 1 }, | ||
| 1852 | ], | ||
| 1853 | includeScore: true, | 1855 | includeScore: true, |
| 1854 | ignoreLocation: true, | 1856 | ignoreLocation: true, |
| 1855 | useExtendedSearch: true, | 1857 | useExtendedSearch: true, |
| @@ -1857,109 +1859,110 @@ export function fuzzySearchCharacters(searchValue) { | |||
| 1857 | }); | 1859 | }); |
| 1858 | 1860 | ||
| 1859 | const results = fuse.search(searchValue); | 1861 | const results = fuse.search(searchValue); |
| 1860 | console.debug('Characters fuzzy search results for ' + searchValue, results); | 1862 | |
| 1863 | // Store in cache if provided | ||
| 1864 | if (fuzzySearchCaches) { | ||
| 1865 | fuzzySearchCaches[type].resultMap.set(searchValue, results); | ||
| 1866 | } | ||
| 1861 | return results; | 1867 | return results; |
| 1862 | } | 1868 | } |
| 1863 | 1869 | ||
| 1864 | /** | 1870 | /** |
| 1871 | * Fuzzy search characters by a search term | ||
| 1872 | * @param {string} searchValue - The search term | ||
| 1873 | * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | ||
| 1874 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | ||
| 1875 | */ | ||
| 1876 | export function fuzzySearchCharacters(searchValue, fuzzySearchCaches = null) { | ||
| 1877 | const keys = [ | ||
| 1878 | { name: 'data.name', weight: 20 }, | ||
| 1879 | { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') }, | ||
| 1880 | { name: 'data.description', weight: 3 }, | ||
| 1881 | { name: 'data.mes_example', weight: 3 }, | ||
| 1882 | { name: 'data.scenario', weight: 2 }, | ||
| 1883 | { name: 'data.personality', weight: 2 }, | ||
| 1884 | { name: 'data.first_mes', weight: 2 }, | ||
| 1885 | { name: 'data.creator_notes', weight: 2 }, | ||
| 1886 | { name: 'data.creator', weight: 1 }, | ||
| 1887 | { name: 'data.tags', weight: 1 }, | ||
| 1888 | { name: 'data.alternate_greetings', weight: 1 }, | ||
| 1889 | ]; | ||
| 1890 | |||
| 1891 | return performFuzzySearch(fuzzySearchCategories.characters, characters, keys, searchValue, fuzzySearchCaches); | ||
| 1892 | } | ||
| 1893 | |||
| 1894 | /** | ||
| 1865 | * Fuzzy search world info entries by a search term | 1895 | * Fuzzy search world info entries by a search term |
| 1866 | * @param {*[]} data - WI items data array | 1896 | * @param {*[]} data - WI items data array |
| 1867 | * @param {string} searchValue - The search term | 1897 | * @param {string} searchValue - The search term |
| 1898 | * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | ||
| 1868 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | 1899 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score |
| 1869 | */ | 1900 | */ |
| 1870 | export function fuzzySearchWorldInfo(data, searchValue) { | 1901 | export function fuzzySearchWorldInfo(data, searchValue, fuzzySearchCaches = null) { |
| 1871 | // @ts-ignore | 1902 | const keys = [ |
| 1872 | const fuse = new Fuse(data, { | 1903 | { name: 'key', weight: 20 }, |
| 1873 | keys: [ | 1904 | { name: 'group', weight: 15 }, |
| 1874 | { name: 'key', weight: 20 }, | 1905 | { name: 'comment', weight: 10 }, |
| 1875 | { name: 'group', weight: 15 }, | 1906 | { name: 'keysecondary', weight: 10 }, |
| 1876 | { name: 'comment', weight: 10 }, | 1907 | { name: 'content', weight: 3 }, |
| 1877 | { name: 'keysecondary', weight: 10 }, | 1908 | { name: 'uid', weight: 1 }, |
| 1878 | { name: 'content', weight: 3 }, | 1909 | { name: 'automationId', weight: 1 }, |
| 1879 | { name: 'uid', weight: 1 }, | 1910 | ]; |
| 1880 | { name: 'automationId', weight: 1 }, | ||
| 1881 | ], | ||
| 1882 | includeScore: true, | ||
| 1883 | ignoreLocation: true, | ||
| 1884 | useExtendedSearch: true, | ||
| 1885 | threshold: 0.2, | ||
| 1886 | }); | ||
| 1887 | 1911 | ||
| 1888 | const results = fuse.search(searchValue); | 1912 | return performFuzzySearch(fuzzySearchCategories.worldInfo, data, keys, searchValue, fuzzySearchCaches); |
| 1889 | console.debug('World Info fuzzy search results for ' + searchValue, results); | ||
| 1890 | return results; | ||
| 1891 | } | 1913 | } |
| 1892 | 1914 | ||
| 1893 | /** | 1915 | /** |
| 1894 | * Fuzzy search persona entries by a search term | 1916 | * Fuzzy search persona entries by a search term |
| 1895 | * @param {*[]} data - persona data array | 1917 | * @param {*[]} data - persona data array |
| 1896 | * @param {string} searchValue - The search term | 1918 | * @param {string} searchValue - The search term |
| 1919 | * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | ||
| 1897 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | 1920 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score |
| 1898 | */ | 1921 | */ |
| 1899 | export function fuzzySearchPersonas(data, searchValue) { | 1922 | export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) { |
| 1900 | data = data.map(x => ({ key: x, name: power_user.personas[x] ?? '', description: power_user.persona_descriptions[x]?.description ?? '' })); | 1923 | const mappedData = data.map(x => ({ |
| 1901 | // @ts-ignore | 1924 | key: x, |
| 1902 | const fuse = new Fuse(data, { | 1925 | name: power_user.personas[x] ?? '', |
| 1903 | keys: [ | 1926 | description: power_user.persona_descriptions[x]?.description ?? '' |
| 1904 | { name: 'name', weight: 20 }, | 1927 | })); |
| 1905 | { name: 'description', weight: 3 }, | ||
| 1906 | ], | ||
| 1907 | includeScore: true, | ||
| 1908 | ignoreLocation: true, | ||
| 1909 | useExtendedSearch: true, | ||
| 1910 | threshold: 0.2, | ||
| 1911 | }); | ||
| 1912 | 1928 | ||
| 1913 | const results = fuse.search(searchValue); | 1929 | const keys = [ |
| 1914 | console.debug('Personas fuzzy search results for ' + searchValue, results); | 1930 | { name: 'name', weight: 20 }, |
| 1915 | return results; | 1931 | { name: 'description', weight: 3 }, |
| 1932 | ]; | ||
| 1933 | |||
| 1934 | return performFuzzySearch(fuzzySearchCategories.personas, mappedData, keys, searchValue, fuzzySearchCaches); | ||
| 1916 | } | 1935 | } |
| 1917 | 1936 | ||
| 1918 | /** | 1937 | /** |
| 1919 | * Fuzzy search tags by a search term | 1938 | * Fuzzy search tags by a search term |
| 1920 | * @param {string} searchValue - The search term | 1939 | * @param {string} searchValue - The search term |
| 1940 | * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | ||
| 1921 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | 1941 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score |
| 1922 | */ | 1942 | */ |
| 1923 | export function fuzzySearchTags(searchValue) { | 1943 | export function fuzzySearchTags(searchValue, fuzzySearchCaches = null) { |
| 1924 | // @ts-ignore | 1944 | const keys = [ |
| 1925 | const fuse = new Fuse(tags, { | 1945 | { name: 'name', weight: 1 }, |
| 1926 | keys: [ | 1946 | ]; |
| 1927 | { name: 'name', weight: 1 }, | ||
| 1928 | ], | ||
| 1929 | includeScore: true, | ||
| 1930 | ignoreLocation: true, | ||
| 1931 | useExtendedSearch: true, | ||
| 1932 | threshold: 0.2, | ||
| 1933 | }); | ||
| 1934 | 1947 | ||
| 1935 | const results = fuse.search(searchValue); | 1948 | return performFuzzySearch(fuzzySearchCategories.tags, tags, keys, searchValue, fuzzySearchCaches); |
| 1936 | console.debug('Tags fuzzy search results for ' + searchValue, results); | ||
| 1937 | return results; | ||
| 1938 | } | 1949 | } |
| 1939 | 1950 | ||
| 1940 | /** | 1951 | /** |
| 1941 | * Fuzzy search groups by a search term | 1952 | * Fuzzy search groups by a search term |
| 1942 | * @param {string} searchValue - The search term | 1953 | * @param {string} searchValue - The search term |
| 1954 | * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | ||
| 1943 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | 1955 | * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score |
| 1944 | */ | 1956 | */ |
| 1945 | export function fuzzySearchGroups(searchValue) { | 1957 | export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) { |
| 1946 | // @ts-ignore | 1958 | const keys = [ |
| 1947 | const fuse = new Fuse(groups, { | 1959 | { name: 'name', weight: 20 }, |
| 1948 | keys: [ | 1960 | { name: 'members', weight: 15 }, |
| 1949 | { name: 'name', weight: 20 }, | 1961 | { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') }, |
| 1950 | { name: 'members', weight: 15 }, | 1962 | { name: 'id', weight: 1 }, |
| 1951 | { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') }, | 1963 | ]; |
| 1952 | { name: 'id', weight: 1 }, | ||
| 1953 | ], | ||
| 1954 | includeScore: true, | ||
| 1955 | ignoreLocation: true, | ||
| 1956 | useExtendedSearch: true, | ||
| 1957 | threshold: 0.2, | ||
| 1958 | }); | ||
| 1959 | 1964 | ||
| 1960 | const results = fuse.search(searchValue); | 1965 | return performFuzzySearch(fuzzySearchCategories.groups, groups, keys, searchValue, fuzzySearchCaches); |
| 1961 | console.debug('Groups fuzzy search results for ' + searchValue, results); | ||
| 1962 | return results; | ||
| 1963 | } | 1966 | } |
| 1964 | 1967 | ||
| 1965 | /** | 1968 | /** |