@memberjunction/ng-search 5.24.0 → 5.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/search-types.test.js +6 -2
- package/dist/__tests__/search-types.test.js.map +1 -1
- package/dist/__tests__/search.service.test.js +25 -18
- package/dist/__tests__/search.service.test.js.map +1 -1
- package/dist/lib/module.d.ts +7 -4
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/module.js +19 -4
- package/dist/lib/module.js.map +1 -1
- package/dist/lib/search-composite.component.d.ts +79 -0
- package/dist/lib/search-composite.component.d.ts.map +1 -0
- package/dist/lib/search-composite.component.js +258 -0
- package/dist/lib/search-composite.component.js.map +1 -0
- package/dist/lib/search-filter.component.d.ts +14 -1
- package/dist/lib/search-filter.component.d.ts.map +1 -1
- package/dist/lib/search-filter.component.js +128 -26
- package/dist/lib/search-filter.component.js.map +1 -1
- package/dist/lib/search-input.component.d.ts +61 -0
- package/dist/lib/search-input.component.d.ts.map +1 -0
- package/dist/lib/search-input.component.js +192 -0
- package/dist/lib/search-input.component.js.map +1 -0
- package/dist/lib/search-overlay.component.js +2 -2
- package/dist/lib/search-results.component.d.ts +9 -5
- package/dist/lib/search-results.component.d.ts.map +1 -1
- package/dist/lib/search-results.component.js +117 -98
- package/dist/lib/search-results.component.js.map +1 -1
- package/dist/lib/search-suggest.component.d.ts +95 -0
- package/dist/lib/search-suggest.component.d.ts.map +1 -0
- package/dist/lib/search-suggest.component.js +491 -0
- package/dist/lib/search-suggest.component.js.map +1 -0
- package/dist/lib/search-types.d.ts +27 -1
- package/dist/lib/search-types.d.ts.map +1 -1
- package/dist/lib/search-types.js.map +1 -1
- package/dist/lib/search.service.d.ts +27 -5
- package/dist/lib/search.service.d.ts.map +1 -1
- package/dist/lib/search.service.js +221 -92
- package/dist/lib/search.service.js.map +1 -1
- package/dist/public-api.d.ts +3 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +6 -4
|
@@ -18,6 +18,8 @@ export interface SearchResultItem {
|
|
|
18
18
|
RecordID: string;
|
|
19
19
|
/** Classification of the result source */
|
|
20
20
|
SourceType: 'entity' | 'content-item' | 'file' | 'web-page';
|
|
21
|
+
/** Discriminator for UI rendering: 'entity-record', 'storage-file', or 'content-item' */
|
|
22
|
+
ResultType?: string;
|
|
21
23
|
/** Relevance score between 0 and 1 */
|
|
22
24
|
Score: number;
|
|
23
25
|
/** Breakdown of how the score was computed */
|
|
@@ -34,6 +36,12 @@ export interface SearchResultItem {
|
|
|
34
36
|
MatchedAt: Date;
|
|
35
37
|
/** Raw vector metadata JSON — contains all entity fields stored in the vector DB */
|
|
36
38
|
RawMetadata?: string;
|
|
39
|
+
/** ID of the SearchProvider metadata record that produced this result */
|
|
40
|
+
ProviderId?: string;
|
|
41
|
+
/** Display label from the SearchProvider metadata (e.g., "Database", "Semantic Search") */
|
|
42
|
+
ProviderLabel?: string;
|
|
43
|
+
/** Font Awesome icon class from the SearchProvider metadata */
|
|
44
|
+
ProviderIcon?: string;
|
|
37
45
|
}
|
|
38
46
|
/** Breakdown of score contribution from each search source */
|
|
39
47
|
export interface ScoreBreakdown {
|
|
@@ -85,7 +93,7 @@ export interface SearchRequest {
|
|
|
85
93
|
/** Active filter selections */
|
|
86
94
|
ActiveFilters: Record<string, string[]>;
|
|
87
95
|
/** Which search sources to include */
|
|
88
|
-
IncludeSources: ('vector' | 'fulltext' | 'entity')[];
|
|
96
|
+
IncludeSources: ('vector' | 'fulltext' | 'entity' | 'storage')[];
|
|
89
97
|
/**
|
|
90
98
|
* Minimum relevance score (0-1) to include in results.
|
|
91
99
|
* Results below this threshold are filtered out.
|
|
@@ -93,6 +101,21 @@ export interface SearchRequest {
|
|
|
93
101
|
*/
|
|
94
102
|
MinScore?: number;
|
|
95
103
|
}
|
|
104
|
+
/** Metadata about an active search provider from the server */
|
|
105
|
+
export interface SearchProviderInfo {
|
|
106
|
+
/** SearchProvider record ID */
|
|
107
|
+
ID: string;
|
|
108
|
+
/** Provider name */
|
|
109
|
+
Name: string;
|
|
110
|
+
/** UI display label */
|
|
111
|
+
DisplayName: string;
|
|
112
|
+
/** Font Awesome icon class */
|
|
113
|
+
Icon: string;
|
|
114
|
+
/** The SourceType key this provider uses */
|
|
115
|
+
SourceType: string;
|
|
116
|
+
/** Priority (lower = higher) */
|
|
117
|
+
Priority: number;
|
|
118
|
+
}
|
|
96
119
|
/** Search response from the service */
|
|
97
120
|
export interface SearchResponse {
|
|
98
121
|
/** Whether the search succeeded */
|
|
@@ -112,7 +135,10 @@ export interface SearchResponse {
|
|
|
112
135
|
Vector: number;
|
|
113
136
|
FullText: number;
|
|
114
137
|
Entity: number;
|
|
138
|
+
Storage: number;
|
|
115
139
|
};
|
|
140
|
+
/** Active search providers metadata (for UI labels and icons) */
|
|
141
|
+
Providers: SearchProviderInfo[];
|
|
116
142
|
/** Error message if Success is false */
|
|
117
143
|
ErrorMessage?: string;
|
|
118
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-types.d.ts","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC7B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,UAAU,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,UAAU,CAAC;IAC5D,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,cAAc,EAAE,cAAc,CAAC;IAC/B,0BAA0B;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,SAAS,EAAE,IAAI,CAAC;IAChB,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"search-types.d.ts","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-types.js","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * @fileoverview Shared types for the ng-search package.\n *\n * Defines the data contracts for search results, filters,\n * and events used across all search components.\n */\n\n/** Represents a single search result item */\nexport interface SearchResultItem {\n /** Unique identifier */\n ID: string;\n /** Display title */\n Title: string;\n /** Short text snippet showing match context */\n Snippet: string;\n /** The entity name this result belongs to */\n EntityName: string;\n /** The primary key of the source record */\n RecordID: string;\n /** Classification of the result source */\n SourceType: 'entity' | 'content-item' | 'file' | 'web-page';\n /** Relevance score between 0 and 1 */\n Score: number;\n /** Breakdown of how the score was computed */\n ScoreBreakdown: ScoreBreakdown;\n /** Human-readable tags */\n Tags: string[];\n /** Font Awesome icon class for the source type */\n SourceIcon: string;\n /** Entity-specific icon from EntityInfo.Icon (e.g., \"fa-solid fa-robot\") */\n EntityIcon?: string;\n /** Human-readable record name resolved from EntityRecordName */\n RecordName?: string;\n /** When this result was matched */\n MatchedAt: Date;\n /** Raw vector metadata JSON — contains all entity fields stored in the vector DB */\n RawMetadata?: string;\n}\n\n/** Breakdown of score contribution from each search source */\nexport interface ScoreBreakdown {\n Vector?: number;\n FullText?: number;\n Entity?: number;\n}\n\n/** Grouped search results by source type */\nexport interface SearchResultGroup {\n /** The source type label */\n Label: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The source type key */\n SourceType: string;\n /** Results in this group */\n Results: SearchResultItem[];\n /** Total count for this source (may exceed Results.length if paginated) */\n TotalCount: number;\n}\n\n/** Faceted filter configuration */\nexport interface SearchFilter {\n /** Filter category name */\n Category: string;\n /** Filter options within this category */\n Options: SearchFilterOption[];\n /** Whether multiple options can be selected */\n MultiSelect: boolean;\n}\n\n/** Single filter option within a category */\nexport interface SearchFilterOption {\n /** Display label */\n Label: string;\n /** Value to filter by */\n Value: string;\n /** Count of results matching this option */\n Count: number;\n /** Whether this option is currently selected */\n IsSelected: boolean;\n /** Font Awesome icon class */\n Icon?: string;\n}\n\n/** Search request parameters */\nexport interface SearchRequest {\n /** The search query string */\n Query: string;\n /** Maximum number of results to return */\n MaxResults: number;\n /** Active filter selections */\n ActiveFilters: Record<string, string[]>;\n /** Which search sources to include */\n IncludeSources: ('vector' | 'fulltext' | 'entity')[];\n /**\n * Minimum relevance score (0-1) to include in results.\n * Results below this threshold are filtered out.\n * Default: 0.35 (35%).\n */\n MinScore?: number;\n}\n\n/** Search response from the service */\nexport interface SearchResponse {\n /** Whether the search succeeded */\n Success: boolean;\n /** The search results */\n Results: SearchResultItem[];\n /** Grouped results by source type */\n Groups: SearchResultGroup[];\n /** Available filters based on results */\n Filters: SearchFilter[];\n /** Total result count across all sources */\n TotalCount: number;\n /** Time taken in milliseconds */\n ElapsedMs: number;\n /** Count of results from each source */\n SourceCounts: { Vector: number; FullText: number; Entity: number };\n /** Error message if Success is false */\n ErrorMessage?: string;\n}\n\n/** Event emitted when a search result is selected */\nexport interface SearchResultSelectedEvent {\n /** The selected result item */\n Result: SearchResultItem;\n /** Whether to open in a new tab */\n OpenInNewTab: boolean;\n}\n\n/** Event emitted when filters change */\nexport interface SearchFilterChangeEvent {\n /** The filter category that changed */\n Category: string;\n /** The new selected values */\n SelectedValues: string[];\n}\n\n/** Event emitted when search is executed */\nexport interface SearchExecutedEvent {\n /** The search query */\n Query: string;\n /** Active filters */\n Filters: Record<string, string[]>;\n /** Number of results returned */\n ResultCount: number;\n /** Time taken in ms */\n ElapsedMs: number;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search-types.js","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * @fileoverview Shared types for the ng-search package.\n *\n * Defines the data contracts for search results, filters,\n * and events used across all search components.\n */\n\n/** Represents a single search result item */\nexport interface SearchResultItem {\n /** Unique identifier */\n ID: string;\n /** Display title */\n Title: string;\n /** Short text snippet showing match context */\n Snippet: string;\n /** The entity name this result belongs to */\n EntityName: string;\n /** The primary key of the source record */\n RecordID: string;\n /** Classification of the result source */\n SourceType: 'entity' | 'content-item' | 'file' | 'web-page';\n /** Discriminator for UI rendering: 'entity-record', 'storage-file', or 'content-item' */\n ResultType?: string;\n /** Relevance score between 0 and 1 */\n Score: number;\n /** Breakdown of how the score was computed */\n ScoreBreakdown: ScoreBreakdown;\n /** Human-readable tags */\n Tags: string[];\n /** Font Awesome icon class for the source type */\n SourceIcon: string;\n /** Entity-specific icon from EntityInfo.Icon (e.g., \"fa-solid fa-robot\") */\n EntityIcon?: string;\n /** Human-readable record name resolved from EntityRecordName */\n RecordName?: string;\n /** When this result was matched */\n MatchedAt: Date;\n /** Raw vector metadata JSON — contains all entity fields stored in the vector DB */\n RawMetadata?: string;\n /** ID of the SearchProvider metadata record that produced this result */\n ProviderId?: string;\n /** Display label from the SearchProvider metadata (e.g., \"Database\", \"Semantic Search\") */\n ProviderLabel?: string;\n /** Font Awesome icon class from the SearchProvider metadata */\n ProviderIcon?: string;\n}\n\n/** Breakdown of score contribution from each search source */\nexport interface ScoreBreakdown {\n Vector?: number;\n FullText?: number;\n Entity?: number;\n}\n\n/** Grouped search results by source type */\nexport interface SearchResultGroup {\n /** The source type label */\n Label: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The source type key */\n SourceType: string;\n /** Results in this group */\n Results: SearchResultItem[];\n /** Total count for this source (may exceed Results.length if paginated) */\n TotalCount: number;\n}\n\n/** Faceted filter configuration */\nexport interface SearchFilter {\n /** Filter category name */\n Category: string;\n /** Filter options within this category */\n Options: SearchFilterOption[];\n /** Whether multiple options can be selected */\n MultiSelect: boolean;\n}\n\n/** Single filter option within a category */\nexport interface SearchFilterOption {\n /** Display label */\n Label: string;\n /** Value to filter by */\n Value: string;\n /** Count of results matching this option */\n Count: number;\n /** Whether this option is currently selected */\n IsSelected: boolean;\n /** Font Awesome icon class */\n Icon?: string;\n}\n\n/** Search request parameters */\nexport interface SearchRequest {\n /** The search query string */\n Query: string;\n /** Maximum number of results to return */\n MaxResults: number;\n /** Active filter selections */\n ActiveFilters: Record<string, string[]>;\n /** Which search sources to include */\n IncludeSources: ('vector' | 'fulltext' | 'entity' | 'storage')[];\n /**\n * Minimum relevance score (0-1) to include in results.\n * Results below this threshold are filtered out.\n * Default: 0.35 (35%).\n */\n MinScore?: number;\n}\n\n/** Metadata about an active search provider from the server */\nexport interface SearchProviderInfo {\n /** SearchProvider record ID */\n ID: string;\n /** Provider name */\n Name: string;\n /** UI display label */\n DisplayName: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The SourceType key this provider uses */\n SourceType: string;\n /** Priority (lower = higher) */\n Priority: number;\n}\n\n/** Search response from the service */\nexport interface SearchResponse {\n /** Whether the search succeeded */\n Success: boolean;\n /** The search results */\n Results: SearchResultItem[];\n /** Grouped results by source type */\n Groups: SearchResultGroup[];\n /** Available filters based on results */\n Filters: SearchFilter[];\n /** Total result count across all sources */\n TotalCount: number;\n /** Time taken in milliseconds */\n ElapsedMs: number;\n /** Count of results from each source */\n SourceCounts: { Vector: number; FullText: number; Entity: number; Storage: number };\n /** Active search providers metadata (for UI labels and icons) */\n Providers: SearchProviderInfo[];\n /** Error message if Success is false */\n ErrorMessage?: string;\n}\n\n/** Event emitted when a search result is selected */\nexport interface SearchResultSelectedEvent {\n /** The selected result item */\n Result: SearchResultItem;\n /** Whether to open in a new tab */\n OpenInNewTab: boolean;\n}\n\n/** Event emitted when filters change */\nexport interface SearchFilterChangeEvent {\n /** The filter category that changed */\n Category: string;\n /** The new selected values */\n SelectedValues: string[];\n}\n\n/** Event emitted when search is executed */\nexport interface SearchExecutedEvent {\n /** The search query */\n Query: string;\n /** Active filters */\n Filters: Record<string, string[]>;\n /** Number of results returned */\n ResultCount: number;\n /** Time taken in ms */\n ElapsedMs: number;\n}\n"]}
|
|
@@ -12,18 +12,27 @@ export declare class SearchService {
|
|
|
12
12
|
SearchResults$: BehaviorSubject<SearchResponse | null>;
|
|
13
13
|
/** Whether a search is in progress */
|
|
14
14
|
IsSearching$: BehaviorSubject<boolean>;
|
|
15
|
-
/** Recent search history (
|
|
15
|
+
/** Recent search history (persisted via UserInfoEngine, max 20 entries) */
|
|
16
16
|
RecentSearches$: BehaviorSubject<RecentSearch[]>;
|
|
17
17
|
/** Error stream */
|
|
18
18
|
Errors$: Subject<string>;
|
|
19
19
|
private readonly maxRecentSearches;
|
|
20
|
+
private recentSearchesLoaded;
|
|
21
|
+
/** Cached provider metadata keyed by SourceType, populated from search responses */
|
|
22
|
+
private providersBySourceType;
|
|
20
23
|
/**
|
|
21
24
|
* Execute a search request via the SearchKnowledge GraphQL mutation.
|
|
22
25
|
*/
|
|
23
26
|
ExecuteSearch(request: SearchRequest): Promise<SearchResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* Lightweight preview search for autocomplete/typeahead.
|
|
29
|
+
* Does NOT update SearchResults$ or IsSearching$ observables,
|
|
30
|
+
* and does NOT add to recent search history.
|
|
31
|
+
*/
|
|
32
|
+
PreviewSearch(query: string, maxResults?: number): Promise<SearchResponse>;
|
|
24
33
|
/** Clear the current search results */
|
|
25
34
|
ClearResults(): void;
|
|
26
|
-
/** Clear recent search history */
|
|
35
|
+
/** Clear recent search history (memory and persisted) */
|
|
27
36
|
ClearRecentSearches(): void;
|
|
28
37
|
/**
|
|
29
38
|
* Group flat results by source type for display.
|
|
@@ -37,15 +46,28 @@ export declare class SearchService {
|
|
|
37
46
|
GetSourceIcon(sourceType: string): string;
|
|
38
47
|
private buildSourceTypeFilter;
|
|
39
48
|
private buildEntityNameFilter;
|
|
49
|
+
private buildFileTypeFilter;
|
|
40
50
|
private buildTagFilter;
|
|
51
|
+
/**
|
|
52
|
+
* Load persisted recent searches from UserInfoEngine.
|
|
53
|
+
* Safe to call multiple times — only loads once.
|
|
54
|
+
*/
|
|
55
|
+
LoadRecentSearches(): Promise<void>;
|
|
41
56
|
private addToRecentSearches;
|
|
57
|
+
private persistRecentSearches;
|
|
58
|
+
/** Cache provider metadata by SourceType for use in GroupResults/BuildFilters */
|
|
59
|
+
private cacheProviders;
|
|
42
60
|
private createEmptyResponse;
|
|
43
61
|
/**
|
|
44
|
-
* Execute search via the
|
|
62
|
+
* Execute search via the GraphQLSearchClient.
|
|
45
63
|
*/
|
|
46
64
|
private executeGraphQLSearch;
|
|
47
|
-
/**
|
|
48
|
-
private
|
|
65
|
+
/** Convert a SearchClientResponse into the SearchResponse shape used by this service */
|
|
66
|
+
private mapClientResponseToSearchResponse;
|
|
67
|
+
/** Map a single SearchClientResultItem to a SearchResultItem */
|
|
68
|
+
private mapClientResultItem;
|
|
69
|
+
/** Build the client filters from active filter selections */
|
|
70
|
+
private buildClientFilters;
|
|
49
71
|
static ɵfac: i0.ɵɵFactoryDeclaration<SearchService, never>;
|
|
50
72
|
static ɵprov: i0.ɵɵInjectableDeclaration<SearchService>;
|
|
51
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAUhD,OAAO,EACH,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAGf,MAAM,gBAAgB,CAAC;;AA8BxB,sCAAsC;AACtC,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACvB;AASD,qBACa,aAAa;IACtB,8BAA8B;IACvB,cAAc,yCAAoD;IAEzE,sCAAsC;IAC/B,YAAY,2BAAuC;IAE1D,2EAA2E;IACpE,eAAe,kCAA2C;IAEjE,mBAAmB;IACZ,OAAO,kBAAyB;IAEvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IACxC,OAAO,CAAC,oBAAoB,CAAS;IAErC,oFAAoF;IACpF,OAAO,CAAC,qBAAqB,CAAyC;IAEtE;;OAEG;IACU,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAuB3E;;;;OAIG;IACU,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IAoB1F,uCAAuC;IAChC,YAAY,IAAI,IAAI;IAI3B,yDAAyD;IAClD,mBAAmB,IAAI,IAAI;IAKlC;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,EAAE;IA0BrE;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE;IAShE,2CAA2C;IACpC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAIhD,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,qBAAqB;IAkC7B,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,cAAc;IAoBtB;;;OAGG;IACU,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BhD,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,qBAAqB;IAa7B,iFAAiF;IACjF,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,mBAAmB;IAc3B;;OAEG;YACW,oBAAoB;IAwBlC,wFAAwF;IACxF,OAAO,CAAC,iCAAiC;IAsCzC,gEAAgE;IAChE,OAAO,CAAC,mBAAmB;IAuB3B,6DAA6D;IAC7D,OAAO,CAAC,kBAAkB;yCAvYjB,aAAa;6CAAb,aAAa;CA+ZzB"}
|
|
@@ -6,34 +6,48 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Injectable } from '@angular/core';
|
|
8
8
|
import { BehaviorSubject, Subject } from 'rxjs';
|
|
9
|
-
import { Metadata } from '@memberjunction/core';
|
|
9
|
+
import { Metadata, StartupManager } from '@memberjunction/core';
|
|
10
|
+
import { UserInfoEngine } from '@memberjunction/core-entities';
|
|
11
|
+
import { MJEventType, MJGlobal } from '@memberjunction/global';
|
|
12
|
+
import { GraphQLSearchClient, GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';
|
|
10
13
|
import * as i0 from "@angular/core";
|
|
11
14
|
/** Default minimum relevance score threshold (0-1). Results below this are filtered out. */
|
|
12
15
|
const DEFAULT_MIN_SCORE = 0.35;
|
|
13
|
-
/**
|
|
14
|
-
const
|
|
16
|
+
/** Fallback icon mapping for source types (used when provider metadata is not available) */
|
|
17
|
+
const FALLBACK_SOURCE_ICONS = {
|
|
15
18
|
'entity': 'fa-solid fa-database',
|
|
19
|
+
'vector': 'fa-solid fa-brain',
|
|
20
|
+
'fulltext': 'fa-solid fa-magnifying-glass',
|
|
16
21
|
'content-item': 'fa-solid fa-file-lines',
|
|
17
22
|
'file': 'fa-solid fa-file',
|
|
18
23
|
'web-page': 'fa-solid fa-globe',
|
|
24
|
+
'storage': 'fa-solid fa-folder-open',
|
|
19
25
|
};
|
|
20
|
-
/**
|
|
21
|
-
const
|
|
22
|
-
'entity': '
|
|
26
|
+
/** Fallback labels for source types (used when provider metadata is not available) */
|
|
27
|
+
const FALLBACK_SOURCE_LABELS = {
|
|
28
|
+
'entity': 'Database',
|
|
29
|
+
'vector': 'Semantic Search',
|
|
30
|
+
'fulltext': 'Full-Text Search',
|
|
23
31
|
'content-item': 'Content Items',
|
|
24
32
|
'file': 'Documents',
|
|
25
33
|
'web-page': 'Web Pages',
|
|
34
|
+
'storage': 'File Storage',
|
|
26
35
|
};
|
|
36
|
+
/** User setting key for persisting recent searches */
|
|
37
|
+
const RECENT_SEARCHES_KEY = 'search.recentSearches';
|
|
27
38
|
export class SearchService {
|
|
28
39
|
/** Current search response */
|
|
29
40
|
SearchResults$ = new BehaviorSubject(null);
|
|
30
41
|
/** Whether a search is in progress */
|
|
31
42
|
IsSearching$ = new BehaviorSubject(false);
|
|
32
|
-
/** Recent search history (
|
|
43
|
+
/** Recent search history (persisted via UserInfoEngine, max 20 entries) */
|
|
33
44
|
RecentSearches$ = new BehaviorSubject([]);
|
|
34
45
|
/** Error stream */
|
|
35
46
|
Errors$ = new Subject();
|
|
36
47
|
maxRecentSearches = 20;
|
|
48
|
+
recentSearchesLoaded = false;
|
|
49
|
+
/** Cached provider metadata keyed by SourceType, populated from search responses */
|
|
50
|
+
providersBySourceType = new Map();
|
|
37
51
|
/**
|
|
38
52
|
* Execute a search request via the SearchKnowledge GraphQL mutation.
|
|
39
53
|
*/
|
|
@@ -58,13 +72,37 @@ export class SearchService {
|
|
|
58
72
|
this.IsSearching$.next(false);
|
|
59
73
|
}
|
|
60
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Lightweight preview search for autocomplete/typeahead.
|
|
77
|
+
* Does NOT update SearchResults$ or IsSearching$ observables,
|
|
78
|
+
* and does NOT add to recent search history.
|
|
79
|
+
*/
|
|
80
|
+
async PreviewSearch(query, maxResults = 8) {
|
|
81
|
+
try {
|
|
82
|
+
if (!query.trim()) {
|
|
83
|
+
return this.createEmptyResponse();
|
|
84
|
+
}
|
|
85
|
+
const provider = Metadata.Provider;
|
|
86
|
+
if (!(provider instanceof GraphQLDataProvider)) {
|
|
87
|
+
return this.createEmptyResponse('GraphQL provider not available');
|
|
88
|
+
}
|
|
89
|
+
const client = new GraphQLSearchClient(provider);
|
|
90
|
+
const clientResponse = await client.PreviewSearch(query, maxResults);
|
|
91
|
+
return this.mapClientResponseToSearchResponse(clientResponse);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const errorMessage = err instanceof Error ? err.message : 'Preview search failed';
|
|
95
|
+
return this.createEmptyResponse(errorMessage);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
61
98
|
/** Clear the current search results */
|
|
62
99
|
ClearResults() {
|
|
63
100
|
this.SearchResults$.next(null);
|
|
64
101
|
}
|
|
65
|
-
/** Clear recent search history */
|
|
102
|
+
/** Clear recent search history (memory and persisted) */
|
|
66
103
|
ClearRecentSearches() {
|
|
67
104
|
this.RecentSearches$.next([]);
|
|
105
|
+
this.persistRecentSearches([]);
|
|
68
106
|
}
|
|
69
107
|
/**
|
|
70
108
|
* Group flat results by source type for display.
|
|
@@ -81,13 +119,17 @@ export class SearchService {
|
|
|
81
119
|
groupMap.set(key, [result]);
|
|
82
120
|
}
|
|
83
121
|
}
|
|
84
|
-
return Array.from(groupMap.entries()).map(([sourceType, items]) =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
122
|
+
return Array.from(groupMap.entries()).map(([sourceType, items]) => {
|
|
123
|
+
// Priority: cached provider metadata > per-result metadata > fallback maps
|
|
124
|
+
const cached = this.providersBySourceType.get(sourceType);
|
|
125
|
+
return {
|
|
126
|
+
Label: cached?.DisplayName ?? items[0]?.ProviderLabel ?? FALLBACK_SOURCE_LABELS[sourceType] ?? sourceType,
|
|
127
|
+
Icon: cached?.Icon ?? items[0]?.ProviderIcon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle',
|
|
128
|
+
SourceType: sourceType,
|
|
129
|
+
Results: items,
|
|
130
|
+
TotalCount: items.length
|
|
131
|
+
};
|
|
132
|
+
});
|
|
91
133
|
}
|
|
92
134
|
/**
|
|
93
135
|
* Build filter facets from a set of results.
|
|
@@ -96,31 +138,41 @@ export class SearchService {
|
|
|
96
138
|
return [
|
|
97
139
|
this.buildSourceTypeFilter(results),
|
|
98
140
|
this.buildEntityNameFilter(results),
|
|
141
|
+
this.buildFileTypeFilter(results),
|
|
99
142
|
this.buildTagFilter(results)
|
|
100
|
-
];
|
|
143
|
+
].filter(f => f.Options.length > 0); // Hide categories with no options
|
|
101
144
|
}
|
|
102
145
|
/** Get the icon for a given source type */
|
|
103
146
|
GetSourceIcon(sourceType) {
|
|
104
|
-
return
|
|
147
|
+
return this.providersBySourceType.get(sourceType)?.Icon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle';
|
|
105
148
|
}
|
|
106
149
|
buildSourceTypeFilter(results) {
|
|
107
150
|
const counts = new Map();
|
|
108
151
|
for (const r of results) {
|
|
109
152
|
counts.set(r.SourceType, (counts.get(r.SourceType) ?? 0) + 1);
|
|
110
153
|
}
|
|
111
|
-
const options = Array.from(counts.entries()).map(([type, count]) =>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
154
|
+
const options = Array.from(counts.entries()).map(([type, count]) => {
|
|
155
|
+
const cached = this.providersBySourceType.get(type);
|
|
156
|
+
return {
|
|
157
|
+
Label: cached?.DisplayName ?? FALLBACK_SOURCE_LABELS[type] ?? type,
|
|
158
|
+
Value: type,
|
|
159
|
+
Count: count,
|
|
160
|
+
IsSelected: false,
|
|
161
|
+
Icon: cached?.Icon ?? FALLBACK_SOURCE_ICONS[type]
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
return { Category: 'Source', Options: options, MultiSelect: true };
|
|
119
165
|
}
|
|
120
166
|
buildEntityNameFilter(results) {
|
|
121
167
|
const counts = new Map();
|
|
168
|
+
let fileCount = 0;
|
|
122
169
|
for (const r of results) {
|
|
123
|
-
|
|
170
|
+
if (r.ResultType === 'storage-file') {
|
|
171
|
+
fileCount++;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
counts.set(r.EntityName, (counts.get(r.EntityName) ?? 0) + 1);
|
|
175
|
+
}
|
|
124
176
|
}
|
|
125
177
|
const options = Array.from(counts.entries())
|
|
126
178
|
.sort((a, b) => b[1] - a[1])
|
|
@@ -131,8 +183,39 @@ export class SearchService {
|
|
|
131
183
|
IsSelected: false,
|
|
132
184
|
Icon: 'fa-solid fa-table'
|
|
133
185
|
}));
|
|
186
|
+
// Add a single "Files" entry for all storage file results
|
|
187
|
+
if (fileCount > 0) {
|
|
188
|
+
options.push({
|
|
189
|
+
Label: 'Files',
|
|
190
|
+
Value: '__storage-files__',
|
|
191
|
+
Count: fileCount,
|
|
192
|
+
IsSelected: false,
|
|
193
|
+
Icon: 'fa-solid fa-file'
|
|
194
|
+
});
|
|
195
|
+
}
|
|
134
196
|
return { Category: 'Entity', Options: options, MultiSelect: true };
|
|
135
197
|
}
|
|
198
|
+
buildFileTypeFilter(results) {
|
|
199
|
+
const counts = new Map();
|
|
200
|
+
for (const r of results) {
|
|
201
|
+
if (r.ResultType !== 'storage-file')
|
|
202
|
+
continue;
|
|
203
|
+
const ext = r.Title?.split('.').pop()?.toUpperCase();
|
|
204
|
+
if (ext && ext.length <= 6) {
|
|
205
|
+
counts.set(ext, (counts.get(ext) ?? 0) + 1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const options = Array.from(counts.entries())
|
|
209
|
+
.sort((a, b) => b[1] - a[1])
|
|
210
|
+
.map(([ext, count]) => ({
|
|
211
|
+
Label: ext,
|
|
212
|
+
Value: ext,
|
|
213
|
+
Count: count,
|
|
214
|
+
IsSelected: false,
|
|
215
|
+
Icon: 'fa-solid fa-file'
|
|
216
|
+
}));
|
|
217
|
+
return { Category: 'File Type', Options: options, MultiSelect: true };
|
|
218
|
+
}
|
|
136
219
|
buildTagFilter(results) {
|
|
137
220
|
const counts = new Map();
|
|
138
221
|
for (const r of results) {
|
|
@@ -152,6 +235,38 @@ export class SearchService {
|
|
|
152
235
|
}));
|
|
153
236
|
return { Category: 'Tags', Options: options, MultiSelect: true };
|
|
154
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Load persisted recent searches from UserInfoEngine.
|
|
240
|
+
* Safe to call multiple times — only loads once.
|
|
241
|
+
*/
|
|
242
|
+
async LoadRecentSearches() {
|
|
243
|
+
if (this.recentSearchesLoaded)
|
|
244
|
+
return;
|
|
245
|
+
try {
|
|
246
|
+
MJGlobal.Instance.GetEventListener(true).subscribe(async (event) => {
|
|
247
|
+
if (event.event === MJEventType.LoggedIn) {
|
|
248
|
+
await StartupManager.Instance.Startup();
|
|
249
|
+
const engine = UserInfoEngine.Instance;
|
|
250
|
+
console.log('[SearchService] After Startup — engine.Loaded:', engine.Loaded, 'UserSettings count:', engine.UserSettings.length);
|
|
251
|
+
const json = engine.GetSetting(RECENT_SEARCHES_KEY);
|
|
252
|
+
console.log('[SearchService] GetSetting("' + RECENT_SEARCHES_KEY + '"):', json?.substring(0, 100));
|
|
253
|
+
if (json) {
|
|
254
|
+
const parsed = JSON.parse(json);
|
|
255
|
+
const searches = parsed.map(s => ({
|
|
256
|
+
Query: s.Query,
|
|
257
|
+
Timestamp: new Date(s.Timestamp),
|
|
258
|
+
ResultCount: s.ResultCount
|
|
259
|
+
}));
|
|
260
|
+
this.RecentSearches$.next(searches);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
console.error('[SearchService] LoadRecentSearches error:', err);
|
|
267
|
+
}
|
|
268
|
+
this.recentSearchesLoaded = true;
|
|
269
|
+
}
|
|
155
270
|
addToRecentSearches(query, resultCount) {
|
|
156
271
|
const current = this.RecentSearches$.value;
|
|
157
272
|
const filtered = current.filter(s => s.Query !== query);
|
|
@@ -160,6 +275,26 @@ export class SearchService {
|
|
|
160
275
|
...filtered
|
|
161
276
|
].slice(0, this.maxRecentSearches);
|
|
162
277
|
this.RecentSearches$.next(updated);
|
|
278
|
+
this.persistRecentSearches(updated);
|
|
279
|
+
}
|
|
280
|
+
persistRecentSearches(searches) {
|
|
281
|
+
try {
|
|
282
|
+
const json = searches.map(s => ({
|
|
283
|
+
Query: s.Query,
|
|
284
|
+
Timestamp: s.Timestamp.toISOString(),
|
|
285
|
+
ResultCount: s.ResultCount
|
|
286
|
+
}));
|
|
287
|
+
UserInfoEngine.Instance.SetSettingDebounced(RECENT_SEARCHES_KEY, JSON.stringify(json));
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Silently fail if UserInfoEngine not available
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** Cache provider metadata by SourceType for use in GroupResults/BuildFilters */
|
|
294
|
+
cacheProviders(providers) {
|
|
295
|
+
for (const p of providers) {
|
|
296
|
+
this.providersBySourceType.set(p.SourceType, p);
|
|
297
|
+
}
|
|
163
298
|
}
|
|
164
299
|
createEmptyResponse(errorMessage) {
|
|
165
300
|
return {
|
|
@@ -169,107 +304,101 @@ export class SearchService {
|
|
|
169
304
|
Filters: [],
|
|
170
305
|
TotalCount: 0,
|
|
171
306
|
ElapsedMs: 0,
|
|
172
|
-
SourceCounts: { Vector: 0, FullText: 0, Entity: 0 },
|
|
307
|
+
SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },
|
|
308
|
+
Providers: [],
|
|
173
309
|
ErrorMessage: errorMessage
|
|
174
310
|
};
|
|
175
311
|
}
|
|
176
312
|
/**
|
|
177
|
-
* Execute search via the
|
|
313
|
+
* Execute search via the GraphQLSearchClient.
|
|
178
314
|
*/
|
|
179
315
|
async executeGraphQLSearch(request) {
|
|
180
316
|
if (!request.Query.trim()) {
|
|
181
317
|
return this.createEmptyResponse();
|
|
182
318
|
}
|
|
183
319
|
const provider = Metadata.Provider;
|
|
184
|
-
if (!provider
|
|
320
|
+
if (!(provider instanceof GraphQLDataProvider)) {
|
|
185
321
|
return this.createEmptyResponse('GraphQL provider not available');
|
|
186
322
|
}
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
SearchKnowledge(query: $query, maxResults: $maxResults, filters: $filters, minScore: $minScore) {
|
|
190
|
-
Success
|
|
191
|
-
TotalCount
|
|
192
|
-
ElapsedMs
|
|
193
|
-
ErrorMessage
|
|
194
|
-
SourceCounts {
|
|
195
|
-
Vector
|
|
196
|
-
FullText
|
|
197
|
-
Entity
|
|
198
|
-
}
|
|
199
|
-
Results {
|
|
200
|
-
ID
|
|
201
|
-
EntityName
|
|
202
|
-
RecordID
|
|
203
|
-
SourceType
|
|
204
|
-
Title
|
|
205
|
-
Snippet
|
|
206
|
-
Score
|
|
207
|
-
ScoreBreakdown {
|
|
208
|
-
Vector
|
|
209
|
-
FullText
|
|
210
|
-
Entity
|
|
211
|
-
}
|
|
212
|
-
Tags
|
|
213
|
-
EntityIcon
|
|
214
|
-
RecordName
|
|
215
|
-
MatchedAt
|
|
216
|
-
RawMetadata
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
`;
|
|
221
|
-
const filtersInput = this.buildFiltersInput(request);
|
|
323
|
+
const client = new GraphQLSearchClient(provider);
|
|
324
|
+
const filters = this.buildClientFilters(request);
|
|
222
325
|
const minScore = request.MinScore ?? DEFAULT_MIN_SCORE;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
326
|
+
const clientResponse = await client.ExecuteSearch({
|
|
327
|
+
Query: request.Query,
|
|
328
|
+
MaxResults: request.MaxResults || 20,
|
|
329
|
+
MinScore: minScore > 0 ? minScore : undefined,
|
|
330
|
+
Filters: filters
|
|
331
|
+
});
|
|
332
|
+
return this.mapClientResponseToSearchResponse(clientResponse);
|
|
333
|
+
}
|
|
334
|
+
/** Convert a SearchClientResponse into the SearchResponse shape used by this service */
|
|
335
|
+
mapClientResponseToSearchResponse(clientResponse) {
|
|
336
|
+
if (!clientResponse.Success) {
|
|
337
|
+
return this.createEmptyResponse(clientResponse.ErrorMessage ?? 'Search failed');
|
|
233
338
|
}
|
|
234
|
-
|
|
339
|
+
// Cache provider metadata for use in GroupResults/BuildFilters
|
|
340
|
+
const providers = (clientResponse.Providers ?? []).map(p => ({
|
|
341
|
+
ID: p.ID,
|
|
342
|
+
Name: p.Name,
|
|
343
|
+
DisplayName: p.DisplayName,
|
|
344
|
+
Icon: p.Icon,
|
|
345
|
+
SourceType: p.SourceType,
|
|
346
|
+
Priority: p.Priority,
|
|
347
|
+
}));
|
|
348
|
+
this.cacheProviders(providers);
|
|
349
|
+
const results = (clientResponse.Results ?? []).map(r => this.mapClientResultItem(r));
|
|
350
|
+
const groups = this.GroupResults(results);
|
|
351
|
+
const filters = this.BuildFilters(results);
|
|
352
|
+
return {
|
|
353
|
+
Success: true,
|
|
354
|
+
Results: results,
|
|
355
|
+
Groups: groups,
|
|
356
|
+
Filters: filters,
|
|
357
|
+
TotalCount: clientResponse.TotalCount,
|
|
358
|
+
ElapsedMs: clientResponse.ElapsedMs,
|
|
359
|
+
SourceCounts: {
|
|
360
|
+
Vector: clientResponse.SourceCounts.Vector,
|
|
361
|
+
FullText: clientResponse.SourceCounts.FullText,
|
|
362
|
+
Entity: clientResponse.SourceCounts.Entity,
|
|
363
|
+
Storage: clientResponse.SourceCounts.Storage
|
|
364
|
+
},
|
|
365
|
+
Providers: providers,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/** Map a single SearchClientResultItem to a SearchResultItem */
|
|
369
|
+
mapClientResultItem(r) {
|
|
370
|
+
return {
|
|
235
371
|
ID: r.ID,
|
|
236
372
|
Title: r.RecordName || r.Title,
|
|
237
373
|
Snippet: r.Snippet,
|
|
238
374
|
EntityName: r.EntityName,
|
|
239
375
|
RecordID: r.RecordID,
|
|
240
376
|
SourceType: r.SourceType || 'entity',
|
|
377
|
+
ResultType: r.ResultType,
|
|
241
378
|
Score: r.Score,
|
|
242
379
|
ScoreBreakdown: r.ScoreBreakdown ?? {},
|
|
243
380
|
Tags: r.Tags ?? [],
|
|
244
|
-
SourceIcon: r.EntityIcon ||
|
|
381
|
+
SourceIcon: r.EntityIcon || r.ProviderIcon || FALLBACK_SOURCE_ICONS[r.SourceType] || 'fa-solid fa-database',
|
|
245
382
|
EntityIcon: r.EntityIcon,
|
|
246
383
|
RecordName: r.RecordName,
|
|
247
384
|
MatchedAt: new Date(r.MatchedAt),
|
|
248
385
|
RawMetadata: r.RawMetadata,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
Success: true,
|
|
254
|
-
Results: results,
|
|
255
|
-
Groups: groups,
|
|
256
|
-
Filters: filters,
|
|
257
|
-
TotalCount: data.TotalCount,
|
|
258
|
-
ElapsedMs: data.ElapsedMs,
|
|
259
|
-
SourceCounts: data.SourceCounts,
|
|
386
|
+
ProviderId: r.ProviderId,
|
|
387
|
+
ProviderLabel: r.ProviderLabel,
|
|
388
|
+
ProviderIcon: r.ProviderIcon,
|
|
260
389
|
};
|
|
261
390
|
}
|
|
262
|
-
/** Build the
|
|
263
|
-
|
|
391
|
+
/** Build the client filters from active filter selections */
|
|
392
|
+
buildClientFilters(request) {
|
|
264
393
|
if (!request.ActiveFilters || Object.keys(request.ActiveFilters).length === 0) {
|
|
265
|
-
return
|
|
394
|
+
return undefined;
|
|
266
395
|
}
|
|
267
396
|
const result = {};
|
|
268
397
|
const entityFilters = request.ActiveFilters['Entity'];
|
|
269
398
|
if (entityFilters?.length) {
|
|
270
399
|
result.EntityNames = entityFilters;
|
|
271
400
|
}
|
|
272
|
-
const sourceFilters = request.ActiveFilters['Source
|
|
401
|
+
const sourceFilters = request.ActiveFilters['Source'];
|
|
273
402
|
if (sourceFilters?.length) {
|
|
274
403
|
result.SourceTypes = sourceFilters;
|
|
275
404
|
}
|
|
@@ -277,7 +406,7 @@ export class SearchService {
|
|
|
277
406
|
if (tagFilters?.length) {
|
|
278
407
|
result.Tags = tagFilters;
|
|
279
408
|
}
|
|
280
|
-
return Object.keys(result).length > 0 ? result :
|
|
409
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
281
410
|
}
|
|
282
411
|
static ɵfac = function SearchService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SearchService)(); };
|
|
283
412
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: SearchService, factory: SearchService.ɵfac, providedIn: 'root' });
|