@miso.ai/client-sdk-ui 1.10.3 → 1.11.0-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 (101) hide show
  1. package/dist/cjs/actor/autocomplete.js +4 -1
  2. package/dist/cjs/actor/data.js +15 -6
  3. package/dist/cjs/actor/fields.js +10 -10
  4. package/dist/cjs/actor/filters.js +114 -0
  5. package/dist/cjs/actor/hub.js +2 -1
  6. package/dist/cjs/actor/sessions.js +3 -5
  7. package/dist/cjs/actor/view.js +19 -6
  8. package/dist/cjs/actor/views.js +32 -19
  9. package/dist/cjs/constants.js +9 -2
  10. package/dist/cjs/defaults/ask/constants.js +3 -2
  11. package/dist/cjs/defaults/ask/templates.js +112 -219
  12. package/dist/cjs/defaults/common/templates.js +234 -0
  13. package/dist/cjs/defaults/hybrid-search/answer-box.js +115 -0
  14. package/dist/cjs/defaults/hybrid-search/constants.js +8 -0
  15. package/dist/cjs/defaults/hybrid-search/controls.js +17 -0
  16. package/dist/cjs/defaults/hybrid-search/index.js +31 -0
  17. package/dist/cjs/defaults/hybrid-search/phrases.js +27 -0
  18. package/dist/cjs/defaults/hybrid-search/templates.js +127 -0
  19. package/dist/cjs/defaults/index.js +4 -2
  20. package/dist/cjs/element/container/index.js +7 -0
  21. package/dist/cjs/element/container/miso-hybrid-search.js +21 -0
  22. package/dist/cjs/element/role/index.js +21 -0
  23. package/dist/cjs/element/role/miso-facets.js +20 -0
  24. package/dist/cjs/element/role/miso-hits.js +20 -0
  25. package/dist/cjs/element/role/miso-keywords.js +20 -0
  26. package/dist/cjs/layout/index.js +7 -0
  27. package/dist/cjs/layout/input/facets.js +294 -0
  28. package/dist/cjs/layout/input/option-list.js +4 -4
  29. package/dist/cjs/layout/input/search-box.js +6 -8
  30. package/dist/cjs/layout/list/collection.js +1 -2
  31. package/dist/cjs/layout/radio/radio.js +1 -1
  32. package/dist/cjs/layout/raf.js +1 -0
  33. package/dist/cjs/layout/template.js +3 -1
  34. package/dist/cjs/layout/templates.js +15 -0
  35. package/dist/cjs/layout/text/text.js +38 -5
  36. package/dist/cjs/layout/trackable.js +2 -16
  37. package/dist/cjs/plugin.js +4 -1
  38. package/dist/cjs/{source/index.js → source.js} +7 -4
  39. package/dist/cjs/util/bindings.js +3 -0
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/workflow/answer-based.js +335 -0
  42. package/dist/cjs/workflow/ask.js +98 -271
  43. package/dist/cjs/workflow/base.js +69 -35
  44. package/dist/cjs/workflow/explore.js +10 -0
  45. package/dist/cjs/workflow/hybrid-search.js +140 -0
  46. package/dist/cjs/workflow/index.js +7 -0
  47. package/dist/cjs/workflow/processors.js +113 -0
  48. package/dist/cjs/workflow/recommendation.js +12 -3
  49. package/dist/cjs/workflow/search.js +34 -12
  50. package/package.json +2 -2
  51. package/src/actor/autocomplete.js +4 -2
  52. package/src/actor/data.js +8 -2
  53. package/src/actor/fields.js +10 -10
  54. package/src/actor/filters.js +103 -0
  55. package/src/actor/hub.js +2 -1
  56. package/src/actor/sessions.js +3 -4
  57. package/src/actor/view.js +19 -2
  58. package/src/actor/views.js +30 -16
  59. package/src/constants.js +10 -0
  60. package/src/defaults/ask/constants.js +1 -0
  61. package/src/defaults/ask/templates.js +70 -179
  62. package/src/defaults/common/templates.js +173 -0
  63. package/src/defaults/hybrid-search/answer-box.js +118 -0
  64. package/src/defaults/hybrid-search/constants.js +2 -0
  65. package/src/defaults/hybrid-search/controls.js +11 -0
  66. package/src/defaults/hybrid-search/index.js +9 -0
  67. package/src/defaults/hybrid-search/phrases.js +22 -0
  68. package/src/defaults/hybrid-search/templates.js +83 -0
  69. package/src/defaults/index.js +2 -0
  70. package/src/element/container/index.js +1 -0
  71. package/src/element/container/miso-hybrid-search.js +19 -0
  72. package/src/element/role/index.js +3 -0
  73. package/src/element/role/miso-facets.js +16 -0
  74. package/src/element/role/miso-hits.js +16 -0
  75. package/src/element/role/miso-keywords.js +16 -0
  76. package/src/layout/index.js +1 -0
  77. package/src/layout/input/facets.js +242 -0
  78. package/src/layout/input/option-list.js +3 -4
  79. package/src/layout/input/search-box.js +6 -7
  80. package/src/layout/list/collection.js +1 -2
  81. package/src/layout/radio/radio.js +1 -1
  82. package/src/layout/raf.js +1 -0
  83. package/src/layout/template.js +2 -1
  84. package/src/layout/templates.js +15 -0
  85. package/src/layout/text/text.js +28 -3
  86. package/src/layout/trackable.js +2 -15
  87. package/src/plugin.js +6 -2
  88. package/src/{source/index.js → source.js} +7 -4
  89. package/src/util/bindings.js +4 -0
  90. package/src/version.js +1 -1
  91. package/src/workflow/answer-based.js +280 -0
  92. package/src/workflow/ask.js +82 -204
  93. package/src/workflow/base.js +64 -27
  94. package/src/workflow/explore.js +10 -1
  95. package/src/workflow/hybrid-search.js +125 -0
  96. package/src/workflow/index.js +1 -0
  97. package/src/workflow/processors.js +95 -0
  98. package/src/workflow/recommendation.js +9 -2
  99. package/src/workflow/search.js +35 -7
  100. package/dist/cjs/source/api.js +0 -23
  101. package/src/source/api.js +0 -12
@@ -5,10 +5,35 @@ import TemplateBasedLayout from '../template.js';
5
5
  const TYPE = 'text';
6
6
  const DEFAULT_CLASSNAME = 'miso-text';
7
7
 
8
- function root(layout, state) {
9
- const { className, role, options: { tag } } = layout;
8
+ function root(layout, { value }) {
9
+ const { className, role, options: { raw = false, tag } } = layout;
10
10
  const roleAttr = role ? `data-role="${role}"` : '';
11
- return `<${tag} class="${className}" ${roleAttr}>${escapeHtml(state.value || '')}</${tag}>`;
11
+ const content = value !== undefined ? escapeHtml(getFormatFunction(layout)(value)) : '';
12
+ return raw ? content : `<${tag} class="${className}" ${roleAttr}>${content}</${tag}>`;
13
+ }
14
+
15
+ function getFormatFunction(layout) {
16
+ let { format } = layout.options;
17
+ const { formatDate, formatNumber } = layout.templates.helpers;
18
+ if (!format) {
19
+ return v => `${v}`;
20
+ }
21
+ switch (typeof format) {
22
+ case 'function':
23
+ return format;
24
+ case 'string':
25
+ format = [format];
26
+ break;
27
+ }
28
+ let [type, args] = format;
29
+ switch (type) {
30
+ case 'date':
31
+ return v => formatDate(v, args);
32
+ case 'number':
33
+ return v => formatNumber(v, args);
34
+ default:
35
+ return v => `${v}`;
36
+ }
12
37
  }
13
38
 
14
39
  const DEFAULT_TEMPLATES = Object.freeze({
@@ -1,23 +1,10 @@
1
+ import { mixin } from '@miso.ai/commons';
1
2
  import Bindings from '../util/bindings.js';
2
3
  import Viewables from '../util/viewables.js';
3
4
  import { validateClick } from '../util/trackers.js';
4
5
 
5
6
  export function makeTrackable(prototype) {
6
- _mixin(prototype, TrackableMixin.prototype);
7
- }
8
-
9
- function _mixin(target, source) {
10
- const descriptors = Object.getOwnPropertyDescriptors(source);
11
- for (const key in descriptors) {
12
- if (key === 'constructor') {
13
- continue;
14
- }
15
- const { value } = descriptors[key];
16
- if (target[key] === undefined && typeof value === 'function') {
17
- target[key] = value;
18
- }
19
- // TODO: getter, setter
20
- }
7
+ mixin(prototype, TrackableMixin.prototype);
21
8
  }
22
9
 
23
10
  export class TrackableMixin {
package/src/plugin.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Component, Resolution, defineAndUpgrade, delegateGetters, defineValues } from '@miso.ai/commons';
2
- import { Asks, Explore, Search, Recommendations } from './workflow/index.js';
2
+ import { Asks, HybridSearch, Explore, Search, Recommendations } from './workflow/index.js';
3
3
  import { AskCombo } from './combo/index.js';
4
4
  import Layouts from './layouts.js';
5
5
  import * as elements from './element/index.js';
6
6
  import * as layouts from './layout/index.js';
7
- import * as sources from './source/index.js';
7
+ import * as sources from './source.js';
8
8
  import * as defaults from './defaults/index.js';
9
9
  import { loadStylesIfNecessary } from './styles.js';
10
10
 
@@ -176,6 +176,10 @@ class Ui {
176
176
  return this.asks.root;
177
177
  }
178
178
 
179
+ get hybridSearch() {
180
+ return this._hybridSearch || (this._hybridSearch = new HybridSearch(this._plugin, this._client));
181
+ }
182
+
179
183
  get explore() {
180
184
  return this._explore || (this._explore = new Explore(this._plugin, this._client));
181
185
  }
@@ -1,12 +1,15 @@
1
1
  import { API } from '@miso.ai/commons';
2
2
 
3
- //export { default as api } from './api.js';
4
-
5
3
  export function api(client) {
6
4
  // TODO: send uuid & unit id
7
5
  return async ({ group, name, payload, options }) => {
8
- if (group === API.GROUP.ASK && name === API.NAME.QUESTIONS) {
9
- return client.api.ask.questions(payload, options);
6
+ if (group === API.GROUP.ASK) {
7
+ switch (name) {
8
+ case API.NAME.QUESTIONS:
9
+ return client.api.ask.questions(payload, options);
10
+ case API.NAME.SEARCH:
11
+ return client.api.ask.search(payload, options);
12
+ }
10
13
  }
11
14
  // because name is in snake case
12
15
  return client.api[group]._run(name, payload, options);
@@ -41,6 +41,10 @@ export default class Bindings {
41
41
  return this._e2b.get(element) || undefined;
42
42
  }
43
43
 
44
+ find(key) {
45
+ return this._k2b.get(key) || undefined;
46
+ }
47
+
44
48
  update(keys, values, elements) {
45
49
  const newTuple = tuple(keys, values, elements);
46
50
  const oldTuple = { k2b: this._k2b, e2b: this._e2b, entries: this._entries };
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export default '1.10.3';
1
+ export default '1.11.0-beta.0';
@@ -0,0 +1,280 @@
1
+ import { trimObj, API } from '@miso.ai/commons';
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';
7
+ import { SearchBoxLayout, TextLayout, ListLayout, TypewriterLayout, FeedbackLayout, AffiliationLayout } from '../layout/index.js';
8
+ import { processData as processAffiliationData } from '../affiliation/index.js';
9
+ import { writeAnswerStageToMeta } from './processors.js';
10
+
11
+ const DEFAULT_API_OPTIONS = Object.freeze({
12
+ group: API.GROUP.ASK,
13
+ payload: {
14
+ source_fl: ['cover_image', 'url', 'created_at', 'updated_at', 'published_at'],
15
+ cite_link: 1,
16
+ cite_start: '[',
17
+ cite_end: ']',
18
+ },
19
+ });
20
+
21
+ const DEFAULT_AUTOCOMPLETE_OPTIONS = Object.freeze({
22
+ actor: false,
23
+ api: {
24
+ group: API.GROUP.ASK,
25
+ name: API.NAME.AUTOCOMPLETE,
26
+ },
27
+ });
28
+
29
+ const DEFAULT_LAYOUTS = Object.freeze({
30
+ [ROLE.QUERY]: [SearchBoxLayout.type, { placeholder: 'Ask a question' }],
31
+ [ROLE.QUESTION]: [TextLayout.type, { tag: 'h2' }],
32
+ [ROLE.ANSWER]: TypewriterLayout.type,
33
+ [ROLE.FEEDBACK]: FeedbackLayout.type,
34
+ [ROLE.SOURCES]: [ListLayout.type, { incremental: true, itemType: 'article', templates: { ordered: true } }],
35
+ [ROLE.AFFILIATION]: [AffiliationLayout.type, { incremental: true, itemType: 'affiliation', link: { rel: 'noopener nofollow' } }],
36
+ });
37
+
38
+ const DEFAULT_TRACKERS = Object.freeze({
39
+ [ROLE.SOURCES]: {},
40
+ [ROLE.AFFILIATION]: {},
41
+ });
42
+
43
+ const DEFAULT_OPTIONS = Object.freeze({
44
+ api: DEFAULT_API_OPTIONS,
45
+ autocomplete: DEFAULT_AUTOCOMPLETE_OPTIONS,
46
+ layouts: DEFAULT_LAYOUTS,
47
+ trackers: DEFAULT_TRACKERS,
48
+ });
49
+
50
+ export default class AnswerBasedWorkflow extends Workflow {
51
+
52
+ // constructor //
53
+ _initProperties(args) {
54
+ super._initProperties(args);
55
+ this._questionId = undefined;
56
+ this._autoQuery = false;
57
+ }
58
+
59
+ _initActors(args) {
60
+ super._initActors(args);
61
+ 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
+ }
69
+
70
+ _initSubscriptions(args) {
71
+ super._initSubscriptions(args);
72
+ this._unsubscribes = [
73
+ ...this._unsubscribes,
74
+ this._hub.on(fields.query(), args => this._query(args)),
75
+ this._hub.on(fields.view(ROLE.ANSWER), data => this._onAnswerViewUpdate(data)),
76
+ this._views.get(ROLE.ANSWER).on('citation-click', event => this._onCitationClick(event)),
77
+ ];
78
+ }
79
+
80
+ // lifecycle //
81
+ restart() {
82
+ // clear question id from previous session
83
+ if (this._questionId) {
84
+ this._questionId = undefined;
85
+ }
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
+ // restart
96
+ super.restart();
97
+ }
98
+
99
+ // properties //
100
+ get questionId() {
101
+ return this._questionId;
102
+ }
103
+
104
+ // query //
105
+ autoQuery(options = {}) {
106
+ const { setValue = true, focus = true } = this._autoQuery = options;
107
+ const searchParams = new URLSearchParams(window.location.search);
108
+ const q = searchParams.get('q');
109
+ const qs = searchParams.get('qs');
110
+ const { layout } = this._views.get(ROLE.QUERY);
111
+ if (layout) {
112
+ if (q && setValue) {
113
+ layout.value = q;
114
+ }
115
+ if (!q && focus) {
116
+ layout.focus();
117
+ }
118
+ }
119
+ if (q) {
120
+ this.query({ q, qs });
121
+ }
122
+ }
123
+
124
+ query(args) {
125
+ // TODO: also accept "question", "questionSource"?
126
+ if (!args.q) {
127
+ throw new Error(`q is required in query() call`);
128
+ }
129
+ this._hub.update(fields.query(), args);
130
+ }
131
+
132
+ _query(args = {}) {
133
+ // start a new session
134
+ this.restart();
135
+
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
139
+
140
+ // build payload and trigger request
141
+ const payload = this._buildPayload(args);
142
+ const event = mergeApiOptions(this._options.resolved.api, { payload, session });
143
+ this._request(event);
144
+ }
145
+
146
+ _request(event) {
147
+ this._hub.update(fields.request(), event);
148
+ }
149
+
150
+ _buildPayload() {
151
+ throw new Error(`Not implemented`);
152
+ }
153
+
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;
163
+ }
164
+ */
165
+
166
+ // data //
167
+ _defaultProcessData(data) {
168
+ data = super._defaultProcessData(data);
169
+
170
+ // capture question ID and register at context
171
+ this._writeQuestionIdFromData(data);
172
+
173
+ // update URL if autoQuery.updateUrl is not false
174
+ this._updateUrlIfNecessary(data);
175
+
176
+ // affiliation
177
+ data = processAffiliationData(data);
178
+
179
+ return writeAnswerStageToMeta(data);
180
+ }
181
+
182
+ _writeQuestionIdFromData({ value }) {
183
+ if (!value) {
184
+ return;
185
+ }
186
+ const { question_id: questionId } = value;
187
+ this._writeQuestionId(questionId);
188
+ }
189
+
190
+ _updateUrlIfNecessary({ value, request }) {
191
+ if (!value || !this._autoQuery || this._autoQuery.updateUrl === false) { // explicitly false
192
+ return;
193
+ }
194
+ const { question } = value;
195
+ const organic = request && request.payload && request.payload._meta && request.payload._meta.question_source === ORGANIC_QUESTION_SOURCE;
196
+
197
+ const url = new URL(window.location);
198
+ const currentQuestion = url.searchParams.get('q');
199
+ // TODO: review this, handle &qs=
200
+ if (question !== currentQuestion && (!organic || this._autoQuery.updateUrl === true)) {
201
+ url.searchParams.set('q', question);
202
+ window.history.replaceState({}, '', url);
203
+ }
204
+ }
205
+
206
+ // 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,
214
+ context: {
215
+ ...context,
216
+ custom_context: trimObj({
217
+ question_id: questionId,
218
+ ...custom_context,
219
+ }),
220
+ },
221
+ };
222
+ }
223
+
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
+ }
237
+ }
238
+
239
+ _onCitationClick({ index }) {
240
+ const { value } = this.states[fields.data()] || {};
241
+ const { sources } = value || {};
242
+ // index is 1-based
243
+ const source = sources && sources[index - 1];
244
+ if (!source || !source.product_id) {
245
+ return;
246
+ }
247
+ // TODO: should we track impression as well?
248
+ this._trackers.sources.click([source.product_id], { manual: false });
249
+ }
250
+
251
+ // helpers //
252
+ _writeQuestionId(questionId) {
253
+ if (!this._questionId && questionId) {
254
+ this._questionId = questionId;
255
+ }
256
+ }
257
+
258
+ _clearQuestionId() {
259
+ // clear question id from previous session
260
+ this._questionId = undefined;
261
+ }
262
+
263
+ // destroy //
264
+ _destroy(options) {
265
+ this._feedback._destroy();
266
+ // this._autocomplete._destroy();
267
+ super._destroy(options);
268
+ }
269
+
270
+ }
271
+
272
+ // makeConfigurable(AnswerBasedWorkflow.prototype, ['autocomplete']);
273
+
274
+ Object.assign(AnswerBasedWorkflow, {
275
+ DEFAULT_API_OPTIONS,
276
+ DEFAULT_AUTOCOMPLETE_OPTIONS,
277
+ DEFAULT_LAYOUTS,
278
+ DEFAULT_TRACKERS,
279
+ DEFAULT_OPTIONS,
280
+ });