@miso.ai/client-sdk-ui 1.12.2 → 1.12.3-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/constants.js +8 -3
- package/dist/cjs/defaults/ask/phrases.js +7 -3
- package/dist/cjs/defaults/ask/templates.js +19 -19
- package/dist/cjs/defaults/common/templates.js +6 -4
- package/dist/cjs/element/context/miso-follow-ups.js +45 -13
- package/dist/cjs/element/role/index.js +7 -0
- package/dist/cjs/element/role/miso-follow-up-questions.js +20 -0
- package/dist/cjs/layout/input/option-list.js +4 -5
- package/dist/cjs/layout/text/typewriter/index.js +19 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/workflow/answer-based.js +10 -5
- package/dist/cjs/workflow/ask.js +66 -5
- package/dist/cjs/workflow/asks.js +2 -1
- package/dist/cjs/workflow/base.js +1 -1
- package/dist/cjs/workflow/hybrid-search-answer.js +1 -1
- package/dist/cjs/workflow/processors.js +32 -1
- package/dist/cjs/workflow/search-based.js +24 -1
- package/package.json +2 -2
- package/src/constants.js +8 -1
- package/src/defaults/ask/phrases.js +5 -2
- package/src/defaults/ask/templates.js +12 -12
- package/src/defaults/common/templates.js +6 -6
- package/src/element/context/miso-follow-ups.js +36 -11
- package/src/element/role/index.js +1 -0
- package/src/element/role/miso-follow-up-questions.js +16 -0
- package/src/layout/input/option-list.js +3 -4
- package/src/layout/text/typewriter/index.js +14 -1
- package/src/version.js +1 -1
- package/src/workflow/answer-based.js +11 -6
- package/src/workflow/ask.js +48 -5
- package/src/workflow/asks.js +2 -1
- package/src/workflow/base.js +1 -1
- package/src/workflow/hybrid-search-answer.js +2 -2
- package/src/workflow/processors.js +28 -2
- package/src/workflow/search-based.js +24 -2
|
@@ -239,7 +239,7 @@ class SearchBasedWorkflow extends _base.default {
|
|
|
239
239
|
_meta
|
|
240
240
|
} = request.payload;
|
|
241
241
|
if (!_meta || !_meta.page) {
|
|
242
|
-
return data; // not from "more" request
|
|
242
|
+
return (0, _processors.writeMisoIdAsRootMisoId)(data); // not from "more" request
|
|
243
243
|
}
|
|
244
244
|
// concat records if it's from "more" request
|
|
245
245
|
const currentData = this._hub.states[_index.fields.data()];
|
|
@@ -260,6 +260,29 @@ class SearchBasedWorkflow extends _base.default {
|
|
|
260
260
|
});
|
|
261
261
|
this._views.filters.apply();
|
|
262
262
|
}
|
|
263
|
+
|
|
264
|
+
// interactions //
|
|
265
|
+
_defaultProcessInteraction(payload, args) {
|
|
266
|
+
payload = super._defaultProcessInteraction(payload, args);
|
|
267
|
+
payload = this._writeRootMisoIdToInteraction(payload);
|
|
268
|
+
return payload;
|
|
269
|
+
}
|
|
270
|
+
_writeRootMisoIdToInteraction(payload) {
|
|
271
|
+
const data = this._hub.states[_index.fields.data()];
|
|
272
|
+
const {
|
|
273
|
+
root_miso_id
|
|
274
|
+
} = data && data.value || {};
|
|
275
|
+
if (!root_miso_id) {
|
|
276
|
+
return payload;
|
|
277
|
+
}
|
|
278
|
+
return (0, _processors.mergeInteraction)(payload, {
|
|
279
|
+
context: {
|
|
280
|
+
custom_context: {
|
|
281
|
+
root_miso_id
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
263
286
|
}
|
|
264
287
|
exports.default = SearchBasedWorkflow;
|
|
265
288
|
(0, _index3.makeConfigurable)(SearchBasedWorkflow.prototype, [_constants.WORKFLOW_CONFIGURABLE.PAGINATION, _constants.WORKFLOW_CONFIGURABLE.FILTERS]);
|
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export const ATTR_DATA_MISO_UNIT_ID = `data-miso-unit-id`;
|
|
2
2
|
export const ATTR_DATA_MISO_PRODUCT_ID = `data-miso-product-id`;
|
|
3
3
|
export const DEFAULT_UNIT_ID = 'default';
|
|
4
|
-
export const ORGANIC_QUESTION_SOURCE = '_organic';
|
|
5
4
|
|
|
6
5
|
export const ROLE = Object.freeze({
|
|
7
6
|
CONTAINER: 'container',
|
|
@@ -19,6 +18,7 @@ export const ROLE = Object.freeze({
|
|
|
19
18
|
IMAGES: 'images',
|
|
20
19
|
SOURCES: 'sources',
|
|
21
20
|
RELATED_RESOURCES: 'related_resources',
|
|
21
|
+
FOLLOW_UP_QUESTIONS: 'follow_up_questions',
|
|
22
22
|
QUERY_SUGGESTIONS: 'query_suggestions',
|
|
23
23
|
RELATED_QUESTIONS: 'related_questions',
|
|
24
24
|
AFFILIATION: 'affiliation',
|
|
@@ -42,6 +42,7 @@ const DATA_ROLE_SET = new Set([
|
|
|
42
42
|
ROLE.IMAGES,
|
|
43
43
|
ROLE.SOURCES,
|
|
44
44
|
ROLE.RELATED_RESOURCES,
|
|
45
|
+
ROLE.FOLLOW_UP_QUESTIONS,
|
|
45
46
|
ROLE.QUERY_SUGGESTIONS,
|
|
46
47
|
ROLE.RELATED_QUESTIONS,
|
|
47
48
|
ROLE.AFFILIATION,
|
|
@@ -110,6 +111,12 @@ export const STATUS = Object.freeze({
|
|
|
110
111
|
UNANSWERABLE: 'unanswerable',
|
|
111
112
|
});
|
|
112
113
|
|
|
114
|
+
export const QUESTION_SOURCE = Object.freeze({
|
|
115
|
+
ORGANIC: '_organic',
|
|
116
|
+
SUGGESTED_QUESTIONS: '_suggested_questions',
|
|
117
|
+
INLINE_QUESTION_LINK: '_inline_question_link',
|
|
118
|
+
});
|
|
119
|
+
|
|
113
120
|
export function validateEventType(value) {
|
|
114
121
|
if (TRACKING_EVENT_TYPES.indexOf(value) < 0) {
|
|
115
122
|
throw new Error(`Unrecognized event type: ${value}`);
|
|
@@ -9,13 +9,16 @@ export const question = 'You asked...';
|
|
|
9
9
|
export const sources = 'This reply is based on the following articles';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The phrase displayed right before <miso-
|
|
12
|
+
* The phrase displayed right before <miso-follow-up-questions>
|
|
13
13
|
*/
|
|
14
|
-
export const
|
|
14
|
+
export const followUpQuestions = 'Related questions you can explore';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The phrase displayed right before <miso-related-resources>
|
|
18
18
|
*/
|
|
19
19
|
export const relatedResources = 'Go beyond, and learn more about this topic';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The phrase displayed at the header of <miso-reasoning> box
|
|
23
|
+
*/
|
|
21
24
|
export const thoughts = 'Thoughts';
|
|
@@ -18,6 +18,7 @@ class Sections extends _Sections {
|
|
|
18
18
|
this.answer(options),
|
|
19
19
|
this.affiliation(options),
|
|
20
20
|
this.sources(options),
|
|
21
|
+
this.followUpQuestions(options),
|
|
21
22
|
container(options, { name: 'bottom-spacing', visibleWhen: 'ongoing' }),
|
|
22
23
|
];
|
|
23
24
|
}
|
|
@@ -76,6 +77,16 @@ class Sections extends _Sections {
|
|
|
76
77
|
]);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
followUpQuestions(options) {
|
|
81
|
+
const { container, phrase } = this._helpers;
|
|
82
|
+
const name = 'follow-up-questions';
|
|
83
|
+
const extraNames = ['query-suggestions']; // backward compatibility (styling)
|
|
84
|
+
return container(options, { name, extraNames, visibleWhen: 'nonempty' }, [
|
|
85
|
+
phrase(options, { name, extraNames, tag: 'h3' }),
|
|
86
|
+
'<miso-follow-up-questions></miso-follow-up-questions>',
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
|
|
79
90
|
followUps(options) {
|
|
80
91
|
const { features = {} } = options;
|
|
81
92
|
return features.followUpQuestions === false ? '' : `<miso-follow-ups id="miso-ask-combo__follow-ups" class="miso-ask-combo__follow-ups"></miso-follow-ups>`;
|
|
@@ -91,16 +102,6 @@ class Sections extends _Sections {
|
|
|
91
102
|
return section(options, { name: 'related-resources' }, body);
|
|
92
103
|
}
|
|
93
104
|
|
|
94
|
-
querySuggestions(options) {
|
|
95
|
-
const { container, phrase } = this._helpers;
|
|
96
|
-
const { features = {} } = options;
|
|
97
|
-
return features.querySuggestions === false ? '' :
|
|
98
|
-
container(options, { name: 'query-suggestions', visibleWhen: 'initial+nonempty' }, [
|
|
99
|
-
phrase(options, { name: 'query-suggestions', tag: 'h3' }),
|
|
100
|
-
'<miso-query-suggestions></miso-query-suggestions>',
|
|
101
|
-
]);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
export const helpers = externalize(new Helpers({ ...CONSTANTS, DEFAULT_PHRASES }));
|
|
@@ -122,10 +123,9 @@ export function root(options = {}) {
|
|
|
122
123
|
|
|
123
124
|
export function followUp(options = {}) {
|
|
124
125
|
const { normalizeOptions, section, container } = helpers;
|
|
125
|
-
const {
|
|
126
|
+
const { answerGroup } = sections;
|
|
126
127
|
options = normalizeOptions(options);
|
|
127
128
|
return section(options, { name: 'follow-up' }, [
|
|
128
|
-
querySuggestions(options),
|
|
129
129
|
container(options, { name: 'query', visibleWhen: 'initial loading' }, '<miso-query></miso-query>'),
|
|
130
130
|
...answerGroup(options),
|
|
131
131
|
]);
|
|
@@ -94,17 +94,17 @@ export class Helpers {
|
|
|
94
94
|
|
|
95
95
|
normalizePhraseOptionKey(key) {
|
|
96
96
|
key = kebabOrSnakeToLowerCamel(key);
|
|
97
|
-
if (key === 'relatedQuestions') {
|
|
98
|
-
return '
|
|
97
|
+
if (key === 'relatedQuestions' || key === 'querySuggestions') {
|
|
98
|
+
return 'followUpQuestions'; // legacy key
|
|
99
99
|
}
|
|
100
100
|
return key;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// html //
|
|
104
|
-
phrase(options, { name, tag = 'div', inline = false }) {
|
|
104
|
+
phrase(options, { name, extraNames = [], tag = 'div', inline = false }) {
|
|
105
105
|
const { classPrefix } = options;
|
|
106
106
|
const mainClass = inline ? `${classPrefix}__phrase-inline` : `${classPrefix}__phrase`;
|
|
107
|
-
return `<${tag} class="${mainClass} ${classPrefix}__${name}-phrase">${this.phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
|
|
107
|
+
return `<${tag} class="${mainClass} ${[name, ...extraNames].map(name => `${classPrefix}__${name}-phrase`).join(' ')}">${this.phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
phraseText(options, key) {
|
|
@@ -126,8 +126,8 @@ export class Helpers {
|
|
|
126
126
|
}, body);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
container({ classPrefix, circledCitationIndex, parentQuestionId }, { name, visibleWhen, trait, logo = false }, body) {
|
|
130
|
-
const classes = [`${classPrefix}__${name}-container`
|
|
129
|
+
container({ classPrefix, circledCitationIndex, parentQuestionId }, { name, extraNames = [], visibleWhen, trait, logo = false }, body) {
|
|
130
|
+
const classes = [name, ...extraNames].map(name => `${classPrefix}__${name}-container`);
|
|
131
131
|
circledCitationIndex && classes.push('miso-circled-citation-index');
|
|
132
132
|
return this.element(this._context.CONAINER_TAG, { classes, visibleWhen, trait, logo, parentQuestionId }, body);
|
|
133
133
|
}
|
|
@@ -14,6 +14,7 @@ export default class MisoFollowUpsElement extends MisoContextElement {
|
|
|
14
14
|
|
|
15
15
|
constructor() {
|
|
16
16
|
super();
|
|
17
|
+
this._elementToWorkflow = new WeakMap();
|
|
17
18
|
this._unsubscribes = [];
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -41,21 +42,45 @@ export default class MisoFollowUpsElement extends MisoContextElement {
|
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
// 1.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// 1. call auto-next if unset
|
|
46
|
+
if (!context._autoNextFn) {
|
|
47
|
+
context.autoNext();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. when a new workflow is created, insert a new section
|
|
51
|
+
this._unsubscribes.push(context.on('create', (workflow) => {
|
|
52
|
+
const { parentQuestionId } = workflow;
|
|
53
|
+
const template = workflow._options.resolved.templates.followUp;
|
|
54
|
+
if (!parentQuestionId || !template) {
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
50
|
-
|
|
57
|
+
const oldChildCount = this.children.length;
|
|
58
|
+
this.insertAdjacentHTML('beforeend', template({ parentQuestionId }));
|
|
59
|
+
const newChildCount = this.children.length;
|
|
60
|
+
// keep track of the elements
|
|
61
|
+
for (const element of Array.prototype.slice.call(this.children, oldChildCount, newChildCount)) {
|
|
62
|
+
this._elementToWorkflow.set(element, workflow);
|
|
63
|
+
}
|
|
51
64
|
}));
|
|
52
65
|
|
|
53
|
-
//
|
|
54
|
-
this._unsubscribes.push(context.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
// 3. when a workflow is loading, scroll into view
|
|
67
|
+
this._unsubscribes.push(context.on('loading', ({ workflow }) => {
|
|
68
|
+
for (const element of this.children) {
|
|
69
|
+
if (this._elementToWorkflow.get(element) === workflow) {
|
|
70
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// 4. when a workflow is destroyed, remove the section
|
|
77
|
+
this._unsubscribes.push(context.on('destroy', ({ workflow }) => {
|
|
78
|
+
for (const element of this.children) {
|
|
79
|
+
if (this._elementToWorkflow.get(element) === workflow) {
|
|
80
|
+
this._elementToWorkflow.delete(element);
|
|
81
|
+
element.remove();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
59
84
|
}));
|
|
60
85
|
}
|
|
61
86
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as MisoComponentElement } from './miso-component.js';
|
|
2
2
|
export { default as MisoQueryElement } from './miso-query.js';
|
|
3
|
+
export { default as MisoFollowUpQuestionsElement } from './miso-follow-up-questions.js';
|
|
3
4
|
export { default as MisoQuerySuggestionsElement } from './miso-query-suggestions.js';
|
|
4
5
|
export { default as MisoFeedbackElement } from './miso-feedback.js';
|
|
5
6
|
export { default as MisoResultsElement } from './miso-results.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import MisoComponentElement from './miso-component.js';
|
|
2
|
+
import { ROLE } from '../../constants.js';
|
|
3
|
+
|
|
4
|
+
export default class MisoFollowUpQuestionsElement extends MisoComponentElement {
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
role: ROLE.FOLLOW_UP_QUESTIONS,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get tagName() {
|
|
13
|
+
return 'miso-follow-up-questions';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -116,15 +116,14 @@ export default class OptionListLayout extends TemplateBasedLayout {
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
this._trackClick(event, binding);
|
|
119
|
-
this.
|
|
119
|
+
this._select(option.text);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
async
|
|
122
|
+
async _select(value) {
|
|
123
123
|
if (!value) {
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
-
|
|
127
|
-
this._view.hub.update(fields.query(), { q: value });
|
|
126
|
+
this._view._events.emit('select', { value });
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
destroy() {
|
|
@@ -151,8 +151,9 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
151
151
|
// event //
|
|
152
152
|
_onClick(event) {
|
|
153
153
|
const { target } = event;
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
// citation link click
|
|
156
|
+
const citationLinkElement = target.closest(`[data-role="citation-link"]`);
|
|
156
157
|
if (citationLinkElement) {
|
|
157
158
|
const index = parseInt(citationLinkElement.dataset.index);
|
|
158
159
|
if (!Number.isNaN(index)) {
|
|
@@ -160,6 +161,18 @@ export default class TypewriterLayout extends ProgressiveLayout {
|
|
|
160
161
|
}
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
164
|
+
|
|
165
|
+
// follow-up link click
|
|
166
|
+
const followUpLinkElement = target.closest(`[data-role="follow-up-link"]`);
|
|
167
|
+
if (followUpLinkElement) {
|
|
168
|
+
let { q } = followUpLinkElement.dataset;
|
|
169
|
+
q = q ? q.trim() : undefined;
|
|
170
|
+
if (q) {
|
|
171
|
+
this._view._events.emit('follow-up-click', { event, q });
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
163
176
|
// generic link click
|
|
164
177
|
const anchorElement = target.closest('a');
|
|
165
178
|
if (anchorElement) {
|
package/src/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.12.
|
|
1
|
+
export default '1.12.3-beta.0';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { API } from '@miso.ai/commons';
|
|
2
2
|
import Workflow from './base.js';
|
|
3
3
|
import { fields, FeedbackActor } from '../actor/index.js';
|
|
4
|
-
import { ROLE, STATUS,
|
|
4
|
+
import { ROLE, STATUS, QUESTION_SOURCE } from '../constants.js';
|
|
5
5
|
import { SearchBoxLayout, TextLayout, ListLayout, GalleryLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
|
|
6
6
|
import { processData as processAffiliationData } from '../affiliation/index.js';
|
|
7
7
|
import { mergeRolesOptions, autoQuery as autoQueryFn, DEFAULT_AUTO_QUERY_PARAM, DEFAULT_TRACKER_OPTIONS } from './options/index.js';
|
|
@@ -174,7 +174,7 @@ export default class AnswerBasedWorkflow extends Workflow {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
_writeQuestionSourceToSession(args) {
|
|
177
|
-
this.session.meta.question_source = args.qs ||
|
|
177
|
+
this.session.meta.question_source = args.qs || QUESTION_SOURCE.ORGANIC; // might be null, not undefined
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
_buildPayload() {
|
|
@@ -234,12 +234,12 @@ export default class AnswerBasedWorkflow extends Workflow {
|
|
|
234
234
|
|
|
235
235
|
const url = new URL(window.location);
|
|
236
236
|
const currentQuestion = url.searchParams.get(param);
|
|
237
|
-
const currentQuestionSource = url.searchParams.get(sourceParam) ||
|
|
237
|
+
const currentQuestionSource = url.searchParams.get(sourceParam) || QUESTION_SOURCE.ORGANIC;
|
|
238
238
|
if (question === currentQuestion && questionSource === currentQuestionSource) {
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
241
|
url.searchParams.set(param, question);
|
|
242
|
-
if (questionSource ===
|
|
242
|
+
if (questionSource === QUESTION_SOURCE.ORGANIC) {
|
|
243
243
|
url.searchParams.delete(sourceParam);
|
|
244
244
|
} else {
|
|
245
245
|
url.searchParams.set(sourceParam, questionSource);
|
|
@@ -294,10 +294,13 @@ export default class AnswerBasedWorkflow extends Workflow {
|
|
|
294
294
|
|
|
295
295
|
// handlers //
|
|
296
296
|
_onCitationClick({ index, event }) {
|
|
297
|
+
if (event.button !== 0) {
|
|
298
|
+
return; // only left click
|
|
299
|
+
}
|
|
297
300
|
if (isTracked(event)) {
|
|
298
301
|
return;
|
|
299
302
|
}
|
|
300
|
-
const { value } = this.states[fields.data()] || {};
|
|
303
|
+
const { value } = this._hub.states[fields.data()] || {};
|
|
301
304
|
const { sources } = value || {};
|
|
302
305
|
// index is 1-based
|
|
303
306
|
const source = sources && sources[index - 1];
|
|
@@ -307,11 +310,13 @@ export default class AnswerBasedWorkflow extends Workflow {
|
|
|
307
310
|
// TODO: should we track impression as well?
|
|
308
311
|
markAsTracked(event);
|
|
309
312
|
// distinguish from regular sources element click
|
|
310
|
-
// TODO
|
|
311
313
|
this._views.trackers.sources.click([source.product_id], { event_target: 'citation-link' });
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
_onAnswerLinkClick({ event, ...item }) {
|
|
317
|
+
if (event.button !== 0) {
|
|
318
|
+
return; // only left click
|
|
319
|
+
}
|
|
315
320
|
if (isTracked(event)) {
|
|
316
321
|
return;
|
|
317
322
|
}
|
package/src/workflow/ask.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defineValues, trimObj, API } from '@miso.ai/commons';
|
|
2
2
|
import AnswerBasedWorkflow from './answer-based.js';
|
|
3
3
|
import { fields } from '../actor/index.js';
|
|
4
|
-
import { ROLE, STATUS,
|
|
4
|
+
import { ROLE, STATUS, QUESTION_SOURCE } from '../constants.js';
|
|
5
5
|
import { OptionListLayout, ListLayout, SearchBoxLayout, TypewriterLayout } from '../layout/index.js';
|
|
6
6
|
import { mergeInteraction } from './processors.js';
|
|
7
7
|
import { mergeRolesOptions, DEFAULT_TRACKER_OPTIONS } from './options/index.js';
|
|
8
|
-
import { mappingSuggestionsData } from './processors.js';
|
|
8
|
+
import { mappingSuggestionsData, mappingFollowUpQuestionsData } from './processors.js';
|
|
9
9
|
import { followUp as followUpTemplate } from '../defaults/ask/templates.js';
|
|
10
10
|
|
|
11
11
|
const DEFAULT_API_OPTIONS = Object.freeze({
|
|
@@ -23,6 +23,7 @@ const DEFAULT_LAYOUTS = Object.freeze({
|
|
|
23
23
|
[ROLE.REASONING]: TypewriterLayout.type,
|
|
24
24
|
[ROLE.RELATED_RESOURCES]: [ListLayout.type, { incremental: true, itemType: 'article' }],
|
|
25
25
|
[ROLE.QUERY_SUGGESTIONS]: OptionListLayout.type,
|
|
26
|
+
[ROLE.FOLLOW_UP_QUESTIONS]: OptionListLayout.type,
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
const DEFAULT_TRACKERS = Object.freeze({
|
|
@@ -53,6 +54,7 @@ const ROLES_OPTIONS = mergeRolesOptions(AnswerBasedWorkflow.ROLES_OPTIONS, {
|
|
|
53
54
|
members: Object.keys(DEFAULT_LAYOUTS),
|
|
54
55
|
mappings: {
|
|
55
56
|
[ROLE.QUERY_SUGGESTIONS]: mappingSuggestionsData,
|
|
57
|
+
[ROLE.FOLLOW_UP_QUESTIONS]: mappingFollowUpQuestionsData,
|
|
56
58
|
},
|
|
57
59
|
});
|
|
58
60
|
|
|
@@ -79,6 +81,9 @@ export default class Ask extends AnswerBasedWorkflow {
|
|
|
79
81
|
this._unsubscribes = [
|
|
80
82
|
...this._unsubscribes,
|
|
81
83
|
this._hub.on(fields.view(ROLE.REASONING), state => this._onReasoningViewUpdate(state)),
|
|
84
|
+
this._views.get(ROLE.ANSWER).on('follow-up-click', event => this._onFollowUpClick(event)),
|
|
85
|
+
this._views.get(ROLE.FOLLOW_UP_QUESTIONS).on('select', event => this._onFollowUpQuestionSelect(event)),
|
|
86
|
+
this._views.get(ROLE.QUERY_SUGGESTIONS).on('select', event => this._onQuerySuggestionSelect(event)),
|
|
82
87
|
];
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -91,6 +96,8 @@ export default class Ask extends AnswerBasedWorkflow {
|
|
|
91
96
|
|
|
92
97
|
// lifecycle //
|
|
93
98
|
restart() {
|
|
99
|
+
const { next } = this;
|
|
100
|
+
next && next.destroy();
|
|
94
101
|
if (this._questionId) {
|
|
95
102
|
this._context._byQid.delete(this._questionId);
|
|
96
103
|
}
|
|
@@ -126,7 +133,7 @@ export default class Ask extends AnswerBasedWorkflow {
|
|
|
126
133
|
parent_question_id: parentQuestionId,
|
|
127
134
|
_meta: {
|
|
128
135
|
...options._meta,
|
|
129
|
-
question_source: qs ||
|
|
136
|
+
question_source: qs || QUESTION_SOURCE.ORGANIC, // might be null, not undefined
|
|
130
137
|
},
|
|
131
138
|
});
|
|
132
139
|
payload = this._writeWikiLinkTemplateToPayload(payload);
|
|
@@ -163,8 +170,11 @@ export default class Ask extends AnswerBasedWorkflow {
|
|
|
163
170
|
let { property } = (payload.context && payload.context.custom_context) || {};
|
|
164
171
|
let question_id, parent_question_id, question_source;
|
|
165
172
|
|
|
166
|
-
if (args.role === ROLE.QUERY_SUGGESTIONS) {
|
|
173
|
+
if (args.role === ROLE.FOLLOW_UP_QUESTIONS || args.role === ROLE.QUERY_SUGGESTIONS) {
|
|
167
174
|
property = 'suggested_followup_questions';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (args.role === ROLE.QUERY_SUGGESTIONS) {
|
|
168
178
|
parent_question_id = this.previous && this.previous.parentQuestionId;
|
|
169
179
|
question_id = this.parentQuestionId;
|
|
170
180
|
question_source = this.previous && this.previous.session.meta.question_source;
|
|
@@ -187,6 +197,36 @@ export default class Ask extends AnswerBasedWorkflow {
|
|
|
187
197
|
});
|
|
188
198
|
}
|
|
189
199
|
|
|
200
|
+
// handlers //
|
|
201
|
+
_onFollowUpClick({ q, event } = {}) {
|
|
202
|
+
if (event.button !== 0) {
|
|
203
|
+
return; // only left click
|
|
204
|
+
}
|
|
205
|
+
this._submitFollowUpQuestion({ q, qs: QUESTION_SOURCE.INLINE_QUESTION_LINK });
|
|
206
|
+
// TODO: track click?
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_onFollowUpQuestionSelect({ value: q }) {
|
|
210
|
+
this._submitFollowUpQuestion({ q, qs: QUESTION_SOURCE.SUGGESTED_QUESTIONS });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_submitFollowUpQuestion({ q = '', qs } = {}) {
|
|
214
|
+
q = q.trim();
|
|
215
|
+
if (!q) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const next = this.getOrCreateNext();
|
|
219
|
+
next.query({ q, qs });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
_onQuerySuggestionSelect({ value: q }) {
|
|
223
|
+
if (!q) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// backward compatible: query on current workflow
|
|
227
|
+
this.query({ q, qs: QUESTION_SOURCE.SUGGESTED_QUESTIONS });
|
|
228
|
+
}
|
|
229
|
+
|
|
190
230
|
// helpers //
|
|
191
231
|
_writeQuestionId(questionId) {
|
|
192
232
|
// capture question ID and register at context
|
|
@@ -212,7 +252,10 @@ export default class Ask extends AnswerBasedWorkflow {
|
|
|
212
252
|
|
|
213
253
|
// destroy //
|
|
214
254
|
_destroy(options) {
|
|
215
|
-
const { parentQuestionId, questionId } = this;
|
|
255
|
+
const { next, parentQuestionId, questionId } = this;
|
|
256
|
+
if (next) {
|
|
257
|
+
next.destroy(options);
|
|
258
|
+
}
|
|
216
259
|
if (parentQuestionId) {
|
|
217
260
|
this._context._byPqid.delete(parentQuestionId);
|
|
218
261
|
}
|
package/src/workflow/asks.js
CHANGED
|
@@ -19,7 +19,7 @@ function autoNextOptionsAsPredicate(options = DEFAULT_AUTO_NEXT_OPTIONS) {
|
|
|
19
19
|
case 'function':
|
|
20
20
|
return options;
|
|
21
21
|
default:
|
|
22
|
-
return false;
|
|
22
|
+
return () => false;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -31,6 +31,7 @@ export default class Asks extends WorkflowContext {
|
|
|
31
31
|
this._byPqid = new Map();
|
|
32
32
|
this._root = new Ask(this); // initialize with default workflow
|
|
33
33
|
this._events.on('*', event => this._createNextIfNecessary(event));
|
|
34
|
+
this._autoNextFn = undefined;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
get root() {
|
package/src/workflow/base.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AnswerBasedWorkflow from './answer-based.js';
|
|
2
|
-
import { ROLE, STATUS,
|
|
2
|
+
import { ROLE, STATUS, QUESTION_SOURCE } from '../constants.js';
|
|
3
3
|
import { mergeRolesOptions } from './options/index.js';
|
|
4
4
|
import { writeKeywordsToData, writeMisoIdToSession, writeMisoIdFromSession, writeUnanswerableToMeta } from './processors.js';
|
|
5
5
|
import { makeAutocompletable } from './autocompletable.js';
|
|
@@ -54,7 +54,7 @@ export default class HybridSearchAnswer extends AnswerBasedWorkflow {
|
|
|
54
54
|
...payload,
|
|
55
55
|
_meta: {
|
|
56
56
|
...payload._meta,
|
|
57
|
-
question_source: qs ||
|
|
57
|
+
question_source: qs || QUESTION_SOURCE.ORGANIC, // might be null, not undefined
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -54,6 +54,13 @@ export function mappingSuggestionsData(_, { workflow } = {}) {
|
|
|
54
54
|
return value && value.map(text => ({ text }));
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
export function mappingFollowUpQuestionsData(data) {
|
|
58
|
+
if (!data || !data.value) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return (data.value.followup_questions || []).map(text => ({ text }));
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
// data //
|
|
58
65
|
export function getRevision(data) {
|
|
59
66
|
return data && data.value && data.value.revision;
|
|
@@ -186,11 +193,29 @@ export function retainFacetCountsInData(data, currentFacetCounts) {
|
|
|
186
193
|
};
|
|
187
194
|
}
|
|
188
195
|
|
|
196
|
+
export function writeMisoIdAsRootMisoId(data) {
|
|
197
|
+
const { value } = data;
|
|
198
|
+
if (!value) {
|
|
199
|
+
return data;
|
|
200
|
+
}
|
|
201
|
+
const { miso_id } = value;
|
|
202
|
+
if (!miso_id) {
|
|
203
|
+
return data;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
...data,
|
|
207
|
+
value: {
|
|
208
|
+
...value,
|
|
209
|
+
root_miso_id: miso_id,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
189
214
|
export function concatItemsFromMoreResponse(oldData, newData, { role = ROLE.PRODUCTS } = {}) {
|
|
190
215
|
if (!newData.value) {
|
|
191
216
|
return oldData;
|
|
192
217
|
}
|
|
193
|
-
const { [role]: oldProducts = [], facet_counts, total } = oldData.value;
|
|
218
|
+
const { [role]: oldProducts = [], facet_counts, total, root_miso_id } = oldData.value;
|
|
194
219
|
const { [role]: newProducts = [] } = newData.value;
|
|
195
220
|
// keep old exhaustion flag, just in case
|
|
196
221
|
// TODO
|
|
@@ -202,6 +227,7 @@ export function concatItemsFromMoreResponse(oldData, newData, { role = ROLE.PROD
|
|
|
202
227
|
...newData.value,
|
|
203
228
|
facet_counts,
|
|
204
229
|
total,
|
|
230
|
+
root_miso_id,
|
|
205
231
|
[role]: [...oldProducts, ...newProducts],
|
|
206
232
|
},
|
|
207
233
|
}
|
|
@@ -304,7 +330,7 @@ export function mergeInteraction(base = {}, patch = {}) {
|
|
|
304
330
|
return trimObj({
|
|
305
331
|
...base,
|
|
306
332
|
...patch,
|
|
307
|
-
context:trimObj({
|
|
333
|
+
context: trimObj({
|
|
308
334
|
...base.context,
|
|
309
335
|
...patch.context,
|
|
310
336
|
custom_context: trimObj({
|
|
@@ -3,7 +3,7 @@ import Workflow from './base.js';
|
|
|
3
3
|
import { fields } from '../actor/index.js';
|
|
4
4
|
import { STATUS, ROLE, WORKFLOW_CONFIGURABLE } from '../constants.js';
|
|
5
5
|
import { SearchBoxLayout, ListLayout, TextLayout, FacetsLayout, SelectLayout, MoreButtonLayout } from '../layout/index.js';
|
|
6
|
-
import { mappingSortData, writeKeywordsToData, retainFacetCountsInData, writeExhaustionToData, concatItemsFromMoreResponse } from './processors.js';
|
|
6
|
+
import { mappingSortData, writeKeywordsToData, retainFacetCountsInData, writeExhaustionToData, writeMisoIdAsRootMisoId, concatItemsFromMoreResponse, mergeInteraction } from './processors.js';
|
|
7
7
|
import { mergeRolesOptions, autoQuery as autoQueryFn, makeConfigurable, DEFAULT_TRACKER_OPTIONS } from './options/index.js';
|
|
8
8
|
|
|
9
9
|
const DEFAULT_ROWS = 10;
|
|
@@ -245,7 +245,7 @@ export default class SearchBasedWorkflow extends Workflow {
|
|
|
245
245
|
}
|
|
246
246
|
const { _meta } = request.payload;
|
|
247
247
|
if (!_meta || !_meta.page) {
|
|
248
|
-
return data; // not from "more" request
|
|
248
|
+
return writeMisoIdAsRootMisoId(data); // not from "more" request
|
|
249
249
|
}
|
|
250
250
|
// concat records if it's from "more" request
|
|
251
251
|
const currentData = this._hub.states[fields.data()];
|
|
@@ -261,6 +261,28 @@ export default class SearchBasedWorkflow extends Workflow {
|
|
|
261
261
|
this._views.filters.apply();
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// interactions //
|
|
265
|
+
_defaultProcessInteraction(payload, args) {
|
|
266
|
+
payload = super._defaultProcessInteraction(payload, args);
|
|
267
|
+
payload = this._writeRootMisoIdToInteraction(payload);
|
|
268
|
+
return payload;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_writeRootMisoIdToInteraction(payload) {
|
|
272
|
+
const data = this._hub.states[fields.data()];
|
|
273
|
+
const { root_miso_id } = (data && data.value) || {};
|
|
274
|
+
if (!root_miso_id) {
|
|
275
|
+
return payload;
|
|
276
|
+
}
|
|
277
|
+
return mergeInteraction(payload, {
|
|
278
|
+
context: {
|
|
279
|
+
custom_context: {
|
|
280
|
+
root_miso_id,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
264
286
|
}
|
|
265
287
|
|
|
266
288
|
makeConfigurable(SearchBasedWorkflow.prototype, [WORKFLOW_CONFIGURABLE.PAGINATION, WORKFLOW_CONFIGURABLE.FILTERS]);
|