[Missing change] Adapt local autocompleter to the new API

This commit is contained in:
MareStare 2025-03-14 23:29:52 +00:00
parent 76f434b039
commit caa2688c73
2 changed files with 78 additions and 36 deletions

View file

@ -3,9 +3,8 @@ import { promises } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { TextDecoder } from 'util'; import { TextDecoder } from 'util';
describe('Local Autocompleter', () => { describe('LocalAutocompleter', () => {
let mockData: ArrayBuffer; let mockData: ArrayBuffer;
const defaultK = 5;
beforeAll(async () => { beforeAll(async () => {
const mockDataPath = join(__dirname, 'autocomplete-compiled-v2.bin'); const mockDataPath = join(__dirname, 'autocomplete-compiled-v2.bin');
@ -44,59 +43,81 @@ describe('Local Autocompleter', () => {
}); });
}); });
describe('topK', () => { describe('matchPrefix', () => {
const termStem = ['f', 'o'].join(''); const termStem = ['f', 'o'].join('');
let localAutocomplete: LocalAutocompleter; function expectLocalAutocomplete(term: string, topK = 5) {
const localAutocomplete = new LocalAutocompleter(mockData);
const results = localAutocomplete.matchPrefix(term, topK);
const actual = results.map(result => {
const canonical = `${result.canonical} (${result.images})`;
return result.alias ? `${result.alias} -> ${canonical}` : canonical;
});
beforeAll(() => { return expect(actual);
localAutocomplete = new LocalAutocompleter(mockData); }
});
beforeEach(() => { beforeEach(() => {
window.booru.hiddenTagList = []; window.booru.hiddenTagList = [];
}); });
it('should return suggestions for exact tag name match', () => { it('should return suggestions for exact tag name match', () => {
const result = localAutocomplete.matchPrefix('safe', defaultK); expectLocalAutocomplete('safe').toMatchInlineSnapshot(`
expect(result).toEqual([expect.objectContaining({ aliasName: 'safe', name: 'safe', imageCount: 6 })]); [
"safe (6)",
]
`);
}); });
it('should return suggestion for original tag when passed an alias', () => { it('should return suggestion for an alias', () => {
const result = localAutocomplete.matchPrefix('flowers', defaultK); expectLocalAutocomplete('flowers').toMatchInlineSnapshot(`
expect(result).toEqual([expect.objectContaining({ aliasName: 'flowers', name: 'flower', imageCount: 1 })]); [
"flowers -> flower (1)",
]
`);
});
it('should prefer canonical tag over an alias when both match', () => {
expectLocalAutocomplete('flo').toMatchInlineSnapshot(`
[
"flower (1)",
]
`);
}); });
it('should return suggestions sorted by image count', () => { it('should return suggestions sorted by image count', () => {
const result = localAutocomplete.matchPrefix(termStem, defaultK); expectLocalAutocomplete(termStem).toMatchInlineSnapshot(`
expect(result).toEqual([ [
expect.objectContaining({ aliasName: 'forest', name: 'forest', imageCount: 3 }), "forest (3)",
expect.objectContaining({ aliasName: 'fog', name: 'fog', imageCount: 1 }), "fog (1)",
expect.objectContaining({ aliasName: 'force field', name: 'force field', imageCount: 1 }), "force field (1)",
]); ]
`);
}); });
it('should return namespaced suggestions without including namespace', () => { it('should return namespaced suggestions without including namespace', () => {
const result = localAutocomplete.matchPrefix('test', defaultK); expectLocalAutocomplete('test').toMatchInlineSnapshot(`
expect(result).toEqual([ [
expect.objectContaining({ aliasName: 'artist:test', name: 'artist:test', imageCount: 1 }), "artist:test (1)",
]); ]
`);
}); });
it('should return only the required number of suggestions', () => { it('should return only the required number of suggestions', () => {
const result = localAutocomplete.matchPrefix(termStem, 1); expectLocalAutocomplete(termStem, 1).toMatchInlineSnapshot(`
expect(result).toEqual([expect.objectContaining({ aliasName: 'forest', name: 'forest', imageCount: 3 })]); [
"forest (3)",
]
`);
}); });
it('should NOT return suggestions associated with hidden tags', () => { it('should NOT return suggestions associated with hidden tags', () => {
window.booru.hiddenTagList = [1]; window.booru.hiddenTagList = [1];
const result = localAutocomplete.matchPrefix(termStem, defaultK); expectLocalAutocomplete(termStem).toMatchInlineSnapshot(`[]`);
expect(result).toEqual([]);
}); });
it('should return empty array for empty prefix', () => { it('should return empty array for empty prefix', () => {
const result = localAutocomplete.matchPrefix('', defaultK); expectLocalAutocomplete('').toMatchInlineSnapshot(`[]`);
expect(result).toEqual([]);
}); });
}); });
}); });

View file

@ -3,9 +3,21 @@ import { UniqueHeap } from './unique-heap';
import store from './store'; import store from './store';
export interface Result { export interface Result {
aliasName: string; /**
name: string; * If present, then this suggestion is for a tag alias.
imageCount: number; * If absent, then this suggestion is for the `canonical` tag name.
*/
alias?: null | string;
/**
* The canonical name of the tag (non-alias).
*/
canonical: string;
/**
* Number of images tagged with this tag.
*/
images: number;
} }
/** /**
@ -253,10 +265,19 @@ export class LocalAutocompleter {
this.scanResults(referenceToAliasIndex, namespaceMatch, hasFilteredAssociation, isAlias, results); this.scanResults(referenceToAliasIndex, namespaceMatch, hasFilteredAssociation, isAlias, results);
// Convert top K from heap into result array // Convert top K from heap into result array
return results.topK(k).map((i: TagReferenceIndex) => ({ return results.topK(k).map((i: TagReferenceIndex) => {
aliasName: this.decoder.decode(this.referenceToName(i, false)), const alias = this.decoder.decode(this.referenceToName(i, false));
name: this.decoder.decode(this.referenceToName(i)), const canonical = this.decoder.decode(this.referenceToName(i));
imageCount: this.getImageCount(i), const result: Result = {
})); canonical,
images: this.getImageCount(i),
};
if (alias !== canonical) {
result.alias = alias;
}
return result;
});
} }
} }