@assistant-ui/vue 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -2
  3. package/dist/AuiIf.d.ts +27 -0
  4. package/dist/AuiIf.d.ts.map +1 -0
  5. package/dist/AuiIf.js +30 -0
  6. package/dist/AuiIf.js.map +1 -0
  7. package/dist/AuiProvider.d.ts +41 -0
  8. package/dist/AuiProvider.d.ts.map +1 -0
  9. package/dist/AuiProvider.js +61 -0
  10. package/dist/AuiProvider.js.map +1 -0
  11. package/dist/context.d.ts +10 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/context.js +17 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/index.d.ts +34 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +35 -0
  18. package/dist/primitives/AttachmentByIndexProvider.d.ts +30 -0
  19. package/dist/primitives/AttachmentByIndexProvider.d.ts.map +1 -0
  20. package/dist/primitives/AttachmentByIndexProvider.js +63 -0
  21. package/dist/primitives/AttachmentByIndexProvider.js.map +1 -0
  22. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.d.ts +11 -0
  23. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.d.ts.map +1 -0
  24. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.js +31 -0
  25. package/dist/primitives/ChainOfThoughtPrimitiveAccordionTrigger.js.map +1 -0
  26. package/dist/primitives/ChainOfThoughtPrimitiveParts.d.ts +19 -0
  27. package/dist/primitives/ChainOfThoughtPrimitiveParts.d.ts.map +1 -0
  28. package/dist/primitives/ChainOfThoughtPrimitiveParts.js +85 -0
  29. package/dist/primitives/ChainOfThoughtPrimitiveParts.js.map +1 -0
  30. package/dist/primitives/ComposerPrimitiveCancel.d.ts +12 -0
  31. package/dist/primitives/ComposerPrimitiveCancel.d.ts.map +1 -0
  32. package/dist/primitives/ComposerPrimitiveCancel.js +33 -0
  33. package/dist/primitives/ComposerPrimitiveCancel.js.map +1 -0
  34. package/dist/primitives/ComposerPrimitiveInput.d.ts +26 -0
  35. package/dist/primitives/ComposerPrimitiveInput.d.ts.map +1 -0
  36. package/dist/primitives/ComposerPrimitiveInput.js +57 -0
  37. package/dist/primitives/ComposerPrimitiveInput.js.map +1 -0
  38. package/dist/primitives/ComposerPrimitiveSend.d.ts +13 -0
  39. package/dist/primitives/ComposerPrimitiveSend.d.ts.map +1 -0
  40. package/dist/primitives/ComposerPrimitiveSend.js +31 -0
  41. package/dist/primitives/ComposerPrimitiveSend.js.map +1 -0
  42. package/dist/primitives/MessageByIdProvider.d.ts +23 -0
  43. package/dist/primitives/MessageByIdProvider.d.ts.map +1 -0
  44. package/dist/primitives/MessageByIdProvider.js +70 -0
  45. package/dist/primitives/MessageByIdProvider.js.map +1 -0
  46. package/dist/primitives/MessagePrimitiveParts.d.ts +50 -0
  47. package/dist/primitives/MessagePrimitiveParts.d.ts.map +1 -0
  48. package/dist/primitives/MessagePrimitiveParts.js +93 -0
  49. package/dist/primitives/MessagePrimitiveParts.js.map +1 -0
  50. package/dist/primitives/PartByIndexProvider.d.ts +21 -0
  51. package/dist/primitives/PartByIndexProvider.d.ts.map +1 -0
  52. package/dist/primitives/PartByIndexProvider.js +50 -0
  53. package/dist/primitives/PartByIndexProvider.js.map +1 -0
  54. package/dist/primitives/ThreadListItemPrimitiveRoot.d.ts +7 -0
  55. package/dist/primitives/ThreadListItemPrimitiveRoot.d.ts.map +1 -0
  56. package/dist/primitives/ThreadListItemPrimitiveRoot.js +36 -0
  57. package/dist/primitives/ThreadListItemPrimitiveRoot.js.map +1 -0
  58. package/dist/primitives/ThreadListItemPrimitiveTrigger.d.ts +14 -0
  59. package/dist/primitives/ThreadListItemPrimitiveTrigger.d.ts.map +1 -0
  60. package/dist/primitives/ThreadListItemPrimitiveTrigger.js +58 -0
  61. package/dist/primitives/ThreadListItemPrimitiveTrigger.js.map +1 -0
  62. package/dist/primitives/ThreadListPrimitiveRoot.d.ts +7 -0
  63. package/dist/primitives/ThreadListPrimitiveRoot.d.ts.map +1 -0
  64. package/dist/primitives/ThreadListPrimitiveRoot.js +16 -0
  65. package/dist/primitives/ThreadListPrimitiveRoot.js.map +1 -0
  66. package/dist/primitives/ThreadPrimitiveMessages.d.ts +23 -0
  67. package/dist/primitives/ThreadPrimitiveMessages.d.ts.map +1 -0
  68. package/dist/primitives/ThreadPrimitiveMessages.js +37 -0
  69. package/dist/primitives/ThreadPrimitiveMessages.js.map +1 -0
  70. package/dist/primitives/ThreadPrimitiveScrollToBottom.d.ts +25 -0
  71. package/dist/primitives/ThreadPrimitiveScrollToBottom.d.ts.map +1 -0
  72. package/dist/primitives/ThreadPrimitiveScrollToBottom.js +43 -0
  73. package/dist/primitives/ThreadPrimitiveScrollToBottom.js.map +1 -0
  74. package/dist/primitives/ThreadPrimitiveViewport.d.ts +57 -0
  75. package/dist/primitives/ThreadPrimitiveViewport.d.ts.map +1 -0
  76. package/dist/primitives/ThreadPrimitiveViewport.js +174 -0
  77. package/dist/primitives/ThreadPrimitiveViewport.js.map +1 -0
  78. package/dist/primitives/ThreadPrimitiveViewportFooter.d.ts +13 -0
  79. package/dist/primitives/ThreadPrimitiveViewportFooter.d.ts.map +1 -0
  80. package/dist/primitives/ThreadPrimitiveViewportFooter.js +46 -0
  81. package/dist/primitives/ThreadPrimitiveViewportFooter.js.map +1 -0
  82. package/dist/primitives/actionBar.d.ts +41 -0
  83. package/dist/primitives/actionBar.d.ts.map +1 -0
  84. package/dist/primitives/actionBar.js +110 -0
  85. package/dist/primitives/actionBar.js.map +1 -0
  86. package/dist/primitives/attachment.d.ts +27 -0
  87. package/dist/primitives/attachment.d.ts.map +1 -0
  88. package/dist/primitives/attachment.js +63 -0
  89. package/dist/primitives/attachment.js.map +1 -0
  90. package/dist/primitives/attrDisabled.d.ts +2 -0
  91. package/dist/primitives/attrDisabled.d.ts.map +1 -0
  92. package/dist/primitives/attrDisabled.js +6 -0
  93. package/dist/primitives/attrDisabled.js.map +1 -0
  94. package/dist/primitives/branchPicker.d.ts +18 -0
  95. package/dist/primitives/branchPicker.d.ts.map +1 -0
  96. package/dist/primitives/branchPicker.js +48 -0
  97. package/dist/primitives/branchPicker.js.map +1 -0
  98. package/dist/primitives/composerAttachments.d.ts +57 -0
  99. package/dist/primitives/composerAttachments.d.ts.map +1 -0
  100. package/dist/primitives/composerAttachments.js +132 -0
  101. package/dist/primitives/composerAttachments.js.map +1 -0
  102. package/dist/primitives/error.d.ts +15 -0
  103. package/dist/primitives/error.d.ts.map +1 -0
  104. package/dist/primitives/error.js +29 -0
  105. package/dist/primitives/error.js.map +1 -0
  106. package/dist/primitives/lastValidCache.d.ts +6 -0
  107. package/dist/primitives/lastValidCache.d.ts.map +1 -0
  108. package/dist/primitives/lastValidCache.js +9 -0
  109. package/dist/primitives/lastValidCache.js.map +1 -0
  110. package/dist/primitives/message.d.ts +12 -0
  111. package/dist/primitives/message.d.ts.map +1 -0
  112. package/dist/primitives/message.js +44 -0
  113. package/dist/primitives/message.js.map +1 -0
  114. package/dist/primitives/messageAttachments.d.ts +12 -0
  115. package/dist/primitives/messageAttachments.d.ts.map +1 -0
  116. package/dist/primitives/messageAttachments.js +27 -0
  117. package/dist/primitives/messageAttachments.js.map +1 -0
  118. package/dist/primitives/stableKeys.d.ts +5 -0
  119. package/dist/primitives/stableKeys.d.ts.map +1 -0
  120. package/dist/primitives/stableKeys.js +14 -0
  121. package/dist/primitives/stableKeys.js.map +1 -0
  122. package/dist/primitives/suggestions.d.ts +66 -0
  123. package/dist/primitives/suggestions.d.ts.map +1 -0
  124. package/dist/primitives/suggestions.js +137 -0
  125. package/dist/primitives/suggestions.js.map +1 -0
  126. package/dist/primitives/thread.d.ts +11 -0
  127. package/dist/primitives/thread.d.ts.map +1 -0
  128. package/dist/primitives/thread.js +37 -0
  129. package/dist/primitives/thread.js.map +1 -0
  130. package/dist/primitives/threadList.d.ts +82 -0
  131. package/dist/primitives/threadList.d.ts.map +1 -0
  132. package/dist/primitives/threadList.js +128 -0
  133. package/dist/primitives/threadList.js.map +1 -0
  134. package/dist/primitives/threadListFocusGroup.d.ts +14 -0
  135. package/dist/primitives/threadListFocusGroup.d.ts.map +1 -0
  136. package/dist/primitives/threadListFocusGroup.js +32 -0
  137. package/dist/primitives/threadListFocusGroup.js.map +1 -0
  138. package/dist/primitives/threadListStructural.d.ts +29 -0
  139. package/dist/primitives/threadListStructural.d.ts.map +1 -0
  140. package/dist/primitives/threadListStructural.js +55 -0
  141. package/dist/primitives/threadListStructural.js.map +1 -0
  142. package/dist/primitives/useComposerSendState.d.ts +6 -0
  143. package/dist/primitives/useComposerSendState.d.ts.map +1 -0
  144. package/dist/primitives/useComposerSendState.js +15 -0
  145. package/dist/primitives/useComposerSendState.js.map +1 -0
  146. package/dist/primitives/viewportContext.d.ts +12 -0
  147. package/dist/primitives/viewportContext.d.ts.map +1 -0
  148. package/dist/primitives/viewportContext.js +6 -0
  149. package/dist/primitives/viewportContext.js.map +1 -0
  150. package/dist/useAui.d.ts +15 -0
  151. package/dist/useAui.d.ts.map +1 -0
  152. package/dist/useAui.js +21 -0
  153. package/dist/useAui.js.map +1 -0
  154. package/dist/useAuiEvent.d.ts +17 -0
  155. package/dist/useAuiEvent.d.ts.map +1 -0
  156. package/dist/useAuiEvent.js +52 -0
  157. package/dist/useAuiEvent.js.map +1 -0
  158. package/dist/useAuiState.d.ts +23 -0
  159. package/dist/useAuiState.d.ts.map +1 -0
  160. package/dist/useAuiState.js +42 -0
  161. package/dist/useAuiState.js.map +1 -0
  162. package/dist/useScrollLock.d.ts +3 -0
  163. package/dist/useScrollLock.d.ts.map +1 -0
  164. package/dist/useScrollLock.js +53 -0
  165. package/dist/useScrollLock.js.map +1 -0
  166. package/package.json +57 -6
  167. package/src/AuiIf.ts +34 -0
  168. package/src/AuiProvider.ts +90 -0
  169. package/src/__tests__/AuiIf.test.ts +54 -0
  170. package/src/__tests__/AuiProvider.test.ts +266 -0
  171. package/src/__tests__/byindex-shrink-race.test.ts +411 -0
  172. package/src/__tests__/dist-graph.test.ts +49 -0
  173. package/src/__tests__/fixtures.ts +51 -0
  174. package/src/__tests__/primitives-actionbar.test.ts +469 -0
  175. package/src/__tests__/primitives-attachment.test.ts +493 -0
  176. package/src/__tests__/primitives-content.test.ts +749 -0
  177. package/src/__tests__/primitives-keyed-items.test.ts +270 -0
  178. package/src/__tests__/primitives-keyed-messages.test.ts +264 -0
  179. package/src/__tests__/primitives-structural.test.ts +573 -0
  180. package/src/__tests__/primitives-suggestions.test.ts +374 -0
  181. package/src/__tests__/primitives-threadlist.test.ts +443 -0
  182. package/src/__tests__/primitives-tool-ui.test.ts +579 -0
  183. package/src/__tests__/primitives.test.ts +361 -0
  184. package/src/__tests__/useAuiEvent.test.ts +159 -0
  185. package/src/__tests__/useAuiState.test.ts +124 -0
  186. package/src/context.ts +31 -0
  187. package/src/index.ts +87 -0
  188. package/src/primitives/AttachmentByIndexProvider.ts +88 -0
  189. package/src/primitives/ChainOfThoughtPrimitiveAccordionTrigger.test.ts +105 -0
  190. package/src/primitives/ChainOfThoughtPrimitiveAccordionTrigger.ts +38 -0
  191. package/src/primitives/ChainOfThoughtPrimitiveParts.test.ts +254 -0
  192. package/src/primitives/ChainOfThoughtPrimitiveParts.ts +112 -0
  193. package/src/primitives/ComposerPrimitiveCancel.ts +41 -0
  194. package/src/primitives/ComposerPrimitiveInput.ts +64 -0
  195. package/src/primitives/ComposerPrimitiveSend.ts +39 -0
  196. package/src/primitives/MessageByIdProvider.ts +89 -0
  197. package/src/primitives/MessagePrimitiveParts.tool-interaction.test.ts +88 -0
  198. package/src/primitives/MessagePrimitiveParts.ts +166 -0
  199. package/src/primitives/PartByIndexProvider.ts +62 -0
  200. package/src/primitives/ThreadListItemPrimitiveRoot.ts +57 -0
  201. package/src/primitives/ThreadListItemPrimitiveTrigger.ts +74 -0
  202. package/src/primitives/ThreadListKeyboardNav.test.ts +298 -0
  203. package/src/primitives/ThreadListPrimitiveRoot.ts +18 -0
  204. package/src/primitives/ThreadPrimitiveMessages.ts +40 -0
  205. package/src/primitives/ThreadPrimitiveScrollToBottom.ts +65 -0
  206. package/src/primitives/ThreadPrimitiveViewport.ts +233 -0
  207. package/src/primitives/ThreadPrimitiveViewportFooter.test.ts +305 -0
  208. package/src/primitives/ThreadPrimitiveViewportFooter.ts +60 -0
  209. package/src/primitives/actionBar.ts +159 -0
  210. package/src/primitives/attachment.ts +76 -0
  211. package/src/primitives/attrDisabled.ts +6 -0
  212. package/src/primitives/branchPicker.ts +77 -0
  213. package/src/primitives/composerAttachments.ts +181 -0
  214. package/src/primitives/error.ts +36 -0
  215. package/src/primitives/lastValidCache.ts +12 -0
  216. package/src/primitives/message.ts +54 -0
  217. package/src/primitives/messageAttachments.ts +32 -0
  218. package/src/primitives/stableKeys.test.ts +19 -0
  219. package/src/primitives/stableKeys.ts +21 -0
  220. package/src/primitives/suggestions.ts +179 -0
  221. package/src/primitives/thread.ts +46 -0
  222. package/src/primitives/threadList.ts +163 -0
  223. package/src/primitives/threadListFocusGroup.ts +56 -0
  224. package/src/primitives/threadListStructural.ts +86 -0
  225. package/src/primitives/useComposerSendState.ts +13 -0
  226. package/src/primitives/viewportContext.ts +16 -0
  227. package/src/useAui.ts +18 -0
  228. package/src/useAuiEvent.ts +64 -0
  229. package/src/useAuiState.ts +55 -0
  230. package/src/useScrollLock.test.ts +207 -0
  231. package/src/useScrollLock.ts +79 -0
@@ -0,0 +1,181 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ ref,
6
+ watch,
7
+ type SlotsType,
8
+ type VNodeChild,
9
+ } from "vue";
10
+ import { isAttrDisabled } from "./attrDisabled";
11
+ import { useAui } from "../useAui";
12
+ import { useAuiState } from "../useAuiState";
13
+ import { AttachmentByIndexProvider } from "./AttachmentByIndexProvider";
14
+ import { getAttachmentKeys, useStableKeys } from "./stableKeys";
15
+
16
+ /**
17
+ * Renders the composer's pending attachments in order, each scoped through
18
+ * {@link AttachmentByIndexProvider}; the `default` slot renders each
19
+ * attachment.
20
+ */
21
+ export const ComposerPrimitiveAttachments = defineComponent({
22
+ name: "ComposerPrimitiveAttachments",
23
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
24
+ setup(_, { slots }) {
25
+ const attachments = useAuiState((s) => s.composer.attachments);
26
+ const attachmentKeys = useStableKeys(() =>
27
+ getAttachmentKeys(attachments.value),
28
+ );
29
+ return () =>
30
+ attachmentKeys.value.map((key, index) =>
31
+ h(
32
+ AttachmentByIndexProvider,
33
+ { source: "composer", index, key },
34
+ { default: () => slots.default?.() },
35
+ ),
36
+ );
37
+ },
38
+ });
39
+
40
+ /**
41
+ * A button that opens a file picker and adds the selected files to the
42
+ * composer. Disabled while the composer is not editable. The picker's accept
43
+ * filter follows the composer's `attachmentAccept`.
44
+ */
45
+ export const ComposerPrimitiveAddAttachment = defineComponent({
46
+ name: "ComposerPrimitiveAddAttachment",
47
+ inheritAttrs: false,
48
+ props: {
49
+ multiple: {
50
+ type: Boolean,
51
+ default: true,
52
+ },
53
+ },
54
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
55
+ setup(props, { attrs, slots }) {
56
+ const aui = useAui();
57
+ const disabled = useAuiState((s) => !s.composer.isEditing);
58
+ const onClick = (event: MouseEvent) => {
59
+ if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
60
+ return;
61
+
62
+ const input = document.createElement("input");
63
+ input.type = "file";
64
+ input.multiple = props.multiple;
65
+ input.hidden = true;
66
+
67
+ const attachmentAccept = aui.composer.getState().attachmentAccept;
68
+ if (attachmentAccept !== "*") {
69
+ input.accept = attachmentAccept;
70
+ }
71
+
72
+ document.body.appendChild(input);
73
+
74
+ input.onchange = (e) => {
75
+ const fileList = (e.target as HTMLInputElement).files;
76
+ if (fileList) {
77
+ for (const file of Array.from(fileList)) {
78
+ aui.composer.addAttachment(file).catch(() => {});
79
+ }
80
+ }
81
+ input.remove();
82
+ };
83
+ input.oncancel = () => input.remove();
84
+
85
+ input.click();
86
+ };
87
+ return () =>
88
+ h(
89
+ "button",
90
+ mergeProps(attrs, {
91
+ type: "button",
92
+ disabled: disabled.value || isAttrDisabled(attrs),
93
+ onClick,
94
+ }),
95
+ slots.default?.(),
96
+ );
97
+ },
98
+ });
99
+
100
+ /**
101
+ * A drop target that adds dropped files to the composer. `data-dragging` is
102
+ * present while a file drag hovers the element. File drags are claimed via
103
+ * `preventDefault` even when the runtime does not support attachments, since
104
+ * an unprevented file drop navigates the tab to the file.
105
+ */
106
+ export const ComposerPrimitiveAttachmentDropzone = defineComponent({
107
+ name: "ComposerPrimitiveAttachmentDropzone",
108
+ inheritAttrs: false,
109
+ props: {
110
+ disabled: {
111
+ type: Boolean,
112
+ default: false,
113
+ },
114
+ },
115
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
116
+ setup(props, { attrs, slots }) {
117
+ const aui = useAui();
118
+ const isDragging = ref(false);
119
+
120
+ watch(
121
+ () => props.disabled,
122
+ (disabled) => {
123
+ if (disabled) isDragging.value = false;
124
+ },
125
+ );
126
+
127
+ const isFileDrag = (event: DragEvent) =>
128
+ event.dataTransfer?.types.includes("Files") === true;
129
+
130
+ const onDragenterCapture = (event: DragEvent) => {
131
+ if (props.disabled || !isFileDrag(event)) return;
132
+ event.preventDefault();
133
+ if (!aui.thread.getState().capabilities.attachments) {
134
+ event.dataTransfer!.dropEffect = "none";
135
+ return;
136
+ }
137
+ isDragging.value = true;
138
+ };
139
+ const onDragoverCapture = (event: DragEvent) => {
140
+ if (props.disabled || !isFileDrag(event)) return;
141
+ event.preventDefault();
142
+ if (!aui.thread.getState().capabilities.attachments) {
143
+ event.dataTransfer!.dropEffect = "none";
144
+ return;
145
+ }
146
+ if (!isDragging.value) isDragging.value = true;
147
+ };
148
+ const onDragleaveCapture = (event: DragEvent) => {
149
+ if (props.disabled || !isFileDrag(event)) return;
150
+ const related = event.relatedTarget as Node | null;
151
+ if (related && (event.currentTarget as Node).contains(related)) return;
152
+ isDragging.value = false;
153
+ };
154
+ const onDropCapture = (event: DragEvent) => {
155
+ if (props.disabled) return;
156
+ isDragging.value = false;
157
+ if (!isFileDrag(event)) return;
158
+ event.preventDefault();
159
+ const files = Array.from(event.dataTransfer?.files ?? []);
160
+ if (!aui.thread.getState().capabilities.attachments || files.length === 0)
161
+ return;
162
+ for (const file of files) {
163
+ aui.composer.addAttachment(file).catch(() => {});
164
+ }
165
+ };
166
+
167
+ return () =>
168
+ h(
169
+ "div",
170
+ mergeProps(attrs, {
171
+ ...(!props.disabled &&
172
+ isDragging.value && { "data-dragging": "true" }),
173
+ onDragenterCapture,
174
+ onDragoverCapture,
175
+ onDragleaveCapture,
176
+ onDropCapture,
177
+ }),
178
+ slots.default?.(),
179
+ );
180
+ },
181
+ });
@@ -0,0 +1,36 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ type SlotsType,
6
+ type VNodeChild,
7
+ } from "vue";
8
+ import { messageErrorText } from "@assistant-ui/core/store/internal";
9
+ import { useAuiState } from "../useAuiState";
10
+
11
+ /** A wrapper element for the current message's error state. */
12
+ export const ErrorPrimitiveRoot = defineComponent({
13
+ name: "ErrorPrimitiveRoot",
14
+ inheritAttrs: false,
15
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
16
+ setup(_, { attrs, slots }) {
17
+ return () =>
18
+ h("div", mergeProps({ role: "alert" }, attrs), slots.default?.());
19
+ },
20
+ });
21
+
22
+ /**
23
+ * Renders the current assistant message's error text (raw text, slot
24
+ * override supported); renders nothing while the message has no error.
25
+ */
26
+ export const ErrorPrimitiveMessage = defineComponent({
27
+ name: "ErrorPrimitiveMessage",
28
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
29
+ setup(_, { slots }) {
30
+ const error = useAuiState(messageErrorText);
31
+ return () =>
32
+ error.value === undefined
33
+ ? null
34
+ : (slots.default?.() ?? String(error.value));
35
+ },
36
+ });
@@ -0,0 +1,12 @@
1
+ import { nextTick } from "vue";
2
+ import {
3
+ createLastValidCache as createNeutralLastValidCache,
4
+ createStaleReporter,
5
+ } from "@assistant-ui/store/client";
6
+
7
+ const scheduleExpiry = (callback: () => void) => void nextTick(callback);
8
+
9
+ export const createLastValidCache = <T>(reportStale: (() => void) | null) =>
10
+ createNeutralLastValidCache<T>(reportStale, scheduleExpiry);
11
+
12
+ export { createStaleReporter };
@@ -0,0 +1,54 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ onScopeDispose,
6
+ type SlotsType,
7
+ type VNodeChild,
8
+ } from "vue";
9
+ import { useAui } from "../useAui";
10
+ import { useAuiState } from "../useAuiState";
11
+
12
+ /**
13
+ * A wrapper element for one message. Sets `data-message-id` and tracks
14
+ * pointer hover into `s.message.isHovering`, including an element already
15
+ * under the pointer when it mounts.
16
+ */
17
+ export const MessagePrimitiveRoot = defineComponent({
18
+ name: "MessagePrimitiveRoot",
19
+ inheritAttrs: false,
20
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
21
+ setup(_, { attrs, slots }) {
22
+ const aui = useAui();
23
+ const messageId = useAuiState((s) => s.message.id);
24
+ const onMouseenter = () => {
25
+ aui.message.setIsHovering(true);
26
+ };
27
+ const onMouseleave = () => {
28
+ aui.message.setIsHovering(false);
29
+ };
30
+ let active = true;
31
+ onScopeDispose(() => {
32
+ active = false;
33
+ onMouseleave();
34
+ });
35
+ const hoverRef = (el: unknown) => {
36
+ if (el instanceof HTMLElement && el.matches(":hover")) {
37
+ queueMicrotask(() => {
38
+ if (active) onMouseenter();
39
+ });
40
+ }
41
+ };
42
+ return () =>
43
+ h(
44
+ "div",
45
+ mergeProps(attrs, {
46
+ ref: hoverRef,
47
+ "data-message-id": messageId.value,
48
+ onMouseenter,
49
+ onMouseleave,
50
+ }),
51
+ slots.default?.(),
52
+ );
53
+ },
54
+ });
@@ -0,0 +1,32 @@
1
+ import { defineComponent, h, type SlotsType, type VNodeChild } from "vue";
2
+ import { useAuiState } from "../useAuiState";
3
+ import { AttachmentByIndexProvider } from "./AttachmentByIndexProvider";
4
+ import { getAttachmentKeys, useStableKeys } from "./stableKeys";
5
+
6
+ /**
7
+ * Renders the current message's attachments in order, each scoped through
8
+ * {@link AttachmentByIndexProvider}; the `default` slot renders each
9
+ * attachment.
10
+ */
11
+ export const MessagePrimitiveAttachments = defineComponent({
12
+ name: "MessagePrimitiveAttachments",
13
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
14
+ setup(_, { slots }) {
15
+ const attachments = useAuiState((s) =>
16
+ s.message.role === "user"
17
+ ? (s.message.submission?.attachments ?? s.message.attachments)
18
+ : undefined,
19
+ );
20
+ const attachmentKeys = useStableKeys(() =>
21
+ getAttachmentKeys(attachments.value ?? []),
22
+ );
23
+ return () =>
24
+ attachmentKeys.value.map((key, index) =>
25
+ h(
26
+ AttachmentByIndexProvider,
27
+ { source: "message", index, key },
28
+ { default: () => slots.default?.() },
29
+ ),
30
+ );
31
+ },
32
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getAttachmentKeys } from "./stableKeys";
3
+
4
+ describe("getAttachmentKeys", () => {
5
+ it("keys unique attachments by id and repeated ids by position", () => {
6
+ expect(
7
+ getAttachmentKeys([{ id: "a" }, { id: "b" }, { id: "a" }, { id: "c" }]),
8
+ ).toEqual(["attachment@0", "attachment:b", "attachment@2", "attachment:c"]);
9
+ });
10
+
11
+ it("never lets an id collide with a positional key", () => {
12
+ const keys = getAttachmentKeys([
13
+ { id: "x" },
14
+ { id: "x" },
15
+ { id: "attachment@0" },
16
+ ]);
17
+ expect(new Set(keys).size).toBe(keys.length);
18
+ });
19
+ });
@@ -0,0 +1,21 @@
1
+ import { computed } from "vue";
2
+
3
+ export const useStableKeys = (getKeys: () => string[]) =>
4
+ computed<string[]>((previous) => {
5
+ const keys = getKeys();
6
+ return previous?.length === keys.length &&
7
+ previous.every((key, index) => key === keys[index])
8
+ ? previous
9
+ : keys;
10
+ });
11
+
12
+ export const getAttachmentKeys = (
13
+ attachments: readonly { readonly id: string }[],
14
+ ) => {
15
+ const ids = attachments.map((attachment) => attachment.id);
16
+ return ids.map((id, index) =>
17
+ ids.indexOf(id) === ids.lastIndexOf(id)
18
+ ? `attachment:${id}`
19
+ : `attachment@${index}`,
20
+ );
21
+ };
@@ -0,0 +1,179 @@
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ h,
5
+ mergeProps,
6
+ onScopeDispose,
7
+ type SlotsType,
8
+ type VNodeChild,
9
+ } from "vue";
10
+ import { AuiConfig, Derived } from "@assistant-ui/store/client";
11
+ import { flushTapSync } from "@assistant-ui/tap";
12
+ import type { SuggestionMethods } from "@assistant-ui/core/store";
13
+ import { getSuggestionKeys } from "@assistant-ui/core/internal";
14
+ import {
15
+ suggestionSendMode,
16
+ suggestionTriggerDisabled,
17
+ } from "@assistant-ui/core/store/internal";
18
+ import { AuiProvider } from "../AuiProvider";
19
+ import { isAttrDisabled } from "./attrDisabled";
20
+ import { createLastValidCache, createStaleReporter } from "./lastValidCache";
21
+ import { useStableKeys } from "./stableKeys";
22
+ import { useAui } from "../useAui";
23
+ import { useAuiState } from "../useAuiState";
24
+
25
+ /**
26
+ * Scopes the subtree to the suggestion at `index`: descendants read it
27
+ * through `s.suggestion`.
28
+ */
29
+ export const SuggestionByIndexProvider = defineComponent({
30
+ name: "SuggestionByIndexProvider",
31
+ props: {
32
+ index: {
33
+ type: Number,
34
+ required: true,
35
+ },
36
+ },
37
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
38
+ setup(props, { slots }) {
39
+ const aui = useAui();
40
+ let disposed = false;
41
+ onScopeDispose(() => {
42
+ disposed = true;
43
+ });
44
+ const config = computed(() => {
45
+ const index = props.index;
46
+ const cache = createLastValidCache<SuggestionMethods>(
47
+ createStaleReporter({
48
+ name: "SuggestionByIndexProvider",
49
+ index,
50
+ isCurrent: () => !disposed && index === props.index,
51
+ isValid: () => index < aui.suggestions.getState().suggestions.length,
52
+ }),
53
+ );
54
+ return AuiConfig({
55
+ suggestion: Derived({
56
+ source: "suggestions",
57
+ query: { index },
58
+ get: (aui) =>
59
+ cache.resolve(
60
+ index < aui.suggestions.getState().suggestions.length,
61
+ () => aui.suggestions.suggestion({ index }),
62
+ ),
63
+ }),
64
+ });
65
+ });
66
+ return () =>
67
+ h(
68
+ AuiProvider,
69
+ { config: config.value, extends: aui },
70
+ { default: () => slots.default?.() },
71
+ );
72
+ },
73
+ });
74
+
75
+ /**
76
+ * Renders the default slot once per thread suggestion, each instance scoped
77
+ * through {@link SuggestionByIndexProvider}.
78
+ */
79
+ export const ThreadPrimitiveSuggestions = defineComponent({
80
+ name: "ThreadPrimitiveSuggestions",
81
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
82
+ setup(_, { slots }) {
83
+ const suggestions = useAuiState((s) => s.suggestions.suggestions);
84
+ const suggestionKeys = useStableKeys(() =>
85
+ getSuggestionKeys(suggestions.value),
86
+ );
87
+ return () =>
88
+ suggestionKeys.value.map((key, index) =>
89
+ h(
90
+ SuggestionByIndexProvider,
91
+ { index, key },
92
+ { default: () => slots.default?.() },
93
+ ),
94
+ );
95
+ },
96
+ });
97
+
98
+ /**
99
+ * A button that triggers the current suggestion. With `send`, the prompt is
100
+ * appended as a message (queued sends never clear a draft the user is still
101
+ * composing); without it, the prompt replaces the composer text, or is
102
+ * appended to it when `clearComposer` is false.
103
+ */
104
+ export const SuggestionPrimitiveTrigger = defineComponent({
105
+ name: "SuggestionPrimitiveTrigger",
106
+ inheritAttrs: false,
107
+ props: {
108
+ send: {
109
+ type: Boolean,
110
+ default: false,
111
+ },
112
+ clearComposer: {
113
+ type: Boolean,
114
+ default: true,
115
+ },
116
+ },
117
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
118
+ setup(props, { attrs, slots }) {
119
+ const aui = useAui();
120
+ const prompt = useAuiState((s) => s.suggestion.prompt);
121
+ const disabled = useAuiState((s) =>
122
+ suggestionTriggerDisabled(s, props.send),
123
+ );
124
+
125
+ const onClick = (event: MouseEvent) => {
126
+ if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
127
+ return;
128
+ flushTapSync(() => {
129
+ if (props.send) {
130
+ const mode = suggestionSendMode(aui.thread.getState());
131
+ if (mode === "blocked") return;
132
+ aui.thread.append({
133
+ content: [{ type: "text", text: prompt.value }],
134
+ runConfig: aui.composer.getState().runConfig,
135
+ });
136
+ if (props.clearComposer && mode === "now") {
137
+ aui.composer.setText("");
138
+ }
139
+ } else if (props.clearComposer) {
140
+ aui.composer.setText(prompt.value);
141
+ } else {
142
+ const currentText = aui.composer.getState().text;
143
+ aui.composer.setText(
144
+ [currentText, prompt.value].filter((part) => part.trim()).join(" "),
145
+ );
146
+ }
147
+ });
148
+ };
149
+
150
+ return () =>
151
+ h(
152
+ "button",
153
+ mergeProps(attrs, {
154
+ type: "button",
155
+ disabled: disabled.value || isAttrDisabled(attrs),
156
+ onClick,
157
+ }),
158
+ slots.default?.(),
159
+ );
160
+ },
161
+ });
162
+
163
+ /** Renders the current suggestion's title as text. */
164
+ export const SuggestionPrimitiveTitle = defineComponent({
165
+ name: "SuggestionPrimitiveTitle",
166
+ setup() {
167
+ const title = useAuiState((s) => s.suggestion.title);
168
+ return () => title.value;
169
+ },
170
+ });
171
+
172
+ /** Renders the current suggestion's label as text. */
173
+ export const SuggestionPrimitiveDescription = defineComponent({
174
+ name: "SuggestionPrimitiveDescription",
175
+ setup() {
176
+ const label = useAuiState((s) => s.suggestion.label);
177
+ return () => label.value;
178
+ },
179
+ });
@@ -0,0 +1,46 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ onMounted,
6
+ onScopeDispose,
7
+ type SlotsType,
8
+ type VNodeChild,
9
+ } from "vue";
10
+ import { useAui } from "../useAui";
11
+
12
+ /**
13
+ * A wrapper element for the thread. While mounted, Escape stops an active
14
+ * speech synthesis run.
15
+ */
16
+ export const ThreadPrimitiveRoot = defineComponent({
17
+ name: "ThreadPrimitiveRoot",
18
+ inheritAttrs: false,
19
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
20
+ setup(_, { attrs, slots }) {
21
+ const aui = useAui();
22
+ const handleKeyDown = (event: KeyboardEvent) => {
23
+ if (event.key !== "Escape" || event.defaultPrevented) return;
24
+ if (aui.thread.source === null) return;
25
+ if (aui.thread.getState().speech == null) return;
26
+ event.preventDefault();
27
+ try {
28
+ aui.thread.stopSpeaking();
29
+ } catch (error) {
30
+ if (
31
+ !(error instanceof Error) ||
32
+ error.message !== "No message is being spoken"
33
+ ) {
34
+ throw error;
35
+ }
36
+ }
37
+ };
38
+ onMounted(() => {
39
+ document.addEventListener("keydown", handleKeyDown);
40
+ });
41
+ onScopeDispose(() => {
42
+ document.removeEventListener("keydown", handleKeyDown);
43
+ });
44
+ return () => h("div", mergeProps(attrs, {}), slots.default?.());
45
+ },
46
+ });