@miso.ai/client-sdk-ui 1.12.2 → 1.12.3-beta.1

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 (41) hide show
  1. package/dist/cjs/actor/data.js +4 -16
  2. package/dist/cjs/constants.js +8 -3
  3. package/dist/cjs/defaults/ask/phrases.js +7 -3
  4. package/dist/cjs/defaults/ask/templates.js +19 -19
  5. package/dist/cjs/defaults/common/templates.js +6 -4
  6. package/dist/cjs/element/context/miso-follow-ups.js +45 -13
  7. package/dist/cjs/element/role/index.js +7 -0
  8. package/dist/cjs/element/role/miso-follow-up-questions.js +20 -0
  9. package/dist/cjs/layout/input/option-list.js +4 -5
  10. package/dist/cjs/layout/text/typewriter/index.js +19 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/cjs/workflow/answer-based.js +12 -7
  13. package/dist/cjs/workflow/ask.js +66 -5
  14. package/dist/cjs/workflow/asks.js +2 -1
  15. package/dist/cjs/workflow/base.js +19 -8
  16. package/dist/cjs/workflow/explore.js +2 -2
  17. package/dist/cjs/workflow/hybrid-search-answer.js +3 -3
  18. package/dist/cjs/workflow/hybrid-search-results.js +2 -2
  19. package/dist/cjs/workflow/processors.js +45 -1
  20. package/dist/cjs/workflow/search-based.js +26 -3
  21. package/package.json +2 -2
  22. package/src/actor/data.js +2 -10
  23. package/src/constants.js +8 -1
  24. package/src/defaults/ask/phrases.js +5 -2
  25. package/src/defaults/ask/templates.js +12 -12
  26. package/src/defaults/common/templates.js +6 -6
  27. package/src/element/context/miso-follow-ups.js +36 -11
  28. package/src/element/role/index.js +1 -0
  29. package/src/element/role/miso-follow-up-questions.js +16 -0
  30. package/src/layout/input/option-list.js +3 -4
  31. package/src/layout/text/typewriter/index.js +14 -1
  32. package/src/version.js +1 -1
  33. package/src/workflow/answer-based.js +13 -8
  34. package/src/workflow/ask.js +48 -5
  35. package/src/workflow/asks.js +2 -1
  36. package/src/workflow/base.js +16 -8
  37. package/src/workflow/explore.js +2 -2
  38. package/src/workflow/hybrid-search-answer.js +4 -4
  39. package/src/workflow/hybrid-search-results.js +2 -2
  40. package/src/workflow/processors.js +40 -2
  41. package/src/workflow/search-based.js +26 -4
@@ -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, ORGANIC_QUESTION_SOURCE } from '../constants.js';
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 || ORGANIC_QUESTION_SOURCE, // might be null, not undefined
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
  }
@@ -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() {
@@ -4,7 +4,7 @@ import * as sources from '../source.js';
4
4
  import { STATUS, ROLE } from '../constants.js';
5
5
  import { ContainerLayout, ErrorLayout } from '../layout/index.js';
6
6
  import { WorkflowOptions, mergeApiOptions, makeConfigurable } from './options/index.js';
7
- import { getRevision, writeDataStatus, writeMisoIdToMeta, buildBaseInteraction, writeAffiliationInfoToInteraction, mergeInteraction } from './processors.js';
7
+ import { getRevision, writeRequestFromPreviousData, writeDataStatus, writeMisoIdToMeta, buildBaseInteraction, writeAffiliationInfoToInteraction, mergeInteraction } from './processors.js';
8
8
 
9
9
  const DEFAULT_API_OPTIONS = Object.freeze({});
10
10
 
@@ -75,6 +75,7 @@ export default class Workflow extends Component {
75
75
 
76
76
  _initSubscriptions({ roles }) {
77
77
  this._unsubscribes = [
78
+ this._hub.on(fields.session(), session => this._onSession(session)),
78
79
  this._hub.on(fields.response(), data => this.updateData(data)),
79
80
  this._hub.on(fields.tracker(), args => this._onTracker(args)),
80
81
  ];
@@ -154,6 +155,10 @@ export default class Workflow extends Component {
154
155
  }
155
156
  }
156
157
 
158
+ _onSession(session) {
159
+ this.updateData({ session });
160
+ }
161
+
157
162
  _onMainViewUpdate(state) {
158
163
  const { status } = state;
159
164
  const done = status === STATUS.READY;
@@ -186,8 +191,9 @@ export default class Workflow extends Component {
186
191
  // request //
187
192
  _request(options = {}) {
188
193
  const { session } = this;
189
- const event = mergeApiOptions(this._options.resolved.api, { ...options, session });
190
- this._hub.update(fields.request(), event);
194
+ const request = mergeApiOptions(this._options.resolved.api, options);
195
+ this.updateData({ session, request });
196
+ this._hub.update(fields.request(), { ...request, session });
191
197
  }
192
198
 
193
199
  // data //
@@ -221,15 +227,17 @@ export default class Workflow extends Component {
221
227
  }
222
228
 
223
229
  _updateData(data) {
224
- data = this._defaultProcessData(data);
230
+ const oldData = this._hub.states[fields.data()];
231
+ data = this._defaultProcessData(data, oldData);
225
232
  for (const process of this._options.resolved.dataProcessor) {
226
- data = process(data);
233
+ data = process(data, oldData);
227
234
  }
228
235
 
229
- this._updateDataInHub(data);
236
+ this._updateDataInHub(data, oldData);
230
237
  }
231
238
 
232
- _defaultProcessData(data) {
239
+ _defaultProcessData(data, oldData) {
240
+ data = writeRequestFromPreviousData(data, oldData);
233
241
  data = writeDataStatus(data);
234
242
  data = writeMisoIdToMeta(data);
235
243
  return data;
@@ -308,7 +316,7 @@ export default class Workflow extends Component {
308
316
 
309
317
  // destroy //
310
318
  destroy(options) {
311
- this._events.emit('destroy');
319
+ this._emit('destroy');
312
320
  this._destroy(options);
313
321
  }
314
322
 
@@ -121,8 +121,8 @@ export default class Explore extends UnitWorkflow {
121
121
  return this;
122
122
  }
123
123
 
124
- _defaultProcessData(data) {
125
- data = super._defaultProcessData(data);
124
+ _defaultProcessData(data, oldData) {
125
+ data = super._defaultProcessData(data, oldData);
126
126
  data = this._addUrlToRelatedQuestions(data);
127
127
  return data;
128
128
  }
@@ -1,5 +1,5 @@
1
1
  import AnswerBasedWorkflow from './answer-based.js';
2
- import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
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,14 +54,14 @@ export default class HybridSearchAnswer extends AnswerBasedWorkflow {
54
54
  ...payload,
55
55
  _meta: {
56
56
  ...payload._meta,
57
- question_source: qs || ORGANIC_QUESTION_SOURCE, // might be null, not undefined
57
+ question_source: qs || QUESTION_SOURCE.ORGANIC, // might be null, not undefined
58
58
  },
59
59
  };
60
60
  }
61
61
 
62
62
  // data //
63
- _defaultProcessData(data) {
64
- data = super._defaultProcessData(data);
63
+ _defaultProcessData(data, oldData) {
64
+ data = super._defaultProcessData(data, oldData);
65
65
  data = writeKeywordsToData(data);
66
66
  data = writeMisoIdFromSession(data);
67
67
  data = writeUnanswerableToMeta(data);
@@ -71,8 +71,8 @@ export default class HybridSearchResults extends SearchBasedWorkflow {
71
71
  }
72
72
 
73
73
  // data //
74
- _defaultProcessData(data) {
75
- data = super._defaultProcessData(data);
74
+ _defaultProcessData(data, oldData) {
75
+ data = super._defaultProcessData(data, oldData);
76
76
  writeMisoIdToSession(data);
77
77
  return data;
78
78
  }
@@ -54,11 +54,30 @@ 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;
60
67
  }
61
68
 
69
+ export function writeRequestFromPreviousData(data, oldData) {
70
+ // patch request in case user call updateData() without request object in custom data scenario
71
+ if (!data || !data.value || data.request || !oldData || !oldData.request) {
72
+ return data;
73
+ }
74
+ const { request } = oldData;
75
+ return {
76
+ ...data,
77
+ request,
78
+ };
79
+ }
80
+
62
81
  export function writeDataStatus(data) {
63
82
  const status = getStatus(data);
64
83
  const ongoing = status === STATUS.READY ? data.ongoing : undefined;
@@ -186,11 +205,29 @@ export function retainFacetCountsInData(data, currentFacetCounts) {
186
205
  };
187
206
  }
188
207
 
208
+ export function writeMisoIdAsRootMisoId(data) {
209
+ const { value } = data;
210
+ if (!value) {
211
+ return data;
212
+ }
213
+ const { miso_id } = value;
214
+ if (!miso_id) {
215
+ return data;
216
+ }
217
+ return {
218
+ ...data,
219
+ value: {
220
+ ...value,
221
+ root_miso_id: miso_id,
222
+ },
223
+ };
224
+ }
225
+
189
226
  export function concatItemsFromMoreResponse(oldData, newData, { role = ROLE.PRODUCTS } = {}) {
190
227
  if (!newData.value) {
191
228
  return oldData;
192
229
  }
193
- const { [role]: oldProducts = [], facet_counts, total } = oldData.value;
230
+ const { [role]: oldProducts = [], facet_counts, total, root_miso_id } = oldData.value;
194
231
  const { [role]: newProducts = [] } = newData.value;
195
232
  // keep old exhaustion flag, just in case
196
233
  // TODO
@@ -202,6 +239,7 @@ export function concatItemsFromMoreResponse(oldData, newData, { role = ROLE.PROD
202
239
  ...newData.value,
203
240
  facet_counts,
204
241
  total,
242
+ root_miso_id,
205
243
  [role]: [...oldProducts, ...newProducts],
206
244
  },
207
245
  }
@@ -304,7 +342,7 @@ export function mergeInteraction(base = {}, patch = {}) {
304
342
  return trimObj({
305
343
  ...base,
306
344
  ...patch,
307
- context:trimObj({
345
+ context: trimObj({
308
346
  ...base.context,
309
347
  ...patch.context,
310
348
  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;
@@ -225,8 +225,8 @@ export default class SearchBasedWorkflow extends Workflow {
225
225
  }
226
226
 
227
227
  // data //
228
- _defaultProcessData(data) {
229
- data = super._defaultProcessData(data);
228
+ _defaultProcessData(data, oldData) {
229
+ data = super._defaultProcessData(data, oldData);
230
230
  data = writeKeywordsToData(data);
231
231
  data = retainFacetCountsInData(data, this._currentFacetCounts);
232
232
  data = writeExhaustionToData(data, { role: this._roles.main });
@@ -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]);