@miso.ai/client-sdk-ui 1.11.0-beta.1 → 1.11.0-beta.10
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/cjs/actor/data.js +6 -7
- package/dist/cjs/actor/feedback.js +6 -39
- package/dist/cjs/actor/fields.js +14 -6
- package/dist/cjs/actor/hub.js +1 -2
- package/dist/cjs/actor/index.js +0 -7
- package/dist/cjs/actor/interactions.js +4 -15
- package/dist/cjs/actor/sessions.js +2 -33
- package/dist/cjs/actor/tracker.js +25 -26
- package/dist/cjs/actor/utils.js +0 -49
- package/dist/cjs/actor/view.js +40 -40
- package/dist/cjs/actor/views.js +32 -8
- package/dist/cjs/asset/svgs.js +18 -8
- package/dist/cjs/constants.js +18 -19
- package/dist/cjs/defaults/hybrid-search/answer-box.js +19 -3
- package/dist/cjs/defaults/hybrid-search/phrases.js +5 -5
- package/dist/cjs/defaults/hybrid-search/templates.js +37 -9
- package/dist/cjs/element/role/index.js +12 -5
- package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
- package/dist/cjs/element/role/miso-total.js +20 -0
- package/dist/cjs/layout/affiliation/affiliation.js +0 -3
- package/dist/cjs/layout/banner/banner.js +0 -4
- package/dist/cjs/layout/button/more-button.js +73 -0
- package/dist/cjs/layout/container/container.js +0 -3
- package/dist/cjs/layout/error/error.js +0 -4
- package/dist/cjs/layout/index.js +7 -0
- package/dist/cjs/layout/input/facets.js +5 -5
- package/dist/cjs/layout/input/option-list.js +0 -4
- package/dist/cjs/layout/input/search-box.js +33 -12
- package/dist/cjs/layout/list/cards.js +0 -3
- package/dist/cjs/layout/list/carousel.js +0 -3
- package/dist/cjs/layout/list/collection.js +25 -7
- package/dist/cjs/layout/list/list.js +0 -3
- package/dist/cjs/layout/radio/feedback.js +0 -3
- package/dist/cjs/layout/radio/radio.js +0 -4
- package/dist/cjs/layout/raf.js +4 -2
- package/dist/cjs/layout/text/text.js +0 -4
- package/dist/cjs/layout/text/typewriter/index.js +0 -3
- package/dist/cjs/layout/triggerable.js +79 -0
- package/dist/cjs/util/states.js +0 -15
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/answer-based.js +109 -111
- package/dist/cjs/workflow/ask.js +35 -27
- package/dist/cjs/workflow/asks.js +1 -1
- package/dist/cjs/workflow/autocompletable.js +33 -0
- package/dist/cjs/workflow/autocomplete.js +122 -0
- package/dist/cjs/workflow/base.js +169 -102
- package/dist/cjs/workflow/explore.js +26 -19
- package/dist/cjs/workflow/hybrid-search-answer.js +99 -0
- package/dist/cjs/workflow/hybrid-search-results.js +70 -0
- package/dist/cjs/workflow/hybrid-search-views.js +122 -0
- package/dist/cjs/workflow/hybrid-search.js +98 -103
- package/dist/cjs/workflow/options.js +32 -17
- package/dist/cjs/workflow/processors.js +185 -17
- package/dist/cjs/workflow/recommendation.js +36 -49
- package/dist/cjs/workflow/search-based.js +186 -0
- package/dist/cjs/workflow/search.js +21 -68
- package/package.json +2 -2
- package/src/actor/data.js +2 -4
- package/src/actor/feedback.js +6 -23
- package/src/actor/fields.js +14 -6
- package/src/actor/hub.js +1 -2
- package/src/actor/index.js +0 -1
- package/src/actor/interactions.js +4 -10
- package/src/actor/sessions.js +3 -30
- package/src/actor/tracker.js +25 -19
- package/src/actor/utils.js +0 -47
- package/src/actor/view.js +27 -15
- package/src/actor/views.js +36 -8
- package/src/asset/svgs.js +19 -7
- package/src/constants.js +25 -16
- package/src/defaults/hybrid-search/answer-box.js +17 -5
- package/src/defaults/hybrid-search/phrases.js +4 -4
- package/src/defaults/hybrid-search/templates.js +25 -9
- package/src/element/role/index.js +2 -1
- package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
- package/src/element/role/miso-total.js +16 -0
- package/src/layout/affiliation/affiliation.js +1 -5
- package/src/layout/banner/banner.js +0 -5
- package/src/layout/button/more-button.js +69 -0
- package/src/layout/container/container.js +1 -5
- package/src/layout/error/error.js +0 -5
- package/src/layout/index.js +2 -1
- package/src/layout/input/facets.js +5 -6
- package/src/layout/input/option-list.js +0 -5
- package/src/layout/input/search-box.js +27 -14
- package/src/layout/list/cards.js +0 -4
- package/src/layout/list/carousel.js +0 -4
- package/src/layout/list/collection.js +22 -9
- package/src/layout/list/list.js +0 -4
- package/src/layout/radio/feedback.js +0 -4
- package/src/layout/radio/radio.js +0 -5
- package/src/layout/raf.js +2 -2
- package/src/layout/text/text.js +0 -5
- package/src/layout/text/typewriter/index.js +1 -5
- package/src/layout/trackable.js +1 -1
- package/src/layout/triggerable.js +76 -0
- package/src/util/states.js +0 -14
- package/src/version.js +1 -1
- package/src/workflow/answer-based.js +101 -77
- package/src/workflow/ask.js +33 -25
- package/src/workflow/asks.js +1 -1
- package/src/workflow/autocompletable.js +29 -0
- package/src/workflow/autocomplete.js +113 -0
- package/src/workflow/base.js +147 -93
- package/src/workflow/explore.js +23 -8
- package/src/workflow/hybrid-search-answer.js +92 -0
- package/src/workflow/hybrid-search-results.js +63 -0
- package/src/workflow/hybrid-search-views.js +124 -0
- package/src/workflow/hybrid-search.js +102 -73
- package/src/workflow/options.js +26 -11
- package/src/workflow/processors.js +165 -14
- package/src/workflow/recommendation.js +32 -33
- package/src/workflow/search-based.js +183 -0
- package/src/workflow/search.js +21 -65
- package/dist/cjs/actor/autocomplete.js +0 -160
- package/dist/cjs/workflow/utils.js +0 -42
- package/src/actor/autocomplete.js +0 -133
- package/src/workflow/utils.js +0 -29
package/src/workflow/explore.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import { API } from '@miso.ai/commons';
|
|
2
2
|
import Workflow from './base.js';
|
|
3
|
-
import { mergeApiOptions } from './options.js';
|
|
4
3
|
import { fields } from '../actor/index.js';
|
|
5
4
|
import { ROLE, EVENT_TYPE } from '../constants.js';
|
|
6
5
|
import { ListLayout, SearchBoxLayout } from '../layout/index.js';
|
|
6
|
+
import { mergeRolesOptions } from './options.js';
|
|
7
7
|
|
|
8
8
|
const DEFAULT_API_OPTIONS = Object.freeze({
|
|
9
|
+
...Workflow.DEFAULT_API_OPTIONS,
|
|
9
10
|
group: API.GROUP.ASK,
|
|
10
11
|
name: API.NAME.RELATED_QUESTIONS,
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
const DEFAULT_LAYOUTS = Object.freeze({
|
|
15
|
+
...Workflow.DEFAULT_LAYOUTS,
|
|
14
16
|
[ROLE.RELATED_QUESTIONS]: [ListLayout.type, { itemType: 'question', link: { rel: 'noopener nofollow' } }],
|
|
15
17
|
[ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
21
|
+
...Workflow.DEFAULT_TRACKERS,
|
|
19
22
|
[ROLE.RELATED_QUESTIONS]: {},
|
|
20
23
|
[ROLE.CONTAINER]: {},
|
|
21
24
|
[ROLE.QUERY]: {
|
|
@@ -24,11 +27,17 @@ const DEFAULT_TRACKERS = Object.freeze({
|
|
|
24
27
|
});
|
|
25
28
|
|
|
26
29
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
30
|
+
...Workflow.DEFAULT_OPTIONS,
|
|
27
31
|
api: DEFAULT_API_OPTIONS,
|
|
28
32
|
layouts: DEFAULT_LAYOUTS,
|
|
29
33
|
trackers: DEFAULT_TRACKERS,
|
|
30
34
|
});
|
|
31
35
|
|
|
36
|
+
const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
|
|
37
|
+
main: ROLE.RELATED_QUESTIONS,
|
|
38
|
+
members: Object.keys(DEFAULT_LAYOUTS),
|
|
39
|
+
});
|
|
40
|
+
|
|
32
41
|
export default class Explore extends Workflow {
|
|
33
42
|
|
|
34
43
|
constructor(plugin, client) {
|
|
@@ -36,7 +45,7 @@ export default class Explore extends Workflow {
|
|
|
36
45
|
name: 'explore',
|
|
37
46
|
plugin,
|
|
38
47
|
client,
|
|
39
|
-
roles:
|
|
48
|
+
roles: ROLES_OPTIONS,
|
|
40
49
|
defaults: DEFAULT_OPTIONS,
|
|
41
50
|
});
|
|
42
51
|
}
|
|
@@ -52,7 +61,7 @@ export default class Explore extends Workflow {
|
|
|
52
61
|
this._unsubscribes = [
|
|
53
62
|
...this._unsubscribes,
|
|
54
63
|
this._views.get(ROLE.RELATED_QUESTIONS).on('click', event => this._handleRelatedQuestionClick(event)),
|
|
55
|
-
this._hub.on(fields.query(),
|
|
64
|
+
this._hub.on(fields.query(), args => this._submit(args)),
|
|
56
65
|
];
|
|
57
66
|
}
|
|
58
67
|
|
|
@@ -82,17 +91,16 @@ export default class Explore extends Workflow {
|
|
|
82
91
|
|
|
83
92
|
// lifecycle //
|
|
84
93
|
start({ relatedQuestions = true } = {}) {
|
|
85
|
-
if (
|
|
94
|
+
if (this._linkFn === undefined) {
|
|
86
95
|
throw new Error('Define link mapping function before calling start()');
|
|
87
96
|
}
|
|
88
|
-
this._sessions.start();
|
|
89
97
|
// in explore workflow, start() triggers query
|
|
90
98
|
// TODO: we should still make the query lifecycle
|
|
91
|
-
const { session } = this;
|
|
92
99
|
if (relatedQuestions) {
|
|
93
|
-
this.
|
|
100
|
+
this._request();
|
|
94
101
|
} else {
|
|
95
102
|
// TODO: ad-hoc
|
|
103
|
+
const { session } = this;
|
|
96
104
|
this.updateData({ session, request: {}, value: { related_questions: [] } });
|
|
97
105
|
}
|
|
98
106
|
return this;
|
|
@@ -124,7 +132,14 @@ export default class Explore extends Workflow {
|
|
|
124
132
|
this._events.emit('select', Object.freeze({ ...event, question }));
|
|
125
133
|
}
|
|
126
134
|
|
|
127
|
-
query(
|
|
135
|
+
query(args) {
|
|
136
|
+
if (!args.q) {
|
|
137
|
+
throw new Error(`q is required in query() call`);
|
|
138
|
+
}
|
|
139
|
+
this._hub.update(fields.query(), args);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_submit({ q } = {}) {
|
|
128
143
|
if (!this._linkFn) {
|
|
129
144
|
return;
|
|
130
145
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import AnswerBasedWorkflow from './answer-based.js';
|
|
2
|
+
import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
|
|
3
|
+
import { mergeRolesOptions } from './options.js';
|
|
4
|
+
import { writeKeywordsToData } from './processors.js';
|
|
5
|
+
import { makeAutocompletable } from './autocompletable.js';
|
|
6
|
+
|
|
7
|
+
const ROLES_OPTIONS = mergeRolesOptions(AnswerBasedWorkflow.ROLES_OPTIONS, {
|
|
8
|
+
mappings: {
|
|
9
|
+
[ROLE.QUESTION]: ROLE.KEYWORDS,
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export default class HybridSearchAnswer extends AnswerBasedWorkflow {
|
|
14
|
+
|
|
15
|
+
constructor(superworkflow) {
|
|
16
|
+
super({
|
|
17
|
+
name: 'hybrid-search/answer',
|
|
18
|
+
plugin: superworkflow._plugin,
|
|
19
|
+
client: superworkflow._client,
|
|
20
|
+
options: superworkflow._options,
|
|
21
|
+
defaults: superworkflow._defaults,
|
|
22
|
+
roles: ROLES_OPTIONS,
|
|
23
|
+
superworkflow,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_initProperties(args) {
|
|
28
|
+
super._initProperties(args);
|
|
29
|
+
this._initAutocomplete(args);
|
|
30
|
+
this._superworkflow = args.superworkflow;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_initSession() {} // no reset here, will manually reset later
|
|
34
|
+
|
|
35
|
+
restart() {
|
|
36
|
+
super.restart();
|
|
37
|
+
// cascade restart to the sibling
|
|
38
|
+
const results = this._superworkflow._results;
|
|
39
|
+
results._views.filters.reset();
|
|
40
|
+
results.restart();
|
|
41
|
+
this._resultsSession = results.session; // keep track of the session
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// query //
|
|
45
|
+
_buildPayload({ q, qs, ...payload } = {}) {
|
|
46
|
+
return {
|
|
47
|
+
...payload,
|
|
48
|
+
q, // q, not question
|
|
49
|
+
_meta: {
|
|
50
|
+
...payload._meta,
|
|
51
|
+
question_source: qs || ORGANIC_QUESTION_SOURCE, // might be null, not undefined
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// data //
|
|
57
|
+
_defaultProcessData(data) {
|
|
58
|
+
data = super._defaultProcessData(data);
|
|
59
|
+
data = writeKeywordsToData(data);
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_updateDataInHub(data) {
|
|
64
|
+
switch (data.status) {
|
|
65
|
+
case STATUS.INITIAL:
|
|
66
|
+
case STATUS.LOADING:
|
|
67
|
+
// share the initial/loading status with its sibling
|
|
68
|
+
this._dispatchDataToSibling(data);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
super._updateDataInHub(data);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_handleHeadResponse(data) {
|
|
75
|
+
super._handleHeadResponse(data);
|
|
76
|
+
// share the search results with its sibling
|
|
77
|
+
this._dispatchDataToSibling(data);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_dispatchDataToSibling(data) {
|
|
81
|
+
this._resultsSession && this._superworkflow._results.updateData({ ...data, session: this._resultsSession });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// destroy //
|
|
85
|
+
_destroy(options) {
|
|
86
|
+
this._destroyAutocomplete();
|
|
87
|
+
super._destroy(options);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
makeAutocompletable(HybridSearchAnswer.prototype);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { trimObj } from '@miso.ai/commons';
|
|
2
|
+
import SearchBasedWorkflow from './search-based.js';
|
|
3
|
+
import { fields } from '../actor/index.js';
|
|
4
|
+
import { ROLE } from '../constants.js';
|
|
5
|
+
import { mergeRolesOptions } from './options.js';
|
|
6
|
+
import { writeFiltersToPayload } from './processors.js';
|
|
7
|
+
|
|
8
|
+
const ROLES_OPTIONS = mergeRolesOptions(SearchBasedWorkflow.ROLES_OPTIONS, {
|
|
9
|
+
members: [ROLE.PRODUCTS, ROLE.KEYWORDS, ROLE.TOTAL, ROLE.FACETS, ROLE.MORE],
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default class HybridSearchResults extends SearchBasedWorkflow {
|
|
13
|
+
|
|
14
|
+
constructor(superworkflow) {
|
|
15
|
+
super({
|
|
16
|
+
name: 'hybrid-search/results',
|
|
17
|
+
plugin: superworkflow._plugin,
|
|
18
|
+
client: superworkflow._client,
|
|
19
|
+
options: superworkflow._options,
|
|
20
|
+
defaults: superworkflow._defaults,
|
|
21
|
+
roles: ROLES_OPTIONS,
|
|
22
|
+
superworkflow,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_initProperties(args) {
|
|
27
|
+
super._initProperties(args);
|
|
28
|
+
this._superworkflow = args.superworkflow;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_initSession() {} // no reset here, will manually reset later
|
|
32
|
+
|
|
33
|
+
// lifecycle //
|
|
34
|
+
_emitLifecycleEvent(name, event) {
|
|
35
|
+
super._emitLifecycleEvent(name, event);
|
|
36
|
+
this._superworkflow._emitLifecycleEvent(name, event);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// query //
|
|
40
|
+
_getQuery() {
|
|
41
|
+
// get stored query from sibling
|
|
42
|
+
return this._superworkflow._answer._hub.states[fields.query()];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_buildPayload({ filters, ...payload } = {}) {
|
|
46
|
+
// borrow the work from the sibling
|
|
47
|
+
payload = this._superworkflow._answer._buildPayload(payload);
|
|
48
|
+
payload = writeFiltersToPayload(payload, filters);
|
|
49
|
+
payload = this._writeQuestionIdToPayload(payload);
|
|
50
|
+
return { ...payload, answer: false };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_writeQuestionIdToPayload(payload) {
|
|
54
|
+
return {
|
|
55
|
+
...payload,
|
|
56
|
+
_meta: trimObj({
|
|
57
|
+
...payload._meta,
|
|
58
|
+
question_id: this._superworkflow._answer.questionId,
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ROLE } from '../constants.js';
|
|
2
|
+
|
|
3
|
+
function getSubworkflowByRole(role) {
|
|
4
|
+
// TODO: ROLE.ERROR
|
|
5
|
+
switch (role) {
|
|
6
|
+
case ROLE.PRODUCTS:
|
|
7
|
+
case ROLE.TOTAL:
|
|
8
|
+
case ROLE.FACETS:
|
|
9
|
+
case ROLE.KEYWORDS:
|
|
10
|
+
case ROLE.MORE:
|
|
11
|
+
return 'results';
|
|
12
|
+
default:
|
|
13
|
+
return 'answer';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class HybridSearchViewsActor {
|
|
18
|
+
|
|
19
|
+
constructor(workflow) {
|
|
20
|
+
this._workflow = workflow;
|
|
21
|
+
this._containers = new Set();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get(role) {
|
|
25
|
+
return this._getSubviews(role).get(role);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
addContainer(element) {
|
|
29
|
+
if (this._containers.has(element)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this._containers.add(element);
|
|
33
|
+
|
|
34
|
+
const { components } = element;
|
|
35
|
+
for (const component of components) {
|
|
36
|
+
this.addComponent(component);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
removeContainer(element) {
|
|
41
|
+
for (const subworkflow of this._workflow._subworkflows) {
|
|
42
|
+
subworkflow._views.removeContainer(element);
|
|
43
|
+
}
|
|
44
|
+
this._containers.delete(element);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
addComponent(element) {
|
|
48
|
+
this._getViewByElement(element).element = element;
|
|
49
|
+
this._addContainerToSubviews(element._container, element);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
removeComponent(element) {
|
|
53
|
+
this._getViewByElement(element).element = undefined;
|
|
54
|
+
this._removeContainerFromSubviewsIfNecessary(element._container);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
updateComponentRole(element, oldRole, newRole) {
|
|
58
|
+
// TODO
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
refreshElement(element) {
|
|
62
|
+
const view = element.isContainer ? this._containers.get(element) : this._getViewByElement(element);
|
|
63
|
+
view && view.refresh({ force: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// helpers //
|
|
67
|
+
_getSubworkflow(role) {
|
|
68
|
+
return this._workflow._getSubworkflow(getSubworkflowByRole(role));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_getAllSubviews() {
|
|
72
|
+
return this._workflow._subworkflows.flatMap(subworkflow => subworkflow._views);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_getSubviews(role) {
|
|
76
|
+
return this._getSubworkflow(role)._views;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_getViewByElement(element) {
|
|
80
|
+
let { role } = element;
|
|
81
|
+
if (!role) {
|
|
82
|
+
throw new Error('Component must have a role');
|
|
83
|
+
}
|
|
84
|
+
return this.get(role);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_addContainerToSubviews(container, child) {
|
|
88
|
+
if (!container) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const { role } = child;
|
|
92
|
+
const subviews = this._getSubviews(role);
|
|
93
|
+
if (subviews._containers.has(container)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// check if the container is in other subviews
|
|
97
|
+
for (const otherSubviews of this._getAllSubviews()) {
|
|
98
|
+
if (otherSubviews === subviews) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (otherSubviews._containers.has(container)) {
|
|
102
|
+
// find the conflicting element
|
|
103
|
+
for (const component of container.components) {
|
|
104
|
+
if (otherSubviews.containsElement(component)) {
|
|
105
|
+
throw new Error(`<${container.tagName}> cannot contain both <${component.tagName}> and <${child.tagName}>, for they don't share the same data lifecycle.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Failed to add container <${container.tagName}> with child component <${child.tagName}>.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// add the container to the subviews
|
|
112
|
+
subviews.addContainer(container);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_removeContainerFromSubviewsIfNecessary(container) {
|
|
116
|
+
if (!container || container.components.length > 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const subviews of this._getAllSubviews()) {
|
|
120
|
+
subviews.removeContainer(container);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
}
|
|
@@ -1,125 +1,154 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { API } from '@miso.ai/commons';
|
|
2
|
+
import { InteractionsActor } from '../actor/index.js';
|
|
3
|
+
import Workflow from './base.js';
|
|
4
|
+
import SearchBasedWorkflow from './search-based.js';
|
|
2
5
|
import AnswerBasedWorkflow from './answer-based.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import { ROLE } from '../constants.js';
|
|
7
|
+
import { SearchBoxLayout, TextLayout } from '../layout/index.js';
|
|
8
|
+
import HybridSearchAnswer from './hybrid-search-answer.js';
|
|
9
|
+
import HybridSearchResults from './hybrid-search-results.js';
|
|
10
|
+
import HybridSearchViewsActor from './hybrid-search-views.js';
|
|
8
11
|
|
|
9
12
|
const DEFAULT_API_OPTIONS = Object.freeze({
|
|
10
|
-
|
|
13
|
+
group: API.GROUP.ASK,
|
|
11
14
|
name: API.NAME.SEARCH,
|
|
12
15
|
payload: {
|
|
13
16
|
...AnswerBasedWorkflow.DEFAULT_API_OPTIONS.payload,
|
|
17
|
+
...SearchBasedWorkflow.DEFAULT_API_OPTIONS.payload,
|
|
18
|
+
source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at', 'title'],
|
|
14
19
|
},
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
const DEFAULT_LAYOUTS = Object.freeze({
|
|
18
23
|
...AnswerBasedWorkflow.DEFAULT_LAYOUTS,
|
|
19
|
-
|
|
20
|
-
[ROLE.
|
|
21
|
-
[ROLE.
|
|
22
|
-
[ROLE.FACETS]: [FacetsLayout.type],
|
|
24
|
+
...SearchBasedWorkflow.DEFAULT_LAYOUTS,
|
|
25
|
+
[ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: '' }],
|
|
26
|
+
[ROLE.QUESTION]: [TextLayout.type, { raw: true }],
|
|
23
27
|
});
|
|
24
28
|
|
|
25
29
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
26
30
|
...AnswerBasedWorkflow.DEFAULT_TRACKERS,
|
|
31
|
+
...SearchBasedWorkflow.DEFAULT_TRACKERS,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const DEFAULT_PAGINATION = Object.freeze({
|
|
35
|
+
...SearchBasedWorkflow.DEFAULT_PAGINATION,
|
|
36
|
+
active: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
|
|
40
|
+
api: {
|
|
41
|
+
group: API.GROUP.ASK,
|
|
42
|
+
name: API.NAME.SEARCH_AUTOCOMPLETE,
|
|
43
|
+
payload: {
|
|
44
|
+
completion_fields: ['suggested_queries'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
27
47
|
});
|
|
28
48
|
|
|
29
49
|
const DEFAULT_OPTIONS = Object.freeze({
|
|
30
50
|
...AnswerBasedWorkflow.DEFAULT_OPTIONS,
|
|
51
|
+
...SearchBasedWorkflow.DEFAULT_OPTIONS,
|
|
31
52
|
api: DEFAULT_API_OPTIONS,
|
|
32
53
|
layouts: DEFAULT_LAYOUTS,
|
|
33
54
|
trackers: DEFAULT_TRACKERS,
|
|
55
|
+
autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
|
|
56
|
+
pagination: DEFAULT_PAGINATION,
|
|
34
57
|
});
|
|
35
58
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
},
|
|
59
|
+
const ROLES_OPTIONS = Object.freeze({
|
|
60
|
+
main: ROLE.PRODUCTS,
|
|
61
|
+
members: Object.keys(DEFAULT_LAYOUTS),
|
|
62
|
+
mappings: Object.freeze({
|
|
63
|
+
...AnswerBasedWorkflow.ROLES_OPTIONS.mappings,
|
|
64
|
+
...SearchBasedWorkflow.ROLES_OPTIONS.mappings,
|
|
65
|
+
}),
|
|
43
66
|
});
|
|
44
67
|
|
|
45
|
-
export default class HybridSearch extends
|
|
68
|
+
export default class HybridSearch extends Workflow {
|
|
46
69
|
|
|
47
70
|
constructor(plugin, client) {
|
|
48
71
|
super({
|
|
49
72
|
name: 'hybrid-search',
|
|
50
73
|
plugin,
|
|
51
74
|
client,
|
|
52
|
-
roles:
|
|
53
|
-
rolesConfig: ROLES_CONFIG,
|
|
75
|
+
roles: ROLES_OPTIONS,
|
|
54
76
|
defaults: DEFAULT_OPTIONS,
|
|
55
77
|
});
|
|
56
78
|
}
|
|
57
79
|
|
|
58
|
-
|
|
59
|
-
super.
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
this.
|
|
80
|
+
_initProperties(args) {
|
|
81
|
+
super._initProperties(args);
|
|
82
|
+
this._subworkflows = [
|
|
83
|
+
this._answer = new HybridSearchAnswer(this),
|
|
84
|
+
this._results = new HybridSearchResults(this),
|
|
63
85
|
];
|
|
64
86
|
}
|
|
65
87
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
question_source: qs || ORGANIC_QUESTION_SOURCE, // might be null, not undefined
|
|
76
|
-
},
|
|
77
|
-
});
|
|
88
|
+
_getSubworkflow(name) {
|
|
89
|
+
switch (name) {
|
|
90
|
+
case 'answer':
|
|
91
|
+
return this._answer;
|
|
92
|
+
case 'results':
|
|
93
|
+
return this._results;
|
|
94
|
+
default:
|
|
95
|
+
throw new Error(`Invalid subworkflow: ${name}`);
|
|
96
|
+
}
|
|
78
97
|
}
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const values = facets[field];
|
|
88
|
-
if (!values || !values.length) {
|
|
89
|
-
continue; // just in case
|
|
90
|
-
}
|
|
91
|
-
clauses.push(`${field}:(${values.map(v => `"${v}"`).join(' OR ')})`);
|
|
92
|
-
}
|
|
93
|
-
return clauses.map(c => `(${c})`).join(' AND ');
|
|
99
|
+
_initActors() {
|
|
100
|
+
const hub = this._hub;
|
|
101
|
+
const client = this._client;
|
|
102
|
+
const options = this._options;
|
|
103
|
+
|
|
104
|
+
this._views = new HybridSearchViewsActor(this);
|
|
105
|
+
this._interactions = new InteractionsActor(hub, { client, options });
|
|
94
106
|
}
|
|
95
107
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
_initSession() {
|
|
109
|
+
this._results._sessions.restart();
|
|
110
|
+
this._answer._sessions.restart();
|
|
111
|
+
}
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
// configuration //
|
|
114
|
+
useLink(fn) {
|
|
115
|
+
this._answer.useLink(fn);
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
// properties //
|
|
120
|
+
get questionId() {
|
|
121
|
+
return this._answer.questionId;
|
|
106
122
|
}
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
data = super._defaultProcessData(data);
|
|
111
|
-
data = writeKeywordsToData(data);
|
|
112
|
-
data = instructPartialUpdates(data);
|
|
113
|
-
return data;
|
|
124
|
+
get filters() {
|
|
125
|
+
return this._results._views.filters;
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
|
|
128
|
+
get autocomplete() {
|
|
129
|
+
return this._answer.autocomplete;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// query //
|
|
133
|
+
autoQuery(options) {
|
|
134
|
+
this._answer.autoQuery(options);
|
|
135
|
+
}
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return data;
|
|
137
|
+
query(args) {
|
|
138
|
+
this._answer.query(args);
|
|
121
139
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
140
|
+
|
|
141
|
+
// interactions //
|
|
142
|
+
_defaultProcessInteraction(payload, args) {
|
|
143
|
+
return this._answer._defaultProcessInteraction(payload, args);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// destroy //
|
|
147
|
+
_destroy() {
|
|
148
|
+
for (const subworkflow of this._subworkflows) {
|
|
149
|
+
subworkflow.destroy();
|
|
150
|
+
}
|
|
151
|
+
super._destroy();
|
|
152
|
+
}
|
|
153
|
+
|
|
125
154
|
}
|
package/src/workflow/options.js
CHANGED
|
@@ -110,15 +110,14 @@ export function normalizeInteractionsOptions(options) {
|
|
|
110
110
|
return { ...options, preprocess };
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
export function
|
|
113
|
+
export function normalizePaginationOptions(options) {
|
|
114
114
|
if (options === undefined) {
|
|
115
|
-
throw new Error(`Expect
|
|
115
|
+
throw new Error(`Expect pagination options to be an object or a boolean value: ${options}`);
|
|
116
116
|
}
|
|
117
117
|
if (typeof options === 'boolean') {
|
|
118
|
-
options = {
|
|
118
|
+
options = { active: options };
|
|
119
119
|
}
|
|
120
|
-
|
|
121
|
-
return { ...options, processData };
|
|
120
|
+
return options;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
// merge //
|
|
@@ -206,17 +205,33 @@ export function mergeInteractionsOptions(...optionsList) {
|
|
|
206
205
|
}));
|
|
207
206
|
}
|
|
208
207
|
|
|
209
|
-
export function
|
|
208
|
+
export function mergePaginationOptions(...optionsList) {
|
|
210
209
|
if (optionsList[optionsList.length - 1] === false) {
|
|
211
210
|
return false;
|
|
212
211
|
}
|
|
212
|
+
return mergeOptions(optionsList, (merged, options) => Object.assign(merged, options));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function mergeAutocompleteOptions(...optionsList) {
|
|
213
216
|
return mergeOptions(optionsList, (merged, options) => Object.assign(merged, {
|
|
214
217
|
...options,
|
|
215
218
|
api: mergeApiOptions(merged.api, options.api),
|
|
216
|
-
processData: mergeDataProcessorOptions(merged.processData, options.processData),
|
|
217
219
|
}));
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
export function mergeRolesOptions(base, overrides) {
|
|
223
|
+
return Object.freeze({
|
|
224
|
+
...base,
|
|
225
|
+
...overrides,
|
|
226
|
+
members: mergeRolesMembers(base.members, overrides.members),
|
|
227
|
+
mappings: Object.freeze({ ...base.mappings, ...overrides.mappings }),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function mergeRolesMembers(base = [], overrides = []) {
|
|
232
|
+
return [...new Set([...base, ...overrides])];
|
|
233
|
+
}
|
|
234
|
+
|
|
220
235
|
|
|
221
236
|
|
|
222
237
|
// manifest //
|
|
@@ -247,9 +262,9 @@ const FEATURES = [
|
|
|
247
262
|
merge: mergeInteractionsOptions,
|
|
248
263
|
},
|
|
249
264
|
{
|
|
250
|
-
key: '
|
|
251
|
-
normalize:
|
|
252
|
-
merge:
|
|
265
|
+
key: 'pagination',
|
|
266
|
+
normalize: normalizePaginationOptions,
|
|
267
|
+
merge: mergePaginationOptions,
|
|
253
268
|
},
|
|
254
269
|
];
|
|
255
270
|
|
|
@@ -272,7 +287,7 @@ export class WorkflowOptions {
|
|
|
272
287
|
|
|
273
288
|
constructor(context = {}, defaults = {}) {
|
|
274
289
|
this._events = new EventEmitter({ target: this });
|
|
275
|
-
this._defaults = defaults;
|
|
290
|
+
this._defaults = defaults; // TODO: we should call normalize() on defaults
|
|
276
291
|
this._context = context || {};
|
|
277
292
|
this._locals = {};
|
|
278
293
|
|