@miso.ai/client-sdk-ui 1.10.3-beta.0 → 1.10.4-beta.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/cjs/actor/autocomplete.js +4 -1
- package/dist/cjs/actor/data.js +15 -6
- package/dist/cjs/actor/fields.js +10 -10
- package/dist/cjs/actor/filters.js +114 -0
- package/dist/cjs/actor/hub.js +2 -1
- package/dist/cjs/actor/sessions.js +3 -5
- package/dist/cjs/actor/view.js +19 -6
- package/dist/cjs/actor/views.js +32 -19
- package/dist/cjs/constants.js +9 -2
- package/dist/cjs/defaults/ask/constants.js +3 -2
- package/dist/cjs/defaults/ask/templates.js +112 -219
- package/dist/cjs/defaults/common/templates.js +234 -0
- package/dist/cjs/defaults/hybrid-search/answer-box.js +115 -0
- package/dist/cjs/defaults/hybrid-search/constants.js +8 -0
- package/dist/cjs/defaults/hybrid-search/controls.js +17 -0
- package/dist/cjs/defaults/hybrid-search/index.js +31 -0
- package/dist/cjs/defaults/hybrid-search/phrases.js +27 -0
- package/dist/cjs/defaults/hybrid-search/templates.js +127 -0
- package/dist/cjs/defaults/index.js +4 -2
- package/dist/cjs/element/container/index.js +7 -0
- package/dist/cjs/element/container/miso-hybrid-search.js +21 -0
- package/dist/cjs/element/role/index.js +21 -0
- package/dist/cjs/element/role/miso-facets.js +20 -0
- package/dist/cjs/element/role/miso-hits.js +20 -0
- package/dist/cjs/element/role/miso-keywords.js +20 -0
- package/dist/cjs/layout/index.js +7 -0
- package/dist/cjs/layout/input/facets.js +294 -0
- package/dist/cjs/layout/input/option-list.js +4 -4
- package/dist/cjs/layout/input/search-box.js +6 -8
- package/dist/cjs/layout/list/collection.js +1 -2
- package/dist/cjs/layout/raf.js +1 -0
- package/dist/cjs/layout/template.js +3 -1
- package/dist/cjs/layout/templates.js +15 -0
- package/dist/cjs/layout/text/text.js +38 -5
- package/dist/cjs/layout/trackable.js +2 -16
- package/dist/cjs/plugin.js +4 -1
- package/dist/cjs/{source/index.js → source.js} +7 -4
- package/dist/cjs/util/bindings.js +3 -0
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/answer-based.js +335 -0
- package/dist/cjs/workflow/ask.js +98 -271
- package/dist/cjs/workflow/base.js +69 -35
- package/dist/cjs/workflow/explore.js +10 -0
- package/dist/cjs/workflow/hybrid-search.js +140 -0
- package/dist/cjs/workflow/index.js +7 -0
- package/dist/cjs/workflow/processors.js +113 -0
- package/dist/cjs/workflow/recommendation.js +12 -3
- package/dist/cjs/workflow/search.js +34 -12
- package/package.json +2 -2
- package/src/actor/autocomplete.js +4 -2
- package/src/actor/data.js +8 -2
- package/src/actor/fields.js +10 -10
- package/src/actor/filters.js +103 -0
- package/src/actor/hub.js +2 -1
- package/src/actor/sessions.js +3 -4
- package/src/actor/view.js +19 -2
- package/src/actor/views.js +30 -16
- package/src/constants.js +10 -0
- package/src/defaults/ask/constants.js +1 -0
- package/src/defaults/ask/templates.js +70 -179
- package/src/defaults/common/templates.js +173 -0
- package/src/defaults/hybrid-search/answer-box.js +118 -0
- package/src/defaults/hybrid-search/constants.js +2 -0
- package/src/defaults/hybrid-search/controls.js +11 -0
- package/src/defaults/hybrid-search/index.js +9 -0
- package/src/defaults/hybrid-search/phrases.js +22 -0
- package/src/defaults/hybrid-search/templates.js +83 -0
- package/src/defaults/index.js +2 -0
- package/src/element/container/index.js +1 -0
- package/src/element/container/miso-hybrid-search.js +19 -0
- package/src/element/role/index.js +3 -0
- package/src/element/role/miso-facets.js +16 -0
- package/src/element/role/miso-hits.js +16 -0
- package/src/element/role/miso-keywords.js +16 -0
- package/src/layout/index.js +1 -0
- package/src/layout/input/facets.js +242 -0
- package/src/layout/input/option-list.js +3 -4
- package/src/layout/input/search-box.js +6 -7
- package/src/layout/list/collection.js +1 -2
- package/src/layout/raf.js +1 -0
- package/src/layout/template.js +2 -1
- package/src/layout/templates.js +15 -0
- package/src/layout/text/text.js +28 -3
- package/src/layout/trackable.js +2 -15
- package/src/plugin.js +6 -2
- package/src/{source/index.js → source.js} +7 -4
- package/src/util/bindings.js +4 -0
- package/src/version.js +1 -1
- package/src/workflow/answer-based.js +280 -0
- package/src/workflow/ask.js +82 -204
- package/src/workflow/base.js +64 -27
- package/src/workflow/explore.js +10 -1
- package/src/workflow/hybrid-search.js +125 -0
- package/src/workflow/index.js +1 -0
- package/src/workflow/processors.js +95 -0
- package/src/workflow/recommendation.js +9 -2
- package/src/workflow/search.js +35 -7
- package/dist/cjs/source/api.js +0 -23
- package/src/source/api.js +0 -12
package/src/actor/views.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { defineValues, delegateGetters } from '@miso.ai/commons';
|
|
2
|
-
import {
|
|
2
|
+
import { ROLE } from '../constants.js';
|
|
3
3
|
import * as fields from './fields.js';
|
|
4
4
|
import ViewActor from './view.js';
|
|
5
|
+
import Filters from './filters.js';
|
|
5
6
|
import Tracker from './tracker.js';
|
|
6
7
|
import { normalizeTrackerOptions } from '../util/trackers.js';
|
|
7
8
|
|
|
@@ -12,6 +13,7 @@ export default class ViewsActor {
|
|
|
12
13
|
extensions,
|
|
13
14
|
layouts,
|
|
14
15
|
roles = [],
|
|
16
|
+
rolesConfig = {},
|
|
15
17
|
options,
|
|
16
18
|
}) {
|
|
17
19
|
this._hub = hub;
|
|
@@ -19,10 +21,12 @@ export default class ViewsActor {
|
|
|
19
21
|
this._extensions = extensions;
|
|
20
22
|
this._layoutFactory = layouts;
|
|
21
23
|
this._options = options;
|
|
24
|
+
this._rolesConfig = rolesConfig;
|
|
22
25
|
this._containers = new Map();
|
|
23
26
|
this._containerTracker = undefined;
|
|
24
27
|
this._views = {};
|
|
25
28
|
this._trackers = new Trackers(this, roles);
|
|
29
|
+
this._filters = new Filters(this);
|
|
26
30
|
|
|
27
31
|
for (const role of roles) {
|
|
28
32
|
this._views[role] = new ViewActor(this, role);
|
|
@@ -37,7 +41,7 @@ export default class ViewsActor {
|
|
|
37
41
|
|
|
38
42
|
this._unsubscribes = [
|
|
39
43
|
() => window.removeEventListener('resize', syncSize),
|
|
40
|
-
hub.on(fields.data(),
|
|
44
|
+
hub.on(fields.data(), data => this.refresh({ data })),
|
|
41
45
|
options.on('trackers', () => this._syncTrackers()),
|
|
42
46
|
options.on('layouts', () => this._syncLayouts()),
|
|
43
47
|
];
|
|
@@ -50,6 +54,10 @@ export default class ViewsActor {
|
|
|
50
54
|
return this._trackers;
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
get filters() {
|
|
58
|
+
return this._filters;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
// elements //
|
|
54
62
|
addContainer(element) {
|
|
55
63
|
if (this._containers.has(element)) {
|
|
@@ -177,22 +185,27 @@ export default class ViewsActor {
|
|
|
177
185
|
}
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
async refresh({ force } = {}) {
|
|
181
|
-
|
|
188
|
+
async refresh({ force, data } = {}) {
|
|
189
|
+
data = this._getData(data);
|
|
182
190
|
// container first
|
|
183
|
-
|
|
184
|
-
}
|
|
191
|
+
let views = this._getViews(true);
|
|
185
192
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
if (!force && data._inst) {
|
|
194
|
+
const { includes, excludes } = data._inst;
|
|
195
|
+
if (includes) {
|
|
196
|
+
// always include containers
|
|
197
|
+
views = views.filter(view => view.role === ROLE.CONTAINER || includes.includes(view.role));
|
|
198
|
+
}
|
|
199
|
+
if (excludes) {
|
|
200
|
+
views = views.filter(view => !excludes.includes(view.role));
|
|
201
|
+
}
|
|
194
202
|
}
|
|
195
|
-
|
|
203
|
+
|
|
204
|
+
await Promise.all(views.map(view => view.refresh({ force, data })));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_getData(data) {
|
|
208
|
+
return data || this._hub.states[fields.data()];
|
|
196
209
|
}
|
|
197
210
|
|
|
198
211
|
_syncTrackers() {
|
|
@@ -227,6 +240,7 @@ export default class ViewsActor {
|
|
|
227
240
|
unsubscribe();
|
|
228
241
|
}
|
|
229
242
|
this._unsubscribes = [];
|
|
243
|
+
this._filters._destroy();
|
|
230
244
|
}
|
|
231
245
|
|
|
232
246
|
}
|
|
@@ -235,7 +249,7 @@ class Views {
|
|
|
235
249
|
|
|
236
250
|
constructor(actor) {
|
|
237
251
|
this._actor = actor;
|
|
238
|
-
delegateGetters(this, actor, ['syncSize', 'refresh', 'trackers']);
|
|
252
|
+
delegateGetters(this, actor, ['syncSize', 'refresh', 'trackers', 'filters']);
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
get(role) {
|
package/src/constants.js
CHANGED
|
@@ -20,6 +20,9 @@ export const ROLE = Object.freeze({
|
|
|
20
20
|
QUERY_SUGGESTIONS: 'query_suggestions',
|
|
21
21
|
RELATED_QUESTIONS: 'related_questions',
|
|
22
22
|
AFFILIATION: 'affiliation',
|
|
23
|
+
KEYWORDS: 'keywords',
|
|
24
|
+
FACETS: 'facets',
|
|
25
|
+
HITS: 'hits',
|
|
23
26
|
ERROR: 'error',
|
|
24
27
|
});
|
|
25
28
|
|
|
@@ -36,18 +39,25 @@ const DATA_ROLE_SET = new Set([
|
|
|
36
39
|
ROLE.QUERY_SUGGESTIONS,
|
|
37
40
|
ROLE.RELATED_QUESTIONS,
|
|
38
41
|
ROLE.AFFILIATION,
|
|
42
|
+
ROLE.FACETS,
|
|
43
|
+
ROLE.HITS,
|
|
39
44
|
]);
|
|
40
45
|
|
|
41
46
|
export function isDataRole(role) {
|
|
42
47
|
return DATA_ROLE_SET.has(role);
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
export const DATA_ASPECT = Object.freeze({
|
|
51
|
+
AUTOCOMPLETE: 'autocomplete',
|
|
52
|
+
});
|
|
53
|
+
|
|
45
54
|
export const LAYOUT_CATEGORY = Object.freeze({
|
|
46
55
|
CONTAINER: 'container',
|
|
47
56
|
BANNER: 'banner',
|
|
48
57
|
LIST: 'list',
|
|
49
58
|
QUERY: 'query',
|
|
50
59
|
RADIO: 'radio',
|
|
60
|
+
FILTERS: 'filters',
|
|
51
61
|
TEXT: 'text',
|
|
52
62
|
AFFILIATION: 'affiliation',
|
|
53
63
|
ERROR: 'error',
|
|
@@ -1,11 +1,74 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { externalize } from '@miso.ai/commons';
|
|
2
|
+
import * as CONSTANTS from './constants.js';
|
|
3
3
|
import * as DEFAULT_PHRASES from './phrases.js';
|
|
4
|
+
import { Helpers, Sections as _Sections } from '../common/templates.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
class Sections extends _Sections {
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
super({ helpers });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
answerGroup(options) {
|
|
13
|
+
const { container } = this._helpers;
|
|
14
|
+
return [
|
|
15
|
+
...super.answerGroup(options),
|
|
16
|
+
container(options, { name: 'bottom-spacing', visibleWhen: 'ongoing' }),
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
answer(options) {
|
|
21
|
+
const { container } = this._helpers;
|
|
22
|
+
return container(options, { name: 'answer', visibleWhen: 'ready' }, [
|
|
23
|
+
options.parentQuestionId ? '<hr>' : '',
|
|
24
|
+
...this.question(options),
|
|
25
|
+
'<miso-answer></miso-answer>',
|
|
26
|
+
'<miso-feedback></miso-feedback>',
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
sources(options) {
|
|
31
|
+
const { container, phrase } = this._helpers;
|
|
32
|
+
return container(options, { name: 'sources', visibleWhen: 'nonempty' }, [
|
|
33
|
+
'<hr>',
|
|
34
|
+
phrase(options, { name: 'sources', tag: 'h3' }),
|
|
35
|
+
'<miso-sources></miso-sources>',
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
followUps(options) {
|
|
40
|
+
const { classPrefix, features = {} } = options;
|
|
41
|
+
return features.followUpQuestions === false ? '' : `<div id="${classPrefix}__follow-ups" class="${classPrefix}__follow-ups"></div>`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
relatedResourcesSection(options) {
|
|
45
|
+
const { container, phrase, section } = this._helpers;
|
|
46
|
+
const { features = {}, logo = true } = options;
|
|
47
|
+
const body = features.relatedResources === false ? '' : container(options, { name: 'related-resources', visibleWhen: 'nonempty', logo }, [
|
|
48
|
+
phrase(options, { name: 'related-resources', tag: 'h2' }),
|
|
49
|
+
'<miso-related-resources></miso-related-resources>',
|
|
50
|
+
]);
|
|
51
|
+
return section(options, { name: 'related-resources' }, body);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
querySuggestions(options) {
|
|
55
|
+
const { container, phrase } = this._helpers;
|
|
56
|
+
const { features = {} } = options;
|
|
57
|
+
return features.querySuggestions === false ? '' :
|
|
58
|
+
container(options, { name: 'query-suggestions', visibleWhen: 'initial+nonempty' }, [
|
|
59
|
+
phrase(options, { name: 'query-suggestions', tag: 'h3' }),
|
|
60
|
+
'<miso-query-suggestions></miso-query-suggestions>',
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const helpers = externalize(new Helpers({ ...CONSTANTS, DEFAULT_PHRASES }));
|
|
67
|
+
export const sections = externalize(new Sections({ helpers }));
|
|
7
68
|
|
|
8
69
|
export function root(options = {}) {
|
|
70
|
+
const { normalizeOptions, section, container } = helpers;
|
|
71
|
+
const { followUps, relatedResourcesSection, answerGroup } = sections;
|
|
9
72
|
options = normalizeOptions(options);
|
|
10
73
|
return [
|
|
11
74
|
section(options, { name: 'question' }, [
|
|
@@ -13,11 +76,13 @@ export function root(options = {}) {
|
|
|
13
76
|
]),
|
|
14
77
|
section(options, { name: 'answer' }, answerGroup(options)),
|
|
15
78
|
followUps(options),
|
|
16
|
-
|
|
79
|
+
relatedResourcesSection(options),
|
|
17
80
|
].join('');
|
|
18
81
|
};
|
|
19
82
|
|
|
20
83
|
export function followUp(options = {}) {
|
|
84
|
+
const { normalizeOptions, section, container } = helpers;
|
|
85
|
+
const { querySuggestions, answerGroup } = sections;
|
|
21
86
|
options = normalizeOptions(options);
|
|
22
87
|
return section(options, { name: 'follow-up' }, [
|
|
23
88
|
querySuggestions(options),
|
|
@@ -25,177 +90,3 @@ export function followUp(options = {}) {
|
|
|
25
90
|
...answerGroup(options),
|
|
26
91
|
]);
|
|
27
92
|
};
|
|
28
|
-
|
|
29
|
-
export const sections = Object.freeze({
|
|
30
|
-
followUps,
|
|
31
|
-
relatedResources,
|
|
32
|
-
querySuggestions,
|
|
33
|
-
answerGroup,
|
|
34
|
-
answer,
|
|
35
|
-
sources,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export const helpers = Object.freeze({
|
|
39
|
-
normalizeOptions,
|
|
40
|
-
normalizePhraseOptions,
|
|
41
|
-
normalizePhraseOptionKey,
|
|
42
|
-
phrase,
|
|
43
|
-
phraseText,
|
|
44
|
-
section,
|
|
45
|
-
container,
|
|
46
|
-
element,
|
|
47
|
-
attrs,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// options //
|
|
53
|
-
function normalizeOptions({
|
|
54
|
-
classPrefix = CLASS_PREFIX,
|
|
55
|
-
circledCitationIndex = true,
|
|
56
|
-
phrases,
|
|
57
|
-
...options
|
|
58
|
-
} = {}) {
|
|
59
|
-
phrases = normalizePhraseOptions(phrases);
|
|
60
|
-
return { classPrefix, circledCitationIndex, phrases, ...options };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function normalizePhraseOptions(options = {}) {
|
|
64
|
-
if (!options) {
|
|
65
|
-
return {};
|
|
66
|
-
}
|
|
67
|
-
const normalized = {};
|
|
68
|
-
for (const key in options) {
|
|
69
|
-
normalized[normalizePhraseOptionKey(key)] = options[key];
|
|
70
|
-
}
|
|
71
|
-
return normalized;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function normalizePhraseOptionKey(key) {
|
|
75
|
-
key = kebabOrSnakeToLowerCamel(key);
|
|
76
|
-
if (key === 'relatedQuestions') {
|
|
77
|
-
return 'querySuggestions'; // legacy key
|
|
78
|
-
}
|
|
79
|
-
return key;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// sections & containers //
|
|
83
|
-
function followUps(options) {
|
|
84
|
-
const { classPrefix, features = {} } = options;
|
|
85
|
-
return features.followUpQuestions === false ? '' : `<div id="${classPrefix}__follow-ups" class="${classPrefix}__follow-ups"></div>`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function relatedResources(options) {
|
|
89
|
-
const { features = {}, logo = true } = options;
|
|
90
|
-
const body = features.relatedResources === false ? '' : container(options, { name: 'related-resources', visibleWhen: 'nonempty', logo }, [
|
|
91
|
-
phrase(options, { name: 'related-resources', tag: 'h2' }),
|
|
92
|
-
'<miso-related-resources></miso-related-resources>',
|
|
93
|
-
]);
|
|
94
|
-
return section(options, { name: 'related-resources' }, body);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function querySuggestions(options) {
|
|
98
|
-
const { features = {} } = options;
|
|
99
|
-
return features.querySuggestions === false ? '' :
|
|
100
|
-
container(options, { name: 'query-suggestions', visibleWhen: 'initial+nonempty' }, [
|
|
101
|
-
phrase(options, { name: 'query-suggestions', tag: 'h3' }),
|
|
102
|
-
'<miso-query-suggestions></miso-query-suggestions>',
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function answerGroup(options) {
|
|
107
|
-
return [
|
|
108
|
-
answer(options),
|
|
109
|
-
affiliation(options),
|
|
110
|
-
sources(options),
|
|
111
|
-
container(options, { name: 'bottom-spacing', visibleWhen: 'ongoing' }),
|
|
112
|
-
];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function answer(options) {
|
|
116
|
-
return container(options, { name: 'answer', visibleWhen: 'ready' }, [
|
|
117
|
-
options.parentQuestionId ? '<hr>' : '',
|
|
118
|
-
phrase(options, { name: 'question', tag: 'div' }),
|
|
119
|
-
'<miso-question></miso-question>',
|
|
120
|
-
'<miso-answer></miso-answer>',
|
|
121
|
-
'<miso-feedback></miso-feedback>',
|
|
122
|
-
]);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function sources(options) {
|
|
126
|
-
return container(options, { name: 'sources', visibleWhen: 'nonempty' }, [
|
|
127
|
-
'<hr>',
|
|
128
|
-
phrase(options, { name: 'sources', tag: 'h3' }),
|
|
129
|
-
'<miso-sources></miso-sources>',
|
|
130
|
-
]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function affiliation(options) {
|
|
134
|
-
const { affiliation = true } = options || {};
|
|
135
|
-
if (!affiliation) {
|
|
136
|
-
return '';
|
|
137
|
-
}
|
|
138
|
-
return container(options, { name: 'affiliation', visibleWhen: 'nonempty' }, [
|
|
139
|
-
'<miso-affiliation></miso-affiliation>',
|
|
140
|
-
]);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function phrase(options, { name, tag = 'div' }) {
|
|
144
|
-
const { classPrefix } = options;
|
|
145
|
-
return `<${tag} class="${classPrefix}__phrase ${classPrefix}__${name}-phrase">${phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function phraseText(options, key) {
|
|
149
|
-
const { phrases = {} } = options;
|
|
150
|
-
const fn = phrases[key] || DEFAULT_PHRASES[key];
|
|
151
|
-
switch (typeof fn) {
|
|
152
|
-
case 'function':
|
|
153
|
-
return fn(options);
|
|
154
|
-
case 'string':
|
|
155
|
-
return fn;
|
|
156
|
-
default:
|
|
157
|
-
return '';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function section({ classPrefix }, { name }, body) {
|
|
162
|
-
return element('section', {
|
|
163
|
-
classes: [`${classPrefix}__section`, `${classPrefix}__${name}`],
|
|
164
|
-
}, body);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function container({
|
|
168
|
-
classPrefix,
|
|
169
|
-
circledCitationIndex,
|
|
170
|
-
parentQuestionId,
|
|
171
|
-
}, {
|
|
172
|
-
name,
|
|
173
|
-
visibleWhen,
|
|
174
|
-
logo = false,
|
|
175
|
-
}, body) {
|
|
176
|
-
const classes = [`${classPrefix}__${name}-container`];
|
|
177
|
-
circledCitationIndex && classes.push('miso-circled-citation-index');
|
|
178
|
-
return element('miso-ask', { classes, visibleWhen, logo, parentQuestionId }, body);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function element(tag, attributes = {}, body = '') {
|
|
182
|
-
if (typeof attributes === 'string' || Array.isArray(attributes)) {
|
|
183
|
-
body = attributes;
|
|
184
|
-
attributes = {};
|
|
185
|
-
}
|
|
186
|
-
return `<${tag}${attrs(attributes)}>${Array.isArray(body) ? body.join('') : body}</${tag}>`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function attrs({ classes = [], ...attributes } = {}) {
|
|
190
|
-
let str = ``;
|
|
191
|
-
if (classes.length) {
|
|
192
|
-
str += ` class="${classes.join(' ')}"`;
|
|
193
|
-
}
|
|
194
|
-
for (const name in attributes) {
|
|
195
|
-
const value = attributes[name];
|
|
196
|
-
if (value !== undefined) {
|
|
197
|
-
str += ` ${lowerCamelToKebab(name)}="${value}"`;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return str;
|
|
201
|
-
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { lowerCamelToKebab, kebabToLowerCamel, kebabOrSnakeToLowerCamel, defineValues } from '@miso.ai/commons';
|
|
2
|
+
|
|
3
|
+
// TODO: rename classPrefix -> rootClass?
|
|
4
|
+
// TODO: placeholder
|
|
5
|
+
|
|
6
|
+
export class Sections {
|
|
7
|
+
|
|
8
|
+
constructor({ helpers }) {
|
|
9
|
+
this._helpers = helpers;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
errorSection(options) {
|
|
13
|
+
const { section, container } = this._helpers;
|
|
14
|
+
return section(options, { name: 'error' }, [
|
|
15
|
+
container(options, { name: 'error', visibleWhen: 'erroneous' }, '<miso-error></miso-error>'),
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
answerGroup(options) {
|
|
20
|
+
return [
|
|
21
|
+
this.answer(options),
|
|
22
|
+
this.affiliation(options),
|
|
23
|
+
this.sources(options),
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
answer(options) {
|
|
28
|
+
const { container } = this._helpers;
|
|
29
|
+
return container(options, { name: 'answer', visibleWhen: 'ready' }, [
|
|
30
|
+
...this.question(options),
|
|
31
|
+
'<miso-answer></miso-answer>',
|
|
32
|
+
'<miso-feedback></miso-feedback>',
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
question(options) {
|
|
37
|
+
const { phrase } = this._helpers;
|
|
38
|
+
return [
|
|
39
|
+
phrase(options, { name: 'question', tag: 'div' }),
|
|
40
|
+
'<miso-question></miso-question>',
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
sources(options) {
|
|
45
|
+
const { container, phrase } = this._helpers;
|
|
46
|
+
return container(options, { name: 'sources', visibleWhen: 'nonempty' }, [
|
|
47
|
+
phrase(options, { name: 'sources', tag: 'h3' }),
|
|
48
|
+
'<miso-sources></miso-sources>',
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
affiliation(options) {
|
|
53
|
+
const { container } = this._helpers;
|
|
54
|
+
const { affiliation = true } = options || {};
|
|
55
|
+
if (!affiliation) {
|
|
56
|
+
return '';
|
|
57
|
+
}
|
|
58
|
+
return container(options, { name: 'affiliation', visibleWhen: 'nonempty' }, [
|
|
59
|
+
'<miso-affiliation></miso-affiliation>',
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class Helpers {
|
|
66
|
+
|
|
67
|
+
constructor(context) {
|
|
68
|
+
this._context = context;
|
|
69
|
+
defineValues(this, { lowerCamelToKebab, kebabToLowerCamel, kebabOrSnakeToLowerCamel });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// options //
|
|
73
|
+
normalizeOptions({
|
|
74
|
+
classPrefix,
|
|
75
|
+
circledCitationIndex = true,
|
|
76
|
+
phrases,
|
|
77
|
+
...options
|
|
78
|
+
} = {}) {
|
|
79
|
+
classPrefix = classPrefix || this._context.CLASS_PREFIX;
|
|
80
|
+
phrases = this.normalizePhraseOptions(phrases);
|
|
81
|
+
return { classPrefix, circledCitationIndex, phrases, ...options };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
normalizePhraseOptions(options = {}) {
|
|
85
|
+
if (!options) {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
const normalized = {};
|
|
89
|
+
for (const key in options) {
|
|
90
|
+
normalized[this.normalizePhraseOptionKey(key)] = options[key];
|
|
91
|
+
}
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
normalizePhraseOptionKey(key) {
|
|
96
|
+
key = kebabOrSnakeToLowerCamel(key);
|
|
97
|
+
if (key === 'relatedQuestions') {
|
|
98
|
+
return 'querySuggestions'; // legacy key
|
|
99
|
+
}
|
|
100
|
+
return key;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// html //
|
|
104
|
+
phrase(options, { name, tag = 'div', inline = false }) {
|
|
105
|
+
const { classPrefix } = options;
|
|
106
|
+
const mainClass = inline ? `${classPrefix}__phrase-inline` : `${classPrefix}__phrase`;
|
|
107
|
+
return `<${tag} class="${mainClass} ${classPrefix}__${name}-phrase">${this.phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
phraseText(options, key) {
|
|
111
|
+
const { phrases = {} } = options;
|
|
112
|
+
const fn = phrases[key] || this._context.DEFAULT_PHRASES[key];
|
|
113
|
+
switch (typeof fn) {
|
|
114
|
+
case 'function':
|
|
115
|
+
return fn(options);
|
|
116
|
+
case 'string':
|
|
117
|
+
return fn;
|
|
118
|
+
default:
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
section({ classPrefix }, { name }, body) {
|
|
124
|
+
return this.element('section', {
|
|
125
|
+
classes: [`${classPrefix}__section`, `${classPrefix}__${name}`],
|
|
126
|
+
}, body);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
container({ classPrefix, circledCitationIndex, parentQuestionId }, { name, visibleWhen, logo = false }, body) {
|
|
130
|
+
const classes = [`${classPrefix}__${name}-container`];
|
|
131
|
+
circledCitationIndex && classes.push('miso-circled-citation-index');
|
|
132
|
+
return this.element(this._context.CONAINER_TAG, { classes, visibleWhen, logo, parentQuestionId }, body);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
element(tag, attributes = {}, body = '') {
|
|
136
|
+
return `<${tag}${this.attrs(attributes)}>${Array.isArray(body) ? body.join('') : body}</${tag}>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
attrs({ classes = [], ...attributes } = {}) {
|
|
140
|
+
let str = ``;
|
|
141
|
+
if (classes.length) {
|
|
142
|
+
str += ` class="${classes.join(' ')}"`;
|
|
143
|
+
}
|
|
144
|
+
for (const name in attributes) {
|
|
145
|
+
const value = attributes[name];
|
|
146
|
+
if (value !== undefined) {
|
|
147
|
+
str += ` ${lowerCamelToKebab(name)}="${value}"`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return str;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function externalize(object) {
|
|
156
|
+
const externalized = {};
|
|
157
|
+
const props = new Set();
|
|
158
|
+
for (let o = object; o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
159
|
+
const descriptors = Object.getOwnPropertyDescriptors(o);
|
|
160
|
+
for (const key in descriptors) {
|
|
161
|
+
if (key === 'constructor' || key.startsWith('_') || props.has(key)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
props.add(key);
|
|
165
|
+
// TODO: getter, setter
|
|
166
|
+
const { value } = descriptors[key];
|
|
167
|
+
if (value) {
|
|
168
|
+
externalized[key] = typeof value === 'function' ? value.bind(object) : value;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return Object.freeze(externalized);
|
|
173
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { CLASS_PREFIX } from './constants.js';
|
|
2
|
+
import { STATUS } from '../../constants.js';
|
|
3
|
+
import { showMore as DEFAULT_PHRASE_SHOW_MORE, showLess as DEFAULT_PHRASE_SHOW_LESS } from './phrases.js';
|
|
4
|
+
|
|
5
|
+
const CLASS_OPEN = 'answer-box-open';
|
|
6
|
+
const CLASS_SHOWN = 'answer-box-shown';
|
|
7
|
+
const TOGGLE_BUTTON_SELECTOR = '[data-role="answer-box-toggle"]';
|
|
8
|
+
|
|
9
|
+
export default class AnswerBox {
|
|
10
|
+
|
|
11
|
+
constructor(client, element, { classPrefix = CLASS_PREFIX, phrases = {} } = {}) {
|
|
12
|
+
this._client = client;
|
|
13
|
+
this._workflow = client.ui.hybridSearch;
|
|
14
|
+
this._element = element;
|
|
15
|
+
this._classPrefix = classPrefix;
|
|
16
|
+
this._phrases = phrases;
|
|
17
|
+
this._init();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_init() {
|
|
21
|
+
const element = this._element;
|
|
22
|
+
const workflow = this._workflow;
|
|
23
|
+
|
|
24
|
+
// expose widget API
|
|
25
|
+
workflow.answerBox = this;
|
|
26
|
+
workflow._unsubscribes.push(() => this.destroy()); // TODO: review
|
|
27
|
+
|
|
28
|
+
// handle toggle button clicks
|
|
29
|
+
const callback = event => this._handleClick(event);
|
|
30
|
+
element.addEventListener('click', callback);
|
|
31
|
+
|
|
32
|
+
// sync state
|
|
33
|
+
this._syncWofkflowStatus();
|
|
34
|
+
this._syncButtonText();
|
|
35
|
+
|
|
36
|
+
this._unsubscribes = [
|
|
37
|
+
() => element.removeEventListener('click', callback),
|
|
38
|
+
workflow.on('loading', () => this.hide()),
|
|
39
|
+
workflow.on('error', () => this.show()),
|
|
40
|
+
workflow.on('ready', () => this.show()),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_handleClick({ target, button }) {
|
|
45
|
+
// only left click
|
|
46
|
+
if (button !== 0) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (target.closest(TOGGLE_BUTTON_SELECTOR)) {
|
|
50
|
+
this.toggleOpen();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_syncWofkflowStatus() {
|
|
55
|
+
const { status } = this._workflow;
|
|
56
|
+
switch (status) {
|
|
57
|
+
case STATUS.ERRONEOUS:
|
|
58
|
+
case STATUS.READY:
|
|
59
|
+
this.show();
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
this.hide();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_updateBoxStatus(method, className) {
|
|
67
|
+
this._element.classList[method](`${this._classPrefix}__${className}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_syncButtonText() {
|
|
71
|
+
const phrase = this.isOpen ? this._phrases.showLess || DEFAULT_PHRASE_SHOW_LESS : this._phrases.showMore || DEFAULT_PHRASE_SHOW_MORE;
|
|
72
|
+
for (const button of this._element.querySelectorAll(TOGGLE_BUTTON_SELECTOR)) {
|
|
73
|
+
button.textContent = phrase;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
destroy() {
|
|
78
|
+
for (const unsubscribe of this._unsubscribes) {
|
|
79
|
+
unsubscribe();
|
|
80
|
+
}
|
|
81
|
+
const workflow = this._workflow;
|
|
82
|
+
if (workflow && workflow.answerBox === this) {
|
|
83
|
+
delete workflow.answerBox;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const GETTERS = {
|
|
90
|
+
isOpen: CLASS_OPEN,
|
|
91
|
+
shown: CLASS_SHOWN,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const METHODS = {
|
|
95
|
+
toggleOpen: ['toggle', CLASS_OPEN],
|
|
96
|
+
open: ['open', CLASS_OPEN],
|
|
97
|
+
close: ['remove', CLASS_OPEN],
|
|
98
|
+
toggleHidden: ['toggle', CLASS_SHOWN],
|
|
99
|
+
show: ['add', CLASS_SHOWN],
|
|
100
|
+
hide: ['remove', CLASS_SHOWN],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
for (const [prop, className] of Object.entries(GETTERS)) {
|
|
104
|
+
Object.defineProperty(AnswerBox.prototype, prop, {
|
|
105
|
+
get() {
|
|
106
|
+
return this._element.classList.contains(`${this._classPrefix}__${className}`);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const [method, [action, className]] of Object.entries(METHODS)) {
|
|
112
|
+
AnswerBox.prototype[method] = function() {
|
|
113
|
+
this._updateBoxStatus(action, className);
|
|
114
|
+
if (className === CLASS_OPEN) {
|
|
115
|
+
this._syncButtonText();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|