@assistant-ui/vue 0.0.0-stage → 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 +64 -3
  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
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
+ });
@@ -0,0 +1,112 @@
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ h,
5
+ onScopeDispose,
6
+ type SlotsType,
7
+ type VNodeChild,
8
+ } from "vue";
9
+ import type { PartMethods, PartState } from "@assistant-ui/core/store";
10
+ import { getMessagePartKeys } from "@assistant-ui/core/internal";
11
+ import { AuiConfig, Derived } from "@assistant-ui/store/client";
12
+ import { AuiProvider } from "../AuiProvider";
13
+ import { useAui } from "../useAui";
14
+ import { useAuiState } from "../useAuiState";
15
+ import { createLastValidCache, createStaleReporter } from "./lastValidCache";
16
+ import { useStableKeys } from "./stableKeys";
17
+
18
+ type ChainOfThoughtPartsSlots = {
19
+ default?: (props: { part: PartState }) => VNodeChild[];
20
+ };
21
+
22
+ const ChainOfThoughtPartByIndexProvider = defineComponent({
23
+ name: "ChainOfThoughtPartByIndexProvider",
24
+ props: {
25
+ index: {
26
+ type: Number,
27
+ required: true,
28
+ },
29
+ },
30
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
31
+ setup(props, { slots }) {
32
+ const aui = useAui();
33
+ let disposed = false;
34
+ onScopeDispose(() => {
35
+ disposed = true;
36
+ });
37
+ const config = computed(() => {
38
+ const index = props.index;
39
+ const cache = createLastValidCache<PartMethods>(
40
+ createStaleReporter({
41
+ name: "ChainOfThoughtPartByIndexProvider",
42
+ index,
43
+ isCurrent: () => !disposed && index === props.index,
44
+ isValid: () => index < aui.chainOfThought.getState().parts.length,
45
+ }),
46
+ );
47
+ return AuiConfig({
48
+ part: Derived({
49
+ source: "chainOfThought",
50
+ query: { type: "index", index },
51
+ // chainOfThought.part() delegates to a userland getMessagePart, so
52
+ // validity is not decided by parts.length alone; a lookup throw is
53
+ // absorbed into the stale cache like an invalid index.
54
+ get: (aui) => {
55
+ const valid = index < aui.chainOfThought.getState().parts.length;
56
+ try {
57
+ return cache.resolve(valid, () =>
58
+ aui.chainOfThought.part({ index }),
59
+ );
60
+ } catch (error) {
61
+ return cache.resolve(false, () => {
62
+ throw error;
63
+ });
64
+ }
65
+ },
66
+ }),
67
+ });
68
+ });
69
+ return () =>
70
+ h(
71
+ AuiProvider,
72
+ { config: config.value, extends: aui },
73
+ { default: () => slots.default?.() },
74
+ );
75
+ },
76
+ });
77
+
78
+ const ChainOfThoughtPartView = defineComponent({
79
+ name: "ChainOfThoughtPartView",
80
+ slots: Object as SlotsType<ChainOfThoughtPartsSlots>,
81
+ setup(_, { slots }) {
82
+ const part = useAuiState((s) => s.part);
83
+ return () => slots.default?.({ part: part.value });
84
+ },
85
+ });
86
+
87
+ /**
88
+ * Renders the parts within a chain of thought through the default slot, one
89
+ * invocation per entry of `s.chainOfThought.parts`.
90
+ *
91
+ * Rendering is not gated on `s.chainOfThought.collapsed`; gate visibility in
92
+ * the caller (for example with `AuiIf`), matching the React primitive.
93
+ */
94
+ export const ChainOfThoughtPrimitiveParts = defineComponent({
95
+ name: "ChainOfThoughtPrimitiveParts",
96
+ slots: Object as SlotsType<ChainOfThoughtPartsSlots>,
97
+ setup(_, { slots }) {
98
+ const parts = useAuiState((s) => s.chainOfThought.parts);
99
+ const partKeys = useStableKeys(() => getMessagePartKeys(parts.value));
100
+ return () =>
101
+ partKeys.value.map((key, index) =>
102
+ h(
103
+ ChainOfThoughtPartByIndexProvider,
104
+ { index, key },
105
+ {
106
+ default: () =>
107
+ h(ChainOfThoughtPartView, null, { default: slots.default }),
108
+ },
109
+ ),
110
+ );
111
+ },
112
+ });