@miso.ai/client-sdk-ui 1.14.0-beta.2 → 1.14.0-beta.3

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 (44) hide show
  1. package/dist/cjs/asset/svgs.js +4 -2
  2. package/dist/cjs/constants.js +2 -0
  3. package/dist/cjs/defaults.js +100 -10
  4. package/dist/cjs/element/container/index.js +14 -0
  5. package/dist/cjs/element/container/miso-message-item.js +91 -0
  6. package/dist/cjs/element/container/miso-thread-item.js +91 -0
  7. package/dist/cjs/element/role/index.js +7 -0
  8. package/dist/cjs/element/role/miso-delete.js +20 -0
  9. package/dist/cjs/layout/button/button.js +45 -5
  10. package/dist/cjs/layout/button/checkbox.js +2 -1
  11. package/dist/cjs/layout/container/item-container.js +99 -0
  12. package/dist/cjs/layout/index.js +14 -0
  13. package/dist/cjs/layout/input/search-box.js +9 -4
  14. package/dist/cjs/layout/list/collection.js +10 -3
  15. package/dist/cjs/layout/list/messages.js +239 -226
  16. package/dist/cjs/layout/list/threads.js +97 -155
  17. package/dist/cjs/layout/templates.js +65 -38
  18. package/dist/cjs/layout/text/question.js +71 -0
  19. package/dist/cjs/layout/text/typewriter/index.js +14 -21
  20. package/dist/cjs/util/dom.js +21 -0
  21. package/dist/cjs/version.js +1 -1
  22. package/package.json +3 -3
  23. package/src/CLAUDE.md +6 -2
  24. package/src/asset/svgs.js +3 -1
  25. package/src/constants.js +2 -0
  26. package/src/defaults.js +71 -4
  27. package/src/element/container/index.js +2 -0
  28. package/src/element/container/miso-message-item.js +94 -0
  29. package/src/element/container/miso-thread-item.js +94 -0
  30. package/src/element/role/index.js +1 -0
  31. package/src/element/role/miso-delete.js +16 -0
  32. package/src/layout/button/button.js +40 -4
  33. package/src/layout/button/checkbox.js +2 -1
  34. package/src/layout/container/item-container.js +99 -0
  35. package/src/layout/index.js +2 -0
  36. package/src/layout/input/search-box.js +4 -2
  37. package/src/layout/list/collection.js +12 -4
  38. package/src/layout/list/messages.js +202 -204
  39. package/src/layout/list/threads.js +90 -130
  40. package/src/layout/templates.js +74 -39
  41. package/src/layout/text/question.js +58 -0
  42. package/src/layout/text/typewriter/index.js +10 -15
  43. package/src/util/dom.js +16 -0
  44. package/src/version.js +1 -1
@@ -8,41 +8,37 @@ var _clientSdkWorkflow = require("@miso.ai/client-sdk-workflow");
8
8
  var _constants = require("../../constants.js");
9
9
  var _collection = _interopRequireDefault(require("./collection.js"));
10
10
  var _dom = require("../../util/dom.js");
11
- var _confirm = _interopRequireDefault(require("../../util/confirm.js"));
12
- var _prompt = _interopRequireDefault(require("../../util/prompt.js"));
13
- const _excluded = ["className", "templates"];
11
+ const _excluded = ["className"];
14
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
13
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
16
14
  const TYPE = _constants.LAYOUT_TYPE.THREADS;
17
15
  const DEFAULT_CLASSNAME = 'miso-threads';
18
16
 
19
- // the thread template renders the <li> itself (it is the item, carrying the
20
- // item role and state), so the stock <li> wrapping is skipped
21
- function item(layout, state, value, index) {
22
- const {
23
- templates,
24
- options
25
- } = layout;
26
- return templates[options.itemType](layout, state, value, {
27
- index
28
- });
29
- }
30
-
31
17
  /**
32
- * The thread list of the chat history interface: a list of `thread` items.
18
+ * The thread list of the chat history interface: a shell that renders one
19
+ * <miso-thread-item> container element per thread item, incrementally —
20
+ * fresh items land at either end of the list: a newly created thread
21
+ * renders as a prepended item (the list is newest-first), while an older
22
+ * page fetched by the more flow (infinite scroll: the collection layout's
23
+ * stock trigger, viewable at the list's bottom, fires the `more` hub
24
+ * field) renders as appended items — told apart by which boundary the
25
+ * already-rendered records line up with. The content
26
+ * of an item is not rendered here: each <miso-thread-item> is assigned its item
27
+ * subworkflow (workflow._getThreadWorkflow, off the item binding) in the
28
+ * post-render sync pass, and the role elements inside (the title, and the
29
+ * context menu's rename/delete buttons) render through that workflow's own
30
+ * layouts — so a record change re-renders the affected item's roles alone.
33
31
  *
34
- * Selection comes with the data: the history workflow stamps
35
- * `selectedThreadId` into its value and the role mapping decorates each
36
- * thread record with a `selected` flag, so any selection change flows down
37
- * the regular data path and refreshes the view. The list renders
38
- * incrementally (append-only); in-place state changes on existing items
39
- * (selected, unread) are applied by `_syncSelection` after each render,
40
- * reading the fresh values off the item bindings — no item re-render needed.
41
- *
42
- * Each item carries a context menu (vertical dots) with thread actions,
43
- * each asking through a shared modal dialog util, then emitting a view event
44
- * for the history workflow to act on: `rename` (a prompt dialog pre-filled
45
- * with the current title) and `delete` (a confirm dialog).
32
+ * The shell keeps the list-level behaviors. Selection comes with the data:
33
+ * the history workflow stamps `selectedThreadId` into its value and
34
+ * decorates each thread record with a `selected` flag, so any selection
35
+ * change flows down the regular data path; in-place state changes on
36
+ * existing items (selected, unread, the thread identity of a settled
37
+ * placeholder) are applied by `_syncItems` after each render, reading the
38
+ * fresh values off the item bindings — no item re-render needed. Each item
39
+ * carries a context menu (vertical dots), but that is the thread workflow's
40
+ * too: its item-container layout inserts and toggles it — the shell only
41
+ * keeps menu clicks (by their item-menu data-roles) from selecting.
46
42
  */
47
43
  class ThreadsLayout extends _collection.default {
48
44
  static get type() {
@@ -53,43 +49,66 @@ class ThreadsLayout extends _collection.default {
53
49
  }
54
50
  constructor(_ref = {}) {
55
51
  let {
56
- className = DEFAULT_CLASSNAME,
57
- templates
52
+ className = DEFAULT_CLASSNAME
58
53
  } = _ref,
59
54
  options = _objectWithoutPropertiesLoose(_ref, _excluded);
60
55
  super(Object.assign({
61
- className,
62
- templates: Object.assign({
63
- item
64
- }, templates)
56
+ className
65
57
  }, options));
66
58
  }
67
- initialize(view) {
68
- super.initialize(view);
69
- // clicking outside the list closes any open context menu
70
- if (typeof document !== 'undefined') {
71
- const onDocumentClick = event => {
72
- const element = this._element;
73
- if (element && !element.contains(event.target)) {
74
- this._closeMenus();
75
- }
76
- };
77
- document.addEventListener('click', onDocumentClick);
78
- this._unsubscribes.push(() => document.removeEventListener('click', onDocumentClick));
59
+
60
+ // item identity: the thread id, or the local placeholder id standing in
61
+ // for it while a thread is being created — the workflow adopts the thread
62
+ // id at the settle, so the element and its workflow survive it
63
+ _getItemKey(thread) {
64
+ return thread.thread_id || thread.placeholder_id || thread;
65
+ }
66
+
67
+ // incremental renders cover a pure prepend (a created thread) or a pure
68
+ // append (an older page) — anything else re-renders in full
69
+ _shallRenderIncrementally(state, rendered) {
70
+ if (!super._shallRenderIncrementally(state, rendered)) {
71
+ return false;
79
72
  }
73
+ const values = this._getItems(state) || [];
74
+ const renderedValues = rendered.value;
75
+ const count = values.length - renderedValues.length;
76
+ return count === 0 ||
77
+ // in-place changes only
78
+ this._getItemKey(values[0]) === this._getItemKey(renderedValues[0]) ||
79
+ // appended
80
+ this._getItemKey(values[count]) === this._getItemKey(renderedValues[0]); // prepended
80
81
  }
81
82
 
82
- // the list is newest-first, so incremental renders PREPEND the fresh items
83
- // (the collection layout's stock incremental mode is append-only)
83
+ // the fresh items land at the head (a newly created thread — the list is
84
+ // newest-first) or at the tail (an older page fetched by the more flow):
85
+ // told apart by which boundary the already-rendered records line up with
84
86
  _html(state, rendered, incremental) {
85
- if (incremental) {
86
- const values = this._getItems(state) || [];
87
- const fresh = values.slice(0, values.length - rendered.value.length);
88
- return fresh.length > 0 ? this.templates.items(this, state, fresh, {
89
- offset: 0
90
- }) : '';
87
+ if (!incremental) {
88
+ return this.templates.root(this, state);
89
+ }
90
+ const values = this._getItems(state) || [];
91
+ const renderedValues = rendered.value;
92
+ const count = values.length - renderedValues.length;
93
+ if (count === 0) {
94
+ return '';
95
+ }
96
+ if (this._getItemKey(values[0]) === this._getItemKey(renderedValues[0])) {
97
+ const fresh = values.slice(renderedValues.length);
98
+ return {
99
+ position: 'beforeend',
100
+ items: this.templates.items(this, state, fresh, {
101
+ offset: renderedValues.length
102
+ })
103
+ };
91
104
  }
92
- return this.templates.root(this, state);
105
+ const fresh = values.slice(0, count);
106
+ return {
107
+ position: 'afterbegin',
108
+ items: this.templates.items(this, state, fresh, {
109
+ offset: 0
110
+ })
111
+ };
93
112
  }
94
113
  _render(element, {
95
114
  state
@@ -102,7 +121,7 @@ class ThreadsLayout extends _collection.default {
102
121
  } = state;
103
122
  if (incremental) {
104
123
  if (html) {
105
- this._getListElement(element).insertAdjacentHTML('afterbegin', html);
124
+ this._getListElement(element).insertAdjacentHTML(html.position, html.items);
106
125
  } else {
107
126
  notifyUpdate(false);
108
127
  }
@@ -112,12 +131,28 @@ class ThreadsLayout extends _collection.default {
112
131
  }
113
132
  _afterRender(element, state) {
114
133
  super._afterRender(element, state); // syncs bindings to the latest values
134
+ this._syncWorkflows(element);
115
135
  this._syncItems(element);
116
136
  }
117
137
 
118
- // sync in-place item changes from the bound values onto the existing item
119
- // elements: selection/unread state, and record changes that arrive without
120
- // a re-render (rename, a placeholder thread resolving to its real record)
138
+ // assign each <miso-thread-item> its item subworkflow, off the item binding
139
+ _syncWorkflows(element) {
140
+ const workflow = this._view && this._view.workflow;
141
+ if (!workflow || typeof workflow._getThreadWorkflow !== 'function') {
142
+ return;
143
+ }
144
+ for (const item of this._getItemElements(element)) {
145
+ const binding = this._bindings.get(item);
146
+ if (binding && item.isContainer) {
147
+ item.workflow = workflow._getThreadWorkflow(binding.value);
148
+ }
149
+ }
150
+ }
151
+
152
+ // sync in-place item state changes from the bound values onto the existing
153
+ // item elements: selection/unread state, and the thread identity of a
154
+ // placeholder that settled into its real record — the item contents render
155
+ // through the thread workflows, outside this layout's render cycle
121
156
  _syncItems(element) {
122
157
  for (const item of this._getItemElements(element)) {
123
158
  const binding = this._bindings.get(item);
@@ -129,38 +164,16 @@ class ThreadsLayout extends _collection.default {
129
164
  } = binding;
130
165
  (0, _dom.setOrRemoveAttribute)(item, 'data-selected', value.selected ? '' : undefined);
131
166
  (0, _dom.setOrRemoveAttribute)(item, 'data-unread', (0, _clientSdkWorkflow.isThreadUnread)(value) ? '' : undefined);
132
- (0, _dom.setOrRemoveAttribute)(item, 'data-thread-id', (0, _clientSdkWorkflow.getThreadId)(value) || undefined);
133
- const titleElement = item.querySelector(`.${this.className}__title`);
134
- const title = value.title || 'Untitled';
135
- if (titleElement && titleElement.textContent !== title) {
136
- titleElement.textContent = title;
137
- }
167
+ (0, _dom.setOrRemoveAttribute)(item, 'data-thread-id', value.thread_id || undefined);
138
168
  }
139
169
  }
140
170
 
141
171
  // a click on a thread item means selection — a navigation action, not a
142
172
  // content-engagement click: emit a select view event and skip click tracking
143
173
  _onClick(event) {
144
- if (event.target.closest(`[data-role="thread-menu-button"]`)) {
145
- this._toggleMenu(event.target.closest(`[data-role="item"]`));
146
- return;
147
- }
148
- if (event.target.closest(`[data-role="thread-rename"]`)) {
149
- this._closeMenus();
150
- const binding = this._bindings.get(event.target.closest(`[data-role="item"]`));
151
- binding && this._requestRename(binding);
152
- return;
153
- }
154
- if (event.target.closest(`[data-role="thread-delete"]`)) {
155
- this._closeMenus();
156
- const binding = this._bindings.get(event.target.closest(`[data-role="item"]`));
157
- binding && this._requestDelete(binding);
158
- return;
159
- }
160
- if (event.target.closest(`[data-role="thread-menu"]`)) {
161
- return; // other clicks inside the menu don't select
174
+ if (event.target.closest(`[data-role="item-menu-button"], [data-role="item-menu"]`)) {
175
+ return; // the item's own context menu, owned by its container layout
162
176
  }
163
- this._closeMenus();
164
177
  const element = event.target.closest(`[data-role="item"]`);
165
178
  if (!element) {
166
179
  return;
@@ -182,76 +195,5 @@ class ThreadsLayout extends _collection.default {
182
195
  domEvent: event
183
196
  });
184
197
  }
185
-
186
- // context menu //
187
- _toggleMenu(item) {
188
- const menu = item && item.querySelector(`[data-role="thread-menu"]`);
189
- if (!menu) {
190
- return;
191
- }
192
- const open = menu.hidden;
193
- this._closeMenus();
194
- menu.hidden = !open;
195
- }
196
- _closeMenus() {
197
- const element = this._element;
198
- if (!element) {
199
- return;
200
- }
201
- for (const menu of element.querySelectorAll(`[data-role="thread-menu"]:not([hidden])`)) {
202
- menu.hidden = true;
203
- }
204
- }
205
-
206
- // rename //
207
- async _requestRename(binding) {
208
- const {
209
- value,
210
- element
211
- } = binding;
212
- const title = await (0, _prompt.default)({
213
- title: 'Rename thread',
214
- value: value.title || '',
215
- placeholder: 'Thread name',
216
- confirmText: 'Rename'
217
- });
218
- if (!title || title === value.title) {
219
- return; // cancelled, cleared, or unchanged
220
- }
221
- const {
222
- session
223
- } = this._view._state;
224
- this._view._emit('rename', {
225
- session,
226
- value,
227
- element,
228
- title
229
- });
230
- }
231
-
232
- // delete //
233
- async _requestDelete(binding) {
234
- const {
235
- value,
236
- element
237
- } = binding;
238
- const confirmed = await (0, _confirm.default)({
239
- title: 'Delete thread',
240
- message: `Are you sure you want to delete "${value.title || 'this thread'}"? This cannot be undone.`,
241
- confirmText: 'Delete',
242
- danger: true
243
- });
244
- if (!confirmed) {
245
- return;
246
- }
247
- const {
248
- session
249
- } = this._view._state;
250
- this._view._emit('delete', {
251
- session,
252
- value,
253
- element
254
- });
255
- }
256
198
  }
257
199
  exports.default = ThreadsLayout;
@@ -23,15 +23,16 @@ exports.imageBlock = imageBlock;
23
23
  exports.imageInfoBlock = imageInfoBlock;
24
24
  exports.indexBlock = indexBlock;
25
25
  exports.message = message;
26
- exports.messageAnswerBlock = messageAnswerBlock;
27
- exports.messageAuthor = messageAuthor;
28
- exports.messageQuestionBlock = messageQuestionBlock;
26
+ exports.messageBody = messageBody;
27
+ exports.messageErrorBlock = messageErrorBlock;
28
+ exports.messageSourcesBlock = messageSourcesBlock;
29
29
  exports.priceBlock = priceBlock;
30
30
  exports.product = product;
31
31
  exports.productInfoBlock = productInfoBlock;
32
32
  exports.question = question;
33
33
  exports.requiresImplementation = requiresImplementation;
34
34
  exports.thread = thread;
35
+ exports.threadBody = threadBody;
35
36
  exports.threadMenuBlock = threadMenuBlock;
36
37
  exports.titleBlock = titleBlock;
37
38
  exports.unimplemented = unimplemented;
@@ -64,66 +65,92 @@ function question(layout, state, data) {
64
65
  return `${openTag}${data.value || data.text || data}${closeTag}`;
65
66
  }
66
67
 
67
- // the <li> itself is the item: it carries the item role, the thread identity
68
- // and the item state — a thread item is not a link, so no anchor body (and
69
- // the wrapper div around it) is needed
68
+ /**
69
+ * A thread item is a <miso-thread-item> container element hosting the thread
70
+ * item subworkflow — the threads layout is a shell: it assigns the workflow
71
+ * (and syncs the item-level attributes) in its post-render pass, and the
72
+ * content renders through the thread workflow's own layouts: the title role
73
+ * element in the body, plus the context menu (with the
74
+ * rename/subscription/delete role elements) inserted as chrome by the workflow's item-container layout. The
75
+ * item state renders as attributes right in the template (and stays in
76
+ * sync thereafter): the thread identity, the selection, and the unread
77
+ * mark.
78
+ */
70
79
  function thread(layout, state, data) {
71
80
  const {
72
81
  className,
73
82
  templates
74
83
  } = layout;
75
- const threadId = (0, _clientSdkWorkflow.getThreadId)(data);
84
+ const threadId = data.thread_id;
76
85
  const threadIdAttr = threadId ? ` data-thread-id="${threadId}"` : '';
77
86
  const unreadAttr = (0, _clientSdkWorkflow.isThreadUnread)(data) ? ' data-unread' : '';
78
87
  const selectedAttr = data.selected ? ' data-selected' : '';
79
- const title = (0, _commons.escapeHtml)(data.title || 'Untitled');
80
- return `<li class="${className}__item" data-role="item"${threadIdAttr}${unreadAttr}${selectedAttr}><div class="${className}__title">${title}</div>${(templates.threadMenuBlock || threadMenuBlock)(layout, data)}</li>`;
88
+ return [`<miso-thread-item class="${className}__item-body" data-role="item"${threadIdAttr}${unreadAttr}${selectedAttr}>`, (templates.threadBody || threadBody)(layout, data), `</miso-thread-item>`].join('');
81
89
  }
90
+ function threadBody(layout, data) {
91
+ const {
92
+ className,
93
+ templates
94
+ } = layout;
95
+ return `<miso-title class="${className}__title"></miso-title>` + (templates.threadMenuBlock || threadMenuBlock)(layout, data);
96
+ }
97
+
98
+ // the item-menu data-roles are the contract with the thread workflow's
99
+ // item-container layout, which drives the menu's open/close behavior; the
100
+ // menu items are the thread workflow's own controls, rendered by the
101
+ // generic button layout with a prompt (rename) or a confirm (delete) dialog,
102
+ // and by the checkbox layout (the subscription toggle)
82
103
  function threadMenuBlock({
83
104
  className
84
105
  }) {
85
- return `<button type="button" class="${className}__menu-button" data-role="thread-menu-button" aria-label="Thread actions" aria-haspopup="menu">${(0, _svgs.getIcon)('dots-vertical')}</button>` + `<div class="${className}__menu" data-role="thread-menu" role="menu" hidden>` + `<button type="button" class="${className}__menu-item" data-role="thread-rename" role="menuitem">Rename</button>` + `<button type="button" class="${className}__menu-item ${className}__menu-item--danger" data-role="thread-delete" role="menuitem">Delete</button>` + `</div>`;
106
+ return `<button type="button" class="${className}__menu-button" data-role="item-menu-button" aria-label="Thread actions" aria-haspopup="menu">${(0, _svgs.getIcon)('dots-vertical')}</button>` + `<div class="${className}__menu" data-role="item-menu" role="menu" hidden>` + `<miso-rename class="${className}__menu-item"></miso-rename>` + `<miso-subscription class="${className}__menu-item"></miso-subscription>` + `<miso-delete class="${className}__menu-item ${className}__menu-item--danger"></miso-delete>` + `</div>`;
86
107
  }
108
+
109
+ /**
110
+ * A message item is a <miso-message-item> container element hosting the message
111
+ * item subworkflow — the messages layout is a shell: it assigns the workflow
112
+ * (and the item-level attributes) in its post-render sync pass, and the role
113
+ * elements inside render the actual content through the message workflow's
114
+ * own layouts.
115
+ */
87
116
  function message(layout, state, data) {
88
117
  const {
89
118
  className,
90
119
  templates
91
120
  } = layout;
92
- return [`<div class="${className}__item-body" data-role="item">`, (templates.messageQuestionBlock || messageQuestionBlock)(layout, data), (templates.messageAnswerBlock || messageAnswerBlock)(layout, data), `</div>`].join('');
121
+ // miso-circled-citation-index covers both the citation links in the answer
122
+ // and the indexes on the source cards, as the hybrid-search UI scopes it
123
+ return [`<miso-message-item class="${className}__item-body miso-circled-citation-index" data-role="item">`, (templates.messageBody || messageBody)(layout, data), `</miso-message-item>`].join('');
93
124
  }
94
- function messageQuestionBlock({
95
- className
96
- }, message) {
125
+
126
+ // the regular content is packed together, so the erroneous presentation
127
+ // swaps it out wholesale for the error notice (the content wrapper hides by
128
+ // a class rule off the item's status — visible-when suits the notice, but
129
+ // not a wrapper: it forces `display: block !important` and only negates in
130
+ // combination with positive terms)
131
+ function messageBody(layout) {
97
132
  const {
98
- question
99
- } = message;
100
- const generatedBy = (0, _clientSdkWorkflow.getGeneratedBy)(message);
101
- const author = messageAuthor(message);
102
- // always rendered so it can be filled in place when the question text
103
- // arrives later (the list renders incrementally); the authorship attrs
104
- // drive the author label and the bubble color
105
- const attrs = `${author ? ` data-author="${(0, _commons.escapeHtml)(author)}"` : ''}${generatedBy ? ` data-generated-by="${(0, _commons.escapeHtml)(generatedBy)}"` : ''}`;
106
- return `<div class="${className}__question" data-role="question"${attrs}${question ? '' : ' hidden'}>${question ? (0, _commons.escapeHtml)(question) : ''}</div>`;
133
+ className,
134
+ templates
135
+ } = layout;
136
+ return `<div class="${className}__content">` + `<miso-question class="${className}__question" hidden></miso-question>` + `<miso-answer class="${className}__answer"></miso-answer>` + `<miso-feedback class="${className}__feedback"></miso-feedback>` + (templates.messageSourcesBlock || messageSourcesBlock)(layout) + `</div>` + (templates.messageErrorBlock || messageErrorBlock)(layout);
107
137
  }
108
138
 
109
- /**
110
- * The author label of a message's question bubble: labeled only when the
111
- * question was written by the answer-updates monitor — the user's own
112
- * questions carry no label.
113
- */
114
- function messageAuthor(message) {
115
- return (0, _clientSdkWorkflow.getGeneratedBy)(message) === 'answer_update_monitor' ? 'Written by Miso' : undefined;
139
+ // the sources with their heading, as the hybrid-search UI presents them —
140
+ // shown once the item carries content (visible-when nonempty), so no
141
+ // orphan heading sits over a message still loading
142
+ function messageSourcesBlock({
143
+ className
144
+ }) {
145
+ return `<div class="${className}__sources-container" visible-when="nonempty">` + `<h3 class="${className}__sources-phrase">Sources</h3>` + `<miso-sources class="${className}__sources"></miso-sources>` + `</div>`;
116
146
  }
117
- function messageAnswerBlock({
147
+
148
+ // shown by the item's own status (visible-when): a message whose content
149
+ // fetch failed for good presents this in place of its content
150
+ function messageErrorBlock({
118
151
  className
119
- }, {
120
- answer
121
152
  }) {
122
- // the content is left blank here: the layout fills it in a second pass
123
- // (markdown -> HTML); a spinner shows while the answer body is still
124
- // being fetched from the answers API
125
- const content = answer === undefined ? `<div class="miso-loading" aria-label="Loading" data-role="loading"></div>` : '';
126
- return `<div class="${className}__answer miso-markdown miso-circled-citation-index" data-role="answer">${content}</div>`;
153
+ return `<div class="${className}__error" visible-when="erroneous">This message could not be loaded.</div>`;
127
154
  }
128
155
  function article(layout, state, data, meta) {
129
156
  const {
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _commons = require("@miso.ai/commons");
8
+ var _clientSdkWorkflow = require("@miso.ai/client-sdk-workflow");
9
+ var _constants = require("../../constants.js");
10
+ var _dom = require("../../util/dom.js");
11
+ var _text = _interopRequireDefault(require("./text.js"));
12
+ const _excluded = ["className", "tag", "author", "templates"];
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
15
+ const TYPE = _constants.LAYOUT_TYPE.QUESTION;
16
+ const DEFAULT_CLASSNAME = 'miso-question';
17
+
18
+ // the author label: only a question written by the answer-updates monitor is
19
+ // labeled — the user's own questions carry none
20
+ function defaultAuthor(message) {
21
+ return (0, _clientSdkWorkflow.isUpdateMessage)(message) ? 'Written by Miso' : undefined;
22
+ }
23
+ function content(layout, {
24
+ value
25
+ }) {
26
+ const question = value && value.question;
27
+ return question ? (0, _commons.escapeHtml)(question) : '';
28
+ }
29
+
30
+ /**
31
+ * The question of a message: a text layout taking the whole message record —
32
+ * the text is the `question` field, and the authorship is stamped as data
33
+ * attributes on the host element (`data-author` drives the CSS label badge,
34
+ * `data-generated-by` the bubble color), re-applied on every render, since
35
+ * the metadata arrives with the answers response, after the stub render. The
36
+ * host element hides while there is no question text to show. The author
37
+ * label is configurable via the `author` option (message -> label text).
38
+ */
39
+ class QuestionLayout extends _text.default {
40
+ static get type() {
41
+ return TYPE;
42
+ }
43
+ static get defaultClassName() {
44
+ return DEFAULT_CLASSNAME;
45
+ }
46
+ constructor(_ref = {}) {
47
+ let {
48
+ className = DEFAULT_CLASSNAME,
49
+ tag = 'div',
50
+ author = defaultAuthor,
51
+ templates
52
+ } = _ref,
53
+ options = _objectWithoutPropertiesLoose(_ref, _excluded);
54
+ super(Object.assign({
55
+ className,
56
+ tag,
57
+ author,
58
+ templates: Object.assign({
59
+ content
60
+ }, templates)
61
+ }, options));
62
+ }
63
+ _afterRender(element, state) {
64
+ super._afterRender(element, state);
65
+ const message = state.value || {};
66
+ element.hidden = !message.question;
67
+ (0, _dom.setOrRemoveAttribute)(element, 'data-author', this.options.author(message));
68
+ (0, _dom.setOrRemoveAttribute)(element, 'data-generated-by', message.metadata && message.metadata.miso_generated_by || undefined);
69
+ }
70
+ }
71
+ exports.default = QuestionLayout;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  var _commons = require("@miso.ai/commons");
8
8
  var _constants = require("../../../constants.js");
9
+ var _dom = require("../../../util/dom.js");
9
10
  var _progressive = _interopRequireDefault(require("../../progressive.js"));
10
11
  var _plaintext = _interopRequireDefault(require("./plaintext.js"));
11
12
  var _utils = require("./utils.js");
@@ -160,6 +161,17 @@ class TypewriterLayout extends _progressive.default {
160
161
  // new typing streak
161
162
  result = this._renderer.clear(container, rendered);
162
163
  } else {
164
+ result = rendered;
165
+ }
166
+ if (this.options.instant && state.done && !result.cursor) {
167
+ // instant mode: content that finishes before any of it was typed —
168
+ // arrived complete (e.g. a loaded conversation message), or completed
169
+ // while the cursor was still at the start — renders in one shot; only
170
+ // content streaming in as it is generated types out
171
+ result = this._renderer.update(container, result, Object.assign({}, state, {
172
+ cursor: Infinity
173
+ }));
174
+ } else if (!newStreak) {
163
175
  const cursor = this._getNextCursor(rendered.cursor, state.doneAt, rendered.timestamp, state.timestamp);
164
176
  result = this._renderer.update(container, rendered, Object.assign({}, state, {
165
177
  cursor
@@ -228,7 +240,7 @@ class TypewriterLayout extends _progressive.default {
228
240
  url: href,
229
241
  text: innerText ? innerText.trim() : '',
230
242
  className: className || '',
231
- attributes: dumpElementAttributes(anchorElement)
243
+ attributes: (0, _dom.dumpElementAttributes)(anchorElement)
232
244
  }));
233
245
  }
234
246
  }
@@ -236,23 +248,4 @@ class TypewriterLayout extends _progressive.default {
236
248
  }
237
249
  }
238
250
  }
239
- exports.default = TypewriterLayout;
240
- function dumpElementAttributes(element) {
241
- const {
242
- attributes
243
- } = element;
244
- const attrs = {};
245
- for (const {
246
- name,
247
- value
248
- } of attributes) {
249
- if (name.startsWith('data-')) {
250
- attrs[name] = value;
251
- }
252
- }
253
- try {
254
- return JSON.stringify(attrs);
255
- } catch (e) {
256
- return '{}';
257
- }
258
- }
251
+ exports.default = TypewriterLayout;
@@ -5,8 +5,29 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.addOrRemoveClass = addOrRemoveClass;
7
7
  exports.cssAspectRatio = cssAspectRatio;
8
+ exports.dumpElementAttributes = dumpElementAttributes;
8
9
  exports.resolveCssLength = resolveCssLength;
9
10
  exports.setOrRemoveAttribute = setOrRemoveAttribute;
11
+ // data-* attributes of an element as a JSON string, for click interactions
12
+ function dumpElementAttributes(element) {
13
+ const {
14
+ attributes
15
+ } = element;
16
+ const attrs = {};
17
+ for (const {
18
+ name,
19
+ value
20
+ } of attributes) {
21
+ if (name.startsWith('data-')) {
22
+ attrs[name] = value;
23
+ }
24
+ }
25
+ try {
26
+ return JSON.stringify(attrs);
27
+ } catch (e) {
28
+ return '{}';
29
+ }
30
+ }
10
31
  function setOrRemoveAttribute(element, name, value) {
11
32
  if (value === undefined) {
12
33
  element.removeAttribute(name);
@@ -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.14.0-beta.2';
7
+ var _default = exports.default = '1.14.0-beta.3';
package/package.json CHANGED
@@ -30,8 +30,8 @@
30
30
  "url": "https://github.com/MisoAI/miso-client-js-sdk/issues"
31
31
  },
32
32
  "dependencies": {
33
- "@miso.ai/commons": "1.14.0-beta.2",
34
- "@miso.ai/client-sdk-workflow": "1.14.0-beta.2"
33
+ "@miso.ai/commons": "1.14.0-beta.3",
34
+ "@miso.ai/client-sdk-workflow": "1.14.0-beta.3"
35
35
  },
36
- "version": "1.14.0-beta.2"
36
+ "version": "1.14.0-beta.3"
37
37
  }