@miso.ai/client-sdk-ui 1.11.0-beta.1 → 1.11.0-beta.11

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 (118) hide show
  1. package/dist/cjs/actor/data.js +6 -7
  2. package/dist/cjs/actor/feedback.js +6 -39
  3. package/dist/cjs/actor/fields.js +14 -6
  4. package/dist/cjs/actor/hub.js +1 -2
  5. package/dist/cjs/actor/index.js +0 -7
  6. package/dist/cjs/actor/interactions.js +4 -15
  7. package/dist/cjs/actor/sessions.js +2 -33
  8. package/dist/cjs/actor/tracker.js +25 -26
  9. package/dist/cjs/actor/utils.js +0 -49
  10. package/dist/cjs/actor/view.js +40 -40
  11. package/dist/cjs/actor/views.js +32 -8
  12. package/dist/cjs/asset/svgs.js +18 -8
  13. package/dist/cjs/constants.js +18 -19
  14. package/dist/cjs/defaults/hybrid-search/answer-box.js +19 -3
  15. package/dist/cjs/defaults/hybrid-search/phrases.js +5 -5
  16. package/dist/cjs/defaults/hybrid-search/templates.js +37 -9
  17. package/dist/cjs/element/role/index.js +12 -5
  18. package/dist/cjs/element/role/{miso-hits.js → miso-more.js} +4 -4
  19. package/dist/cjs/element/role/miso-total.js +20 -0
  20. package/dist/cjs/layout/affiliation/affiliation.js +0 -3
  21. package/dist/cjs/layout/banner/banner.js +0 -4
  22. package/dist/cjs/layout/button/more-button.js +73 -0
  23. package/dist/cjs/layout/container/container.js +0 -3
  24. package/dist/cjs/layout/error/error.js +0 -4
  25. package/dist/cjs/layout/index.js +7 -0
  26. package/dist/cjs/layout/input/facets.js +5 -5
  27. package/dist/cjs/layout/input/option-list.js +0 -4
  28. package/dist/cjs/layout/input/search-box.js +35 -17
  29. package/dist/cjs/layout/list/cards.js +0 -3
  30. package/dist/cjs/layout/list/carousel.js +0 -3
  31. package/dist/cjs/layout/list/collection.js +22 -7
  32. package/dist/cjs/layout/list/list.js +0 -3
  33. package/dist/cjs/layout/radio/feedback.js +0 -3
  34. package/dist/cjs/layout/radio/radio.js +0 -4
  35. package/dist/cjs/layout/raf.js +4 -2
  36. package/dist/cjs/layout/text/text.js +0 -4
  37. package/dist/cjs/layout/text/typewriter/index.js +0 -3
  38. package/dist/cjs/layout/triggerable.js +79 -0
  39. package/dist/cjs/util/states.js +0 -15
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/workflow/answer-based.js +109 -111
  42. package/dist/cjs/workflow/ask.js +35 -27
  43. package/dist/cjs/workflow/asks.js +1 -1
  44. package/dist/cjs/workflow/autocompletable.js +33 -0
  45. package/dist/cjs/workflow/autocomplete.js +122 -0
  46. package/dist/cjs/workflow/base.js +169 -102
  47. package/dist/cjs/workflow/explore.js +26 -19
  48. package/dist/cjs/workflow/hybrid-search-answer.js +99 -0
  49. package/dist/cjs/workflow/hybrid-search-results.js +66 -0
  50. package/dist/cjs/workflow/hybrid-search-views.js +122 -0
  51. package/dist/cjs/workflow/hybrid-search.js +98 -103
  52. package/dist/cjs/workflow/options.js +32 -17
  53. package/dist/cjs/workflow/processors.js +191 -17
  54. package/dist/cjs/workflow/recommendation.js +36 -49
  55. package/dist/cjs/workflow/search-based.js +228 -0
  56. package/dist/cjs/workflow/search.js +21 -68
  57. package/package.json +2 -2
  58. package/src/actor/data.js +2 -4
  59. package/src/actor/feedback.js +6 -23
  60. package/src/actor/fields.js +14 -6
  61. package/src/actor/hub.js +1 -2
  62. package/src/actor/index.js +0 -1
  63. package/src/actor/interactions.js +4 -10
  64. package/src/actor/sessions.js +3 -30
  65. package/src/actor/tracker.js +25 -19
  66. package/src/actor/utils.js +0 -47
  67. package/src/actor/view.js +27 -15
  68. package/src/actor/views.js +36 -8
  69. package/src/asset/svgs.js +19 -7
  70. package/src/constants.js +25 -16
  71. package/src/defaults/hybrid-search/answer-box.js +17 -5
  72. package/src/defaults/hybrid-search/phrases.js +4 -4
  73. package/src/defaults/hybrid-search/templates.js +25 -9
  74. package/src/element/role/index.js +2 -1
  75. package/src/element/role/{miso-hits.js → miso-more.js} +3 -3
  76. package/src/element/role/miso-total.js +16 -0
  77. package/src/layout/affiliation/affiliation.js +1 -5
  78. package/src/layout/banner/banner.js +0 -5
  79. package/src/layout/button/more-button.js +69 -0
  80. package/src/layout/container/container.js +1 -5
  81. package/src/layout/error/error.js +0 -5
  82. package/src/layout/index.js +2 -1
  83. package/src/layout/input/facets.js +5 -6
  84. package/src/layout/input/option-list.js +0 -5
  85. package/src/layout/input/search-box.js +29 -19
  86. package/src/layout/list/cards.js +0 -4
  87. package/src/layout/list/carousel.js +0 -4
  88. package/src/layout/list/collection.js +21 -9
  89. package/src/layout/list/list.js +0 -4
  90. package/src/layout/radio/feedback.js +0 -4
  91. package/src/layout/radio/radio.js +0 -5
  92. package/src/layout/raf.js +2 -2
  93. package/src/layout/text/text.js +0 -5
  94. package/src/layout/text/typewriter/index.js +1 -5
  95. package/src/layout/trackable.js +1 -1
  96. package/src/layout/triggerable.js +76 -0
  97. package/src/util/states.js +0 -14
  98. package/src/version.js +1 -1
  99. package/src/workflow/answer-based.js +101 -77
  100. package/src/workflow/ask.js +33 -25
  101. package/src/workflow/asks.js +1 -1
  102. package/src/workflow/autocompletable.js +29 -0
  103. package/src/workflow/autocomplete.js +113 -0
  104. package/src/workflow/base.js +147 -93
  105. package/src/workflow/explore.js +23 -8
  106. package/src/workflow/hybrid-search-answer.js +92 -0
  107. package/src/workflow/hybrid-search-results.js +62 -0
  108. package/src/workflow/hybrid-search-views.js +124 -0
  109. package/src/workflow/hybrid-search.js +102 -73
  110. package/src/workflow/options.js +26 -11
  111. package/src/workflow/processors.js +169 -14
  112. package/src/workflow/recommendation.js +32 -33
  113. package/src/workflow/search-based.js +222 -0
  114. package/src/workflow/search.js +21 -65
  115. package/dist/cjs/actor/autocomplete.js +0 -160
  116. package/dist/cjs/workflow/utils.js +0 -42
  117. package/src/actor/autocomplete.js +0 -133
  118. package/src/workflow/utils.js +0 -29
@@ -1,16 +1,17 @@
1
- import { trimObj, API } from '@miso.ai/commons';
1
+ import { API } from '@miso.ai/commons';
2
2
  import Workflow from './base.js';
3
- import { mergeApiOptions } from './options.js';
4
- // import * as sources from '../source.js';
5
- import { fields, FeedbackActor, AutocompleteActor } from '../actor/index.js';
6
- import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE, DATA_ASPECT } from '../constants.js';
3
+ import { fields, FeedbackActor } from '../actor/index.js';
4
+ import { ROLE, STATUS, ORGANIC_QUESTION_SOURCE } from '../constants.js';
7
5
  import { SearchBoxLayout, TextLayout, ListLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
8
6
  import { processData as processAffiliationData } from '../affiliation/index.js';
9
- import { writeAnswerStageToMeta } from './processors.js';
7
+ import { mergeRolesOptions } from './options.js';
8
+ import { writeAnswerStageToMeta, mergeInteraction } from './processors.js';
10
9
 
11
10
  const DEFAULT_API_OPTIONS = Object.freeze({
11
+ ...Workflow.DEFAULT_API_OPTIONS,
12
12
  group: API.GROUP.ASK,
13
13
  payload: {
14
+ ...Workflow.DEFAULT_API_OPTIONS.payload,
14
15
  source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at'],
15
16
  cite_link: 1,
16
17
  cite_start: '[',
@@ -27,6 +28,7 @@ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
27
28
  });
28
29
 
29
30
  const DEFAULT_LAYOUTS = Object.freeze({
31
+ ...Workflow.DEFAULT_LAYOUTS,
30
32
  [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
31
33
  [ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
32
34
  [ROLE.ANSWER]: TypewriterLayout.type,
@@ -36,17 +38,25 @@ const DEFAULT_LAYOUTS = Object.freeze({
36
38
  });
37
39
 
38
40
  const DEFAULT_TRACKERS = Object.freeze({
41
+ ...Workflow.DEFAULT_TRACKERS,
39
42
  [ROLE.SOURCES]: {},
40
43
  [ROLE.AFFILIATION]: {},
44
+ [ROLE.PRODUCTS]: {},
41
45
  });
42
46
 
43
47
  const DEFAULT_OPTIONS = Object.freeze({
48
+ ...Workflow.DEFAULT_OPTIONS,
44
49
  api: DEFAULT_API_OPTIONS,
45
50
  autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
46
51
  layouts: DEFAULT_LAYOUTS,
47
52
  trackers: DEFAULT_TRACKERS,
48
53
  });
49
54
 
55
+ const ROLES_OPTIONS = mergeRolesOptions(Workflow.ROLES_OPTIONS, {
56
+ main: ROLE.ANSWER,
57
+ members: Object.keys(DEFAULT_LAYOUTS),
58
+ });
59
+
50
60
  export default class AnswerBasedWorkflow extends Workflow {
51
61
 
52
62
  // constructor //
@@ -59,12 +69,6 @@ export default class AnswerBasedWorkflow extends Workflow {
59
69
  _initActors(args) {
60
70
  super._initActors(args);
61
71
  this._feedback = new FeedbackActor(this._hub);
62
- /*
63
- this._autocomplete = new AutocompleteActor(this._hub, {
64
- source: sources.api(this._client),
65
- options: this._options,
66
- });
67
- */
68
72
  }
69
73
 
70
74
  _initSubscriptions(args) {
@@ -72,30 +76,49 @@ export default class AnswerBasedWorkflow extends Workflow {
72
76
  this._unsubscribes = [
73
77
  ...this._unsubscribes,
74
78
  this._hub.on(fields.query(), args => this._query(args)),
75
- this._hub.on(fields.view(ROLE.ANSWER), data => this._onAnswerViewUpdate(data)),
76
79
  this._views.get(ROLE.ANSWER).on('citation-click', event => this._onCitationClick(event)),
77
80
  ];
78
81
  }
79
82
 
83
+ // configuration //
84
+ useLink(fn) {
85
+ if (typeof fn !== 'function' && fn !== false) {
86
+ throw new Error('useLink(fn) expects fn to be a function or false');
87
+ }
88
+ this._linkFn = fn;
89
+ return this;
90
+ }
91
+
80
92
  // lifecycle //
81
93
  restart() {
82
94
  // clear question id from previous session
83
95
  if (this._questionId) {
84
96
  this._questionId = undefined;
85
97
  }
86
- // interrupt event
87
- // TODO: move to workflow base
88
- const { session, status, ongoing } = this._hub.states[fields.view(ROLE.ANSWER)] || {};
89
- if (session && status !== STATUS.INITIAL && (status !== STATUS.READY || ongoing)) {
90
- // it's interrupted by a new question
91
- const state = Object.freeze({ session, status, ongoing });
92
- this._emit('interrupt', state);
93
- this._emit('finally', state);
94
- }
95
98
  // restart
96
99
  super.restart();
97
100
  }
98
101
 
102
+ _emitInterruptEventIfNecessary() {
103
+ const state = this._hub.states[fields.view(this._roles.main)] || {};
104
+ if (state.session && state.status !== STATUS.INITIAL && (state.status !== STATUS.READY || state.ongoing)) {
105
+ // it's interrupted by a new question
106
+ this._emitLifecycleEvent('interrupt', state);
107
+ this._emitLifecycleEvent('finally', state);
108
+ }
109
+ }
110
+
111
+ _onMainViewUpdate(state) {
112
+ const { status } = state;
113
+ const done = status === STATUS.READY && !state.ongoing;
114
+ const erroneous = status === STATUS.ERRONEOUS;
115
+ const eventName = done ? 'done' : erroneous ? 'error' : status;
116
+ this._emitLifecycleEvent(eventName, state);
117
+ if (done || erroneous) {
118
+ this._emitLifecycleEvent('finally', state);
119
+ }
120
+ }
121
+
99
122
  // properties //
100
123
  get questionId() {
101
124
  return this._questionId;
@@ -130,53 +153,61 @@ export default class AnswerBasedWorkflow extends Workflow {
130
153
  }
131
154
 
132
155
  _query(args = {}) {
156
+ if (this._linkFn) {
157
+ this._submit(args);
158
+ return;
159
+ }
133
160
  // start a new session
134
161
  this.restart();
135
162
 
136
- // keep track of question source on this session
137
- const { session } = this;
138
- session.meta.question_source = args.questionSource || ORGANIC_QUESTION_SOURCE; // might be null, not undefined
163
+ // keep track of question source on this session, for suggested questions interactions
164
+ this._writeQuestionSourceToSession(args);
139
165
 
140
166
  // build payload and trigger request
141
167
  const payload = this._buildPayload(args);
142
- const event = mergeApiOptions(this._options.resolved.api, { payload, session });
143
- this._request(event);
168
+ this._request({ payload });
144
169
  }
145
170
 
146
- _request(event) {
147
- this._hub.update(fields.request(), event);
171
+ _submit(args) {
172
+ if (!this._linkFn) {
173
+ return;
174
+ }
175
+ // TODO: we want a submit event, but only with linkFn
176
+ const url = this._linkFn(args.q);
177
+ window.open(url, '_blank');
148
178
  }
149
179
 
150
- _buildPayload() {
151
- throw new Error(`Not implemented`);
180
+ _writeQuestionSourceToSession(args) {
181
+ this.session.meta.question_source = args.qs || ORGANIC_QUESTION_SOURCE; // might be null, not undefined
152
182
  }
153
183
 
154
- // autocomplete //
155
- /*
156
- updateCompletions(event) {
157
- // TODO: verify
158
- this._hub.update(fields.data(DATA_ASPECT.AUTOCOMPLETE), {
159
- ...event,
160
- source: 'manual',
161
- });
162
- return this;
184
+ _buildPayload() {
185
+ throw new Error(`Not implemented`);
163
186
  }
164
- */
165
187
 
166
188
  // data //
167
- _defaultProcessData(data) {
168
- data = super._defaultProcessData(data);
169
-
170
- // capture question ID and register at context
171
- this._writeQuestionIdFromData(data);
189
+ _updateData(data) {
190
+ // if it's the head response, write question id and return
191
+ if (data.value && !data.value.answer_stage) {
192
+ this._handleHeadResponse(data);
193
+ return;
194
+ }
195
+ super._updateData(data);
172
196
 
173
197
  // update URL if autoQuery.updateUrl is not false
174
198
  this._updateUrlIfNecessary(data);
199
+ }
175
200
 
176
- // affiliation
177
- data = processAffiliationData(data);
201
+ _handleHeadResponse(data) {
202
+ // capture question ID and register at context
203
+ this._writeQuestionIdFromData(data);
204
+ }
178
205
 
179
- return writeAnswerStageToMeta(data);
206
+ _defaultProcessData(data) {
207
+ data = super._defaultProcessData(data);
208
+ data = processAffiliationData(data);
209
+ data = writeAnswerStageToMeta(data);
210
+ return data;
180
211
  }
181
212
 
182
213
  _writeQuestionIdFromData({ value }) {
@@ -204,38 +235,32 @@ export default class AnswerBasedWorkflow extends Workflow {
204
235
  }
205
236
 
206
237
  // interactions //
207
- _preprocessInteraction(payload) {
208
- payload = super._preprocessInteraction(payload) || {};
209
- const { context = {} } = payload;
210
- const { custom_context = {} } = context;
211
- let { questionId } = this;
212
- return {
213
- ...payload,
238
+ _defaultProcessInteraction(payload, args) {
239
+ payload = super._defaultProcessInteraction(payload, args);
240
+ payload = this._writeAskPropertiesToInteraction(payload, args);
241
+ return payload;
242
+ }
243
+
244
+ _writeAskPropertiesToInteraction(payload = {}, args) {
245
+ const question_source = this._getQuestionSourceFromViewState(args);
246
+ const question_id = this.questionId;
247
+ return mergeInteraction(payload, {
214
248
  context: {
215
- ...context,
216
- custom_context: trimObj({
217
- question_id: questionId,
218
- ...custom_context,
219
- }),
249
+ custom_context: {
250
+ question_source,
251
+ question_id,
252
+ },
220
253
  },
221
- };
254
+ });
222
255
  }
223
256
 
224
- // handlers //
225
- _onAnswerViewUpdate({ session, status, ongoing }) {
226
- if (status === STATUS.INITIAL) {
227
- return;
228
- }
229
- const done = status === STATUS.READY && !ongoing;
230
- const erroneous = status === STATUS.ERRONEOUS;
231
- const eventName = done ? 'done' : erroneous ? 'error' : status;
232
- const state = Object.freeze({ session, status, ongoing });
233
- this._emit(eventName, state);
234
- if (done || erroneous) {
235
- this._emit('finally', state);
236
- }
257
+ _getQuestionSourceFromViewState(args) {
258
+ const { request } = this._hub.states[fields.view(args.role)] || {};
259
+ const { payload } = request || {};
260
+ return (payload && payload._meta && payload._meta.question_source) || undefined;
237
261
  }
238
262
 
263
+ // handlers //
239
264
  _onCitationClick({ index }) {
240
265
  const { value } = this.states[fields.data()] || {};
241
266
  const { sources } = value || {};
@@ -269,12 +294,11 @@ export default class AnswerBasedWorkflow extends Workflow {
269
294
 
270
295
  }
271
296
 
272
- // makeConfigurable(AnswerBasedWorkflow.prototype, ['autocomplete']);
273
-
274
297
  Object.assign(AnswerBasedWorkflow, {
275
298
  DEFAULT_API_OPTIONS,
276
299
  DEFAULT_AUTOCOMPLETE_OPTIONS,
277
300
  DEFAULT_LAYOUTS,
278
301
  DEFAULT_TRACKERS,
279
302
  DEFAULT_OPTIONS,
303
+ ROLES_OPTIONS,
280
304
  });
@@ -2,7 +2,8 @@ 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
4
  import { ROLE, ORGANIC_QUESTION_SOURCE } from '../constants.js';
5
- import { OptionListLayout, ListLayout, TextLayout } from '../layout/index.js';
5
+ import { OptionListLayout, ListLayout, SearchBoxLayout } from '../layout/index.js';
6
+ import { mergeInteraction } from './processors.js';
6
7
 
7
8
  const DEFAULT_API_OPTIONS = Object.freeze({
8
9
  ...AnswerBasedWorkflow.DEFAULT_API_OPTIONS,
@@ -15,6 +16,7 @@ const DEFAULT_API_OPTIONS = Object.freeze({
15
16
 
16
17
  const DEFAULT_LAYOUTS = Object.freeze({
17
18
  ...AnswerBasedWorkflow.DEFAULT_LAYOUTS,
19
+ [ROLE.QUERY]: [SearchBoxLayout.type, { templates: { buttonIcon: 'send' } }],
18
20
  [ROLE.RELATED_RESOURCES]: [ListLayout.type, { incremental: true, itemType: 'article' }],
19
21
  [ROLE.QUERY_SUGGESTIONS]: OptionListLayout.type,
20
22
  });
@@ -36,13 +38,15 @@ const DEFAULT_OPTIONS = Object.freeze({
36
38
  trackers: DEFAULT_TRACKERS,
37
39
  });
38
40
 
41
+ const ROLES_OPTIONS = AnswerBasedWorkflow.ROLES_OPTIONS;
42
+
39
43
  export default class Ask extends AnswerBasedWorkflow {
40
44
 
41
45
  constructor(context, parentQuestionId) {
42
46
  super({
43
47
  name: 'ask',
44
48
  context,
45
- roles: Object.keys(DEFAULT_LAYOUTS),
49
+ roles: ROLES_OPTIONS,
46
50
  defaults: DEFAULT_OPTIONS,
47
51
  parentQuestionId,
48
52
  });
@@ -64,16 +68,17 @@ export default class Ask extends AnswerBasedWorkflow {
64
68
  if (!previous) {
65
69
  return;
66
70
  }
71
+ // TODO: write question_source from payload._meta, so we don't need to write it to session.meta
67
72
  const values = previous.states[fields.data()].value;
68
73
  const value = values.suggested_followup_questions || values.followup_questions || [];
69
74
  this._hub.update(fields.suggestions(), { value: value.map(text => ({ text })) });
70
75
  }
71
76
 
72
- _initReset(args) {
77
+ _initSession(args) {
73
78
  // register at context
74
79
  args.parentQuestionId && this._context._byPqid.set(args.parentQuestionId, this);
75
80
  this._context._events.emit('create', this);
76
- super._initReset(args);
81
+ super._initSession(args);
77
82
  }
78
83
 
79
84
  // lifecycle //
@@ -119,30 +124,33 @@ export default class Ask extends AnswerBasedWorkflow {
119
124
  }
120
125
 
121
126
  // interactions //
122
- _preprocessInteraction(payload) {
123
- payload = super._preprocessInteraction(payload) || {};
124
-
125
- const { context = {} } = payload;
126
- const { ...custom_context } = context.custom_context || {};
127
- let { rootQuestionId, parentQuestionId, questionId } = this;
128
-
129
- if (custom_context.property === ROLE.QUERY_SUGGESTIONS) {
130
- questionId = parentQuestionId;
131
- parentQuestionId = this.previous && this.previous.parentQuestionId;
132
- custom_context.property = 'suggested_followup_questions';
133
- custom_context.question_source = this.previous && this.previous.session.meta.question_source;
127
+ _writeAskPropertiesToInteraction(payload, args) {
128
+ const root_question_id = this.rootQuestionId;
129
+ let { property } = (payload.context && payload.context.custom_context) || {};
130
+ let question_id, parent_question_id, question_source;
131
+
132
+ if (args.role === ROLE.QUERY_SUGGESTIONS) {
133
+ property = 'suggested_followup_questions';
134
+ parent_question_id = this.previous && this.previous.parentQuestionId;
135
+ question_id = this.parentQuestionId;
136
+ question_source = this.previous && this.previous.session.meta.question_source;
137
+ } else {
138
+ parent_question_id = this.parentQuestionId;
139
+ question_id = this.questionId;
140
+ question_source = this._getQuestionSourceFromViewState(args);
134
141
  }
135
- custom_context.question_id = questionId;
136
- custom_context.root_question_id = rootQuestionId;
137
- custom_context.parent_question_id = parentQuestionId;
138
142
 
139
- return {
140
- ...payload,
143
+ return mergeInteraction(payload, {
141
144
  context: {
142
- ...context,
143
- custom_context: trimObj(custom_context),
144
- },
145
- };
145
+ custom_context: {
146
+ property,
147
+ root_question_id,
148
+ parent_question_id,
149
+ question_id,
150
+ question_source,
151
+ },
152
+ }
153
+ });
146
154
  }
147
155
 
148
156
  // helpers //
@@ -55,7 +55,7 @@ export default class Asks extends WorkflowContext {
55
55
 
56
56
  // reset root workflow
57
57
  if (root) {
58
- this._root.reset();
58
+ this._root.restart();
59
59
  }
60
60
  }
61
61
 
@@ -0,0 +1,29 @@
1
+ import { mixin } from '@miso.ai/commons';
2
+ import { fields } from '../actor/index.js';
3
+ import Autocomplete from './autocomplete.js';
4
+
5
+ export function makeAutocompletable(prototype) {
6
+ mixin(prototype, AutocompletableMixin.prototype);
7
+ }
8
+
9
+ export class AutocompletableMixin {
10
+
11
+ _initAutocomplete(args) {
12
+ this._autocomplete = new Autocomplete(this, { defaults: args.defaults.autocomplete });
13
+ }
14
+
15
+ get autocomplete() {
16
+ return this._autocomplete;
17
+ }
18
+
19
+ updateCompletions(data) {
20
+ this._hub.update(fields.completions(), data);
21
+ return this;
22
+ }
23
+
24
+ _destroyAutocomplete() {
25
+ this._autocomplete && this._autocomplete._destroy();
26
+ this._autocomplete = undefined;
27
+ }
28
+
29
+ }
@@ -0,0 +1,113 @@
1
+ import { STATUS } from '../constants.js';
2
+ import { fields } from '../actor/index.js';
3
+ import Workflow from './base.js';
4
+
5
+ const ROLES_OPTIONS = Object.freeze({
6
+ members: [],
7
+ });
8
+
9
+ function shimDefaults({ layouts, trackers, ...options } = {}) {
10
+ return {
11
+ active: false,
12
+ ...options,
13
+ layouts: {},
14
+ trackers: {},
15
+ };
16
+ }
17
+
18
+ function sameInputValue(a, b) {
19
+ return getInputValue(a) === getInputValue(b);
20
+ }
21
+
22
+ function getInputValue(args) {
23
+ return (args && args.value && args.value.trim()) || undefined;
24
+ }
25
+
26
+ export default class Autocomplete extends Workflow {
27
+
28
+ constructor(superworkflow, { defaults, ...args } = {}) {
29
+ super({
30
+ name: 'autocomplete',
31
+ plugin: superworkflow._plugin,
32
+ client: superworkflow._client,
33
+ defaults: shimDefaults(defaults),
34
+ roles: ROLES_OPTIONS,
35
+ superworkflow,
36
+ ...args,
37
+ });
38
+ }
39
+
40
+ _initProperties(args) {
41
+ super._initProperties(args);
42
+ this._superworkflow = args.superworkflow;
43
+ this._active = args.defaults.active;
44
+ }
45
+
46
+ _initSubscriptions(args) {
47
+ // TODO: how to sync options updates
48
+ super._initSubscriptions(args);
49
+ this._unsubscribes = [
50
+ ...this._unsubscribes,
51
+ args.superworkflow._hub.on(fields.input(), args => this._query(args)),
52
+ ];
53
+ }
54
+
55
+ // API //
56
+ get enabled() {
57
+ return this._active;
58
+ }
59
+
60
+ enable() {
61
+ this._active = true;
62
+ }
63
+
64
+ disable() {
65
+ this._active = false;
66
+ }
67
+
68
+ // query //
69
+ _query(args) {
70
+ if (!this._active || sameInputValue(args, this._args)) {
71
+ return;
72
+ }
73
+ this._args = args;
74
+
75
+ // start a new session
76
+ this.restart();
77
+ const { session } = this;
78
+ session.meta.input = args;
79
+
80
+ // if input value is blank, emit empty response
81
+ if (!getInputValue(args)) {
82
+ this._updateCompletions({ session, status: STATUS.INITIAL });
83
+ return;
84
+ }
85
+
86
+ // payload
87
+ const payload = this._buildPayload(args);
88
+
89
+ // request
90
+ this._request({ payload });
91
+ }
92
+
93
+ _buildPayload(args) {
94
+ return { q: getInputValue(args) };
95
+ }
96
+
97
+ // data //
98
+ _updateDataInHub(data) {
99
+ if (data.value) {
100
+ data = {
101
+ ...data,
102
+ value: data.value.completions,
103
+ };
104
+ }
105
+ // update completion field in superworkflow hub
106
+ this._updateCompletions(data);
107
+ }
108
+
109
+ _updateCompletions(data) {
110
+ this._superworkflow.updateCompletions(data);
111
+ }
112
+
113
+ }