@assistant-ui/vue 0.0.0 → 0.0.2

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 +51 -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 +21 -0
  67. package/dist/primitives/ThreadPrimitiveMessages.d.ts.map +1 -0
  68. package/dist/primitives/ThreadPrimitiveMessages.js +35 -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 +99 -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 +829 -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 +408 -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 +70 -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 +167 -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 +38 -0
  205. package/src/primitives/ThreadPrimitiveScrollToBottom.ts +65 -0
  206. package/src/primitives/ThreadPrimitiveViewport.ts +120 -0
  207. package/src/primitives/ThreadPrimitiveViewportFooter.test.ts +400 -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
package/src/context.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { inject, type InjectionKey } from "vue";
2
+ import {
3
+ createClientFacade,
4
+ DefaultAssistantClient,
5
+ type AssistantClient,
6
+ type AssistantClientSource,
7
+ } from "@assistant-ui/store/client";
8
+
9
+ export type AuiContext = {
10
+ source: AssistantClientSource;
11
+ aui: AssistantClient;
12
+ };
13
+
14
+ export const auiInjectionKey: InjectionKey<AuiContext> = Symbol(
15
+ "assistant-ui.vue.aui",
16
+ );
17
+
18
+ const NO_OP_SUBSCRIBE = () => () => {};
19
+
20
+ const defaultContext: AuiContext = {
21
+ source: {
22
+ getClient: () => DefaultAssistantClient,
23
+ subscribe: NO_OP_SUBSCRIBE,
24
+ },
25
+ aui: DefaultAssistantClient,
26
+ };
27
+
28
+ export const useAuiContext = (): AuiContext =>
29
+ inject(auiInjectionKey, defaultContext);
30
+
31
+ export { createClientFacade };
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ export { AuiProvider } from "./AuiProvider";
2
+ export { AuiIf } from "./AuiIf";
3
+ export { useAui } from "./useAui";
4
+ export { useAuiState } from "./useAuiState";
5
+ export { useAuiEvent } from "./useAuiEvent";
6
+ export { useScrollLock } from "./useScrollLock";
7
+
8
+ export { MessageByIdProvider } from "./primitives/MessageByIdProvider";
9
+ export { PartByIndexProvider } from "./primitives/PartByIndexProvider";
10
+ export { ThreadPrimitiveMessages } from "./primitives/ThreadPrimitiveMessages";
11
+ export { ThreadPrimitiveViewport } from "./primitives/ThreadPrimitiveViewport";
12
+ export { ThreadPrimitiveViewportFooter } from "./primitives/ThreadPrimitiveViewportFooter";
13
+ export { ThreadPrimitiveScrollToBottom } from "./primitives/ThreadPrimitiveScrollToBottom";
14
+ export {
15
+ MessagePrimitiveParts,
16
+ type DataUIProps,
17
+ type ToolUIProps,
18
+ } from "./primitives/MessagePrimitiveParts";
19
+ export { ChainOfThoughtPrimitiveParts } from "./primitives/ChainOfThoughtPrimitiveParts";
20
+ export { ChainOfThoughtPrimitiveAccordionTrigger } from "./primitives/ChainOfThoughtPrimitiveAccordionTrigger";
21
+ export { ComposerPrimitiveInput } from "./primitives/ComposerPrimitiveInput";
22
+ export { ComposerPrimitiveSend } from "./primitives/ComposerPrimitiveSend";
23
+ export { ComposerPrimitiveCancel } from "./primitives/ComposerPrimitiveCancel";
24
+ export {
25
+ BranchPickerPrimitivePrevious,
26
+ BranchPickerPrimitiveNext,
27
+ BranchPickerPrimitiveNumber,
28
+ BranchPickerPrimitiveCount,
29
+ } from "./primitives/branchPicker";
30
+ export {
31
+ ActionBarPrimitiveEdit,
32
+ ActionBarPrimitiveReload,
33
+ ActionBarPrimitiveCopy,
34
+ } from "./primitives/actionBar";
35
+ export {
36
+ SuggestionByIndexProvider,
37
+ ThreadPrimitiveSuggestions,
38
+ SuggestionPrimitiveTrigger,
39
+ SuggestionPrimitiveTitle,
40
+ SuggestionPrimitiveDescription,
41
+ } from "./primitives/suggestions";
42
+ export {
43
+ ThreadListItemByIndexProvider,
44
+ ThreadListPrimitiveItems,
45
+ ThreadListPrimitiveNew,
46
+ ThreadListItemPrimitiveTitle,
47
+ ThreadListItemPrimitiveTrigger,
48
+ } from "./primitives/threadList";
49
+ export { ThreadListPrimitiveRoot } from "./primitives/ThreadListPrimitiveRoot";
50
+ export { ThreadListItemPrimitiveRoot } from "./primitives/ThreadListItemPrimitiveRoot";
51
+ export { AttachmentByIndexProvider } from "./primitives/AttachmentByIndexProvider";
52
+ export {
53
+ AttachmentPrimitiveRoot,
54
+ AttachmentPrimitiveName,
55
+ AttachmentPrimitiveThumb,
56
+ AttachmentPrimitiveRemove,
57
+ } from "./primitives/attachment";
58
+ export {
59
+ ComposerPrimitiveAttachments,
60
+ ComposerPrimitiveAddAttachment,
61
+ ComposerPrimitiveAttachmentDropzone,
62
+ } from "./primitives/composerAttachments";
63
+ export { MessagePrimitiveAttachments } from "./primitives/messageAttachments";
64
+ export { ErrorPrimitiveRoot, ErrorPrimitiveMessage } from "./primitives/error";
65
+ export { ThreadPrimitiveRoot } from "./primitives/thread";
66
+ export { MessagePrimitiveRoot } from "./primitives/message";
67
+ export {
68
+ ThreadListPrimitiveLoadMore,
69
+ ThreadListItemPrimitiveArchive,
70
+ ThreadListItemPrimitiveUnarchive,
71
+ ThreadListItemPrimitiveDelete,
72
+ } from "./primitives/threadListStructural";
73
+
74
+ export {
75
+ AuiConfig,
76
+ Derived,
77
+ createAssistantClient,
78
+ type AssistantClient,
79
+ type AssistantClientHandle,
80
+ type AssistantClientSource,
81
+ type AssistantConfigSource,
82
+ type AssistantState,
83
+ type AssistantEventCallback,
84
+ type AssistantEventName,
85
+ type AssistantEventSelector,
86
+ type Unsubscribe,
87
+ } from "@assistant-ui/store/client";
@@ -0,0 +1,88 @@
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ h,
5
+ onScopeDispose,
6
+ type PropType,
7
+ type SlotsType,
8
+ type VNodeChild,
9
+ } from "vue";
10
+ import { AuiConfig, Derived } from "@assistant-ui/store/client";
11
+ import type { AssistantClient } from "@assistant-ui/store/client";
12
+ import type { AttachmentMethods, MessageState } from "@assistant-ui/core/store";
13
+ import { AuiProvider } from "../AuiProvider";
14
+ import { useAui } from "../useAui";
15
+ import { createLastValidCache, createStaleReporter } from "./lastValidCache";
16
+
17
+ const attachmentSourceOf = (
18
+ aui: AssistantClient,
19
+ source: "composer" | "message",
20
+ ) => (source === "composer" ? aui.composer : aui.message);
21
+
22
+ const attachmentCountOf = (
23
+ aui: AssistantClient,
24
+ source: "composer" | "message",
25
+ ) => {
26
+ const state = attachmentSourceOf(aui, source).getState();
27
+ const attachments =
28
+ source === "message"
29
+ ? ((state as MessageState).submission?.attachments ?? state.attachments)
30
+ : state.attachments;
31
+ return attachments?.length ?? 0;
32
+ };
33
+
34
+ /**
35
+ * Scopes the subtree to the attachment at `index` of `source` (the composer's
36
+ * pending attachments or the current message's attachments): descendants read
37
+ * the attachment through `s.attachment`.
38
+ */
39
+ export const AttachmentByIndexProvider = defineComponent({
40
+ name: "AttachmentByIndexProvider",
41
+ props: {
42
+ source: {
43
+ type: String as PropType<"composer" | "message">,
44
+ required: true,
45
+ },
46
+ index: {
47
+ type: Number,
48
+ required: true,
49
+ },
50
+ },
51
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
52
+ setup(props, { slots }) {
53
+ const aui = useAui();
54
+ let disposed = false;
55
+ onScopeDispose(() => {
56
+ disposed = true;
57
+ });
58
+ const config = computed(() => {
59
+ const source = props.source;
60
+ const index = props.index;
61
+ const cache = createLastValidCache<AttachmentMethods>(
62
+ createStaleReporter({
63
+ name: "AttachmentByIndexProvider",
64
+ index,
65
+ isCurrent: () =>
66
+ !disposed && index === props.index && source === props.source,
67
+ isValid: () => index < attachmentCountOf(aui, source),
68
+ }),
69
+ );
70
+ return AuiConfig({
71
+ attachment: Derived({
72
+ source,
73
+ query: { type: "index", index },
74
+ get: (aui) =>
75
+ cache.resolve(index < attachmentCountOf(aui, source), () =>
76
+ attachmentSourceOf(aui, source).attachment({ index }),
77
+ ),
78
+ }),
79
+ });
80
+ });
81
+ return () =>
82
+ h(
83
+ AuiProvider,
84
+ { config: config.value, extends: aui },
85
+ { default: () => slots.default?.() },
86
+ );
87
+ },
88
+ });
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { createApp, defineComponent, h, nextTick } from "vue";
3
+ import { flushTapSync } from "@assistant-ui/tap";
4
+ import { AuiConfig } from "@assistant-ui/store/client";
5
+ import {
6
+ ChainOfThoughtClient,
7
+ type ChainOfThoughtPart,
8
+ } from "@assistant-ui/core/store";
9
+ import { AuiProvider } from "../AuiProvider";
10
+ import { useAuiState } from "../useAuiState";
11
+ import { ChainOfThoughtPrimitiveAccordionTrigger } from "./ChainOfThoughtPrimitiveAccordionTrigger";
12
+
13
+ const parts: readonly ChainOfThoughtPart[] = [
14
+ { type: "reasoning", text: "thinking", status: { type: "complete" } },
15
+ ];
16
+
17
+ const StateProbe = defineComponent({
18
+ setup() {
19
+ const collapsed = useAuiState((s) => s.chainOfThought.collapsed);
20
+ return () => h("span", { class: "collapsed" }, String(collapsed.value));
21
+ },
22
+ });
23
+
24
+ const mountTrigger = (props: Record<string, unknown> = {}) => {
25
+ const app = createApp(
26
+ defineComponent({
27
+ setup: () => () =>
28
+ h(
29
+ AuiProvider,
30
+ {
31
+ config: AuiConfig({
32
+ chainOfThought: ChainOfThoughtClient({
33
+ parts,
34
+ getMessagePart: () => {
35
+ throw new Error("Part access is not needed by this test.");
36
+ },
37
+ }),
38
+ }),
39
+ },
40
+ {
41
+ default: () =>
42
+ h("div", [
43
+ h(ChainOfThoughtPrimitiveAccordionTrigger, props, {
44
+ default: () => "Toggle",
45
+ }),
46
+ h(StateProbe),
47
+ ]),
48
+ },
49
+ ),
50
+ }),
51
+ );
52
+ const el = document.createElement("div");
53
+ app.mount(el);
54
+ return { el, unmount: () => app.unmount() };
55
+ };
56
+
57
+ describe("ChainOfThoughtPrimitiveAccordionTrigger", () => {
58
+ it("toggles the collapsed state", async () => {
59
+ const { el, unmount } = mountTrigger();
60
+ const button = el.querySelector<HTMLButtonElement>("button")!;
61
+
62
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("true");
63
+ button.click();
64
+ await vi.waitFor(async () => {
65
+ await nextTick();
66
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("false");
67
+ });
68
+ button.click();
69
+ await vi.waitFor(async () => {
70
+ await nextTick();
71
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("true");
72
+ });
73
+
74
+ unmount();
75
+ });
76
+
77
+ it("respects caller vetoes", async () => {
78
+ const { el, unmount } = mountTrigger({
79
+ onClick: (event: MouseEvent) => event.preventDefault(),
80
+ });
81
+
82
+ el.querySelector<HTMLButtonElement>("button")!.click();
83
+ await nextTick();
84
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("true");
85
+
86
+ unmount();
87
+ });
88
+
89
+ it("stays disabled when the disabled attribute is set", async () => {
90
+ const { el, unmount } = mountTrigger({ disabled: true });
91
+ const button = el.querySelector<HTMLButtonElement>("button")!;
92
+
93
+ expect(button.disabled).toBe(true);
94
+ flushTapSync(() => button.click());
95
+ await nextTick();
96
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("true");
97
+
98
+ button.disabled = false;
99
+ flushTapSync(() => button.click());
100
+ await nextTick();
101
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("true");
102
+
103
+ unmount();
104
+ });
105
+ });
@@ -0,0 +1,38 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ type SlotsType,
6
+ type VNodeChild,
7
+ } from "vue";
8
+ import { useAui } from "../useAui";
9
+ import { useAuiState } from "../useAuiState";
10
+ import { isAttrDisabled } from "./attrDisabled";
11
+
12
+ /**
13
+ * A button that toggles the collapsed state of the chain of thought accordion.
14
+ * Caller listeners run first and can veto the toggle via `preventDefault`.
15
+ */
16
+ export const ChainOfThoughtPrimitiveAccordionTrigger = defineComponent({
17
+ name: "ChainOfThoughtPrimitiveAccordionTrigger",
18
+ inheritAttrs: false,
19
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
20
+ setup(_, { attrs, slots }) {
21
+ const aui = useAui();
22
+ const collapsed = useAuiState((s) => s.chainOfThought.collapsed);
23
+ const onClick = (event: MouseEvent) => {
24
+ if (event.defaultPrevented || isAttrDisabled(attrs)) return;
25
+ aui.chainOfThought.setCollapsed(!collapsed.value);
26
+ };
27
+ return () =>
28
+ h(
29
+ "button",
30
+ mergeProps(attrs, {
31
+ type: "button",
32
+ disabled: isAttrDisabled(attrs),
33
+ onClick,
34
+ }),
35
+ slots.default?.(),
36
+ );
37
+ },
38
+ });
@@ -0,0 +1,254 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { computed, createApp, defineComponent, h, nextTick } from "vue";
3
+ import { AuiConfig } from "@assistant-ui/store/client";
4
+ import {
5
+ ChainOfThoughtClient,
6
+ RuntimeAdapter,
7
+ type ChainOfThoughtPart,
8
+ type PartState,
9
+ } from "@assistant-ui/core/store";
10
+ import type {
11
+ ExternalStoreAdapter,
12
+ ThreadMessageLike,
13
+ } from "@assistant-ui/core";
14
+ import {
15
+ AssistantRuntimeImpl,
16
+ ExternalStoreRuntimeCore,
17
+ } from "@assistant-ui/core/internal";
18
+ import { AuiProvider } from "../AuiProvider";
19
+ import { useAui } from "../useAui";
20
+ import { useAuiState } from "../useAuiState";
21
+ import { ThreadPrimitiveMessages } from "./ThreadPrimitiveMessages";
22
+ import { ChainOfThoughtPrimitiveParts } from "./ChainOfThoughtPrimitiveParts";
23
+
24
+ type DemoMessage = {
25
+ id: string;
26
+ texts: readonly string[];
27
+ parts?: ThreadMessageLike["content"];
28
+ };
29
+
30
+ const convertDemoMessage = (message: DemoMessage) => ({
31
+ id: message.id,
32
+ role: "assistant" as const,
33
+ content:
34
+ message.parts ??
35
+ message.texts.map((text) => ({
36
+ type: "reasoning" as const,
37
+ text,
38
+ })),
39
+ });
40
+
41
+ const createReasoningRuntime = (initial: readonly string[]) => {
42
+ let messages: DemoMessage[] = [{ id: "a0", texts: initial }];
43
+ const makeAdapter = (): ExternalStoreAdapter<DemoMessage> => ({
44
+ messages,
45
+ isRunning: false,
46
+ convertMessage: convertDemoMessage,
47
+ onNew: async () => {},
48
+ });
49
+ const core = new ExternalStoreRuntimeCore(makeAdapter());
50
+ const runtime = new AssistantRuntimeImpl(core);
51
+ const setTexts = (texts: readonly string[]) => {
52
+ messages = [{ id: "a0", texts }];
53
+ core.setAdapter(makeAdapter());
54
+ };
55
+ const setParts = (parts: ThreadMessageLike["content"]) => {
56
+ messages = [{ id: "a0", texts: [], parts }];
57
+ core.setAdapter(makeAdapter());
58
+ };
59
+ return { runtime, setTexts, setParts };
60
+ };
61
+
62
+ const CollapsedProbe = defineComponent({
63
+ setup() {
64
+ const collapsed = useAuiState((s) => s.chainOfThought.collapsed);
65
+ return () => h("span", { class: "collapsed" }, String(collapsed.value));
66
+ },
67
+ });
68
+
69
+ const ExpandButton = defineComponent({
70
+ setup() {
71
+ const aui = useAui();
72
+ return () =>
73
+ h(
74
+ "button",
75
+ {
76
+ class: "expand",
77
+ onClick: () => aui.chainOfThought.setCollapsed(false),
78
+ },
79
+ "expand",
80
+ );
81
+ },
82
+ });
83
+
84
+ const ChainOfThoughtHost = defineComponent({
85
+ setup(_, { slots }) {
86
+ const aui = useAui();
87
+ const parts = useAuiState((s) => s.message.parts);
88
+ const config = computed(() =>
89
+ AuiConfig({
90
+ chainOfThought: ChainOfThoughtClient({
91
+ parts: parts.value as readonly ChainOfThoughtPart[],
92
+ getMessagePart: ({ index }) => aui.message.part({ index }),
93
+ }),
94
+ }),
95
+ );
96
+ return () =>
97
+ h(
98
+ AuiProvider,
99
+ { config: config.value, extends: aui },
100
+ { default: () => slots.default?.() },
101
+ );
102
+ },
103
+ });
104
+
105
+ const mountParts = (
106
+ initial: readonly string[],
107
+ renderPart: (part: PartState) => ReturnType<typeof h> = (part) =>
108
+ h(
109
+ "li",
110
+ { class: "part" },
111
+ part.type === "reasoning" ? part.text : part.type,
112
+ ),
113
+ ) => {
114
+ const { runtime, setTexts, setParts } = createReasoningRuntime(initial);
115
+ const app = createApp(
116
+ defineComponent({
117
+ setup: () => () =>
118
+ h(
119
+ AuiProvider,
120
+ { config: AuiConfig({ threads: RuntimeAdapter(runtime) }) },
121
+ {
122
+ default: () =>
123
+ h("ul", [
124
+ h(ThreadPrimitiveMessages, null, {
125
+ default: () =>
126
+ h(ChainOfThoughtHost, null, {
127
+ default: () => [
128
+ h(ChainOfThoughtPrimitiveParts, null, {
129
+ default: ({ part }: { part: PartState }) => [
130
+ renderPart(part),
131
+ ],
132
+ }),
133
+ h(CollapsedProbe),
134
+ h(ExpandButton),
135
+ ],
136
+ }),
137
+ }),
138
+ ]),
139
+ },
140
+ ),
141
+ }),
142
+ );
143
+ const el = document.createElement("div");
144
+ app.mount(el);
145
+ return { el, setTexts, setParts, unmount: () => app.unmount() };
146
+ };
147
+
148
+ const partTexts = (el: HTMLElement) =>
149
+ [...el.querySelectorAll("li.part")].map((li) => li.textContent);
150
+
151
+ afterEach(() => {
152
+ vi.restoreAllMocks();
153
+ });
154
+
155
+ describe("ChainOfThoughtPrimitiveParts", () => {
156
+ it("keeps tool-call slot state with its part when chain parts swap", async () => {
157
+ const mounts = vi.fn();
158
+ const Tool = defineComponent({
159
+ setup() {
160
+ mounts();
161
+ const initialName = useAuiState((s) =>
162
+ s.part.type === "tool-call" ? s.part.toolName : "",
163
+ ).value;
164
+ return () => h("li", { class: "part" }, initialName);
165
+ },
166
+ });
167
+ const { el, setParts, unmount } = mountParts([], () => h(Tool));
168
+ const first = {
169
+ type: "tool-call" as const,
170
+ toolCallId: "call-a",
171
+ toolName: "alpha",
172
+ args: {},
173
+ };
174
+ const second = { ...first, toolCallId: "call-b", toolName: "beta" };
175
+
176
+ setParts([first, second]);
177
+ await vi.waitFor(async () => {
178
+ await nextTick();
179
+ expect(partTexts(el)).toEqual(["alpha", "beta"]);
180
+ });
181
+ expect(mounts).toHaveBeenCalledTimes(2);
182
+
183
+ setParts([second, first]);
184
+ await vi.waitFor(async () => {
185
+ await nextTick();
186
+ expect(partTexts(el)).toEqual(["beta", "alpha"]);
187
+ });
188
+ expect(mounts).toHaveBeenCalledTimes(2);
189
+ unmount();
190
+ });
191
+
192
+ it("renders one slot invocation per part with its state", async () => {
193
+ const { el, unmount } = mountParts(["alpha", "beta"]);
194
+
195
+ await vi.waitFor(async () => {
196
+ await nextTick();
197
+ expect(partTexts(el)).toEqual(["alpha", "beta"]);
198
+ });
199
+
200
+ unmount();
201
+ });
202
+
203
+ it("renders regardless of the collapsed state, matching the React primitive", async () => {
204
+ const { el, unmount } = mountParts(["alpha"]);
205
+
206
+ await vi.waitFor(async () => {
207
+ await nextTick();
208
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("true");
209
+ expect(partTexts(el)).toEqual(["alpha"]);
210
+ });
211
+
212
+ el.querySelector<HTMLButtonElement>("button.expand")!.click();
213
+ await vi.waitFor(async () => {
214
+ await nextTick();
215
+ expect(el.querySelector("span.collapsed")?.textContent).toBe("false");
216
+ expect(partTexts(el)).toEqual(["alpha"]);
217
+ });
218
+
219
+ unmount();
220
+ });
221
+
222
+ it("follows a parts shrink and regrowth without crashing", async () => {
223
+ const error = vi.spyOn(console, "error").mockImplementation(() => {});
224
+ const { el, setTexts, unmount } = mountParts(["alpha", "beta"]);
225
+
226
+ await vi.waitFor(async () => {
227
+ await nextTick();
228
+ expect(partTexts(el)).toEqual(["alpha", "beta"]);
229
+ });
230
+
231
+ setTexts(["alpha"]);
232
+ await vi.waitFor(async () => {
233
+ await nextTick();
234
+ expect(partTexts(el)).toEqual(["alpha"]);
235
+ });
236
+
237
+ setTexts(["alpha", "gamma"]);
238
+ await vi.waitFor(async () => {
239
+ await nextTick();
240
+ expect(partTexts(el)).toEqual(["alpha", "gamma"]);
241
+ });
242
+
243
+ const logged = error.mock.calls.map((call) => call.map(String).join(" "));
244
+ for (const message of logged) {
245
+ expect(message).toContain("(ignore if recovered)");
246
+ }
247
+ expect(logged.join("\n")).not.toContain(
248
+ "ChainOfThoughtPartByIndexProvider",
249
+ );
250
+
251
+ unmount();
252
+ error.mockRestore();
253
+ });
254
+ });