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

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 (54) hide show
  1. package/dist/cjs/asset/svgs.js +4 -2
  2. package/dist/cjs/constants.js +2 -0
  3. package/dist/cjs/defaults/ask/index.js +2 -1
  4. package/dist/cjs/defaults/ask/templates.js +2 -1
  5. package/dist/cjs/defaults/hybrid-search/index.js +2 -1
  6. package/dist/cjs/defaults/hybrid-search/templates.js +2 -1
  7. package/dist/cjs/defaults/index.js +2 -1
  8. package/dist/cjs/defaults.js +107 -10
  9. package/dist/cjs/element/container/index.js +14 -0
  10. package/dist/cjs/element/container/miso-message-item.js +91 -0
  11. package/dist/cjs/element/container/miso-thread-item.js +91 -0
  12. package/dist/cjs/element/index.js +2 -1
  13. package/dist/cjs/element/role/index.js +7 -0
  14. package/dist/cjs/element/role/miso-delete.js +20 -0
  15. package/dist/cjs/layout/button/button.js +45 -5
  16. package/dist/cjs/layout/button/checkbox.js +2 -1
  17. package/dist/cjs/layout/container/item-container.js +99 -0
  18. package/dist/cjs/layout/index.js +14 -0
  19. package/dist/cjs/layout/input/search-box.js +9 -4
  20. package/dist/cjs/layout/list/collection.js +21 -4
  21. package/dist/cjs/layout/list/messages.js +261 -224
  22. package/dist/cjs/layout/list/threads.js +106 -150
  23. package/dist/cjs/layout/templates.js +98 -38
  24. package/dist/cjs/layout/text/question.js +71 -0
  25. package/dist/cjs/layout/text/typewriter/index.js +15 -21
  26. package/dist/cjs/plugin.js +6 -3
  27. package/dist/cjs/util/dom.js +21 -0
  28. package/dist/cjs/util/notify.js +74 -0
  29. package/dist/cjs/version.js +1 -1
  30. package/package.json +3 -3
  31. package/src/CLAUDE.md +7 -2
  32. package/src/asset/svgs.js +3 -1
  33. package/src/constants.js +2 -0
  34. package/src/defaults.js +75 -5
  35. package/src/element/container/index.js +2 -0
  36. package/src/element/container/miso-message-item.js +94 -0
  37. package/src/element/container/miso-thread-item.js +94 -0
  38. package/src/element/role/index.js +1 -0
  39. package/src/element/role/miso-delete.js +16 -0
  40. package/src/layout/button/button.js +40 -4
  41. package/src/layout/button/checkbox.js +2 -1
  42. package/src/layout/container/item-container.js +99 -0
  43. package/src/layout/index.js +2 -0
  44. package/src/layout/input/search-box.js +4 -2
  45. package/src/layout/list/collection.js +22 -5
  46. package/src/layout/list/messages.js +231 -201
  47. package/src/layout/list/threads.js +107 -128
  48. package/src/layout/templates.js +100 -38
  49. package/src/layout/text/question.js +58 -0
  50. package/src/layout/text/typewriter/index.js +11 -15
  51. package/src/plugin.js +3 -2
  52. package/src/util/dom.js +16 -0
  53. package/src/util/notify.js +68 -0
  54. package/src/version.js +1 -1
@@ -7,42 +7,47 @@ exports.default = void 0;
7
7
  var _clientSdkWorkflow = require("@miso.ai/client-sdk-workflow");
8
8
  var _constants = require("../../constants.js");
9
9
  var _collection = _interopRequireDefault(require("./collection.js"));
10
+ var _templates = require("../templates.js");
10
11
  var _dom = require("../../util/dom.js");
11
- var _confirm = _interopRequireDefault(require("../../util/confirm.js"));
12
- var _prompt = _interopRequireDefault(require("../../util/prompt.js"));
13
12
  const _excluded = ["className", "templates"];
14
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
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; }
16
15
  const TYPE = _constants.LAYOUT_TYPE.THREADS;
17
16
  const DEFAULT_CLASSNAME = 'miso-threads';
18
-
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
- });
17
+ const DEFAULT_TEMPLATES = Object.freeze({
18
+ [_constants.STATUS.ERRONEOUS]: _templates.listError,
19
+ errorMessage
20
+ });
21
+ const INHERITED_DEFAULT_TEMPLATES = Object.freeze(Object.assign({}, _collection.default.defaultTemplates, DEFAULT_TEMPLATES));
22
+ function errorMessage(layout, error) {
23
+ return (0, _templates.isSessionExpired)(error) ? _templates.SESSION_EXPIRED_MESSAGE : 'Your conversations could not be loaded.';
29
24
  }
30
25
 
31
26
  /**
32
- * The thread list of the chat history interface: a list of `thread` items.
33
- *
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.
27
+ * The thread list of the chat history interface: a shell that renders one
28
+ * <miso-thread-item> container element per thread item, incrementally —
29
+ * fresh items land at either end of the list: a newly created thread
30
+ * renders as a prepended item (the list is newest-first), while an older
31
+ * page fetched by the more flow (infinite scroll: the collection layout's
32
+ * stock trigger, viewable at the list's bottom, fires the `more` hub
33
+ * field) renders as appended items — told apart by which boundary the
34
+ * already-rendered records line up with. The content
35
+ * of an item is not rendered here: each <miso-thread-item> is assigned its item
36
+ * subworkflow (workflow._getThreadWorkflow, off the item binding) in the
37
+ * post-render sync pass, and the role elements inside (the title, and the
38
+ * context menu's rename/delete buttons) render through that workflow's own
39
+ * layouts — so a record change re-renders the affected item's roles alone.
41
40
  *
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).
41
+ * The shell keeps the list-level behaviors. Selection comes with the data:
42
+ * the history workflow stamps `selectedThreadId` into its value and
43
+ * decorates each thread record with a `selected` flag, so any selection
44
+ * change flows down the regular data path; in-place state changes on
45
+ * existing items (selected, unread, the thread identity of a settled
46
+ * placeholder) are applied by `_syncItems` after each render, reading the
47
+ * fresh values off the item bindings — no item re-render needed. Each item
48
+ * carries a context menu (vertical dots), but that is the thread workflow's
49
+ * too: its item-container layout inserts and toggles it — the shell only
50
+ * keeps menu clicks (by their item-menu data-roles) from selecting.
46
51
  */
47
52
  class ThreadsLayout extends _collection.default {
48
53
  static get type() {
@@ -51,6 +56,9 @@ class ThreadsLayout extends _collection.default {
51
56
  static get defaultClassName() {
52
57
  return DEFAULT_CLASSNAME;
53
58
  }
59
+ static get defaultTemplates() {
60
+ return INHERITED_DEFAULT_TEMPLATES;
61
+ }
54
62
  constructor(_ref = {}) {
55
63
  let {
56
64
  className = DEFAULT_CLASSNAME,
@@ -59,37 +67,62 @@ class ThreadsLayout extends _collection.default {
59
67
  options = _objectWithoutPropertiesLoose(_ref, _excluded);
60
68
  super(Object.assign({
61
69
  className,
62
- templates: Object.assign({
63
- item
64
- }, templates)
70
+ templates: Object.assign({}, DEFAULT_TEMPLATES, templates)
65
71
  }, options));
66
72
  }
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));
73
+
74
+ // item identity: the thread id, or the local placeholder id standing in
75
+ // for it while a thread is being created — the workflow adopts the thread
76
+ // id at the settle, so the element and its workflow survive it
77
+ _getItemKey(thread) {
78
+ return thread.thread_id || thread.placeholder_id || thread;
79
+ }
80
+
81
+ // incremental renders cover a pure prepend (a created thread) or a pure
82
+ // append (an older page) — anything else re-renders in full
83
+ _shallRenderIncrementally(state, rendered) {
84
+ if (!super._shallRenderIncrementally(state, rendered)) {
85
+ return false;
79
86
  }
87
+ const values = this._getItems(state) || [];
88
+ const renderedValues = rendered.value;
89
+ const count = values.length - renderedValues.length;
90
+ return count === 0 ||
91
+ // in-place changes only
92
+ this._getItemKey(values[0]) === this._getItemKey(renderedValues[0]) ||
93
+ // appended
94
+ this._getItemKey(values[count]) === this._getItemKey(renderedValues[0]); // prepended
80
95
  }
81
96
 
82
- // the list is newest-first, so incremental renders PREPEND the fresh items
83
- // (the collection layout's stock incremental mode is append-only)
97
+ // the fresh items land at the head (a newly created thread — the list is
98
+ // newest-first) or at the tail (an older page fetched by the more flow):
99
+ // told apart by which boundary the already-rendered records line up with
84
100
  _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
- }) : '';
101
+ if (!incremental) {
102
+ return this.templates.root(this, state);
103
+ }
104
+ const values = this._getItems(state) || [];
105
+ const renderedValues = rendered.value;
106
+ const count = values.length - renderedValues.length;
107
+ if (count === 0) {
108
+ return '';
109
+ }
110
+ if (this._getItemKey(values[0]) === this._getItemKey(renderedValues[0])) {
111
+ const fresh = values.slice(renderedValues.length);
112
+ return {
113
+ position: 'beforeend',
114
+ items: this.templates.items(this, state, fresh, {
115
+ offset: renderedValues.length
116
+ })
117
+ };
91
118
  }
92
- return this.templates.root(this, state);
119
+ const fresh = values.slice(0, count);
120
+ return {
121
+ position: 'afterbegin',
122
+ items: this.templates.items(this, state, fresh, {
123
+ offset: 0
124
+ })
125
+ };
93
126
  }
94
127
  _render(element, {
95
128
  state
@@ -102,7 +135,7 @@ class ThreadsLayout extends _collection.default {
102
135
  } = state;
103
136
  if (incremental) {
104
137
  if (html) {
105
- this._getListElement(element).insertAdjacentHTML('afterbegin', html);
138
+ this._getListElement(element).insertAdjacentHTML(html.position, html.items);
106
139
  } else {
107
140
  notifyUpdate(false);
108
141
  }
@@ -112,12 +145,28 @@ class ThreadsLayout extends _collection.default {
112
145
  }
113
146
  _afterRender(element, state) {
114
147
  super._afterRender(element, state); // syncs bindings to the latest values
148
+ this._syncWorkflows(element);
115
149
  this._syncItems(element);
116
150
  }
117
151
 
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)
152
+ // assign each <miso-thread-item> its item subworkflow, off the item binding
153
+ _syncWorkflows(element) {
154
+ const workflow = this._view && this._view.workflow;
155
+ if (!workflow || typeof workflow._getThreadWorkflow !== 'function') {
156
+ return;
157
+ }
158
+ for (const item of this._getItemElements(element)) {
159
+ const binding = this._bindings.get(item);
160
+ if (binding && item.isContainer) {
161
+ item.workflow = workflow._getThreadWorkflow(binding.value);
162
+ }
163
+ }
164
+ }
165
+
166
+ // sync in-place item state changes from the bound values onto the existing
167
+ // item elements: selection/unread state, and the thread identity of a
168
+ // placeholder that settled into its real record — the item contents render
169
+ // through the thread workflows, outside this layout's render cycle
121
170
  _syncItems(element) {
122
171
  for (const item of this._getItemElements(element)) {
123
172
  const binding = this._bindings.get(item);
@@ -129,38 +178,16 @@ class ThreadsLayout extends _collection.default {
129
178
  } = binding;
130
179
  (0, _dom.setOrRemoveAttribute)(item, 'data-selected', value.selected ? '' : undefined);
131
180
  (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
- }
181
+ (0, _dom.setOrRemoveAttribute)(item, 'data-thread-id', value.thread_id || undefined);
138
182
  }
139
183
  }
140
184
 
141
185
  // a click on a thread item means selection — a navigation action, not a
142
186
  // content-engagement click: emit a select view event and skip click tracking
143
187
  _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
188
+ if (event.target.closest(`[data-role="item-menu-button"], [data-role="item-menu"]`)) {
189
+ return; // the item's own context menu, owned by its container layout
162
190
  }
163
- this._closeMenus();
164
191
  const element = event.target.closest(`[data-role="item"]`);
165
192
  if (!element) {
166
193
  return;
@@ -182,76 +209,5 @@ class ThreadsLayout extends _collection.default {
182
209
  domEvent: event
183
210
  });
184
211
  }
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
212
  }
257
213
  exports.default = ThreadsLayout;
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.SESSION_EXPIRED_MESSAGE = void 0;
6
7
  exports.affiliation = affiliation;
7
8
  exports.affiliationInfoBlock = affiliationInfoBlock;
8
9
  exports.article = article;
@@ -22,16 +23,20 @@ exports.image = image;
22
23
  exports.imageBlock = imageBlock;
23
24
  exports.imageInfoBlock = imageInfoBlock;
24
25
  exports.indexBlock = indexBlock;
26
+ exports.isSessionExpired = isSessionExpired;
27
+ exports.listError = listError;
25
28
  exports.message = message;
26
- exports.messageAnswerBlock = messageAnswerBlock;
27
- exports.messageAuthor = messageAuthor;
28
- exports.messageQuestionBlock = messageQuestionBlock;
29
+ exports.messageBody = messageBody;
30
+ exports.messageErrorBlock = messageErrorBlock;
31
+ exports.messageErrorMessage = messageErrorMessage;
32
+ exports.messageSourcesBlock = messageSourcesBlock;
29
33
  exports.priceBlock = priceBlock;
30
34
  exports.product = product;
31
35
  exports.productInfoBlock = productInfoBlock;
32
36
  exports.question = question;
33
37
  exports.requiresImplementation = requiresImplementation;
34
38
  exports.thread = thread;
39
+ exports.threadBody = threadBody;
35
40
  exports.threadMenuBlock = threadMenuBlock;
36
41
  exports.titleBlock = titleBlock;
37
42
  exports.unimplemented = unimplemented;
@@ -64,67 +69,122 @@ function question(layout, state, data) {
64
69
  return `${openTag}${data.value || data.text || data}${closeTag}`;
65
70
  }
66
71
 
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
72
+ /**
73
+ * A thread item is a <miso-thread-item> container element hosting the thread
74
+ * item subworkflow — the threads layout is a shell: it assigns the workflow
75
+ * (and syncs the item-level attributes) in its post-render pass, and the
76
+ * content renders through the thread workflow's own layouts: the title role
77
+ * element in the body, plus the context menu (with the
78
+ * rename/subscription/delete role elements) inserted as chrome by the workflow's item-container layout. The
79
+ * item state renders as attributes right in the template (and stays in
80
+ * sync thereafter): the thread identity, the selection, and the unread
81
+ * mark.
82
+ */
70
83
  function thread(layout, state, data) {
71
84
  const {
72
85
  className,
73
86
  templates
74
87
  } = layout;
75
- const threadId = (0, _clientSdkWorkflow.getThreadId)(data);
88
+ const threadId = data.thread_id;
76
89
  const threadIdAttr = threadId ? ` data-thread-id="${threadId}"` : '';
77
90
  const unreadAttr = (0, _clientSdkWorkflow.isThreadUnread)(data) ? ' data-unread' : '';
78
91
  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>`;
92
+ return [`<miso-thread-item class="${className}__item-body" data-role="item"${threadIdAttr}${unreadAttr}${selectedAttr}>`, (templates.threadBody || threadBody)(layout, data), `</miso-thread-item>`].join('');
93
+ }
94
+ function threadBody(layout, data) {
95
+ const {
96
+ className,
97
+ templates
98
+ } = layout;
99
+ return `<miso-title class="${className}__title"></miso-title>` + (templates.threadMenuBlock || threadMenuBlock)(layout, data);
81
100
  }
101
+
102
+ // the item-menu data-roles are the contract with the thread workflow's
103
+ // item-container layout, which drives the menu's open/close behavior; the
104
+ // menu items are the thread workflow's own controls, rendered by the
105
+ // generic button layout with a prompt (rename) or a confirm (delete) dialog,
106
+ // and by the checkbox layout (the subscription toggle)
82
107
  function threadMenuBlock({
83
108
  className
84
109
  }) {
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>`;
110
+ 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
111
  }
112
+
113
+ /**
114
+ * A message item is a <miso-message-item> container element hosting the message
115
+ * item subworkflow — the messages layout is a shell: it assigns the workflow
116
+ * (and the item-level attributes) in its post-render sync pass, and the role
117
+ * elements inside render the actual content through the message workflow's
118
+ * own layouts. The item state renders as an attribute right in the template
119
+ * (and stays in sync thereafter): the live mark, for a message generated in
120
+ * this session.
121
+ */
87
122
  function message(layout, state, data) {
88
123
  const {
89
124
  className,
90
125
  templates
91
126
  } = layout;
92
- return [`<div class="${className}__item-body" data-role="item">`, (templates.messageQuestionBlock || messageQuestionBlock)(layout, data), (templates.messageAnswerBlock || messageAnswerBlock)(layout, data), `</div>`].join('');
127
+ const liveAttr = data.live ? ' data-live' : '';
128
+ // miso-circled-citation-index covers both the citation links in the answer
129
+ // and the indexes on the source cards, as the hybrid-search UI scopes it
130
+ return [`<miso-message-item class="${className}__item-body miso-circled-citation-index" data-role="item"${liveAttr}>`, (templates.messageBody || messageBody)(layout, data), `</miso-message-item>`].join('');
93
131
  }
94
- function messageQuestionBlock({
95
- className
96
- }, message) {
132
+
133
+ // the regular content is packed together, so the erroneous presentation
134
+ // swaps it out wholesale for the error notice (the content wrapper hides by
135
+ // a class rule off the item's status — visible-when suits the notice, but
136
+ // not a wrapper: it forces `display: block !important` and only negates in
137
+ // combination with positive terms)
138
+ function messageBody(layout) {
97
139
  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>`;
140
+ className,
141
+ templates
142
+ } = layout;
143
+ 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
144
  }
108
145
 
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;
146
+ // the sources with their heading, as the hybrid-search UI presents them —
147
+ // shown once the item carries content (visible-when nonempty), so no
148
+ // orphan heading sits over a message still loading
149
+ function messageSourcesBlock({
150
+ className
151
+ }) {
152
+ 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
153
  }
117
- function messageAnswerBlock({
154
+
155
+ // shown by the item's own status (visible-when): a message whose content
156
+ // fetch, or (a live one's) posting or answer polling, failed for good
157
+ // presents this in place of its content — the text through the item's own
158
+ // error role element, off the record's error mark (messageErrorMessage)
159
+ function messageErrorBlock({
118
160
  className
119
- }, {
120
- answer
121
161
  }) {
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>`;
162
+ return `<div class="${className}__error" visible-when="erroneous"><miso-error></miso-error></div>`;
163
+ }
164
+
165
+ // the error layout's message template for a message item (seeded in the
166
+ // 'conversation' layout defaults): an expired session reads apart from other
167
+ // failures
168
+ function messageErrorMessage(layout, error) {
169
+ return isSessionExpired(error) ? SESSION_EXPIRED_MESSAGE : 'Something went wrong with this message.';
170
+ }
171
+
172
+ // the list-level error notice of the chat history interface, in place of
173
+ // the list when its load failed (the thread list, or the thread on display);
174
+ // the text comes from the layout's errorMessage template, given the failure
175
+ function listError(layout, state) {
176
+ const {
177
+ className,
178
+ templates
179
+ } = layout;
180
+ return `<div class="${className}__list-error" data-role="error">${templates.errorMessage(layout, state.error)}</div>`;
181
+ }
182
+
183
+ // an expired session (e.g. an expired JWT), to be told apart from other failures
184
+ function isSessionExpired(error) {
185
+ return !!error && error.status === 401;
127
186
  }
187
+ const SESSION_EXPIRED_MESSAGE = exports.SESSION_EXPIRED_MESSAGE = 'Your session has expired. Please sign in again.';
128
188
  function article(layout, state, data, meta) {
129
189
  const {
130
190
  templates
@@ -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,18 @@ 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 && !result.cursor) {
167
+ // instant mode: while nothing is typed yet, the cursor starts at the max
168
+ // available position (the renderer clamps it to the safe right bound) —
169
+ // content that arrived complete (e.g. a loaded conversation message)
170
+ // renders in one shot; ongoing content shows what is there so far at
171
+ // once, and only the part streaming in after that types out
172
+ result = this._renderer.update(container, result, Object.assign({}, state, {
173
+ cursor: Infinity
174
+ }));
175
+ } else if (!newStreak) {
163
176
  const cursor = this._getNextCursor(rendered.cursor, state.doneAt, rendered.timestamp, state.timestamp);
164
177
  result = this._renderer.update(container, rendered, Object.assign({}, state, {
165
178
  cursor
@@ -228,7 +241,7 @@ class TypewriterLayout extends _progressive.default {
228
241
  url: href,
229
242
  text: innerText ? innerText.trim() : '',
230
243
  className: className || '',
231
- attributes: dumpElementAttributes(anchorElement)
244
+ attributes: (0, _dom.dumpElementAttributes)(anchorElement)
232
245
  }));
233
246
  }
234
247
  }
@@ -236,23 +249,4 @@ class TypewriterLayout extends _progressive.default {
236
249
  }
237
250
  }
238
251
  }
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
- }
252
+ exports.default = TypewriterLayout;