@miso.ai/client-sdk-ui 1.12.2-beta.1 → 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.
Files changed (35) hide show
  1. package/dist/cjs/constants.js +8 -3
  2. package/dist/cjs/defaults/ask/phrases.js +7 -3
  3. package/dist/cjs/defaults/ask/templates.js +19 -19
  4. package/dist/cjs/defaults/common/templates.js +6 -4
  5. package/dist/cjs/element/context/miso-follow-ups.js +45 -13
  6. package/dist/cjs/element/role/index.js +7 -0
  7. package/dist/cjs/element/role/miso-follow-up-questions.js +20 -0
  8. package/dist/cjs/layout/input/option-list.js +4 -5
  9. package/dist/cjs/layout/text/typewriter/index.js +19 -1
  10. package/dist/cjs/version.js +1 -1
  11. package/dist/cjs/workflow/answer-based.js +10 -5
  12. package/dist/cjs/workflow/ask.js +66 -5
  13. package/dist/cjs/workflow/asks.js +2 -1
  14. package/dist/cjs/workflow/base.js +1 -1
  15. package/dist/cjs/workflow/hybrid-search-answer.js +1 -1
  16. package/dist/cjs/workflow/processors.js +32 -1
  17. package/dist/cjs/workflow/search-based.js +24 -1
  18. package/package.json +2 -2
  19. package/src/constants.js +8 -1
  20. package/src/defaults/ask/phrases.js +5 -2
  21. package/src/defaults/ask/templates.js +12 -12
  22. package/src/defaults/common/templates.js +6 -6
  23. package/src/element/context/miso-follow-ups.js +36 -11
  24. package/src/element/role/index.js +1 -0
  25. package/src/element/role/miso-follow-up-questions.js +16 -0
  26. package/src/layout/input/option-list.js +3 -4
  27. package/src/layout/text/typewriter/index.js +14 -1
  28. package/src/version.js +1 -1
  29. package/src/workflow/answer-based.js +11 -6
  30. package/src/workflow/ask.js +48 -5
  31. package/src/workflow/asks.js +2 -1
  32. package/src/workflow/base.js +1 -1
  33. package/src/workflow/hybrid-search-answer.js +2 -2
  34. package/src/workflow/processors.js +28 -2
  35. package/src/workflow/search-based.js +24 -2
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.WORKFLOW_CONFIGURABLE = exports.TRACKING_STATUS = exports.TRACKING_EVENT_TYPES = exports.STATUS = exports.ROLE = exports.PERFORMANCE_EVENT_TYPES = exports.ORGANIC_QUESTION_SOURCE = exports.EVENT_TYPE = exports.DEFAULT_UNIT_ID = exports.ATTR_DATA_MISO_UNIT_ID = exports.ATTR_DATA_MISO_PRODUCT_ID = void 0;
6
+ exports.WORKFLOW_CONFIGURABLE = exports.TRACKING_STATUS = exports.TRACKING_EVENT_TYPES = exports.STATUS = exports.ROLE = exports.QUESTION_SOURCE = exports.PERFORMANCE_EVENT_TYPES = exports.EVENT_TYPE = exports.DEFAULT_UNIT_ID = exports.ATTR_DATA_MISO_UNIT_ID = exports.ATTR_DATA_MISO_PRODUCT_ID = void 0;
7
7
  exports.isDataRole = isDataRole;
8
8
  exports.isPerformanceEventType = isPerformanceEventType;
9
9
  exports.isProductRole = isProductRole;
@@ -12,7 +12,6 @@ exports.validateTrackingStatus = validateTrackingStatus;
12
12
  const ATTR_DATA_MISO_UNIT_ID = exports.ATTR_DATA_MISO_UNIT_ID = `data-miso-unit-id`;
13
13
  const ATTR_DATA_MISO_PRODUCT_ID = exports.ATTR_DATA_MISO_PRODUCT_ID = `data-miso-product-id`;
14
14
  const DEFAULT_UNIT_ID = exports.DEFAULT_UNIT_ID = 'default';
15
- const ORGANIC_QUESTION_SOURCE = exports.ORGANIC_QUESTION_SOURCE = '_organic';
16
15
  const ROLE = exports.ROLE = Object.freeze({
17
16
  CONTAINER: 'container',
18
17
  BANNER: 'banner',
@@ -29,6 +28,7 @@ const ROLE = exports.ROLE = Object.freeze({
29
28
  IMAGES: 'images',
30
29
  SOURCES: 'sources',
31
30
  RELATED_RESOURCES: 'related_resources',
31
+ FOLLOW_UP_QUESTIONS: 'follow_up_questions',
32
32
  QUERY_SUGGESTIONS: 'query_suggestions',
33
33
  RELATED_QUESTIONS: 'related_questions',
34
34
  AFFILIATION: 'affiliation',
@@ -39,7 +39,7 @@ const ROLE = exports.ROLE = Object.freeze({
39
39
  SORT: 'sort',
40
40
  ERROR: 'error'
41
41
  });
42
- const DATA_ROLE_SET = new Set([ROLE.PRODUCTS, ROLE.CATEGORIES, ROLE.ATTRIBUTES, ROLE.RESULTS, ROLE.ITEMS, ROLE.QUESTION, ROLE.ANSWER, ROLE.REASONING, ROLE.IMAGES, ROLE.SOURCES, ROLE.RELATED_RESOURCES, ROLE.QUERY_SUGGESTIONS, ROLE.RELATED_QUESTIONS, ROLE.AFFILIATION, ROLE.FACETS, ROLE.TOTAL]);
42
+ const DATA_ROLE_SET = new Set([ROLE.PRODUCTS, ROLE.CATEGORIES, ROLE.ATTRIBUTES, ROLE.RESULTS, ROLE.ITEMS, ROLE.QUESTION, ROLE.ANSWER, ROLE.REASONING, ROLE.IMAGES, ROLE.SOURCES, ROLE.RELATED_RESOURCES, ROLE.FOLLOW_UP_QUESTIONS, ROLE.QUERY_SUGGESTIONS, ROLE.RELATED_QUESTIONS, ROLE.AFFILIATION, ROLE.FACETS, ROLE.TOTAL]);
43
43
  function isDataRole(role) {
44
44
  return DATA_ROLE_SET.has(role);
45
45
  }
@@ -76,6 +76,11 @@ const STATUS = exports.STATUS = Object.freeze({
76
76
  NONEMPTY: 'nonempty',
77
77
  UNANSWERABLE: 'unanswerable'
78
78
  });
79
+ const QUESTION_SOURCE = exports.QUESTION_SOURCE = Object.freeze({
80
+ ORGANIC: '_organic',
81
+ SUGGESTED_QUESTIONS: '_suggested_questions',
82
+ INLINE_QUESTION_LINK: '_inline_question_link'
83
+ });
79
84
  function validateEventType(value) {
80
85
  if (TRACKING_EVENT_TYPES.indexOf(value) < 0) {
81
86
  throw new Error(`Unrecognized event type: ${value}`);
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.thoughts = exports.sources = exports.relatedResources = exports.question = exports.querySuggestions = void 0;
6
+ exports.thoughts = exports.sources = exports.relatedResources = exports.question = exports.followUpQuestions = void 0;
7
7
  /**
8
8
  * The phrase displayed right before <miso-question>
9
9
  */
@@ -15,12 +15,16 @@ const question = exports.question = 'You asked...';
15
15
  const sources = exports.sources = 'This reply is based on the following articles';
16
16
 
17
17
  /**
18
- * The phrase displayed right before <miso-query-suggestions>
18
+ * The phrase displayed right before <miso-follow-up-questions>
19
19
  */
20
- const querySuggestions = exports.querySuggestions = 'Related questions you can explore';
20
+ const followUpQuestions = exports.followUpQuestions = 'Related questions you can explore';
21
21
 
22
22
  /**
23
23
  * The phrase displayed right before <miso-related-resources>
24
24
  */
25
25
  const relatedResources = exports.relatedResources = 'Go beyond, and learn more about this topic';
26
+
27
+ /**
28
+ * The phrase displayed at the header of <miso-reasoning> box
29
+ */
26
30
  const thoughts = exports.thoughts = 'Thoughts';
@@ -23,7 +23,7 @@ class Sections extends _templates.Sections {
23
23
  const {
24
24
  container
25
25
  } = this._helpers;
26
- return [this.question(options), this.reasoning(options), this.answer(options), this.affiliation(options), this.sources(options), container(options, {
26
+ return [this.question(options), this.reasoning(options), this.answer(options), this.affiliation(options), this.sources(options), this.followUpQuestions(options), container(options, {
27
27
  name: 'bottom-spacing',
28
28
  visibleWhen: 'ongoing'
29
29
  })];
@@ -89,6 +89,23 @@ class Sections extends _templates.Sections {
89
89
  tag: 'h3'
90
90
  }), '<miso-sources></miso-sources>']);
91
91
  }
92
+ followUpQuestions(options) {
93
+ const {
94
+ container,
95
+ phrase
96
+ } = this._helpers;
97
+ const name = 'follow-up-questions';
98
+ const extraNames = ['query-suggestions']; // backward compatibility (styling)
99
+ return container(options, {
100
+ name,
101
+ extraNames,
102
+ visibleWhen: 'nonempty'
103
+ }, [phrase(options, {
104
+ name,
105
+ extraNames,
106
+ tag: 'h3'
107
+ }), '<miso-follow-up-questions></miso-follow-up-questions>']);
108
+ }
92
109
  followUps(options) {
93
110
  const {
94
111
  features = {}
@@ -117,22 +134,6 @@ class Sections extends _templates.Sections {
117
134
  name: 'related-resources'
118
135
  }, body);
119
136
  }
120
- querySuggestions(options) {
121
- const {
122
- container,
123
- phrase
124
- } = this._helpers;
125
- const {
126
- features = {}
127
- } = options;
128
- return features.querySuggestions === false ? '' : container(options, {
129
- name: 'query-suggestions',
130
- visibleWhen: 'initial+nonempty'
131
- }, [phrase(options, {
132
- name: 'query-suggestions',
133
- tag: 'h3'
134
- }), '<miso-query-suggestions></miso-query-suggestions>']);
135
- }
136
137
  }
137
138
  const helpers = exports.helpers = (0, _commons.externalize)(new _templates.Helpers(Object.assign({}, CONSTANTS, {
138
139
  DEFAULT_PHRASES
@@ -168,13 +169,12 @@ function followUp(options = {}) {
168
169
  container
169
170
  } = helpers;
170
171
  const {
171
- querySuggestions,
172
172
  answerGroup
173
173
  } = sections;
174
174
  options = normalizeOptions(options);
175
175
  return section(options, {
176
176
  name: 'follow-up'
177
- }, [querySuggestions(options), container(options, {
177
+ }, [container(options, {
178
178
  name: 'query',
179
179
  visibleWhen: 'initial loading'
180
180
  }, '<miso-query></miso-query>'), ...answerGroup(options)]);
@@ -119,8 +119,8 @@ class Helpers {
119
119
  }
120
120
  normalizePhraseOptionKey(key) {
121
121
  key = (0, _commons.kebabOrSnakeToLowerCamel)(key);
122
- if (key === 'relatedQuestions') {
123
- return 'querySuggestions'; // legacy key
122
+ if (key === 'relatedQuestions' || key === 'querySuggestions') {
123
+ return 'followUpQuestions'; // legacy key
124
124
  }
125
125
  return key;
126
126
  }
@@ -128,6 +128,7 @@ class Helpers {
128
128
  // html //
129
129
  phrase(options, {
130
130
  name,
131
+ extraNames = [],
131
132
  tag = 'div',
132
133
  inline = false
133
134
  }) {
@@ -135,7 +136,7 @@ class Helpers {
135
136
  classPrefix
136
137
  } = options;
137
138
  const mainClass = inline ? `${classPrefix}__phrase-inline` : `${classPrefix}__phrase`;
138
- return `<${tag} class="${mainClass} ${classPrefix}__${name}-phrase">${this.phraseText(options, (0, _commons.kebabToLowerCamel)(name))}</${tag}>`;
139
+ return `<${tag} class="${mainClass} ${[name, ...extraNames].map(name => `${classPrefix}__${name}-phrase`).join(' ')}">${this.phraseText(options, (0, _commons.kebabToLowerCamel)(name))}</${tag}>`;
139
140
  }
140
141
  phraseText(options, key) {
141
142
  const {
@@ -166,11 +167,12 @@ class Helpers {
166
167
  parentQuestionId
167
168
  }, {
168
169
  name,
170
+ extraNames = [],
169
171
  visibleWhen,
170
172
  trait,
171
173
  logo = false
172
174
  }, body) {
173
- const classes = [`${classPrefix}__${name}-container`];
175
+ const classes = [name, ...extraNames].map(name => `${classPrefix}__${name}-container`);
174
176
  circledCitationIndex && classes.push('miso-circled-citation-index');
175
177
  return this.element(this._context.CONAINER_TAG, {
176
178
  classes,
@@ -16,6 +16,7 @@ class MisoFollowUpsElement extends _misoContext.default {
16
16
  }
17
17
  constructor() {
18
18
  super();
19
+ this._elementToWorkflow = new WeakMap();
19
20
  this._unsubscribes = [];
20
21
  }
21
22
  _getContext(client) {
@@ -39,25 +40,56 @@ class MisoFollowUpsElement extends _misoContext.default {
39
40
  return;
40
41
  }
41
42
 
42
- // 1. when an answer is fully populated, insert a new section for the follow-up question
43
- this._unsubscribes.push(context.on('done', event => {
44
- const template = event.workflow._options.resolved.templates.followUp;
45
- if (!template) {
43
+ // 1. call auto-next if unset
44
+ if (!context._autoNextFn) {
45
+ context.autoNext();
46
+ }
47
+
48
+ // 2. when a new workflow is created, insert a new section
49
+ this._unsubscribes.push(context.on('create', workflow => {
50
+ const {
51
+ parentQuestionId
52
+ } = workflow;
53
+ const template = workflow._options.resolved.templates.followUp;
54
+ if (!parentQuestionId || !template) {
46
55
  return;
47
56
  }
57
+ const oldChildCount = this.children.length;
48
58
  this.insertAdjacentHTML('beforeend', template({
49
- parentQuestionId: event.workflow.questionId
59
+ parentQuestionId
50
60
  }));
61
+ const newChildCount = this.children.length;
62
+ // keep track of the elements
63
+ for (const element of Array.prototype.slice.call(this.children, oldChildCount, newChildCount)) {
64
+ this._elementToWorkflow.set(element, workflow);
65
+ }
51
66
  }));
52
67
 
53
- // 2. if user starts over, clean up existing follow-up questions
54
- this._unsubscribes.push(context.root.on('loading', () => {
55
- // clean up the entire follow-ups section
56
- this.innerHTML = '';
57
- // destroy all follow-up workflows
58
- context.reset({
59
- root: false
60
- });
68
+ // 3. when a workflow is loading, scroll into view
69
+ this._unsubscribes.push(context.on('loading', ({
70
+ workflow
71
+ }) => {
72
+ for (const element of this.children) {
73
+ if (this._elementToWorkflow.get(element) === workflow) {
74
+ element.scrollIntoView({
75
+ behavior: 'smooth',
76
+ block: 'start'
77
+ });
78
+ break;
79
+ }
80
+ }
81
+ }));
82
+
83
+ // 4. when a workflow is destroyed, remove the section
84
+ this._unsubscribes.push(context.on('destroy', ({
85
+ workflow
86
+ }) => {
87
+ for (const element of this.children) {
88
+ if (this._elementToWorkflow.get(element) === workflow) {
89
+ this._elementToWorkflow.delete(element);
90
+ element.remove();
91
+ }
92
+ }
61
93
  }));
62
94
  }
63
95
  }
@@ -39,6 +39,12 @@ Object.defineProperty(exports, "MisoFeedbackElement", {
39
39
  return _misoFeedback.default;
40
40
  }
41
41
  });
42
+ Object.defineProperty(exports, "MisoFollowUpQuestionsElement", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _misoFollowUpQuestions.default;
46
+ }
47
+ });
42
48
  Object.defineProperty(exports, "MisoImagesElement", {
43
49
  enumerable: true,
44
50
  get: function () {
@@ -125,6 +131,7 @@ Object.defineProperty(exports, "MisoTotalElement", {
125
131
  });
126
132
  var _misoComponent = _interopRequireDefault(require("./miso-component.js"));
127
133
  var _misoQuery = _interopRequireDefault(require("./miso-query.js"));
134
+ var _misoFollowUpQuestions = _interopRequireDefault(require("./miso-follow-up-questions.js"));
128
135
  var _misoQuerySuggestions = _interopRequireDefault(require("./miso-query-suggestions.js"));
129
136
  var _misoFeedback = _interopRequireDefault(require("./miso-feedback.js"));
130
137
  var _misoResults = _interopRequireDefault(require("./miso-results.js"));
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _misoComponent = _interopRequireDefault(require("./miso-component.js"));
8
+ var _constants = require("../../constants.js");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ class MisoFollowUpQuestionsElement extends _misoComponent.default {
11
+ constructor() {
12
+ super({
13
+ role: _constants.ROLE.FOLLOW_UP_QUESTIONS
14
+ });
15
+ }
16
+ static get tagName() {
17
+ return 'miso-follow-up-questions';
18
+ }
19
+ }
20
+ exports.default = MisoFollowUpQuestionsElement;
@@ -122,15 +122,14 @@ class OptionListLayout extends _template.default {
122
122
  return;
123
123
  }
124
124
  this._trackClick(event, binding);
125
- this._submit(option.text);
125
+ this._select(option.text);
126
126
  }
127
- async _submit(value) {
127
+ async _select(value) {
128
128
  if (!value) {
129
129
  return;
130
130
  }
131
- // TODO: q -> value
132
- this._view.hub.update(_index.fields.query(), {
133
- q: value
131
+ this._view._events.emit('select', {
132
+ value
134
133
  });
135
134
  }
136
135
  destroy() {
@@ -180,8 +180,9 @@ class TypewriterLayout extends _progressive.default {
180
180
  const {
181
181
  target
182
182
  } = event;
183
- const citationLinkElement = target.closest(`[data-role="citation-link"]`);
183
+
184
184
  // citation link click
185
+ const citationLinkElement = target.closest(`[data-role="citation-link"]`);
185
186
  if (citationLinkElement) {
186
187
  const index = parseInt(citationLinkElement.dataset.index);
187
188
  if (!Number.isNaN(index)) {
@@ -192,6 +193,23 @@ class TypewriterLayout extends _progressive.default {
192
193
  }
193
194
  return;
194
195
  }
196
+
197
+ // follow-up link click
198
+ const followUpLinkElement = target.closest(`[data-role="follow-up-link"]`);
199
+ if (followUpLinkElement) {
200
+ let {
201
+ q
202
+ } = followUpLinkElement.dataset;
203
+ q = q ? q.trim() : undefined;
204
+ if (q) {
205
+ this._view._events.emit('follow-up-click', {
206
+ event,
207
+ q
208
+ });
209
+ }
210
+ return;
211
+ }
212
+
195
213
  // generic link click
196
214
  const anchorElement = target.closest('a');
197
215
  if (anchorElement) {
@@ -4,4 +4,4 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _default = exports.default = '1.12.2-beta.1';
7
+ var _default = exports.default = '1.12.3-beta.0';
@@ -183,7 +183,7 @@ class AnswerBasedWorkflow extends _base.default {
183
183
  });
184
184
  }
185
185
  _writeQuestionSourceToSession(args) {
186
- this.session.meta.question_source = args.qs || _constants.ORGANIC_QUESTION_SOURCE; // might be null, not undefined
186
+ this.session.meta.question_source = args.qs || _constants.QUESTION_SOURCE.ORGANIC; // might be null, not undefined
187
187
  }
188
188
  _buildPayload() {
189
189
  throw new Error(`Not implemented`);
@@ -242,12 +242,12 @@ class AnswerBasedWorkflow extends _base.default {
242
242
  }
243
243
  const url = new URL(window.location);
244
244
  const currentQuestion = url.searchParams.get(param);
245
- const currentQuestionSource = url.searchParams.get(sourceParam) || _constants.ORGANIC_QUESTION_SOURCE;
245
+ const currentQuestionSource = url.searchParams.get(sourceParam) || _constants.QUESTION_SOURCE.ORGANIC;
246
246
  if (question === currentQuestion && questionSource === currentQuestionSource) {
247
247
  return;
248
248
  }
249
249
  url.searchParams.set(param, question);
250
- if (questionSource === _constants.ORGANIC_QUESTION_SOURCE) {
250
+ if (questionSource === _constants.QUESTION_SOURCE.ORGANIC) {
251
251
  url.searchParams.delete(sourceParam);
252
252
  } else {
253
253
  url.searchParams.set(sourceParam, questionSource);
@@ -308,12 +308,15 @@ class AnswerBasedWorkflow extends _base.default {
308
308
  index,
309
309
  event
310
310
  }) {
311
+ if (event.button !== 0) {
312
+ return; // only left click
313
+ }
311
314
  if ((0, _trackers.isTracked)(event)) {
312
315
  return;
313
316
  }
314
317
  const {
315
318
  value
316
- } = this.states[_index.fields.data()] || {};
319
+ } = this._hub.states[_index.fields.data()] || {};
317
320
  const {
318
321
  sources
319
322
  } = value || {};
@@ -325,7 +328,6 @@ class AnswerBasedWorkflow extends _base.default {
325
328
  // TODO: should we track impression as well?
326
329
  (0, _trackers.markAsTracked)(event);
327
330
  // distinguish from regular sources element click
328
- // TODO
329
331
  this._views.trackers.sources.click([source.product_id], {
330
332
  event_target: 'citation-link'
331
333
  });
@@ -335,6 +337,9 @@ class AnswerBasedWorkflow extends _base.default {
335
337
  event
336
338
  } = _ref,
337
339
  item = _objectWithoutPropertiesLoose(_ref, _excluded);
340
+ if (event.button !== 0) {
341
+ return; // only left click
342
+ }
338
343
  if ((0, _trackers.isTracked)(event)) {
339
344
  return;
340
345
  }
@@ -32,7 +32,8 @@ const DEFAULT_LAYOUTS = Object.freeze(Object.assign({}, _answerBased.default.DEF
32
32
  incremental: true,
33
33
  itemType: 'article'
34
34
  }],
35
- [_constants.ROLE.QUERY_SUGGESTIONS]: _index2.OptionListLayout.type
35
+ [_constants.ROLE.QUERY_SUGGESTIONS]: _index2.OptionListLayout.type,
36
+ [_constants.ROLE.FOLLOW_UP_QUESTIONS]: _index2.OptionListLayout.type
36
37
  }));
37
38
  const DEFAULT_TRACKERS = Object.freeze(Object.assign({}, _answerBased.default.DEFAULT_TRACKERS, {
38
39
  [_constants.ROLE.RELATED_RESOURCES]: _index3.DEFAULT_TRACKER_OPTIONS,
@@ -54,7 +55,8 @@ const DEFAULT_OPTIONS = Object.freeze(Object.assign({}, _answerBased.default.DEF
54
55
  const ROLES_OPTIONS = (0, _index3.mergeRolesOptions)(_answerBased.default.ROLES_OPTIONS, {
55
56
  members: Object.keys(DEFAULT_LAYOUTS),
56
57
  mappings: {
57
- [_constants.ROLE.QUERY_SUGGESTIONS]: _processors.mappingSuggestionsData
58
+ [_constants.ROLE.QUERY_SUGGESTIONS]: _processors.mappingSuggestionsData,
59
+ [_constants.ROLE.FOLLOW_UP_QUESTIONS]: _processors.mappingFollowUpQuestionsData
58
60
  }
59
61
  });
60
62
  class Ask extends _answerBased.default {
@@ -78,7 +80,7 @@ class Ask extends _answerBased.default {
78
80
  }
79
81
  _initSubscriptions(args) {
80
82
  super._initSubscriptions(args);
81
- this._unsubscribes = [...this._unsubscribes, this._hub.on(_index.fields.view(_constants.ROLE.REASONING), state => this._onReasoningViewUpdate(state))];
83
+ this._unsubscribes = [...this._unsubscribes, this._hub.on(_index.fields.view(_constants.ROLE.REASONING), state => this._onReasoningViewUpdate(state)), this._views.get(_constants.ROLE.ANSWER).on('follow-up-click', event => this._onFollowUpClick(event)), this._views.get(_constants.ROLE.FOLLOW_UP_QUESTIONS).on('select', event => this._onFollowUpQuestionSelect(event)), this._views.get(_constants.ROLE.QUERY_SUGGESTIONS).on('select', event => this._onQuerySuggestionSelect(event))];
82
84
  }
83
85
  _initSession(args) {
84
86
  // register at context
@@ -89,6 +91,10 @@ class Ask extends _answerBased.default {
89
91
 
90
92
  // lifecycle //
91
93
  restart() {
94
+ const {
95
+ next
96
+ } = this;
97
+ next && next.destroy();
92
98
  if (this._questionId) {
93
99
  this._context._byQid.delete(this._questionId);
94
100
  }
@@ -135,7 +141,7 @@ class Ask extends _answerBased.default {
135
141
  // question, not q
136
142
  parent_question_id: parentQuestionId,
137
143
  _meta: Object.assign({}, options._meta, {
138
- question_source: qs || _constants.ORGANIC_QUESTION_SOURCE // might be null, not undefined
144
+ question_source: qs || _constants.QUESTION_SOURCE.ORGANIC // might be null, not undefined
139
145
  })
140
146
  }));
141
147
  payload = this._writeWikiLinkTemplateToPayload(payload);
@@ -177,8 +183,10 @@ class Ask extends _answerBased.default {
177
183
  property
178
184
  } = payload.context && payload.context.custom_context || {};
179
185
  let question_id, parent_question_id, question_source;
180
- if (args.role === _constants.ROLE.QUERY_SUGGESTIONS) {
186
+ if (args.role === _constants.ROLE.FOLLOW_UP_QUESTIONS || args.role === _constants.ROLE.QUERY_SUGGESTIONS) {
181
187
  property = 'suggested_followup_questions';
188
+ }
189
+ if (args.role === _constants.ROLE.QUERY_SUGGESTIONS) {
182
190
  parent_question_id = this.previous && this.previous.parentQuestionId;
183
191
  question_id = this.parentQuestionId;
184
192
  question_source = this.previous && this.previous.session.meta.question_source;
@@ -200,6 +208,55 @@ class Ask extends _answerBased.default {
200
208
  });
201
209
  }
202
210
 
211
+ // handlers //
212
+ _onFollowUpClick({
213
+ q,
214
+ event
215
+ } = {}) {
216
+ if (event.button !== 0) {
217
+ return; // only left click
218
+ }
219
+ this._submitFollowUpQuestion({
220
+ q,
221
+ qs: _constants.QUESTION_SOURCE.INLINE_QUESTION_LINK
222
+ });
223
+ // TODO: track click?
224
+ }
225
+ _onFollowUpQuestionSelect({
226
+ value: q
227
+ }) {
228
+ this._submitFollowUpQuestion({
229
+ q,
230
+ qs: _constants.QUESTION_SOURCE.SUGGESTED_QUESTIONS
231
+ });
232
+ }
233
+ _submitFollowUpQuestion({
234
+ q = '',
235
+ qs
236
+ } = {}) {
237
+ q = q.trim();
238
+ if (!q) {
239
+ return;
240
+ }
241
+ const next = this.getOrCreateNext();
242
+ next.query({
243
+ q,
244
+ qs
245
+ });
246
+ }
247
+ _onQuerySuggestionSelect({
248
+ value: q
249
+ }) {
250
+ if (!q) {
251
+ return;
252
+ }
253
+ // backward compatible: query on current workflow
254
+ this.query({
255
+ q,
256
+ qs: _constants.QUESTION_SOURCE.SUGGESTED_QUESTIONS
257
+ });
258
+ }
259
+
203
260
  // helpers //
204
261
  _writeQuestionId(questionId) {
205
262
  // capture question ID and register at context
@@ -224,9 +281,13 @@ class Ask extends _answerBased.default {
224
281
  // destroy //
225
282
  _destroy(options) {
226
283
  const {
284
+ next,
227
285
  parentQuestionId,
228
286
  questionId
229
287
  } = this;
288
+ if (next) {
289
+ next.destroy(options);
290
+ }
230
291
  if (parentQuestionId) {
231
292
  this._context._byPqid.delete(parentQuestionId);
232
293
  }
@@ -30,7 +30,7 @@ function autoNextOptionsAsPredicate(options = DEFAULT_AUTO_NEXT_OPTIONS) {
30
30
  case 'function':
31
31
  return options;
32
32
  default:
33
- return false;
33
+ return () => false;
34
34
  }
35
35
  }
36
36
  class Asks extends _context.default {
@@ -40,6 +40,7 @@ class Asks extends _context.default {
40
40
  this._byPqid = new Map();
41
41
  this._root = new _ask.default(this); // initialize with default workflow
42
42
  this._events.on('*', event => this._createNextIfNecessary(event));
43
+ this._autoNextFn = undefined;
43
44
  }
44
45
  get root() {
45
46
  return this._root;
@@ -324,7 +324,7 @@ class Workflow extends _commons.Component {
324
324
 
325
325
  // destroy //
326
326
  destroy(options) {
327
- this._events.emit('destroy');
327
+ this._emit('destroy');
328
328
  this._destroy(options);
329
329
  }
330
330
  _destroy({
@@ -61,7 +61,7 @@ class HybridSearchAnswer extends _answerBased.default {
61
61
  payload = _objectWithoutPropertiesLoose(_ref, _excluded);
62
62
  return Object.assign({}, payload, {
63
63
  _meta: Object.assign({}, payload._meta, {
64
- question_source: qs || _constants.ORGANIC_QUESTION_SOURCE // might be null, not undefined
64
+ question_source: qs || _constants.QUESTION_SOURCE.ORGANIC // might be null, not undefined
65
65
  })
66
66
  });
67
67
  }
@@ -7,6 +7,7 @@ exports.buildBaseInteraction = buildBaseInteraction;
7
7
  exports.concatItemsFromMoreResponse = concatItemsFromMoreResponse;
8
8
  exports.getRevision = getRevision;
9
9
  exports.mappingAnswerData = mappingAnswerData;
10
+ exports.mappingFollowUpQuestionsData = mappingFollowUpQuestionsData;
10
11
  exports.mappingReasoningData = mappingReasoningData;
11
12
  exports.mappingSortData = mappingSortData;
12
13
  exports.mappingSuggestionsData = mappingSuggestionsData;
@@ -18,6 +19,7 @@ exports.writeDataStatus = writeDataStatus;
18
19
  exports.writeEventTargetToInteraction = writeEventTargetToInteraction;
19
20
  exports.writeExhaustionToData = writeExhaustionToData;
20
21
  exports.writeKeywordsToData = writeKeywordsToData;
22
+ exports.writeMisoIdAsRootMisoId = writeMisoIdAsRootMisoId;
21
23
  exports.writeMisoIdFromSession = writeMisoIdFromSession;
22
24
  exports.writeMisoIdToMeta = writeMisoIdToMeta;
23
25
  exports.writeMisoIdToSession = writeMisoIdToSession;
@@ -99,6 +101,14 @@ function mappingSuggestionsData(_, {
99
101
  text
100
102
  }));
101
103
  }
104
+ function mappingFollowUpQuestionsData(data) {
105
+ if (!data || !data.value) {
106
+ return undefined;
107
+ }
108
+ return (data.value.followup_questions || []).map(text => ({
109
+ text
110
+ }));
111
+ }
102
112
 
103
113
  // data //
104
114
  function getRevision(data) {
@@ -250,6 +260,25 @@ function retainFacetCountsInData(data, currentFacetCounts) {
250
260
  })
251
261
  });
252
262
  }
263
+ function writeMisoIdAsRootMisoId(data) {
264
+ const {
265
+ value
266
+ } = data;
267
+ if (!value) {
268
+ return data;
269
+ }
270
+ const {
271
+ miso_id
272
+ } = value;
273
+ if (!miso_id) {
274
+ return data;
275
+ }
276
+ return Object.assign({}, data, {
277
+ value: Object.assign({}, value, {
278
+ root_miso_id: miso_id
279
+ })
280
+ });
281
+ }
253
282
  function concatItemsFromMoreResponse(oldData, newData, {
254
283
  role = _constants.ROLE.PRODUCTS
255
284
  } = {}) {
@@ -259,7 +288,8 @@ function concatItemsFromMoreResponse(oldData, newData, {
259
288
  const {
260
289
  [role]: oldProducts = [],
261
290
  facet_counts,
262
- total
291
+ total,
292
+ root_miso_id
263
293
  } = oldData.value;
264
294
  const {
265
295
  [role]: newProducts = []
@@ -272,6 +302,7 @@ function concatItemsFromMoreResponse(oldData, newData, {
272
302
  value: Object.assign({}, newData.value, {
273
303
  facet_counts,
274
304
  total,
305
+ root_miso_id,
275
306
  [role]: [...oldProducts, ...newProducts]
276
307
  })
277
308
  });