@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
@@ -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
+ });
@@ -0,0 +1,41 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ type SlotsType,
6
+ type VNodeChild,
7
+ } from "vue";
8
+ import { composerCancelDisabled } from "@assistant-ui/core/store/internal";
9
+ import { isAttrDisabled } from "./attrDisabled";
10
+ import { useAui } from "../useAui";
11
+ import { useAuiState } from "../useAuiState";
12
+
13
+ /**
14
+ * A button that cancels the current run. Disabled while nothing can be
15
+ * cancelled. A fallthrough click listener runs first and can veto the cancel
16
+ * with `preventDefault`.
17
+ */
18
+ export const ComposerPrimitiveCancel = defineComponent({
19
+ name: "ComposerPrimitiveCancel",
20
+ inheritAttrs: false,
21
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
22
+ setup(_, { attrs, slots }) {
23
+ const aui = useAui();
24
+ const disabled = useAuiState(composerCancelDisabled);
25
+ const onClick = (event: MouseEvent) => {
26
+ if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
27
+ return;
28
+ aui.composer.cancel();
29
+ };
30
+ return () =>
31
+ h(
32
+ "button",
33
+ mergeProps(attrs, {
34
+ type: "button",
35
+ disabled: disabled.value || isAttrDisabled(attrs),
36
+ onClick,
37
+ }),
38
+ slots.default?.(),
39
+ );
40
+ },
41
+ });
@@ -0,0 +1,70 @@
1
+ import { defineComponent, h, mergeProps, type SlotsType } from "vue";
2
+ import { flushTapSync } from "@assistant-ui/tap";
3
+ import { composerInputDisabled } from "@assistant-ui/core/store/internal";
4
+ import { isAttrDisabled } from "./attrDisabled";
5
+ import { useAui } from "../useAui";
6
+ import { useAuiState } from "../useAuiState";
7
+ import { useComposerSendState } from "./useComposerSendState";
8
+
9
+ /**
10
+ * A textarea bound to the composer text, disabled while the thread is
11
+ * disabled. Enter submits (Shift+Enter inserts a newline, IME composition is
12
+ * ignored) unless `submitOnEnter` is false; an Enter that cannot send falls
13
+ * through to a newline, and a fallthrough keydown listener can veto the
14
+ * submit with `preventDefault`. While the composer is not editing (an edit
15
+ * composer before `beginEdit`), the value stays controlled to the empty
16
+ * composer text.
17
+ */
18
+ export const ComposerPrimitiveInput = defineComponent({
19
+ name: "ComposerPrimitiveInput",
20
+ inheritAttrs: false,
21
+ props: {
22
+ submitOnEnter: {
23
+ type: Boolean,
24
+ default: true,
25
+ },
26
+ },
27
+ slots: Object as SlotsType<Record<string, never>>,
28
+ setup(props, { attrs }) {
29
+ const aui = useAui();
30
+ const text = useAuiState((s) =>
31
+ s.composer.isEditing ? s.composer.text : "",
32
+ );
33
+ const threadDisabled = useAuiState(composerInputDisabled);
34
+ const { disabled, send } = useComposerSendState();
35
+
36
+ const onInput = (event: Event) => {
37
+ const target = event.target as HTMLTextAreaElement;
38
+ if (!aui.composer.getState().isEditing) {
39
+ target.value = text.value;
40
+ return;
41
+ }
42
+ flushTapSync(() => aui.composer.setText(target.value));
43
+ };
44
+ const onKeydown = (event: KeyboardEvent) => {
45
+ if (event.defaultPrevented || !props.submitOnEnter) return;
46
+ if (
47
+ event.key !== "Enter" ||
48
+ event.shiftKey ||
49
+ event.isComposing ||
50
+ event.keyCode === 229
51
+ )
52
+ return;
53
+ if (disabled.value || threadDisabled.value || isAttrDisabled(attrs))
54
+ return;
55
+ event.preventDefault();
56
+ send();
57
+ };
58
+
59
+ return () =>
60
+ h(
61
+ "textarea",
62
+ mergeProps({ name: "input" }, attrs, {
63
+ value: text.value,
64
+ disabled: threadDisabled.value || isAttrDisabled(attrs),
65
+ onInput,
66
+ onKeydown,
67
+ }),
68
+ );
69
+ },
70
+ });
@@ -0,0 +1,39 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ mergeProps,
5
+ type SlotsType,
6
+ type VNodeChild,
7
+ } from "vue";
8
+ import { isAttrDisabled } from "./attrDisabled";
9
+ import { useComposerSendState } from "./useComposerSendState";
10
+
11
+ /**
12
+ * A button that sends the composer. Disabled while the composer cannot send,
13
+ * or while a run is in flight and the runtime does not queue sends. A
14
+ * fallthrough click listener runs first and can veto the send with
15
+ * `preventDefault`.
16
+ */
17
+ export const ComposerPrimitiveSend = defineComponent({
18
+ name: "ComposerPrimitiveSend",
19
+ inheritAttrs: false,
20
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
21
+ setup(_, { attrs, slots }) {
22
+ const { disabled, send } = useComposerSendState();
23
+ const onClick = (event: MouseEvent) => {
24
+ if (event.defaultPrevented || disabled.value || isAttrDisabled(attrs))
25
+ return;
26
+ send();
27
+ };
28
+ return () =>
29
+ h(
30
+ "button",
31
+ mergeProps(attrs, {
32
+ type: "button",
33
+ disabled: disabled.value || isAttrDisabled(attrs),
34
+ onClick,
35
+ }),
36
+ slots.default?.(),
37
+ );
38
+ },
39
+ });
@@ -0,0 +1,89 @@
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ h,
5
+ onScopeDispose,
6
+ type SlotsType,
7
+ type VNodeChild,
8
+ } from "vue";
9
+ import {
10
+ AuiConfig,
11
+ Derived,
12
+ type AssistantClient,
13
+ } from "@assistant-ui/store/client";
14
+ import type { ComposerMethods, MessageMethods } from "@assistant-ui/core/store";
15
+ import { AuiProvider } from "../AuiProvider";
16
+ import { useAui } from "../useAui";
17
+ import { createLastValidCache, createStaleReporter } from "./lastValidCache";
18
+
19
+ const idSets = new WeakMap<readonly { id: string }[], Set<string>>();
20
+
21
+ const hasMessage = (aui: AssistantClient, id: string) => {
22
+ const messages = aui.thread.getState().messages;
23
+ let set = idSets.get(messages);
24
+ if (!set) {
25
+ set = new Set(messages.map((message) => message.id));
26
+ idSets.set(messages, set);
27
+ }
28
+ return set.has(id);
29
+ };
30
+
31
+ /**
32
+ * Scopes the subtree to the thread message with `id`: descendants read the
33
+ * message through `s.message` and its edit composer through `s.composer`.
34
+ * The scope follows the message's identity rather than a positional slot, so
35
+ * it pairs with id-keyed iteration.
36
+ */
37
+ export const MessageByIdProvider = defineComponent({
38
+ name: "MessageByIdProvider",
39
+ props: {
40
+ id: {
41
+ type: String,
42
+ required: true,
43
+ },
44
+ },
45
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
46
+ setup(props, { slots }) {
47
+ const aui = useAui();
48
+ let disposed = false;
49
+ onScopeDispose(() => {
50
+ disposed = true;
51
+ });
52
+ const config = computed(() => {
53
+ const id = props.id;
54
+ const messageCache = createLastValidCache<MessageMethods>(
55
+ createStaleReporter({
56
+ name: "MessageByIdProvider",
57
+ index: id,
58
+ isCurrent: () => !disposed && id === props.id,
59
+ isValid: () => hasMessage(aui, id),
60
+ }),
61
+ );
62
+ const composerCache = createLastValidCache<ComposerMethods>(null);
63
+ return AuiConfig({
64
+ message: Derived({
65
+ source: "thread",
66
+ query: { type: "id", id },
67
+ get: (aui) =>
68
+ messageCache.resolve(hasMessage(aui, id), () =>
69
+ aui.thread.message({ id }),
70
+ ),
71
+ }),
72
+ composer: Derived({
73
+ source: "message",
74
+ query: {},
75
+ get: (aui) =>
76
+ composerCache.resolve(hasMessage(aui, id), () =>
77
+ aui.thread.message({ id }).composer(),
78
+ ),
79
+ }),
80
+ });
81
+ });
82
+ return () =>
83
+ h(
84
+ AuiProvider,
85
+ { config: config.value, extends: aui },
86
+ { default: () => slots.default?.() },
87
+ );
88
+ },
89
+ });
@@ -0,0 +1,88 @@
1
+ import { createApp, defineComponent, h, type PropType } from "vue";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import type { ToolUIProps } from "./MessagePrimitiveParts";
4
+ import { MessagePrimitiveParts } from "./MessagePrimitiveParts";
5
+
6
+ const fixture = vi.hoisted(() => ({
7
+ recordInteraction: vi.fn(),
8
+ part: {
9
+ type: "tool-call",
10
+ toolCallId: "call-1",
11
+ toolName: "weather",
12
+ args: {},
13
+ argsText: "{}",
14
+ status: { type: "complete" },
15
+ },
16
+ state: {
17
+ message: { parts: [] as unknown[] },
18
+ optional: { tools: { toolUIs: {} as Record<string, unknown> } },
19
+ },
20
+ }));
21
+
22
+ vi.mock("../useAui", async (importOriginal) => ({
23
+ ...(await importOriginal<typeof import("../useAui")>()),
24
+ useAui: () => ({
25
+ part: {
26
+ addToolResult: vi.fn(),
27
+ resumeToolCall: vi.fn(),
28
+ respondToToolApproval: vi.fn(),
29
+ unstable_recordInteraction: fixture.recordInteraction,
30
+ },
31
+ }),
32
+ }));
33
+
34
+ vi.mock("../useAuiState", async (importOriginal) => ({
35
+ ...(await importOriginal<typeof import("../useAuiState")>()),
36
+ useAuiState: <T>(selector: (state: unknown) => T) => ({
37
+ get value() {
38
+ return selector({ ...fixture.state, part: fixture.part });
39
+ },
40
+ }),
41
+ }));
42
+
43
+ vi.mock("./PartByIndexProvider", async (importOriginal) => {
44
+ const { defineComponent } = await import("vue");
45
+ return {
46
+ ...(await importOriginal<typeof import("./PartByIndexProvider")>()),
47
+ PartByIndexProvider: defineComponent({
48
+ setup(_, { slots }) {
49
+ return () => slots.default?.();
50
+ },
51
+ }),
52
+ };
53
+ });
54
+
55
+ const interaction = { type: "action" as const, payload: { choice: "retry" } };
56
+
57
+ let app: ReturnType<typeof createApp> | undefined;
58
+
59
+ beforeEach(() => {
60
+ fixture.recordInteraction.mockReset();
61
+ fixture.state.message.parts = [fixture.part];
62
+ fixture.state.optional.tools.toolUIs = {};
63
+ });
64
+
65
+ afterEach(() => app?.unmount());
66
+
67
+ describe("MessagePrimitiveParts", () => {
68
+ it("forwards unstable_recordInteraction to a registered tool UI", () => {
69
+ const Tool = defineComponent({
70
+ props: {
71
+ tool: { type: Object as PropType<ToolUIProps>, required: true },
72
+ },
73
+ setup(props) {
74
+ return () => {
75
+ void props.tool.unstable_recordInteraction?.(interaction);
76
+ return h("span", "weather");
77
+ };
78
+ },
79
+ });
80
+ fixture.state.optional.tools.toolUIs = { weather: [{ render: Tool }] };
81
+ const target = document.createElement("div");
82
+ app = createApp(MessagePrimitiveParts);
83
+
84
+ app.mount(target);
85
+
86
+ expect(fixture.recordInteraction).toHaveBeenCalledWith(interaction);
87
+ });
88
+ });
@@ -0,0 +1,167 @@
1
+ import {
2
+ defineComponent,
3
+ h,
4
+ type Component,
5
+ type SlotsType,
6
+ type VNodeChild,
7
+ } from "vue";
8
+ import { isMcpAppUri } from "@assistant-ui/core";
9
+ import { getMessagePartKeys } from "@assistant-ui/core/internal";
10
+ import {
11
+ resolveToolCallText,
12
+ type PartMethods,
13
+ } from "@assistant-ui/core/store";
14
+ import { isDevelopment } from "@assistant-ui/core/store/internal";
15
+ import type { AssistantState } from "@assistant-ui/store/client";
16
+ import { useAui } from "../useAui";
17
+ import { useAuiState } from "../useAuiState";
18
+ import { PartByIndexProvider } from "./PartByIndexProvider";
19
+ import { useStableKeys } from "./stableKeys";
20
+
21
+ const warnedTypes = new Set<string>();
22
+
23
+ export const clearPartWarningsForTesting = () => warnedTypes.clear();
24
+
25
+ /**
26
+ * The value of the single `tool` prop passed to a tool UI registered in the
27
+ * `tools` scope. React spreads the part state as individual props; in Vue any
28
+ * prop the component does not declare falls through to `$attrs` and gets
29
+ * stringified onto its root element, so the Vue face passes one `tool` object
30
+ * instead, which a component always declares in full. The tools scope
31
+ * declares renderers as React's `ToolCallMessagePartComponent`, so the Vue
32
+ * face bridges the seam with casts at registration and render; a genuine
33
+ * React renderer arriving through a shared toolkit type-checks at `setToolUI`
34
+ * and then fails when invoked as a Vue component. This type is the Vue-facing
35
+ * contract for custom components. Toolkit `renderText` descriptors resolve
36
+ * through the neutral resolver and render string or number results directly;
37
+ * a react-element-valued descriptor is react-only and renders nothing here.
38
+ */
39
+ export type ToolUIProps = {
40
+ part: Extract<AssistantState["part"], { type: "tool-call" }>;
41
+ addResult: PartMethods["addToolResult"];
42
+ resume: PartMethods["resumeToolCall"];
43
+ respondToApproval: PartMethods["respondToToolApproval"];
44
+ /** @deprecated Experimental since 2026-09-23. Not scheduled for removal; the API may change in any release. */
45
+ unstable_recordInteraction?: PartMethods["unstable_recordInteraction"];
46
+ };
47
+
48
+ /** The value of the single `data` prop passed to a Vue data renderer. */
49
+ export type DataUIProps<T = unknown> = {
50
+ part: Omit<Extract<AssistantState["part"], { type: "data" }>, "data"> & {
51
+ data: T;
52
+ };
53
+ };
54
+
55
+ /**
56
+ * Renders the current message's content parts in order, each scoped through
57
+ * {@link PartByIndexProvider}. A `tool-call` part first resolves a renderer
58
+ * registered in the `tools` scope by tool name (`aui.tools.setToolUI`, or a
59
+ * `Tools({ toolkit })` config entry) and renders it with a single `tool` prop
60
+ * of type {@link ToolUIProps}.
61
+ * Otherwise a slot named after the part type (`text`, `reasoning`,
62
+ * `tool-call`, ...) renders that part; the `default` slot is the fallback for
63
+ * types without a named slot, except text, which always renders its text
64
+ * unless a `text` slot overrides it.
65
+ */
66
+ export const MessagePrimitiveParts = defineComponent({
67
+ name: "MessagePrimitiveParts",
68
+ slots: Object as SlotsType<Record<string, (() => VNodeChild[]) | undefined>>,
69
+ setup(_, { slots }) {
70
+ const parts = useAuiState((s) => s.message.parts);
71
+ const partKeys = useStableKeys(() => getMessagePartKeys(parts.value));
72
+ const PartView = defineComponent({
73
+ name: "MessagePartView",
74
+ setup() {
75
+ const aui = useAui();
76
+ const type = useAuiState((s) => s.part.type);
77
+ const text = useAuiState((s) =>
78
+ s.part.type === "text" ? s.part.text : "",
79
+ );
80
+ const toolUI = useAuiState((s) => {
81
+ if (s.part.type !== "tool-call") return null;
82
+ return s.optional.tools?.toolUIs[s.part.toolName]?.[0] ?? null;
83
+ });
84
+ const mcpAppRender = useAuiState((s) =>
85
+ s.part.type === "tool-call" &&
86
+ isMcpAppUri(s.part.mcp?.app?.resourceUri)
87
+ ? (s.optional.tools?.mcpApp?.render ?? null)
88
+ : null,
89
+ );
90
+ const toolPart = useAuiState((s) =>
91
+ s.part.type === "tool-call" ? s.part : null,
92
+ );
93
+ const dataRenderer = useAuiState((s) => {
94
+ if (s.part.type !== "data") return null;
95
+ const named =
96
+ s.optional.dataRenderers?.renderers[s.part.name]?.[0] ?? null;
97
+ return named ?? s.optional.dataRenderers?.fallbacks[0] ?? null;
98
+ });
99
+ const dataPart = useAuiState((s) =>
100
+ s.part.type === "data" ? s.part : null,
101
+ );
102
+ return () => {
103
+ if (type.value === "tool-call") {
104
+ const registration = toolUI.value;
105
+ const part = toolPart.value;
106
+ if (registration?.renderText && part) {
107
+ const resolved = resolveToolCallText(
108
+ registration.renderText,
109
+ part,
110
+ );
111
+ if (
112
+ typeof resolved === "string" ||
113
+ typeof resolved === "number"
114
+ ) {
115
+ return resolved;
116
+ }
117
+ return null;
118
+ }
119
+ const Render = registration?.render ?? mcpAppRender.value;
120
+ if (Render && part) {
121
+ return h(Render as unknown as Component, {
122
+ tool: {
123
+ part,
124
+ addResult: aui.part.addToolResult,
125
+ resume: aui.part.resumeToolCall,
126
+ respondToApproval: aui.part.respondToToolApproval,
127
+ ...(aui.part.unstable_recordInteraction && {
128
+ unstable_recordInteraction:
129
+ aui.part.unstable_recordInteraction,
130
+ }),
131
+ } satisfies ToolUIProps,
132
+ });
133
+ }
134
+ }
135
+ if (type.value === "data") {
136
+ const Render = dataRenderer.value;
137
+ const part = dataPart.value;
138
+ if (Render && part) {
139
+ return h(Render as unknown as Component, {
140
+ data: { part } satisfies DataUIProps,
141
+ });
142
+ }
143
+ }
144
+ if (type.value === "text") {
145
+ const slot = slots.text;
146
+ return slot
147
+ ? slot()
148
+ : h("p", { style: { whiteSpace: "pre-line" } }, text.value);
149
+ }
150
+ const slot = slots[type.value] ?? slots.default;
151
+ if (slot) return slot();
152
+ if (isDevelopment && !warnedTypes.has(type.value)) {
153
+ warnedTypes.add(type.value);
154
+ console.warn(
155
+ `MessagePrimitiveParts: no slot for part type "${type.value}"; the part renders nothing. Add a #${type.value} or #default slot.`,
156
+ );
157
+ }
158
+ return null;
159
+ };
160
+ },
161
+ });
162
+ return () =>
163
+ partKeys.value.map((key, index) =>
164
+ h(PartByIndexProvider, { index, key }, { default: () => h(PartView) }),
165
+ );
166
+ },
167
+ });
@@ -0,0 +1,62 @@
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ h,
5
+ onScopeDispose,
6
+ type SlotsType,
7
+ type VNodeChild,
8
+ } from "vue";
9
+ import { AuiConfig, Derived } from "@assistant-ui/store/client";
10
+ import type { PartMethods } from "@assistant-ui/core/store";
11
+ import { AuiProvider } from "../AuiProvider";
12
+ import { useAui } from "../useAui";
13
+ import { createLastValidCache, createStaleReporter } from "./lastValidCache";
14
+
15
+ /**
16
+ * Scopes the subtree to the message part at `index`: descendants read the
17
+ * part through `s.part`.
18
+ */
19
+ export const PartByIndexProvider = defineComponent({
20
+ name: "PartByIndexProvider",
21
+ props: {
22
+ index: {
23
+ type: Number,
24
+ required: true,
25
+ },
26
+ },
27
+ slots: Object as SlotsType<{ default?: () => VNodeChild[] }>,
28
+ setup(props, { slots }) {
29
+ const aui = useAui();
30
+ let disposed = false;
31
+ onScopeDispose(() => {
32
+ disposed = true;
33
+ });
34
+ const config = computed(() => {
35
+ const index = props.index;
36
+ const cache = createLastValidCache<PartMethods>(
37
+ createStaleReporter({
38
+ name: "PartByIndexProvider",
39
+ index,
40
+ isCurrent: () => !disposed && index === props.index,
41
+ isValid: () => index < aui.message.getState().parts.length,
42
+ }),
43
+ );
44
+ return AuiConfig({
45
+ part: Derived({
46
+ source: "message",
47
+ query: { type: "index", index },
48
+ get: (aui) =>
49
+ cache.resolve(index < aui.message.getState().parts.length, () =>
50
+ aui.message.part({ index }),
51
+ ),
52
+ }),
53
+ });
54
+ });
55
+ return () =>
56
+ h(
57
+ AuiProvider,
58
+ { config: config.value, extends: aui },
59
+ { default: () => slots.default?.() },
60
+ );
61
+ },
62
+ });