@miso.ai/client-sdk-ui 1.13.2-beta.1 → 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 (226) hide show
  1. package/dist/cjs/asset/svgs.js +17 -4
  2. package/dist/cjs/combo/ask/index.js +2 -2
  3. package/dist/cjs/constants.js +120 -98
  4. package/dist/cjs/defaults/ask/controls.js +1 -1
  5. package/dist/cjs/defaults/hybrid-search/answer-box.js +4 -4
  6. package/dist/cjs/defaults.js +259 -0
  7. package/dist/cjs/element/container/index.js +28 -0
  8. package/dist/cjs/element/container/miso-ask.js +3 -3
  9. package/dist/cjs/element/container/miso-conversation.js +21 -0
  10. package/dist/cjs/element/container/miso-explore.js +1 -1
  11. package/dist/cjs/element/container/miso-history.js +21 -0
  12. package/dist/cjs/element/container/miso-hybrid-search.js +1 -1
  13. package/dist/cjs/element/container/miso-message-item.js +91 -0
  14. package/dist/cjs/element/container/miso-recommendation.js +1 -1
  15. package/dist/cjs/element/container/miso-search.js +1 -1
  16. package/dist/cjs/element/container/miso-thread-item.js +91 -0
  17. package/dist/cjs/element/context/miso-follow-ups.js +1 -1
  18. package/dist/cjs/element/role/index.js +49 -0
  19. package/dist/cjs/element/role/miso-delete.js +20 -0
  20. package/dist/cjs/element/role/miso-messages.js +20 -0
  21. package/dist/cjs/element/role/miso-new-thread.js +20 -0
  22. package/dist/cjs/element/role/miso-rename.js +20 -0
  23. package/dist/cjs/element/role/miso-subscription.js +20 -0
  24. package/dist/cjs/element/role/miso-threads.js +20 -0
  25. package/dist/cjs/element/role/miso-title.js +20 -0
  26. package/dist/cjs/layout/affiliation/affiliation.js +1 -1
  27. package/dist/cjs/layout/banner/banner.js +2 -1
  28. package/dist/cjs/layout/button/button.js +170 -0
  29. package/dist/cjs/layout/button/checkbox.js +116 -0
  30. package/dist/cjs/layout/button/more-button.js +3 -3
  31. package/dist/cjs/layout/container/container.js +1 -1
  32. package/dist/cjs/layout/container/item-container.js +99 -0
  33. package/dist/cjs/layout/error/error.js +2 -1
  34. package/dist/cjs/layout/index.js +42 -0
  35. package/dist/cjs/layout/input/facets.js +4 -3
  36. package/dist/cjs/layout/input/option-list.js +3 -2
  37. package/dist/cjs/layout/input/search-box.js +50 -12
  38. package/dist/cjs/layout/input/select.js +2 -1
  39. package/dist/cjs/layout/list/cards.js +2 -1
  40. package/dist/cjs/layout/list/carousel.js +2 -1
  41. package/dist/cjs/layout/list/collection.js +13 -1
  42. package/dist/cjs/layout/list/gallery.js +2 -1
  43. package/dist/cjs/layout/list/horizontal.js +2 -1
  44. package/dist/cjs/layout/list/list.js +2 -1
  45. package/dist/cjs/layout/list/messages.js +359 -0
  46. package/dist/cjs/layout/list/threads.js +199 -0
  47. package/dist/cjs/layout/mixin/trackable.js +4 -5
  48. package/dist/cjs/layout/mixin/triggerable.js +2 -2
  49. package/dist/cjs/layout/radio/feedback.js +4 -3
  50. package/dist/cjs/layout/templates.js +97 -0
  51. package/dist/cjs/layout/text/question.js +71 -0
  52. package/dist/cjs/layout/text/text.js +2 -1
  53. package/dist/cjs/layout/text/typewriter/index.js +18 -23
  54. package/dist/cjs/plugin.js +43 -104
  55. package/dist/cjs/util/confirm.js +33 -0
  56. package/dist/cjs/util/dom.js +21 -0
  57. package/dist/cjs/util/modal.js +48 -0
  58. package/dist/cjs/util/prompt.js +47 -0
  59. package/dist/cjs/version.js +1 -1
  60. package/package.json +5 -4
  61. package/src/CLAUDE.md +51 -104
  62. package/src/asset/svgs.js +16 -3
  63. package/src/combo/ask/index.js +2 -2
  64. package/src/constants.js +39 -147
  65. package/src/defaults/ask/controls.js +1 -1
  66. package/src/defaults/hybrid-search/answer-box.js +2 -2
  67. package/src/defaults.js +174 -0
  68. package/src/element/container/index.js +4 -0
  69. package/src/element/container/miso-ask.js +3 -3
  70. package/src/element/container/miso-conversation.js +19 -0
  71. package/src/element/container/miso-explore.js +1 -1
  72. package/src/element/container/miso-history.js +19 -0
  73. package/src/element/container/miso-hybrid-search.js +1 -1
  74. package/src/element/container/miso-message-item.js +94 -0
  75. package/src/element/container/miso-recommendation.js +1 -1
  76. package/src/element/container/miso-search.js +1 -1
  77. package/src/element/container/miso-thread-item.js +94 -0
  78. package/src/element/context/miso-follow-ups.js +1 -1
  79. package/src/element/role/index.js +7 -0
  80. package/src/element/role/miso-delete.js +16 -0
  81. package/src/element/role/miso-messages.js +16 -0
  82. package/src/element/role/miso-new-thread.js +16 -0
  83. package/src/element/role/miso-rename.js +16 -0
  84. package/src/element/role/miso-subscription.js +16 -0
  85. package/src/element/role/miso-threads.js +16 -0
  86. package/src/element/role/miso-title.js +16 -0
  87. package/src/layout/affiliation/affiliation.js +2 -2
  88. package/src/layout/banner/banner.js +2 -1
  89. package/src/layout/button/button.js +152 -0
  90. package/src/layout/button/checkbox.js +92 -0
  91. package/src/layout/button/more-button.js +3 -3
  92. package/src/layout/container/container.js +2 -2
  93. package/src/layout/container/item-container.js +99 -0
  94. package/src/layout/error/error.js +2 -1
  95. package/src/layout/index.js +6 -0
  96. package/src/layout/input/facets.js +3 -2
  97. package/src/layout/input/option-list.js +3 -2
  98. package/src/layout/input/search-box.js +45 -8
  99. package/src/layout/input/select.js +2 -1
  100. package/src/layout/list/cards.js +2 -1
  101. package/src/layout/list/carousel.js +2 -1
  102. package/src/layout/list/collection.js +15 -2
  103. package/src/layout/list/gallery.js +2 -1
  104. package/src/layout/list/horizontal.js +2 -1
  105. package/src/layout/list/list.js +2 -1
  106. package/src/layout/list/messages.js +316 -0
  107. package/src/layout/list/threads.js +166 -0
  108. package/src/layout/mixin/trackable.js +2 -3
  109. package/src/layout/mixin/triggerable.js +1 -1
  110. package/src/layout/radio/feedback.js +3 -2
  111. package/src/layout/templates.js +97 -0
  112. package/src/layout/text/question.js +58 -0
  113. package/src/layout/text/text.js +2 -1
  114. package/src/layout/text/typewriter/index.js +15 -18
  115. package/src/plugin.js +31 -117
  116. package/src/util/confirm.js +28 -0
  117. package/src/util/dom.js +16 -0
  118. package/src/util/modal.js +49 -0
  119. package/src/util/prompt.js +42 -0
  120. package/src/version.js +1 -1
  121. package/dist/cjs/actor/data.js +0 -132
  122. package/dist/cjs/actor/feedback.js +0 -29
  123. package/dist/cjs/actor/fields.js +0 -57
  124. package/dist/cjs/actor/filters.js +0 -161
  125. package/dist/cjs/actor/hub.js +0 -57
  126. package/dist/cjs/actor/index.js +0 -73
  127. package/dist/cjs/actor/interactions.js +0 -36
  128. package/dist/cjs/actor/sessions.js +0 -25
  129. package/dist/cjs/actor/tracker-states.js +0 -61
  130. package/dist/cjs/actor/tracker.js +0 -109
  131. package/dist/cjs/actor/trackers.js +0 -52
  132. package/dist/cjs/actor/utils.js +0 -15
  133. package/dist/cjs/actor/view.js +0 -294
  134. package/dist/cjs/actor/views.js +0 -209
  135. package/dist/cjs/source.js +0 -27
  136. package/dist/cjs/util/proxy.js +0 -88
  137. package/dist/cjs/util/trackers.js +0 -88
  138. package/dist/cjs/workflow/affiliations.js +0 -140
  139. package/dist/cjs/workflow/answer-based.js +0 -337
  140. package/dist/cjs/workflow/ask.js +0 -319
  141. package/dist/cjs/workflow/asks.js +0 -150
  142. package/dist/cjs/workflow/autocompletable.js +0 -63
  143. package/dist/cjs/workflow/autocomplete.js +0 -120
  144. package/dist/cjs/workflow/base.js +0 -418
  145. package/dist/cjs/workflow/context.js +0 -19
  146. package/dist/cjs/workflow/explore.js +0 -210
  147. package/dist/cjs/workflow/explores.js +0 -21
  148. package/dist/cjs/workflow/hybrid-search-answer.js +0 -88
  149. package/dist/cjs/workflow/hybrid-search-views.js +0 -109
  150. package/dist/cjs/workflow/hybrid-search.js +0 -213
  151. package/dist/cjs/workflow/index.js +0 -41
  152. package/dist/cjs/workflow/options/api.js +0 -41
  153. package/dist/cjs/workflow/options/auto-query.js +0 -97
  154. package/dist/cjs/workflow/options/configurable.js +0 -45
  155. package/dist/cjs/workflow/options/data-processor.js +0 -20
  156. package/dist/cjs/workflow/options/filters.js +0 -15
  157. package/dist/cjs/workflow/options/index.js +0 -126
  158. package/dist/cjs/workflow/options/interactions.js +0 -41
  159. package/dist/cjs/workflow/options/layouts.js +0 -79
  160. package/dist/cjs/workflow/options/options.js +0 -156
  161. package/dist/cjs/workflow/options/pagination.js +0 -25
  162. package/dist/cjs/workflow/options/roles.js +0 -15
  163. package/dist/cjs/workflow/options/templates.js +0 -30
  164. package/dist/cjs/workflow/options/trackers.js +0 -83
  165. package/dist/cjs/workflow/options/utils.js +0 -33
  166. package/dist/cjs/workflow/processors.js +0 -607
  167. package/dist/cjs/workflow/recommendation.js +0 -57
  168. package/dist/cjs/workflow/recommendations.js +0 -18
  169. package/dist/cjs/workflow/search-based.js +0 -337
  170. package/dist/cjs/workflow/search.js +0 -64
  171. package/dist/cjs/workflow/unit.js +0 -57
  172. package/dist/cjs/workflow/units.js +0 -42
  173. package/dist/cjs/workflow/use-link.js +0 -49
  174. package/src/actor/data.js +0 -106
  175. package/src/actor/feedback.js +0 -28
  176. package/src/actor/fields.js +0 -51
  177. package/src/actor/filters.js +0 -149
  178. package/src/actor/hub.js +0 -47
  179. package/src/actor/index.js +0 -10
  180. package/src/actor/interactions.js +0 -32
  181. package/src/actor/sessions.js +0 -21
  182. package/src/actor/tracker-states.js +0 -57
  183. package/src/actor/tracker.js +0 -104
  184. package/src/actor/trackers.js +0 -45
  185. package/src/actor/utils.js +0 -7
  186. package/src/actor/view.js +0 -272
  187. package/src/actor/views.js +0 -224
  188. package/src/source.js +0 -17
  189. package/src/util/proxy.js +0 -81
  190. package/src/util/trackers.js +0 -84
  191. package/src/workflow/affiliations.js +0 -121
  192. package/src/workflow/answer-based.js +0 -312
  193. package/src/workflow/ask.js +0 -284
  194. package/src/workflow/asks.js +0 -143
  195. package/src/workflow/autocompletable.js +0 -67
  196. package/src/workflow/autocomplete.js +0 -114
  197. package/src/workflow/base.js +0 -396
  198. package/src/workflow/context.js +0 -16
  199. package/src/workflow/explore.js +0 -194
  200. package/src/workflow/explores.js +0 -18
  201. package/src/workflow/hybrid-search-answer.js +0 -73
  202. package/src/workflow/hybrid-search-views.js +0 -116
  203. package/src/workflow/hybrid-search.js +0 -230
  204. package/src/workflow/index.js +0 -5
  205. package/src/workflow/options/api.js +0 -31
  206. package/src/workflow/options/auto-query.js +0 -70
  207. package/src/workflow/options/configurable.js +0 -48
  208. package/src/workflow/options/data-processor.js +0 -15
  209. package/src/workflow/options/filters.js +0 -10
  210. package/src/workflow/options/index.js +0 -11
  211. package/src/workflow/options/interactions.js +0 -36
  212. package/src/workflow/options/layouts.js +0 -73
  213. package/src/workflow/options/options.js +0 -175
  214. package/src/workflow/options/pagination.js +0 -18
  215. package/src/workflow/options/roles.js +0 -12
  216. package/src/workflow/options/templates.js +0 -27
  217. package/src/workflow/options/trackers.js +0 -80
  218. package/src/workflow/options/utils.js +0 -26
  219. package/src/workflow/processors.js +0 -490
  220. package/src/workflow/recommendation.js +0 -63
  221. package/src/workflow/recommendations.js +0 -14
  222. package/src/workflow/search-based.js +0 -329
  223. package/src/workflow/search.js +0 -73
  224. package/src/workflow/unit.js +0 -51
  225. package/src/workflow/units.js +0 -43
  226. package/src/workflow/use-link.js +0 -41
@@ -0,0 +1,166 @@
1
+ import { isThreadUnread } from '@miso.ai/client-sdk-workflow';
2
+ import { LAYOUT_TYPE } from '../../constants.js';
3
+ import CollectionLayout from './collection.js';
4
+ import { setOrRemoveAttribute } from '../../util/dom.js';
5
+
6
+ const TYPE = LAYOUT_TYPE.THREADS;
7
+ const DEFAULT_CLASSNAME = 'miso-threads';
8
+
9
+ /**
10
+ * The thread list of the chat history interface: a shell that renders one
11
+ * <miso-thread-item> container element per thread item, incrementally —
12
+ * fresh items land at either end of the list: a newly created thread
13
+ * renders as a prepended item (the list is newest-first), while an older
14
+ * page fetched by the more flow (infinite scroll: the collection layout's
15
+ * stock trigger, viewable at the list's bottom, fires the `more` hub
16
+ * field) renders as appended items — told apart by which boundary the
17
+ * already-rendered records line up with. The content
18
+ * of an item is not rendered here: each <miso-thread-item> is assigned its item
19
+ * subworkflow (workflow._getThreadWorkflow, off the item binding) in the
20
+ * post-render sync pass, and the role elements inside (the title, and the
21
+ * context menu's rename/delete buttons) render through that workflow's own
22
+ * layouts — so a record change re-renders the affected item's roles alone.
23
+ *
24
+ * The shell keeps the list-level behaviors. Selection comes with the data:
25
+ * the history workflow stamps `selectedThreadId` into its value and
26
+ * decorates each thread record with a `selected` flag, so any selection
27
+ * change flows down the regular data path; in-place state changes on
28
+ * existing items (selected, unread, the thread identity of a settled
29
+ * placeholder) are applied by `_syncItems` after each render, reading the
30
+ * fresh values off the item bindings — no item re-render needed. Each item
31
+ * carries a context menu (vertical dots), but that is the thread workflow's
32
+ * too: its item-container layout inserts and toggles it — the shell only
33
+ * keeps menu clicks (by their item-menu data-roles) from selecting.
34
+ */
35
+ export default class ThreadsLayout extends CollectionLayout {
36
+
37
+ static get type() {
38
+ return TYPE;
39
+ }
40
+
41
+ static get defaultClassName() {
42
+ return DEFAULT_CLASSNAME;
43
+ }
44
+
45
+ constructor({ className = DEFAULT_CLASSNAME, ...options } = {}) {
46
+ super({ className, ...options });
47
+ }
48
+
49
+ // item identity: the thread id, or the local placeholder id standing in
50
+ // for it while a thread is being created — the workflow adopts the thread
51
+ // id at the settle, so the element and its workflow survive it
52
+ _getItemKey(thread) {
53
+ return thread.thread_id || thread.placeholder_id || thread;
54
+ }
55
+
56
+ // incremental renders cover a pure prepend (a created thread) or a pure
57
+ // append (an older page) — anything else re-renders in full
58
+ _shallRenderIncrementally(state, rendered) {
59
+ if (!super._shallRenderIncrementally(state, rendered)) {
60
+ return false;
61
+ }
62
+ const values = this._getItems(state) || [];
63
+ const renderedValues = rendered.value;
64
+ const count = values.length - renderedValues.length;
65
+ return count === 0 || // in-place changes only
66
+ this._getItemKey(values[0]) === this._getItemKey(renderedValues[0]) || // appended
67
+ this._getItemKey(values[count]) === this._getItemKey(renderedValues[0]); // prepended
68
+ }
69
+
70
+ // the fresh items land at the head (a newly created thread — the list is
71
+ // newest-first) or at the tail (an older page fetched by the more flow):
72
+ // told apart by which boundary the already-rendered records line up with
73
+ _html(state, rendered, incremental) {
74
+ if (!incremental) {
75
+ return this.templates.root(this, state);
76
+ }
77
+ const values = this._getItems(state) || [];
78
+ const renderedValues = rendered.value;
79
+ const count = values.length - renderedValues.length;
80
+ if (count === 0) {
81
+ return '';
82
+ }
83
+ if (this._getItemKey(values[0]) === this._getItemKey(renderedValues[0])) {
84
+ const fresh = values.slice(renderedValues.length);
85
+ return {
86
+ position: 'beforeend',
87
+ items: this.templates.items(this, state, fresh, { offset: renderedValues.length }),
88
+ };
89
+ }
90
+ const fresh = values.slice(0, count);
91
+ return {
92
+ position: 'afterbegin',
93
+ items: this.templates.items(this, state, fresh, { offset: 0 }),
94
+ };
95
+ }
96
+
97
+ _render(element, { state }, { notifyUpdate }) {
98
+ const { incremental, html } = state;
99
+ if (incremental) {
100
+ if (html) {
101
+ this._getListElement(element).insertAdjacentHTML(html.position, html.items);
102
+ } else {
103
+ notifyUpdate(false);
104
+ }
105
+ } else {
106
+ element.innerHTML = html;
107
+ }
108
+ }
109
+
110
+ _afterRender(element, state) {
111
+ super._afterRender(element, state); // syncs bindings to the latest values
112
+ this._syncWorkflows(element);
113
+ this._syncItems(element);
114
+ }
115
+
116
+ // assign each <miso-thread-item> its item subworkflow, off the item binding
117
+ _syncWorkflows(element) {
118
+ const workflow = this._view && this._view.workflow;
119
+ if (!workflow || typeof workflow._getThreadWorkflow !== 'function') {
120
+ return;
121
+ }
122
+ for (const item of this._getItemElements(element)) {
123
+ const binding = this._bindings.get(item);
124
+ if (binding && item.isContainer) {
125
+ item.workflow = workflow._getThreadWorkflow(binding.value);
126
+ }
127
+ }
128
+ }
129
+
130
+ // sync in-place item state changes from the bound values onto the existing
131
+ // item elements: selection/unread state, and the thread identity of a
132
+ // placeholder that settled into its real record — the item contents render
133
+ // through the thread workflows, outside this layout's render cycle
134
+ _syncItems(element) {
135
+ for (const item of this._getItemElements(element)) {
136
+ const binding = this._bindings.get(item);
137
+ if (!binding) {
138
+ continue;
139
+ }
140
+ const { value } = binding;
141
+ setOrRemoveAttribute(item, 'data-selected', value.selected ? '' : undefined);
142
+ setOrRemoveAttribute(item, 'data-unread', isThreadUnread(value) ? '' : undefined);
143
+ setOrRemoveAttribute(item, 'data-thread-id', value.thread_id || undefined);
144
+ }
145
+ }
146
+
147
+ // a click on a thread item means selection — a navigation action, not a
148
+ // content-engagement click: emit a select view event and skip click tracking
149
+ _onClick(event) {
150
+ if (event.target.closest(`[data-role="item-menu-button"], [data-role="item-menu"]`)) {
151
+ return; // the item's own context menu, owned by its container layout
152
+ }
153
+ const element = event.target.closest(`[data-role="item"]`);
154
+ if (!element) {
155
+ return;
156
+ }
157
+ const binding = this._bindings.get(element);
158
+ if (!binding) {
159
+ return;
160
+ }
161
+ const { session } = this._view._state;
162
+ const { value } = binding;
163
+ this._view._emit('select', { session, value, element: binding.element, domEvent: event });
164
+ }
165
+
166
+ }
@@ -1,7 +1,7 @@
1
1
  import { mixin } from '@miso.ai/commons';
2
2
  import Bindings from '../../util/bindings.js';
3
3
  import Viewables from '../../util/viewables.js';
4
- import { validateClick, markAsTracked } from '../../util/trackers.js';
4
+ import { validateClick, markAsTracked } from '@miso.ai/client-sdk-workflow';
5
5
 
6
6
  export function makeTrackable(prototype) {
7
7
  mixin(prototype, TrackableMixin.prototype);
@@ -29,8 +29,7 @@ export class TrackableMixin {
29
29
  }
30
30
 
31
31
  _getItemKey(value) {
32
- // TODO: ad-hoc!
33
- return value.product_id || value.text || value.id || value;
32
+ return this._view.tracker.getItemKey(value);
34
33
  }
35
34
 
36
35
  _getItems(state) {
@@ -1,6 +1,6 @@
1
1
  import { mixin, viewable as whenViewable } from '@miso.ai/commons';
2
2
  import { STATUS } from '../../constants.js';
3
- import { fields } from '../../actor/index.js';
3
+ import { fields } from '@miso.ai/client-sdk-workflow';
4
4
 
5
5
  export function makeTriggerable(prototype) {
6
6
  mixin(prototype, TriggerableMixin.prototype);
@@ -1,8 +1,9 @@
1
- import { fields } from '../../actor/index.js';
1
+ import { LAYOUT_TYPE } from '../../constants.js';
2
+ import { fields } from '@miso.ai/client-sdk-workflow';
2
3
  import RadioLayout from './radio.js';
3
4
  import { THUMB } from '../svgs.js';
4
5
 
5
- const TYPE = 'feedback';
6
+ const TYPE = LAYOUT_TYPE.FEEDBACK;
6
7
  const DEFAULT_CLASSNAME = 'miso-feedback';
7
8
 
8
9
  const OPTIONS = [ 'helpful', 'unhelpful' ];
@@ -1,4 +1,6 @@
1
1
  import { escapeHtml } from '@miso.ai/commons';
2
+ import { isThreadUnread } from '@miso.ai/client-sdk-workflow';
3
+ import { getIcon } from '../asset/svgs.js';
2
4
  import { ATTR_DATA_MISO_PRODUCT_ID } from '../constants.js';
3
5
 
4
6
  export function requiresImplementation(...names) {
@@ -30,6 +32,101 @@ export function question(layout, state, data) {
30
32
  return `${openTag}${data.value || data.text || data}${closeTag}`;
31
33
  }
32
34
 
35
+ /**
36
+ * A thread item is a <miso-thread-item> container element hosting the thread
37
+ * item subworkflow — the threads layout is a shell: it assigns the workflow
38
+ * (and syncs the item-level attributes) in its post-render pass, and the
39
+ * content renders through the thread workflow's own layouts: the title role
40
+ * element in the body, plus the context menu (with the
41
+ * rename/subscription/delete role elements) inserted as chrome by the workflow's item-container layout. The
42
+ * item state renders as attributes right in the template (and stays in
43
+ * sync thereafter): the thread identity, the selection, and the unread
44
+ * mark.
45
+ */
46
+ export function thread(layout, state, data) {
47
+ const { className, templates } = layout;
48
+ const threadId = data.thread_id;
49
+ const threadIdAttr = threadId ? ` data-thread-id="${threadId}"` : '';
50
+ const unreadAttr = isThreadUnread(data) ? ' data-unread' : '';
51
+ const selectedAttr = data.selected ? ' data-selected' : '';
52
+ return [
53
+ `<miso-thread-item class="${className}__item-body" data-role="item"${threadIdAttr}${unreadAttr}${selectedAttr}>`,
54
+ (templates.threadBody || threadBody)(layout, data),
55
+ `</miso-thread-item>`,
56
+ ].join('');
57
+ }
58
+
59
+ export function threadBody(layout, data) {
60
+ const { className, templates } = layout;
61
+ return `<miso-title class="${className}__title"></miso-title>` +
62
+ (templates.threadMenuBlock || threadMenuBlock)(layout, data);
63
+ }
64
+
65
+ // the item-menu data-roles are the contract with the thread workflow's
66
+ // item-container layout, which drives the menu's open/close behavior; the
67
+ // menu items are the thread workflow's own controls, rendered by the
68
+ // generic button layout with a prompt (rename) or a confirm (delete) dialog,
69
+ // and by the checkbox layout (the subscription toggle)
70
+ export function threadMenuBlock({ className }) {
71
+ return `<button type="button" class="${className}__menu-button" data-role="item-menu-button" aria-label="Thread actions" aria-haspopup="menu">${getIcon('dots-vertical')}</button>` +
72
+ `<div class="${className}__menu" data-role="item-menu" role="menu" hidden>` +
73
+ `<miso-rename class="${className}__menu-item"></miso-rename>` +
74
+ `<miso-subscription class="${className}__menu-item"></miso-subscription>` +
75
+ `<miso-delete class="${className}__menu-item ${className}__menu-item--danger"></miso-delete>` +
76
+ `</div>`;
77
+ }
78
+
79
+ /**
80
+ * A message item is a <miso-message-item> container element hosting the message
81
+ * item subworkflow — the messages layout is a shell: it assigns the workflow
82
+ * (and the item-level attributes) in its post-render sync pass, and the role
83
+ * elements inside render the actual content through the message workflow's
84
+ * own layouts.
85
+ */
86
+ export function message(layout, state, data) {
87
+ const { className, templates } = layout;
88
+ // miso-circled-citation-index covers both the citation links in the answer
89
+ // and the indexes on the source cards, as the hybrid-search UI scopes it
90
+ return [
91
+ `<miso-message-item class="${className}__item-body miso-circled-citation-index" data-role="item">`,
92
+ (templates.messageBody || messageBody)(layout, data),
93
+ `</miso-message-item>`,
94
+ ].join('');
95
+ }
96
+
97
+ // the regular content is packed together, so the erroneous presentation
98
+ // swaps it out wholesale for the error notice (the content wrapper hides by
99
+ // a class rule off the item's status — visible-when suits the notice, but
100
+ // not a wrapper: it forces `display: block !important` and only negates in
101
+ // combination with positive terms)
102
+ export function messageBody(layout) {
103
+ const { className, templates } = layout;
104
+ return `<div class="${className}__content">` +
105
+ `<miso-question class="${className}__question" hidden></miso-question>` +
106
+ `<miso-answer class="${className}__answer"></miso-answer>` +
107
+ `<miso-feedback class="${className}__feedback"></miso-feedback>` +
108
+ (templates.messageSourcesBlock || messageSourcesBlock)(layout) +
109
+ `</div>` +
110
+ (templates.messageErrorBlock || messageErrorBlock)(layout);
111
+ }
112
+
113
+ // the sources with their heading, as the hybrid-search UI presents them —
114
+ // shown once the item carries content (visible-when nonempty), so no
115
+ // orphan heading sits over a message still loading
116
+ export function messageSourcesBlock({ className }) {
117
+ return `<div class="${className}__sources-container" visible-when="nonempty">` +
118
+ `<h3 class="${className}__sources-phrase">Sources</h3>` +
119
+ `<miso-sources class="${className}__sources"></miso-sources>` +
120
+ `</div>`;
121
+ }
122
+
123
+ // shown by the item's own status (visible-when): a message whose content
124
+ // fetch failed for good presents this in place of its content
125
+ export function messageErrorBlock({ className }) {
126
+ return `<div class="${className}__error" visible-when="erroneous">This message could not be loaded.</div>`;
127
+ }
128
+
129
+
33
130
  export function article(layout, state, data, meta) {
34
131
  const { templates } = layout;
35
132
  const [openTag, closeTag] = tagPair(layout, data);
@@ -0,0 +1,58 @@
1
+ import { escapeHtml } from '@miso.ai/commons';
2
+ import { isUpdateMessage } from '@miso.ai/client-sdk-workflow';
3
+ import { LAYOUT_TYPE } from '../../constants.js';
4
+ import { setOrRemoveAttribute } from '../../util/dom.js';
5
+ import TextLayout from './text.js';
6
+
7
+ const TYPE = LAYOUT_TYPE.QUESTION;
8
+ const DEFAULT_CLASSNAME = 'miso-question';
9
+
10
+ // the author label: only a question written by the answer-updates monitor is
11
+ // labeled — the user's own questions carry none
12
+ function defaultAuthor(message) {
13
+ return isUpdateMessage(message) ? 'Written by Miso' : undefined;
14
+ }
15
+
16
+ function content(layout, { value }) {
17
+ const question = value && value.question;
18
+ return question ? escapeHtml(question) : '';
19
+ }
20
+
21
+ /**
22
+ * The question of a message: a text layout taking the whole message record —
23
+ * the text is the `question` field, and the authorship is stamped as data
24
+ * attributes on the host element (`data-author` drives the CSS label badge,
25
+ * `data-generated-by` the bubble color), re-applied on every render, since
26
+ * the metadata arrives with the answers response, after the stub render. The
27
+ * host element hides while there is no question text to show. The author
28
+ * label is configurable via the `author` option (message -> label text).
29
+ */
30
+ export default class QuestionLayout extends TextLayout {
31
+
32
+ static get type() {
33
+ return TYPE;
34
+ }
35
+
36
+ static get defaultClassName() {
37
+ return DEFAULT_CLASSNAME;
38
+ }
39
+
40
+ constructor({ className = DEFAULT_CLASSNAME, tag = 'div', author = defaultAuthor, templates, ...options } = {}) {
41
+ super({
42
+ className,
43
+ tag,
44
+ author,
45
+ templates: { content, ...templates },
46
+ ...options,
47
+ });
48
+ }
49
+
50
+ _afterRender(element, state) {
51
+ super._afterRender(element, state);
52
+ const message = state.value || {};
53
+ element.hidden = !message.question;
54
+ setOrRemoveAttribute(element, 'data-author', this.options.author(message));
55
+ setOrRemoveAttribute(element, 'data-generated-by', (message.metadata && message.metadata.miso_generated_by) || undefined);
56
+ }
57
+
58
+ }
@@ -1,7 +1,8 @@
1
+ import { LAYOUT_TYPE } from '../../constants.js';
1
2
  import { escapeHtml } from '@miso.ai/commons';
2
3
  import TemplateBasedLayout from '../template.js';
3
4
 
4
- const TYPE = 'text';
5
+ const TYPE = LAYOUT_TYPE.TEXT;
5
6
  const DEFAULT_CLASSNAME = 'miso-text';
6
7
 
7
8
  function root(layout, state) {
@@ -1,10 +1,11 @@
1
1
  import { trimObj, defineValues, Resolution, pacer } from '@miso.ai/commons';
2
- import { STATUS } from '../../../constants.js';
2
+ import { STATUS, LAYOUT_TYPE } from '../../../constants.js';
3
+ import { dumpElementAttributes } from '../../../util/dom.js';
3
4
  import ProgressiveLayout from '../../progressive.js';
4
5
  import PlaintextRenderer from './plaintext.js';
5
6
  import { containerElement, cursorClassName, fromSameSession, normalizeOnDebug } from './utils.js';
6
7
 
7
- const TYPE = 'typewriter';
8
+ const TYPE = LAYOUT_TYPE.TYPEWRITER;
8
9
  const DEFAULT_CLASSNAME = 'miso-typewriter';
9
10
 
10
11
  export default class TypewriterLayout extends ProgressiveLayout {
@@ -67,10 +68,12 @@ export default class TypewriterLayout extends ProgressiveLayout {
67
68
  }
68
69
 
69
70
  async _setupForMarkdown() {
70
- const context = await this._view._views._extensions.require('markdown');
71
+ const client = this._view.workflow._client;
72
+ const plugin = await TypewriterLayout.MisoClient.plugins.install('std:ui-markdown');
71
73
  if (!this._view) {
72
74
  return; // destroyed
73
75
  }
76
+ const context = plugin.getContext(client);
74
77
  const cursorClass = cursorClassName(this.className);
75
78
  const { onDebug, onCitationLink, variant } = this.options;
76
79
  // TODO: options
@@ -135,6 +138,15 @@ export default class TypewriterLayout extends ProgressiveLayout {
135
138
  // new typing streak
136
139
  result = this._renderer.clear(container, rendered);
137
140
  } else {
141
+ result = rendered;
142
+ }
143
+ if (this.options.instant && state.done && !result.cursor) {
144
+ // instant mode: content that finishes before any of it was typed —
145
+ // arrived complete (e.g. a loaded conversation message), or completed
146
+ // while the cursor was still at the start — renders in one shot; only
147
+ // content streaming in as it is generated types out
148
+ result = this._renderer.update(container, result, { ...state, cursor: Infinity });
149
+ } else if (!newStreak) {
138
150
  const cursor = this._getNextCursor(rendered.cursor, state.doneAt, rendered.timestamp, state.timestamp);
139
151
  result = this._renderer.update(container, rendered, { ...state, cursor });
140
152
  }
@@ -193,18 +205,3 @@ export default class TypewriterLayout extends ProgressiveLayout {
193
205
  }
194
206
  }
195
207
  }
196
-
197
- function dumpElementAttributes(element) {
198
- const { attributes } = element;
199
- const attrs = {};
200
- for (const { name, value } of attributes) {
201
- if (name.startsWith('data-')) {
202
- attrs[name] = value;
203
- }
204
- }
205
- try {
206
- return JSON.stringify(attrs);
207
- } catch (e) {
208
- return '{}';
209
- }
210
- }
package/src/plugin.js CHANGED
@@ -1,13 +1,16 @@
1
1
  import { Component, Resolution, defineAndUpgrade, delegateGetters, defineValues } from '@miso.ai/commons';
2
- import { Asks, HybridSearch, Explores, Search, Recommendations } from './workflow/index.js';
2
+ import { WorkflowPlugin } from '@miso.ai/client-sdk-workflow';
3
+ import { defaultLayouts } from './defaults.js';
3
4
  import { AskCombo } from './combo/index.js';
4
5
  import Layouts from './layouts.js';
5
6
  import * as elements from './element/index.js';
6
7
  import * as layouts from './layout/index.js';
7
- import * as sources from './source.js';
8
8
  import * as defaults from './defaults/index.js';
9
+ import { followUp as followUpTemplate } from './defaults/ask/templates.js';
9
10
  import * as traits from './trait/index.js';
10
11
  import { loadStylesIfNecessary } from './styles.js';
12
+ import confirm from './util/confirm.js';
13
+ import prompt from './util/prompt.js';
11
14
 
12
15
  import MisoContainerElement from './element/container/miso-container.js';
13
16
  import MisoContextElement from './element/context/miso-context.js';
@@ -24,20 +27,24 @@ export default class UiPlugin extends Component {
24
27
  constructor() {
25
28
  super('ui');
26
29
  this.layouts = new Layouts(this);
27
- this._recommendations = new WeakMap();
28
- this._explores = new WeakMap();
29
- this._asks = new WeakMap();
30
- this._extensions = new WeakMap();
31
-
32
30
  this._ready = new Resolution();
33
31
  }
34
32
 
35
- async install(MisoClient, { addSubtree, addExtensionPoints }) {
33
+ async install(MisoClient, context) {
34
+ const { addSubtree, addExtensionPoints } = context;
35
+ this._pluginContext = context;
36
36
  addSubtree(this);
37
+
38
+ // make sure the workflow plugin is installed, then provide the layout
39
+ // registry and seed the default layout options
40
+ MisoClient.plugins.use(WorkflowPlugin);
41
+ context.workflows.plugin.setLayouts(this.layouts);
42
+ this._seedWorkflowDefaults(context.workflows.defaults);
43
+
37
44
  MisoClient.on('create', this._injectClient.bind(this));
38
45
 
39
46
  // the UI interface for MisoClient
40
- const ui = { defaults, traits };
47
+ const ui = { defaults, traits, confirm, prompt };
41
48
  delegateGetters(ui, this, ['layouts', 'ready']);
42
49
  defineValues(this, { MisoClient });
43
50
  defineValues(MisoClient, { ui });
@@ -78,78 +85,27 @@ export default class UiPlugin extends Component {
78
85
  this._ready.resolve();
79
86
  }
80
87
 
81
- async requireExtension(name) {
82
- switch (name) {
83
- case 'markdown':
84
- return await this.MisoClient.plugins.install('std:ui-markdown');
85
- default:
86
- throw new Error(`Unknown extension: ${name}`);
87
- }
88
- }
89
-
90
88
  get ready() {
91
89
  return this._ready.promise;
92
90
  }
93
91
 
92
+ /**
93
+ * Seed the workflow plugin's defaults store with the presentational default
94
+ * options: layouts of each workflow, plus UI templates.
95
+ */
96
+ _seedWorkflowDefaults(workflowDefaults) {
97
+ for (const [name, layouts] of Object.entries(defaultLayouts)) {
98
+ workflowDefaults.set(name, { layouts });
99
+ }
100
+ workflowDefaults.set('ask', { templates: { followUp: followUpTemplate } });
101
+ }
102
+
94
103
  _injectClient(client) {
95
104
  defineValues(client, {
96
105
  ui: new Ui(this, client),
97
106
  });
98
107
  }
99
108
 
100
- _getExtensions(client) {
101
- if (this._extensions.has(client)) {
102
- return this._extensions.get(client);
103
- }
104
- const extensions = new Extensions(this, client);
105
- this._extensions.set(client, extensions);
106
- return extensions;
107
- }
108
-
109
- _getRecommendations(client) {
110
- let recommendations = this._recommendations.get(client);
111
- if (!recommendations) {
112
- this._recommendations.set(client, recommendations = new Recommendations(this, client));
113
- }
114
- return recommendations;
115
- }
116
-
117
- _getExplores(client) {
118
- let explores = this._explores.get(client);
119
- if (!explores) {
120
- this._explores.set(client, explores = new Explores(this, client));
121
- }
122
- return explores;
123
- }
124
-
125
- _getAsks(client) {
126
- let asks = this._asks.get(client);
127
- if (!asks) {
128
- this._asks.set(client, asks = new Asks(this, client));
129
- }
130
- return asks;
131
- }
132
-
133
- }
134
-
135
- // TODO: are we able to eliminate the concept of extension, just use plugins?
136
- class Extensions {
137
-
138
- constructor(plugin, client) {
139
- this._plugin = plugin;
140
- this._client = client;
141
- this._contexts = {};
142
- }
143
-
144
- async require(name) {
145
- if (this._contexts[name]) {
146
- return this._contexts[name];
147
- }
148
- const plugin = await this._plugin.requireExtension(name);
149
- const context = this._contexts[name] = plugin.getContext(this._client);
150
- return context;
151
- }
152
-
153
109
  }
154
110
 
155
111
  function createUiPluginContext(plugin, ui, MisoClient) {
@@ -174,6 +130,9 @@ function createUiPluginContext(plugin, ui, MisoClient) {
174
130
  return { plugin, addElementClasses, addLayoutClasses, addInterface };
175
131
  }
176
132
 
133
+ // workflow accessors delegated to client.workflows for backward compatibility
134
+ const WORKFLOW_ACCESSORS = ['search', 'hybridSearch', 'asks', 'ask', 'explores', 'explore', 'recommendations', 'recommendation', 'sources'];
135
+
177
136
  /**
178
137
  * The UI interface for the client.
179
138
  */
@@ -182,52 +141,7 @@ class Ui {
182
141
  constructor(plugin, client) {
183
142
  this._plugin = plugin;
184
143
  this._client = client;
185
-
186
- defineValues(this, {
187
- sources: {
188
- api: sources.api(client),
189
- },
190
- });
191
- }
192
-
193
- get recommendations() {
194
- return this._recommendations || (this._recommendations = this._plugin._getRecommendations(this._client));
195
- }
196
-
197
- get recommendation() {
198
- return this.recommendations.get(); // get default recommendation unit
199
- }
200
-
201
- get search() {
202
- if (!this._search) {
203
- this._search = new Search(this._plugin, this._client);
204
- this._client._events.emit('postworkflow', this._search);
205
- }
206
- return this._search;
207
- }
208
-
209
- get asks() {
210
- return this._asks || (this._asks = this._plugin._getAsks(this._client));
211
- }
212
-
213
- get ask() {
214
- return this.asks.root;
215
- }
216
-
217
- get hybridSearch() {
218
- if (!this._hybridSearch) {
219
- this._hybridSearch = new HybridSearch(this._plugin, this._client);
220
- this._client._events.emit('postworkflow', this._hybridSearch);
221
- }
222
- return this._hybridSearch;
223
- }
224
-
225
- get explores() {
226
- return this._explores || (this._explores = this._plugin._getExplores(this._client));
227
- }
228
-
229
- get explore() {
230
- return this.explores.get(); // get default explore unit
144
+ delegateGetters(this, client.workflows, WORKFLOW_ACCESSORS);
231
145
  }
232
146
 
233
147
  get ready() {
@@ -0,0 +1,28 @@
1
+ import { openModal } from './modal.js';
2
+
3
+ const DEFAULT_CLASSNAME = 'miso-confirm';
4
+
5
+ /**
6
+ * Show a modal confirmation dialog (a native <dialog>), resolving to whether
7
+ * the user confirmed. Cancel button, backdrop click, and the Escape key all
8
+ * resolve to false. Exposed as `MisoClient.ui.confirm()`.
9
+ */
10
+ export default function confirm({
11
+ title,
12
+ message,
13
+ confirmText = 'Confirm',
14
+ cancelText = 'Cancel',
15
+ danger = false,
16
+ className = DEFAULT_CLASSNAME,
17
+ } = {}) {
18
+ return openModal({
19
+ className,
20
+ title,
21
+ message,
22
+ confirmText,
23
+ cancelText,
24
+ danger,
25
+ result: () => true,
26
+ cancelValue: false,
27
+ });
28
+ }