@miso.ai/client-sdk-ui 1.9.8 → 1.9.9-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.
@@ -24,7 +24,7 @@ function loadSvgSprites() {
24
24
  }
25
25
  loadSvgSprites();
26
26
  function getSvg(name) {
27
- return `<svg class="miso-ui-icon miso-ui-icon-${name}"><use xlink:href="#${PREFIX}-${name}"></use></svg>`;
27
+ return `<svg class="miso-ui-icon miso-ui-icon-${name}"><use href="#${PREFIX}-${name}"></use></svg>`;
28
28
  }
29
29
  const SEND = exports.SEND = getSvg('send');
30
30
  const SEARCH = exports.SEARCH = getSvg('search');
@@ -17,6 +17,7 @@ class AskComboElements {
17
17
  (0, _commons.defineValues)(this, {
18
18
  root,
19
19
  followUpsSection: root.querySelector(`.${classPrefix}__follow-ups`),
20
+ relatedResourcesSection: root.querySelector(`.${classPrefix}__related-resources`),
20
21
  relatedResourcesContainer: root.querySelector(`.${classPrefix}__related-resources miso-ask`)
21
22
  });
22
23
  }
@@ -104,10 +104,15 @@ class AskCombo extends _base.default {
104
104
  }
105
105
  async _setupWorkflows() {
106
106
  const {
107
+ MisoClient,
107
108
  client,
108
109
  elements,
109
110
  resolvedOptions: options
110
111
  } = this;
112
+ const {
113
+ wireFollowUps,
114
+ wireRelatedResources
115
+ } = MisoClient.ui.defaults.ask;
111
116
  const {
112
117
  templates = {},
113
118
  features = {},
@@ -121,34 +126,13 @@ class AskCombo extends _base.default {
121
126
 
122
127
  // TODO: set placeholder if present in options
123
128
 
124
- if (elements.followUpsSection && features.followUpQuestions !== false) {
125
- // when a answer is fully populated, insert a new section for the follow-up question
126
- context.on('done', _ref => {
127
- let {
128
- workflow
129
- } = _ref;
130
- elements.followUpsSection.insertAdjacentHTML('beforeend', templates.followUp(Object.assign({}, options, {
131
- parentQuestionId: workflow.questionId
132
- })));
133
- });
134
- // if user starts over, clean up current follow-up questions
135
- rootWorkflow.on('loading', () => {
136
- // clean up the entire follow-ups section
137
- elements.followUpsSection.innerHTML = '';
138
- // destroy all follow-up workflows
139
- context.reset({
140
- root: false
141
- });
142
- });
129
+ if (features.followUpQuestions !== false) {
130
+ wireFollowUps(client, elements.followUpsSection, Object.assign({}, options, {
131
+ template: templates.followUp
132
+ }));
143
133
  }
144
- if (elements.relatedResourcesContainer && features.relatedResources !== false && features.followUpQuestions !== false) {
145
- // when a new query starts, associate the last section container (for related resources) to that workflow
146
- context.on('loading', _ref2 => {
147
- let {
148
- workflow
149
- } = _ref2;
150
- elements.relatedResourcesContainer.workflow = workflow;
151
- });
134
+ if (features.relatedResources !== false && features.followUpQuestions !== false) {
135
+ wireRelatedResources(client, elements.relatedResourcesSection);
152
136
  }
153
137
 
154
138
  // start query if specified in URL
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.wireFollowUps = wireFollowUps;
7
+ exports.wireRelatedResources = wireRelatedResources;
8
+ var _templates = require("./templates.js");
9
+ const _excluded = ["template"];
10
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
11
+ function wireFollowUps(client, element) {
12
+ let _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
13
+ let {
14
+ template = _templates.followUp
15
+ } = _ref,
16
+ options = _objectWithoutPropertiesLoose(_ref, _excluded);
17
+ if (!client) {
18
+ throw new Error('client is required');
19
+ }
20
+ if (typeof template !== 'function') {
21
+ throw new Error('template must be a function or undefined');
22
+ }
23
+ if (!element) {
24
+ return;
25
+ }
26
+ const context = client.ui.asks;
27
+ const rootWorkflow = client.ui.ask;
28
+
29
+ // 1. when an answer is fully populated, insert a new section for the follow-up question
30
+ context.on('done', event => {
31
+ element.insertAdjacentHTML('beforeend', template(Object.assign({}, options, {
32
+ parentQuestionId: event.workflow.questionId
33
+ })));
34
+ });
35
+ // 2. if user starts over, clean up existing follow-up questions
36
+ rootWorkflow.on('loading', () => {
37
+ // clean up the entire follow-ups section
38
+ element.innerHTML = '';
39
+ // destroy all follow-up workflows
40
+ context.reset({
41
+ root: false
42
+ });
43
+ });
44
+ }
45
+ function wireRelatedResources(client, element) {
46
+ if (!client) {
47
+ throw new Error('client is required');
48
+ }
49
+ if (!element) {
50
+ return;
51
+ }
52
+ client.ui.asks.on('loading', event => {
53
+ // When a new query starts, associate the last section container (for related resources) to that workflow
54
+ for (const container of element.querySelectorAll('miso-ask')) {
55
+ container.workflow = event.workflow;
56
+ }
57
+ });
58
+ }
@@ -3,10 +3,27 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ var _exportNames = {
7
+ constants: true,
8
+ phrases: true,
9
+ templates: true
10
+ };
6
11
  exports.templates = exports.phrases = exports.constants = void 0;
7
12
  var _constants = _interopRequireWildcard(require("./constants.js"));
8
13
  var _phrases = _interopRequireWildcard(require("./phrases.js"));
9
14
  var _templates = _interopRequireWildcard(require("./templates.js"));
15
+ var _controls = require("./controls.js");
16
+ Object.keys(_controls).forEach(function (key) {
17
+ if (key === "default" || key === "__esModule") return;
18
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
19
+ if (key in exports && exports[key] === _controls[key]) return;
20
+ Object.defineProperty(exports, key, {
21
+ enumerable: true,
22
+ get: function () {
23
+ return _controls[key];
24
+ }
25
+ });
26
+ });
10
27
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
11
28
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
12
29
  const constants = exports.constants = Object.freeze(_constants);
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.sources = exports.relatedResources = exports.relatedQuestions = exports.question = void 0;
6
+ exports.sources = exports.relatedResources = exports.question = exports.querySuggestions = void 0;
7
7
  /**
8
8
  * The phrase displayed right before <miso-question>
9
9
  */
@@ -17,7 +17,7 @@ const sources = exports.sources = 'My reply is based on the following';
17
17
  /**
18
18
  * The phrase displayed right before <miso-query-suggestions>
19
19
  */
20
- const relatedQuestions = exports.relatedQuestions = 'Related questions you can explore';
20
+ const querySuggestions = exports.querySuggestions = 'Related questions you can explore';
21
21
 
22
22
  /**
23
23
  * The phrase displayed right before <miso-related-resources>
@@ -5,9 +5,10 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.followUp = followUp;
7
7
  exports.root = root;
8
+ var _commons = require("@miso.ai/commons");
8
9
  var _constants = require("./constants.js");
9
10
  var DEFAULT_PHRASES = _interopRequireWildcard(require("./phrases.js"));
10
- const _excluded = ["classPrefix", "circledCitationIndex"],
11
+ const _excluded = ["classPrefix", "circledCitationIndex", "phrases"],
11
12
  _excluded2 = ["classes"];
12
13
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
14
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -44,14 +45,35 @@ function normalizeOptions() {
44
45
  let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
45
46
  let {
46
47
  classPrefix = _constants.CLASS_PREFIX,
47
- circledCitationIndex = true
48
+ circledCitationIndex = true,
49
+ phrases
48
50
  } = _ref,
49
51
  options = _objectWithoutPropertiesLoose(_ref, _excluded);
52
+ phrases = normalizePhraseOptions(phrases);
50
53
  return Object.assign({
51
54
  classPrefix,
52
- circledCitationIndex
55
+ circledCitationIndex,
56
+ phrases
53
57
  }, options);
54
58
  }
59
+ function normalizePhraseOptions() {
60
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
61
+ if (!options) {
62
+ return {};
63
+ }
64
+ const normalized = {};
65
+ for (const key in options) {
66
+ normalized[normalizePhraseOptionKey(key)] = options[key];
67
+ }
68
+ return normalized;
69
+ }
70
+ function normalizePhraseOptionKey(key) {
71
+ key = (0, _commons.kebabOrSnakeToLowerCamel)(key);
72
+ if (key === 'relatedQuestions') {
73
+ return 'querySuggestions'; // legacy key
74
+ }
75
+ return key;
76
+ }
55
77
 
56
78
  // sections & containers //
57
79
  function followUps(options) {
@@ -86,7 +108,7 @@ function querySuggestions(options) {
86
108
  name: 'query-suggestions',
87
109
  visibleWhen: 'initial+nonempty'
88
110
  }, [phrase(options, {
89
- name: 'related-questions',
111
+ name: 'query-suggestions',
90
112
  tag: 'h3'
91
113
  }), '<miso-query-suggestions></miso-query-suggestions>']);
92
114
  }
@@ -124,7 +146,7 @@ function phrase(options, _ref2) {
124
146
  const {
125
147
  classPrefix
126
148
  } = options;
127
- return `<${tag} class="${classPrefix}__phrase ${classPrefix}__${name}-phrase">${phraseText(options, kebabToLowerCamel(name))}</${tag}>`;
149
+ return `<${tag} class="${classPrefix}__phrase ${classPrefix}__${name}-phrase">${phraseText(options, (0, _commons.kebabToLowerCamel)(name))}</${tag}>`;
128
150
  }
129
151
  function phraseText(options, key) {
130
152
  const {
@@ -193,14 +215,8 @@ function attrs() {
193
215
  for (const name in attributes) {
194
216
  const value = attributes[name];
195
217
  if (value !== undefined) {
196
- str += ` ${lowerCamelToKebab(name)}="${value}"`;
218
+ str += ` ${(0, _commons.lowerCamelToKebab)(name)}="${value}"`;
197
219
  }
198
220
  }
199
221
  return str;
200
- }
201
- function kebabToLowerCamel(str) {
202
- return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
203
- }
204
- function lowerCamelToKebab(str) {
205
- return str.replace(/([A-Z])/g, '-$1').toLowerCase();
206
222
  }
@@ -12,52 +12,53 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
12
12
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
13
13
  const TYPE = 'feedback';
14
14
  const DEFAULT_CLASSNAME = 'miso-feedback';
15
- function root(layout) {
16
- const {
17
- className,
18
- role,
19
- templates,
20
- options
21
- } = layout;
22
- const roleAttr = role ? `data-role="${role}"` : '';
23
- return `
24
- <div class="${className}" ${roleAttr}>
25
- ${options.options.map(option => templates.option(layout, option))}
26
- </div>
27
- `;
28
- }
29
- const OPTIONS = [{
30
- text: 'Helpful',
31
- value: 'helpful',
32
- icon: _svgs.THUMB
33
- }, {
34
- text: 'Not helpful',
35
- value: 'unhelpful',
36
- icon: _svgs.THUMB
37
- }];
15
+ const OPTIONS = ['helpful', 'unhelpful'];
38
16
  function options(layout) {
39
17
  const {
40
18
  templates
41
19
  } = layout;
42
- return OPTIONS.map(data => templates.option(layout, data)).join('');
20
+ return OPTIONS.map(value => templates.option(layout, {
21
+ value
22
+ })).join('');
43
23
  }
44
24
  function content(layout, _ref) {
45
25
  let {
46
- text,
47
- icon
26
+ value
48
27
  } = _ref;
49
28
  const {
50
29
  className,
30
+ templates,
51
31
  options = {}
52
32
  } = layout;
53
33
  return `
54
- ${options.icon !== false ? `<i class="${className}__icon">${icon}</i>` : ''}
55
- ${options.text !== false ? `<span class="${className}__text">${text}</span>` : ''}
34
+ ${options.icon !== false ? `<i class="${className}__icon">${templates.icon(layout, value)}</i>` : ''}
35
+ ${options.text !== false ? `<span class="${className}__text">${templates.text(layout, value)}</span>` : ''}
56
36
  `;
57
37
  }
38
+ function icon(layout, value) {
39
+ switch (value) {
40
+ case 'helpful':
41
+ case 'unhelpful':
42
+ return _svgs.THUMB;
43
+ default:
44
+ return '';
45
+ }
46
+ }
47
+ function text(layout, value) {
48
+ switch (value) {
49
+ case 'helpful':
50
+ return 'Helpful';
51
+ case 'unhelpful':
52
+ return 'Not helpful';
53
+ default:
54
+ return '';
55
+ }
56
+ }
58
57
  const DEFAULT_TEMPLATES = Object.freeze({
59
58
  options,
60
- content
59
+ content,
60
+ icon,
61
+ text
61
62
  });
62
63
  class FeedbackLayout extends _radio.default {
63
64
  static get category() {
@@ -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.9.8';
7
+ var _default = exports.default = '1.9.9-beta.1';
@@ -229,18 +229,6 @@ class Ask extends _base.default {
229
229
  })
230
230
  });
231
231
  }
232
- _onDataUpdate(data) {
233
- // TODO: can we just put this in _defaultProcessData?
234
- if (this._questionId) {
235
- return;
236
- }
237
- const questionId = data && data.value && data.value.question_id;
238
- // capture question ID and register at context
239
- if (questionId) {
240
- this._questionId = questionId;
241
- this._context._byQid.set(questionId, this);
242
- }
243
- }
244
232
  _onAnswerViewUpdate(_ref2) {
245
233
  let {
246
234
  session,
@@ -170,32 +170,6 @@ class Workflow extends _commons.Component {
170
170
  return this;
171
171
  }
172
172
 
173
- // configuration //
174
- useApi() {
175
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
176
- args[_key] = arguments[_key];
177
- }
178
- this._options.api = args;
179
- return this;
180
- }
181
- useLayouts() {
182
- let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
183
- this._options.layouts = options;
184
- return this;
185
- }
186
- useDataProcessor(fn) {
187
- this._options.dataProcessor = fn;
188
- return this;
189
- }
190
- useTrackers(options) {
191
- this._options.trackers = options;
192
- return this;
193
- }
194
- useInteractions(options) {
195
- this._options.interactions = options;
196
- return this;
197
- }
198
-
199
173
  // trackers //
200
174
  get trackers() {
201
175
  return this._trackers;
@@ -263,4 +237,5 @@ class Workflow extends _commons.Component {
263
237
  }
264
238
  }
265
239
  }
266
- exports.default = Workflow;
240
+ exports.default = Workflow;
241
+ (0, _options.makeConfigurable)(Workflow.prototype);
@@ -13,28 +13,6 @@ class WorkflowContext extends _commons.Component {
13
13
  this._client = client;
14
14
  this._options = new _options.WorkflowContextOptions();
15
15
  }
16
- useApi() {
17
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
18
- args[_key] = arguments[_key];
19
- }
20
- this._options.api = args;
21
- return this;
22
- }
23
- useLayouts(options) {
24
- this._options.layouts = options;
25
- return this;
26
- }
27
- useDataProcessor(fn) {
28
- this._options.dataProcessor = fn;
29
- return this;
30
- }
31
- useTrackers(options) {
32
- this._options.trackers = options;
33
- return this;
34
- }
35
- useInteractions(options) {
36
- this._options.interactions = options;
37
- return this;
38
- }
39
16
  }
40
- exports.default = WorkflowContext;
17
+ exports.default = WorkflowContext;
18
+ (0, _options.makeConfigurable)(WorkflowContext.prototype);
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.WorkflowOptions = exports.WorkflowContextOptions = void 0;
7
+ exports.makeConfigurable = makeConfigurable;
7
8
  exports.mergeApiOptions = mergeApiOptions;
8
9
  exports.mergeDataProcessorOptions = mergeDataProcessorOptions;
9
10
  exports.mergeInteractionsOptions = mergeInteractionsOptions;
@@ -58,10 +59,15 @@ function normalizeLayoutsOptions() {
58
59
  }
59
60
  const normalize = {};
60
61
  for (const [role, args] of Object.entries(options)) {
61
- normalize[role] = normalizeLayoutOptions(args);
62
+ normalize[normalizeRole(role)] = normalizeLayoutOptions(args);
62
63
  }
63
64
  return normalize;
64
65
  }
66
+ function normalizeRole(role) {
67
+ role = (0, _commons.lowerCamelToSnake)(role);
68
+ role = (0, _commons.kebabToSnake)(role);
69
+ return role;
70
+ }
65
71
  function normalizeLayoutOptions(args) {
66
72
  // take args:
67
73
  // * undefined
@@ -128,8 +134,8 @@ function normalizeInteractionsOptions(options) {
128
134
 
129
135
  // merge //
130
136
  function mergeApiOptions() {
131
- for (var _len = arguments.length, optionsList = new Array(_len), _key = 0; _key < _len; _key++) {
132
- optionsList[_key] = arguments[_key];
137
+ for (var _len = arguments.length, optionsList = new Array(_len), _key2 = 0; _key2 < _len; _key2++) {
138
+ optionsList[_key2] = arguments[_key2];
133
139
  }
134
140
  if (optionsList[optionsList.length - 1] === false) {
135
141
  return false;
@@ -150,8 +156,8 @@ function mergeObjectValueIfPresent(key, base, overrides) {
150
156
  } : {};
151
157
  }
152
158
  function mergeLayoutsOptions() {
153
- for (var _len2 = arguments.length, optionsList = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
154
- optionsList[_key2] = arguments[_key2];
159
+ for (var _len2 = arguments.length, optionsList = new Array(_len2), _key3 = 0; _key3 < _len2; _key3++) {
160
+ optionsList[_key3] = arguments[_key3];
155
161
  }
156
162
  if (optionsList[optionsList.length - 1] === false) {
157
163
  return false;
@@ -177,14 +183,14 @@ function mergeLayoutParameters(base, overrides) {
177
183
  });
178
184
  }
179
185
  function mergeDataProcessorOptions() {
180
- for (var _len3 = arguments.length, optionsList = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
181
- optionsList[_key3] = arguments[_key3];
186
+ for (var _len3 = arguments.length, optionsList = new Array(_len3), _key4 = 0; _key4 < _len3; _key4++) {
187
+ optionsList[_key4] = arguments[_key4];
182
188
  }
183
189
  return optionsList.flat().filter(Boolean);
184
190
  }
185
191
  function mergeTrackersOptions() {
186
- for (var _len4 = arguments.length, optionsList = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
187
- optionsList[_key4] = arguments[_key4];
192
+ for (var _len4 = arguments.length, optionsList = new Array(_len4), _key5 = 0; _key5 < _len4; _key5++) {
193
+ optionsList[_key5] = arguments[_key5];
188
194
  }
189
195
  if (optionsList[optionsList.length - 1] === false) {
190
196
  return false;
@@ -201,8 +207,8 @@ function mergeTrackerEventOptions() {
201
207
  return overrides === false || base === false ? overrides : Object.assign({}, base, overrides);
202
208
  }
203
209
  function mergeInteractionsOptions() {
204
- for (var _len5 = arguments.length, optionsList = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
205
- optionsList[_key5] = arguments[_key5];
210
+ for (var _len5 = arguments.length, optionsList = new Array(_len5), _key6 = 0; _key6 < _len5; _key6++) {
211
+ optionsList[_key6] = arguments[_key6];
206
212
  }
207
213
  if (optionsList[optionsList.length - 1] === false) {
208
214
  return false;
@@ -251,20 +257,20 @@ class WorkflowContextOptions {
251
257
  exports.WorkflowContextOptions = WorkflowContextOptions;
252
258
  class WorkflowOptions {
253
259
  constructor() {
254
- let globals = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
260
+ let context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
255
261
  let defaults = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
256
262
  this._events = new _commons.EventEmitter({
257
263
  target: this
258
264
  });
259
265
  this._defaults = defaults;
260
- this._globals = globals || {};
266
+ this._context = context || {};
261
267
  this._locals = {};
262
268
  (0, _commons.defineValues)(this, {
263
269
  resolved: new ResolvedWorkflowOptions(this),
264
270
  defaults
265
271
  });
266
- if (globals && globals.on) {
267
- this._unsubscribes = [globals.on('*', event => this._notify(event.name))];
272
+ if (context && context.on) {
273
+ this._unsubscribes = [context.on('*', event => this._notify(event.name))];
268
274
  }
269
275
  }
270
276
  _notify(name) {
@@ -281,6 +287,28 @@ class WorkflowOptions {
281
287
  }
282
288
  }
283
289
  exports.WorkflowOptions = WorkflowOptions;
290
+ class WorkflowFeature {
291
+ constructor(options, store, key, normalize, merge) {
292
+ this._options = options;
293
+ this._store = store;
294
+ this._key = key;
295
+ this._normalize = value => value !== undefined ? normalize(value) : undefined;
296
+ this._merge = merge;
297
+ }
298
+ get() {
299
+ return this._store[this._key];
300
+ }
301
+ set(value) {
302
+ this._set(this._normalize(value));
303
+ }
304
+ merge(value) {
305
+ this._set(this._merge(this.get(), this._normalize(value)));
306
+ }
307
+ _set(value) {
308
+ this._store[this._key] = value;
309
+ this._options._notify(this._key);
310
+ }
311
+ }
284
312
  class ResolvedWorkflowOptions {
285
313
  constructor(options) {
286
314
  this._options = options;
@@ -292,32 +320,57 @@ for (const {
292
320
  merge
293
321
  } of FEATURES) {
294
322
  Object.defineProperty(WorkflowContextOptions.prototype, key, {
295
- set: function (value) {
296
- this._globals[key] = normalize(value);
297
- this._notify(key);
298
- },
299
323
  get: function () {
300
- return this._globals[key];
324
+ const _key = `_${key}`;
325
+ return this[_key] || (this[_key] = new WorkflowFeature(this, this._globals, key, normalize, merge));
301
326
  }
302
327
  });
303
328
  Object.defineProperty(WorkflowOptions.prototype, key, {
304
- set: function (value) {
305
- this._locals[key] = normalize(value);
306
- this._notify(key);
307
- },
308
329
  get: function () {
309
- return this._locals[key];
330
+ const _key = `_${key}`;
331
+ return this[_key] || (this[_key] = new WorkflowFeature(this, this._locals, key, normalize, merge));
310
332
  }
311
333
  });
312
334
  Object.defineProperty(ResolvedWorkflowOptions.prototype, key, {
313
335
  get: function () {
314
336
  const options = this._options;
315
- return merge(options._defaults[key], options._globals[key], options._locals[key]);
337
+ return merge(options._defaults[key], options._context[key].get(), options._locals[key]);
338
+ }
339
+ });
340
+ }
341
+ function makeConfigurable(prototype) {
342
+ Object.assign(prototype, {
343
+ useApi() {
344
+ for (var _len6 = arguments.length, args = new Array(_len6), _key7 = 0; _key7 < _len6; _key7++) {
345
+ args[_key7] = arguments[_key7];
346
+ }
347
+ this._options.api.merge(args);
348
+ return this;
349
+ },
350
+ clearApi() {
351
+ this._options.api.set();
352
+ return this;
316
353
  }
317
354
  });
355
+ for (const feature of ['layouts', 'dataProcessor', 'trackers', 'interactions']) {
356
+ const upperCased = upperCase(feature);
357
+ Object.assign(prototype, {
358
+ [`use${upperCased}`](value) {
359
+ this._options[feature].merge(value);
360
+ return this;
361
+ },
362
+ [`clear${upperCased}`]() {
363
+ this._options[feature].set();
364
+ return this;
365
+ }
366
+ });
367
+ }
318
368
  }
319
369
 
320
370
  // helpers //
371
+ function upperCase(str) {
372
+ return str.charAt(0).toUpperCase() + str.slice(1);
373
+ }
321
374
  function concatArrays(a0, a1) {
322
375
  return a0 && a0.length ? a1 && a1.length ? [...a0, ...a1] : a0 : a1;
323
376
  }
package/package.json CHANGED
@@ -26,7 +26,7 @@
26
26
  "url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
27
27
  },
28
28
  "dependencies": {
29
- "@miso.ai/commons": "1.9.8"
29
+ "@miso.ai/commons": "1.9.9-beta.1"
30
30
  },
31
- "version": "1.9.8"
31
+ "version": "1.9.9-beta.1"
32
32
  }
package/src/asset/svgs.js CHANGED
@@ -24,7 +24,7 @@ function loadSvgSprites() {
24
24
  loadSvgSprites();
25
25
 
26
26
  function getSvg(name) {
27
- return `<svg class="miso-ui-icon miso-ui-icon-${name}"><use xlink:href="#${PREFIX}-${name}"></use></svg>`;
27
+ return `<svg class="miso-ui-icon miso-ui-icon-${name}"><use href="#${PREFIX}-${name}"></use></svg>`;
28
28
  }
29
29
 
30
30
  export const SEND = getSvg('send');
@@ -10,6 +10,7 @@ export default class AskComboElements {
10
10
  defineValues(this, {
11
11
  root,
12
12
  followUpsSection: root.querySelector(`.${classPrefix}__follow-ups`),
13
+ relatedResourcesSection: root.querySelector(`.${classPrefix}__related-resources`),
13
14
  relatedResourcesContainer: root.querySelector(`.${classPrefix}__related-resources miso-ask`),
14
15
  });
15
16
  }
@@ -98,7 +98,8 @@ export default class AskCombo extends Combo {
98
98
  }
99
99
 
100
100
  async _setupWorkflows() {
101
- const { client, elements, resolvedOptions: options } = this;
101
+ const { MisoClient, client, elements, resolvedOptions: options } = this;
102
+ const { wireFollowUps, wireRelatedResources } = MisoClient.ui.defaults.ask;
102
103
  const { templates = {}, features = {}, api } = options;
103
104
 
104
105
  const context = client.ui.asks;
@@ -110,25 +111,11 @@ export default class AskCombo extends Combo {
110
111
 
111
112
  // TODO: set placeholder if present in options
112
113
 
113
- if (elements.followUpsSection && features.followUpQuestions !== false) {
114
- // when a answer is fully populated, insert a new section for the follow-up question
115
- context.on('done', ({ workflow }) => {
116
- elements.followUpsSection.insertAdjacentHTML('beforeend', templates.followUp({ ...options, parentQuestionId: workflow.questionId }));
117
- });
118
- // if user starts over, clean up current follow-up questions
119
- rootWorkflow.on('loading', () => {
120
- // clean up the entire follow-ups section
121
- elements.followUpsSection.innerHTML = '';
122
- // destroy all follow-up workflows
123
- context.reset({ root: false });
124
- });
114
+ if (features.followUpQuestions !== false) {
115
+ wireFollowUps(client, elements.followUpsSection, { ...options, template: templates.followUp });
125
116
  }
126
-
127
- if (elements.relatedResourcesContainer && features.relatedResources !== false && features.followUpQuestions !== false) {
128
- // when a new query starts, associate the last section container (for related resources) to that workflow
129
- context.on('loading', ({ workflow }) => {
130
- elements.relatedResourcesContainer.workflow = workflow;
131
- });
117
+ if (features.relatedResources !== false && features.followUpQuestions !== false) {
118
+ wireRelatedResources(client, elements.relatedResourcesSection);
132
119
  }
133
120
 
134
121
  // start query if specified in URL
@@ -0,0 +1,42 @@
1
+ import { followUp as followUpTemplate } from './templates.js';
2
+
3
+ export function wireFollowUps(client, element, { template = followUpTemplate, ...options } = {}) {
4
+ if (!client) {
5
+ throw new Error('client is required');
6
+ }
7
+ if (typeof template !== 'function') {
8
+ throw new Error('template must be a function or undefined');
9
+ }
10
+ if (!element) {
11
+ return;
12
+ }
13
+ const context = client.ui.asks;
14
+ const rootWorkflow = client.ui.ask;
15
+
16
+ // 1. when an answer is fully populated, insert a new section for the follow-up question
17
+ context.on('done', (event) => {
18
+ element.insertAdjacentHTML('beforeend', template({ ...options, parentQuestionId: event.workflow.questionId }));
19
+ });
20
+ // 2. if user starts over, clean up existing follow-up questions
21
+ rootWorkflow.on('loading', () => {
22
+ // clean up the entire follow-ups section
23
+ element.innerHTML = '';
24
+ // destroy all follow-up workflows
25
+ context.reset({ root: false });
26
+ });
27
+ }
28
+
29
+ export function wireRelatedResources(client, element) {
30
+ if (!client) {
31
+ throw new Error('client is required');
32
+ }
33
+ if (!element) {
34
+ return;
35
+ }
36
+ client.ui.asks.on('loading', (event) => {
37
+ // When a new query starts, associate the last section container (for related resources) to that workflow
38
+ for (const container of element.querySelectorAll('miso-ask')) {
39
+ container.workflow = event.workflow;
40
+ }
41
+ });
42
+ }
@@ -2,6 +2,8 @@ import * as _constants from './constants.js';
2
2
  import * as _phrases from './phrases.js';
3
3
  import * as _templates from './templates.js';
4
4
 
5
+ export * from './controls.js';
6
+
5
7
  export const constants = Object.freeze(_constants);
6
8
  export const phrases = Object.freeze(_phrases);
7
9
  export const templates = Object.freeze(_templates);
@@ -11,7 +11,7 @@ export const sources = 'My reply is based on the following';
11
11
  /**
12
12
  * The phrase displayed right before <miso-query-suggestions>
13
13
  */
14
- export const relatedQuestions = 'Related questions you can explore';
14
+ export const querySuggestions = 'Related questions you can explore';
15
15
 
16
16
  /**
17
17
  * The phrase displayed right before <miso-related-resources>
@@ -1,3 +1,4 @@
1
+ import { lowerCamelToKebab, kebabToLowerCamel, kebabOrSnakeToLowerCamel } from '@miso.ai/commons';
1
2
  import { CLASS_PREFIX } from './constants.js';
2
3
  import * as DEFAULT_PHRASES from './phrases.js';
3
4
 
@@ -31,9 +32,30 @@ export function followUp(options = {}) {
31
32
  function normalizeOptions({
32
33
  classPrefix = CLASS_PREFIX,
33
34
  circledCitationIndex = true,
35
+ phrases,
34
36
  ...options
35
37
  } = {}) {
36
- return { classPrefix, circledCitationIndex, ...options };
38
+ phrases = normalizePhraseOptions(phrases);
39
+ return { classPrefix, circledCitationIndex, phrases, ...options };
40
+ }
41
+
42
+ function normalizePhraseOptions(options = {}) {
43
+ if (!options) {
44
+ return {};
45
+ }
46
+ const normalized = {};
47
+ for (const key in options) {
48
+ normalized[normalizePhraseOptionKey(key)] = options[key];
49
+ }
50
+ return normalized;
51
+ }
52
+
53
+ function normalizePhraseOptionKey(key) {
54
+ key = kebabOrSnakeToLowerCamel(key);
55
+ if (key === 'relatedQuestions') {
56
+ return 'querySuggestions'; // legacy key
57
+ }
58
+ return key;
37
59
  }
38
60
 
39
61
  // sections & containers //
@@ -55,7 +77,7 @@ function querySuggestions(options) {
55
77
  const { features = {} } = options;
56
78
  return features.querySuggestions === false ? '' :
57
79
  container(options, { name: 'query-suggestions', visibleWhen: 'initial+nonempty' }, [
58
- phrase(options, { name: 'related-questions', tag: 'h3' }),
80
+ phrase(options, { name: 'query-suggestions', tag: 'h3' }),
59
81
  '<miso-query-suggestions></miso-query-suggestions>',
60
82
  ]);
61
83
  }
@@ -145,11 +167,3 @@ function attrs({ classes = [], ...attributes } = {}) {
145
167
  }
146
168
  return str;
147
169
  }
148
-
149
- function kebabToLowerCamel(str) {
150
- return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
151
- }
152
-
153
- function lowerCamelToKebab(str) {
154
- return str.replace(/([A-Z])/g, '-$1').toLowerCase();
155
- }
@@ -5,45 +5,47 @@ import { THUMB } from '../svgs.js';
5
5
  const TYPE = 'feedback';
6
6
  const DEFAULT_CLASSNAME = 'miso-feedback';
7
7
 
8
- function root(layout) {
9
- const { className, role, templates, options } = layout;
10
- const roleAttr = role ? `data-role="${role}"` : '';
11
- return `
12
- <div class="${className}" ${roleAttr}>
13
- ${options.options.map((option) => templates.option(layout, option))}
14
- </div>
15
- `;
16
- }
17
-
18
- const OPTIONS = [
19
- {
20
- text: 'Helpful',
21
- value: 'helpful',
22
- icon: THUMB,
23
- },
24
- {
25
- text: 'Not helpful',
26
- value: 'unhelpful',
27
- icon: THUMB,
28
- },
29
- ];
8
+ const OPTIONS = [ 'helpful', 'unhelpful' ];
30
9
 
31
10
  function options(layout) {
32
11
  const { templates } = layout;
33
- return OPTIONS.map((data) => templates.option(layout, data)).join('');
12
+ return OPTIONS.map(value => templates.option(layout, { value })).join('');
34
13
  }
35
14
 
36
- function content(layout, { text, icon }) {
37
- const { className, options = {} } = layout;
15
+ function content(layout, { value }) {
16
+ const { className, templates, options = {} } = layout;
38
17
  return `
39
- ${options.icon !== false ? `<i class="${className}__icon">${icon}</i>` : ''}
40
- ${options.text !== false ? `<span class="${className}__text">${text}</span>` : ''}
18
+ ${options.icon !== false ? `<i class="${className}__icon">${templates.icon(layout, value)}</i>` : ''}
19
+ ${options.text !== false ? `<span class="${className}__text">${templates.text(layout, value)}</span>` : ''}
41
20
  `;
42
21
  }
43
22
 
23
+ function icon(layout, value) {
24
+ switch (value) {
25
+ case 'helpful':
26
+ case 'unhelpful':
27
+ return THUMB;
28
+ default:
29
+ return '';
30
+ }
31
+ }
32
+
33
+ function text(layout, value) {
34
+ switch (value) {
35
+ case 'helpful':
36
+ return 'Helpful';
37
+ case 'unhelpful':
38
+ return 'Not helpful';
39
+ default:
40
+ return '';
41
+ }
42
+ }
43
+
44
44
  const DEFAULT_TEMPLATES = Object.freeze({
45
45
  options,
46
46
  content,
47
+ icon,
48
+ text,
47
49
  });
48
50
 
49
51
  export default class FeedbackLayout extends RadioLayout {
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.9.8';
1
+ export default '1.9.9-beta.1';
@@ -190,19 +190,6 @@ export default class Ask extends Workflow {
190
190
  };
191
191
  }
192
192
 
193
- _onDataUpdate(data) {
194
- // TODO: can we just put this in _defaultProcessData?
195
- if (this._questionId) {
196
- return;
197
- }
198
- const questionId = data && data.value && data.value.question_id;
199
- // capture question ID and register at context
200
- if (questionId) {
201
- this._questionId = questionId;
202
- this._context._byQid.set(questionId, this);
203
- }
204
- }
205
-
206
193
  _onAnswerViewUpdate({ session, status, ongoing }) {
207
194
  if (status === STATUS.INITIAL) {
208
195
  return;
@@ -4,7 +4,7 @@ import * as sources from '../source/index.js';
4
4
  import { STATUS, ROLE } from '../constants.js';
5
5
  import { ContainerLayout, ErrorLayout } from '../layout/index.js';
6
6
  import { injectLogger } from './utils.js';
7
- import { WorkflowOptions, mergeLayoutsOptions, mergeInteractionsOptions } from './options.js';
7
+ import { WorkflowOptions, mergeLayoutsOptions, mergeInteractionsOptions, makeConfigurable } from './options.js';
8
8
 
9
9
  const DEFAULT_LAYOUTS = Object.freeze({
10
10
  [ROLE.CONTAINER]: ContainerLayout.type,
@@ -150,32 +150,6 @@ export default class Workflow extends Component {
150
150
  return this;
151
151
  }
152
152
 
153
- // configuration //
154
- useApi(...args) {
155
- this._options.api = args;
156
- return this;
157
- }
158
-
159
- useLayouts(options = {}) {
160
- this._options.layouts = options;
161
- return this;
162
- }
163
-
164
- useDataProcessor(fn) {
165
- this._options.dataProcessor = fn;
166
- return this;
167
- }
168
-
169
- useTrackers(options) {
170
- this._options.trackers = options;
171
- return this;
172
- }
173
-
174
- useInteractions(options) {
175
- this._options.interactions = options;
176
- return this;
177
- }
178
-
179
153
  // trackers //
180
154
  get trackers() {
181
155
  return this._trackers;
@@ -241,3 +215,5 @@ export default class Workflow extends Component {
241
215
  }
242
216
 
243
217
  }
218
+
219
+ makeConfigurable(Workflow.prototype);
@@ -1,5 +1,5 @@
1
1
  import { Component } from '@miso.ai/commons';
2
- import { WorkflowContextOptions } from './options.js';
2
+ import { WorkflowContextOptions, makeConfigurable } from './options.js';
3
3
 
4
4
  export default class WorkflowContext extends Component {
5
5
 
@@ -10,29 +10,6 @@ export default class WorkflowContext extends Component {
10
10
  this._options = new WorkflowContextOptions();
11
11
  }
12
12
 
13
- useApi(...args) {
14
- this._options.api = args;
15
- return this;
16
- }
17
-
18
- useLayouts(options) {
19
- this._options.layouts = options;
20
- return this;
21
- }
22
-
23
- useDataProcessor(fn) {
24
- this._options.dataProcessor = fn;
25
- return this;
26
- }
27
-
28
- useTrackers(options) {
29
- this._options.trackers = options;
30
- return this;
31
- }
32
-
33
- useInteractions(options) {
34
- this._options.interactions = options;
35
- return this;
36
- }
37
-
38
13
  }
14
+
15
+ makeConfigurable(WorkflowContext.prototype);
@@ -1,4 +1,4 @@
1
- import { EventEmitter, defineValues, asArray, trimObj } from '@miso.ai/commons';
1
+ import { EventEmitter, defineValues, asArray, trimObj, lowerCamelToSnake, kebabToSnake } from '@miso.ai/commons';
2
2
  import { ROLE } from '../constants.js';
3
3
 
4
4
  // normalize //
@@ -28,11 +28,17 @@ export function normalizeLayoutsOptions(options = {}) {
28
28
  }
29
29
  const normalize = {};
30
30
  for (const [role, args] of Object.entries(options)) {
31
- normalize[role] = normalizeLayoutOptions(args);
31
+ normalize[normalizeRole(role)] = normalizeLayoutOptions(args);
32
32
  }
33
33
  return normalize;
34
34
  }
35
35
 
36
+ function normalizeRole(role) {
37
+ role = lowerCamelToSnake(role);
38
+ role = kebabToSnake(role);
39
+ return role;
40
+ }
41
+
36
42
  function normalizeLayoutOptions(args) {
37
43
  // take args:
38
44
  // * undefined
@@ -232,10 +238,10 @@ export class WorkflowContextOptions {
232
238
 
233
239
  export class WorkflowOptions {
234
240
 
235
- constructor(globals = {}, defaults = {}) {
241
+ constructor(context = {}, defaults = {}) {
236
242
  this._events = new EventEmitter({ target: this });
237
243
  this._defaults = defaults;
238
- this._globals = globals || {};
244
+ this._context = context || {};
239
245
  this._locals = {};
240
246
 
241
247
  defineValues(this, {
@@ -243,9 +249,9 @@ export class WorkflowOptions {
243
249
  defaults,
244
250
  });
245
251
 
246
- if (globals && globals.on) {
252
+ if (context && context.on) {
247
253
  this._unsubscribes = [
248
- globals.on('*', event => this._notify(event.name)),
254
+ context.on('*', event => this._notify(event.name)),
249
255
  ];
250
256
  }
251
257
  }
@@ -263,6 +269,35 @@ export class WorkflowOptions {
263
269
 
264
270
  }
265
271
 
272
+ class WorkflowFeature {
273
+
274
+ constructor(options, store, key, normalize, merge) {
275
+ this._options = options;
276
+ this._store = store;
277
+ this._key = key;
278
+ this._normalize = value => value !== undefined ? normalize(value) : undefined;
279
+ this._merge = merge;
280
+ }
281
+
282
+ get() {
283
+ return this._store[this._key];
284
+ }
285
+
286
+ set(value) {
287
+ this._set(this._normalize(value));
288
+ }
289
+
290
+ merge(value) {
291
+ this._set(this._merge(this.get(), this._normalize(value)));
292
+ }
293
+
294
+ _set(value) {
295
+ this._store[this._key] = value;
296
+ this._options._notify(this._key);
297
+ }
298
+
299
+ }
300
+
266
301
  class ResolvedWorkflowOptions {
267
302
 
268
303
  constructor(options) {
@@ -273,32 +308,58 @@ class ResolvedWorkflowOptions {
273
308
 
274
309
  for (const { key, normalize, merge } of FEATURES) {
275
310
  Object.defineProperty(WorkflowContextOptions.prototype, key, {
276
- set: function(value) {
277
- this._globals[key] = normalize(value);
278
- this._notify(key);
279
- },
280
311
  get: function() {
281
- return this._globals[key];
312
+ const _key = `_${key}`;
313
+ return this[_key] || (this[_key] = new WorkflowFeature(this, this._globals, key, normalize, merge));
282
314
  },
283
315
  });
284
316
  Object.defineProperty(WorkflowOptions.prototype, key, {
285
- set: function(value) {
286
- this._locals[key] = normalize(value);
287
- this._notify(key);
288
- },
289
317
  get: function() {
290
- return this._locals[key];
318
+ const _key = `_${key}`;
319
+ return this[_key] || (this[_key] = new WorkflowFeature(this, this._locals, key, normalize, merge));
291
320
  },
292
321
  });
293
322
  Object.defineProperty(ResolvedWorkflowOptions.prototype, key, {
294
323
  get: function() {
295
324
  const options = this._options;
296
- return merge(options._defaults[key], options._globals[key], options._locals[key]);
325
+ return merge(options._defaults[key], options._context[key].get(), options._locals[key]);
326
+ },
327
+ });
328
+ }
329
+
330
+ export function makeConfigurable(prototype) {
331
+ Object.assign(prototype, {
332
+ useApi(...args) {
333
+ this._options.api.merge(args);
334
+ return this;
335
+ },
336
+ clearApi() {
337
+ this._options.api.set();
338
+ return this;
297
339
  },
298
340
  });
341
+ for (const feature of ['layouts', 'dataProcessor', 'trackers', 'interactions']) {
342
+ const upperCased = upperCase(feature);
343
+ Object.assign(prototype, {
344
+ [`use${upperCased}`](value) {
345
+ (this._options[feature]).merge(value);
346
+ return this;
347
+ },
348
+ [`clear${upperCased}`]() {
349
+ (this._options[feature]).set();
350
+ return this;
351
+ },
352
+ });
353
+ }
299
354
  }
300
355
 
356
+
357
+
301
358
  // helpers //
359
+ function upperCase(str) {
360
+ return str.charAt(0).toUpperCase() + str.slice(1);
361
+ }
362
+
302
363
  function concatArrays(a0, a1) {
303
364
  return (a0 && a0.length) ? (a1 && a1.length) ? [...a0, ...a1] : a0 : a1;
304
365
  }