@assistant-ui/react-native 0.1.41 → 0.1.43

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 (170) hide show
  1. package/dist/index.d.ts +5 -4
  2. package/dist/index.js +4 -3
  3. package/dist/primitives/actionBar/ActionBarCopy.d.ts +3 -3
  4. package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
  5. package/dist/primitives/actionBar/ActionBarCopy.js +23 -19
  6. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  7. package/dist/primitives/actionBar/ActionBarEdit.d.ts +7 -5
  8. package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
  9. package/dist/primitives/actionBar/ActionBarEdit.js +21 -11
  10. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  11. package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts +3 -3
  12. package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
  13. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +26 -16
  14. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  15. package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts +3 -3
  16. package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
  17. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +26 -16
  18. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  19. package/dist/primitives/actionBar/ActionBarReload.d.ts +7 -5
  20. package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
  21. package/dist/primitives/actionBar/ActionBarReload.js +21 -11
  22. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  23. package/dist/primitives/actionBar/ActionBarSpeak.d.ts +14 -0
  24. package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -0
  25. package/dist/primitives/actionBar/ActionBarSpeak.js +61 -0
  26. package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -0
  27. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts +14 -0
  28. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -0
  29. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +61 -0
  30. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -0
  31. package/dist/primitives/actionBar.d.ts +4 -2
  32. package/dist/primitives/actionBar.js +6 -2
  33. package/dist/primitives/actionBar.js.map +1 -1
  34. package/dist/primitives/attachment/AttachmentName.d.ts +2 -3
  35. package/dist/primitives/attachment/AttachmentName.d.ts.map +1 -1
  36. package/dist/primitives/attachment/AttachmentRemove.d.ts +2 -3
  37. package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
  38. package/dist/primitives/attachment/AttachmentRoot.d.ts +2 -3
  39. package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
  40. package/dist/primitives/attachment/AttachmentThumb.d.ts +2 -3
  41. package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
  42. package/dist/primitives/branchPicker/BranchPickerCount.d.ts +2 -3
  43. package/dist/primitives/branchPicker/BranchPickerCount.d.ts.map +1 -1
  44. package/dist/primitives/branchPicker/BranchPickerNext.d.ts +7 -5
  45. package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
  46. package/dist/primitives/branchPicker/BranchPickerNext.js +21 -11
  47. package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
  48. package/dist/primitives/branchPicker/BranchPickerNumber.d.ts +2 -3
  49. package/dist/primitives/branchPicker/BranchPickerNumber.d.ts.map +1 -1
  50. package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +7 -5
  51. package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
  52. package/dist/primitives/branchPicker/BranchPickerPrevious.js +21 -11
  53. package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
  54. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +2 -3
  55. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
  56. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts +2 -3
  57. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
  58. package/dist/primitives/composer/ComposerAddAttachment.d.ts +4 -7
  59. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  60. package/dist/primitives/composer/ComposerAddAttachment.js +2 -4
  61. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  62. package/dist/primitives/composer/ComposerAttachments.d.ts +4 -5
  63. package/dist/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  64. package/dist/primitives/composer/ComposerCancel.d.ts +2 -3
  65. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  66. package/dist/primitives/composer/ComposerInput.d.ts +2 -3
  67. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  68. package/dist/primitives/composer/ComposerQuote.d.ts +2 -3
  69. package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
  70. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +2 -3
  71. package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -1
  72. package/dist/primitives/composer/ComposerQuoteText.d.ts +2 -3
  73. package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -1
  74. package/dist/primitives/composer/ComposerRoot.d.ts +2 -3
  75. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  76. package/dist/primitives/composer/ComposerSend.d.ts +2 -3
  77. package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
  78. package/dist/primitives/error/ErrorMessage.d.ts +2 -3
  79. package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
  80. package/dist/primitives/error/ErrorRoot.d.ts +2 -3
  81. package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
  82. package/dist/primitives/message/MessageAttachments.d.ts +4 -5
  83. package/dist/primitives/message/MessageAttachments.d.ts.map +1 -1
  84. package/dist/primitives/message/MessageContent.d.ts +2 -3
  85. package/dist/primitives/message/MessageContent.d.ts.map +1 -1
  86. package/dist/primitives/message/MessageIf.d.ts +2 -3
  87. package/dist/primitives/message/MessageIf.d.ts.map +1 -1
  88. package/dist/primitives/message/MessageParts.d.ts +3 -3
  89. package/dist/primitives/message/MessageParts.d.ts.map +1 -1
  90. package/dist/primitives/message/MessageRoot.d.ts +2 -3
  91. package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
  92. package/dist/primitives/messagePart/MessagePartImage.d.ts +11 -0
  93. package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -0
  94. package/dist/primitives/messagePart/MessagePartImage.js +39 -0
  95. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -0
  96. package/dist/primitives/messagePart/MessagePartInProgress.d.ts +2 -0
  97. package/dist/primitives/messagePart/MessagePartInProgress.js +2 -0
  98. package/dist/primitives/messagePart/MessagePartText.d.ts +11 -0
  99. package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -0
  100. package/dist/primitives/messagePart/MessagePartText.js +32 -0
  101. package/dist/primitives/messagePart/MessagePartText.js.map +1 -0
  102. package/dist/primitives/messagePart.d.ts +10 -0
  103. package/dist/primitives/messagePart.d.ts.map +1 -0
  104. package/dist/primitives/messagePart.js +16 -0
  105. package/dist/primitives/messagePart.js.map +1 -0
  106. package/dist/primitives/queueItem/QueueItemRemove.d.ts +2 -3
  107. package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
  108. package/dist/primitives/queueItem/QueueItemSteer.d.ts +2 -3
  109. package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
  110. package/dist/primitives/queueItem/QueueItemText.d.ts +2 -3
  111. package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
  112. package/dist/primitives/suggestion/SuggestionDescription.d.ts +2 -3
  113. package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
  114. package/dist/primitives/suggestion/SuggestionTitle.d.ts +2 -3
  115. package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
  116. package/dist/primitives/suggestion/SuggestionTrigger.d.ts +2 -3
  117. package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
  118. package/dist/primitives/thread/ThreadEmpty.d.ts +2 -3
  119. package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
  120. package/dist/primitives/thread/ThreadIf.d.ts +2 -3
  121. package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
  122. package/dist/primitives/thread/ThreadMessages.d.ts +9 -5
  123. package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
  124. package/dist/primitives/thread/ThreadMessages.js +244 -171
  125. package/dist/primitives/thread/ThreadMessages.js.map +1 -1
  126. package/dist/primitives/thread/ThreadRoot.d.ts +2 -3
  127. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  128. package/dist/primitives/thread/ThreadSuggestion.d.ts +2 -3
  129. package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
  130. package/dist/primitives/threadList/ThreadListItems.d.ts +2 -3
  131. package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  132. package/dist/primitives/threadList/ThreadListNew.d.ts +2 -3
  133. package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
  134. package/dist/primitives/threadList/ThreadListRoot.d.ts +2 -3
  135. package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
  136. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +2 -3
  137. package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
  138. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +2 -3
  139. package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
  140. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts +2 -3
  141. package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
  142. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +2 -3
  143. package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
  144. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -3
  145. package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
  146. package/package.json +13 -11
  147. package/src/index.ts +30 -0
  148. package/src/primitives/accessibilityRole.test.tsx +20 -0
  149. package/src/primitives/actionBar/ActionBar.test.tsx +299 -0
  150. package/src/primitives/actionBar/ActionBarCopy.tsx +8 -3
  151. package/src/primitives/actionBar/ActionBarEdit.tsx +16 -4
  152. package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +14 -3
  153. package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +14 -3
  154. package/src/primitives/actionBar/ActionBarReload.tsx +16 -4
  155. package/src/primitives/actionBar/ActionBarSpeak.tsx +42 -0
  156. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +42 -0
  157. package/src/primitives/actionBar.ts +8 -0
  158. package/src/primitives/branchPicker/BranchPickerNext.test.tsx +52 -3
  159. package/src/primitives/branchPicker/BranchPickerNext.tsx +16 -4
  160. package/src/primitives/branchPicker/BranchPickerPrevious.test.tsx +52 -6
  161. package/src/primitives/branchPicker/BranchPickerPrevious.tsx +16 -4
  162. package/src/primitives/composer/ComposerAddAttachment.tsx +3 -8
  163. package/src/primitives/messagePart/MessagePartImage.test.tsx +91 -0
  164. package/src/primitives/messagePart/MessagePartImage.tsx +33 -0
  165. package/src/primitives/messagePart/MessagePartInProgress.ts +1 -0
  166. package/src/primitives/messagePart/MessagePartText.test.tsx +80 -0
  167. package/src/primitives/messagePart/MessagePartText.tsx +36 -0
  168. package/src/primitives/messagePart.ts +10 -0
  169. package/src/primitives/thread/ThreadMessages.test.tsx +335 -54
  170. package/src/primitives/thread/ThreadMessages.tsx +104 -26
@@ -8,12 +8,17 @@ const h = vi.hoisted(() => ({
8
8
  state: { disabled: false },
9
9
  }));
10
10
 
11
- vi.mock("@assistant-ui/core/react", () => ({
12
- useBranchPickerPrevious: () => ({
13
- previous: h.previous,
14
- disabled: h.state.disabled,
15
- }),
16
- }));
11
+ vi.mock("@assistant-ui/core/react", async (importOriginal) => {
12
+ const actual =
13
+ await importOriginal<typeof import("@assistant-ui/core/react")>();
14
+ return {
15
+ ...actual,
16
+ useBranchPickerPrevious: () => ({
17
+ previous: h.previous,
18
+ disabled: h.state.disabled,
19
+ }),
20
+ };
21
+ });
17
22
 
18
23
  (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
19
24
 
@@ -97,4 +102,45 @@ describe("BranchPickerPrevious", () => {
97
102
 
98
103
  expect(h.previous).toHaveBeenCalledTimes(1);
99
104
  });
105
+
106
+ it("passes the hook disabled state and an explicit override to render children", async () => {
107
+ h.state.disabled = true;
108
+ let hookState: unknown = null;
109
+
110
+ await act(async () => {
111
+ root.render(
112
+ <BranchPickerPrevious testID="t">
113
+ {(state) => {
114
+ hookState = state;
115
+ return state.disabled ? "disabled" : "enabled";
116
+ }}
117
+ </BranchPickerPrevious>,
118
+ );
119
+ });
120
+
121
+ expect(hookState).toMatchObject({ pressed: false, disabled: true });
122
+ expect(container.textContent).toBe("disabled");
123
+
124
+ let overrideState: unknown = null;
125
+
126
+ await act(async () => {
127
+ root.render(
128
+ <BranchPickerPrevious testID="t" disabled={false}>
129
+ {(state) => {
130
+ overrideState = state;
131
+ return state.disabled ? "disabled" : "enabled";
132
+ }}
133
+ </BranchPickerPrevious>,
134
+ );
135
+ });
136
+
137
+ expect(overrideState).toMatchObject({ pressed: false, disabled: false });
138
+ expect(container.textContent).toBe("enabled");
139
+ });
140
+
141
+ it("renders plain children", async () => {
142
+ const el = await mount();
143
+
144
+ expect(el.textContent).toBe("prev");
145
+ });
100
146
  });
@@ -1,11 +1,20 @@
1
- import { Pressable, type PressableProps } from "react-native";
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Pressable,
4
+ type PressableProps,
5
+ type PressableStateCallbackType,
6
+ } from "react-native";
2
7
  import { useBranchPickerPrevious } from "@assistant-ui/core/react";
3
8
 
4
9
  export type BranchPickerPreviousProps = Omit<
5
10
  PressableProps,
6
11
  "onPress" | "children"
7
12
  > & {
8
- children: PressableProps["children"];
13
+ children:
14
+ | ReactNode
15
+ | ((
16
+ state: PressableStateCallbackType & { disabled: boolean },
17
+ ) => ReactNode);
9
18
  };
10
19
 
11
20
  export const BranchPickerPrevious = ({
@@ -14,15 +23,18 @@ export const BranchPickerPrevious = ({
14
23
  ...pressableProps
15
24
  }: BranchPickerPreviousProps) => {
16
25
  const { previous, disabled } = useBranchPickerPrevious();
26
+ const isDisabled = disabledProp ?? disabled;
17
27
 
18
28
  return (
19
29
  <Pressable
20
30
  onPress={previous}
21
- disabled={disabledProp ?? disabled}
31
+ disabled={isDisabled}
22
32
  accessibilityRole="button"
23
33
  {...pressableProps}
24
34
  >
25
- {children}
35
+ {typeof children === "function"
36
+ ? (state) => children({ ...state, disabled: isDisabled })
37
+ : children}
26
38
  </Pressable>
27
39
  );
28
40
  };
@@ -1,19 +1,14 @@
1
1
  import { Pressable, type PressableProps } from "react-native";
2
2
  import { useComposerAddAttachment } from "@assistant-ui/core/react";
3
3
 
4
- export type ComposerAddAttachmentProps = Omit<
5
- PressableProps,
6
- "onPress" | "children"
7
- > & {
4
+ export type ComposerAddAttachmentProps = Omit<PressableProps, "children"> & {
8
5
  children: PressableProps["children"];
9
6
  };
10
7
 
11
8
  /**
12
- * A button that triggers the attachment adding flow.
9
+ * A button for adding attachments. It is disabled while the composer cannot accept attachments.
13
10
  *
14
- * Note: The actual file picker implementation is platform-specific.
15
- * This component calls `useComposerAddAttachment()` from `@assistant-ui/core/react`.
16
- * You must handle the file selection in your own component using the returned `addAttachment` callback.
11
+ * React Native has no file input, so the caller opens the platform picker (for example `expo-image-picker`) in `onPress` and passes the selection to `aui.composer.addAttachment`.
17
12
  */
18
13
  export const ComposerAddAttachment = ({
19
14
  children,
@@ -0,0 +1,91 @@
1
+ import { act } from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import type { ThreadMessageLike } from "@assistant-ui/core";
5
+ import {
6
+ AssistantRuntimeProvider,
7
+ MessageByIndexProvider,
8
+ PartByIndexProvider,
9
+ useExternalStoreRuntime,
10
+ } from "@assistant-ui/core/react";
11
+ import { MessagePartPrimitiveImage } from "./MessagePartImage";
12
+
13
+ vi.mock("react-native", async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import("react-native")>();
15
+ const React = await import("react");
16
+
17
+ const ImageMock = React.forwardRef(function ImageMock(
18
+ props: { source?: { uri?: string }; testID?: string },
19
+ ref: React.Ref<HTMLImageElement>,
20
+ ) {
21
+ return React.createElement("img", {
22
+ ref,
23
+ src: props.source?.uri,
24
+ "data-testid": props.testID,
25
+ });
26
+ });
27
+
28
+ return { ...actual, Image: ImageMock };
29
+ });
30
+
31
+ (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
32
+
33
+ const messages: ThreadMessageLike[] = [
34
+ {
35
+ role: "assistant",
36
+ content: [
37
+ { type: "text", text: "Answer" },
38
+ { type: "image", image: "https://example.com/image.png" },
39
+ ],
40
+ },
41
+ ];
42
+
43
+ const App = ({ partIndex }: { partIndex: number }) => {
44
+ const runtime = useExternalStoreRuntime({
45
+ messages,
46
+ convertMessage: (message) => message,
47
+ onNew: async () => {
48
+ throw new Error("This thread is read-only");
49
+ },
50
+ });
51
+
52
+ return (
53
+ <AssistantRuntimeProvider runtime={runtime}>
54
+ <MessageByIndexProvider index={0}>
55
+ <PartByIndexProvider index={partIndex}>
56
+ <MessagePartPrimitiveImage testID="image" />
57
+ </PartByIndexProvider>
58
+ </MessageByIndexProvider>
59
+ </AssistantRuntimeProvider>
60
+ );
61
+ };
62
+
63
+ describe("MessagePartPrimitiveImage", () => {
64
+ let container: HTMLDivElement;
65
+ let root: Root;
66
+
67
+ beforeEach(() => {
68
+ container = document.createElement("div");
69
+ document.body.appendChild(container);
70
+ root = createRoot(container);
71
+ });
72
+
73
+ afterEach(async () => {
74
+ await act(async () => root.unmount());
75
+ container.remove();
76
+ });
77
+
78
+ const mount = async (partIndex: number) => {
79
+ await act(async () => root.render(<App partIndex={partIndex} />));
80
+ return container.querySelector<HTMLImageElement>('[data-testid="image"]');
81
+ };
82
+
83
+ it("renders the current image part URI", async () => {
84
+ const image = await mount(1);
85
+ expect(image?.getAttribute("src")).toBe("https://example.com/image.png");
86
+ });
87
+
88
+ it("renders nothing for a non-image part", async () => {
89
+ expect(await mount(0)).toBeNull();
90
+ });
91
+ });
@@ -0,0 +1,33 @@
1
+ import {
2
+ type ComponentRef,
3
+ type ForwardRefExoticComponent,
4
+ forwardRef,
5
+ type RefAttributes,
6
+ } from "react";
7
+ import { Image, type ImageProps } from "react-native";
8
+ import { useAuiState } from "@assistant-ui/store";
9
+
10
+ export namespace MessagePartPrimitiveImage {
11
+ export type Element = ComponentRef<typeof Image>;
12
+ export type Props = Omit<ImageProps, "source">;
13
+ }
14
+
15
+ export const MessagePartPrimitiveImage: ForwardRefExoticComponent<
16
+ MessagePartPrimitiveImage.Props &
17
+ RefAttributes<MessagePartPrimitiveImage.Element>
18
+ > = forwardRef<
19
+ MessagePartPrimitiveImage.Element,
20
+ MessagePartPrimitiveImage.Props
21
+ >((props, forwardedRef) => {
22
+ const uri = useAuiState((s) => {
23
+ const part = s.part;
24
+ return part.type === "image" ? part.image : undefined;
25
+ });
26
+
27
+ if (uri === undefined) return null;
28
+ return <Image source={{ uri }} {...props} ref={forwardedRef} />;
29
+ });
30
+
31
+ MessagePartPrimitiveImage.displayName = "MessagePartPrimitive.Image";
32
+
33
+ export type MessagePartImageProps = MessagePartPrimitiveImage.Props;
@@ -0,0 +1 @@
1
+ export { MessagePartPrimitiveInProgress } from "@assistant-ui/core/react";
@@ -0,0 +1,80 @@
1
+ import { act } from "react";
2
+ import { createRoot, type Root } from "react-dom/client";
3
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
+ import type { ThreadMessageLike } from "@assistant-ui/core";
5
+ import {
6
+ AssistantRuntimeProvider,
7
+ MessageByIndexProvider,
8
+ PartByIndexProvider,
9
+ useExternalStoreRuntime,
10
+ } from "@assistant-ui/core/react";
11
+ import { MessagePartPrimitiveText } from "./MessagePartText";
12
+
13
+ (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
14
+
15
+ const messages: ThreadMessageLike[] = [
16
+ {
17
+ role: "assistant",
18
+ content: [
19
+ { type: "text", text: "Answer" },
20
+ { type: "reasoning", text: "Thinking" },
21
+ { type: "image", image: "https://example.com/image.png" },
22
+ ],
23
+ },
24
+ ];
25
+
26
+ const App = ({ partIndex }: { partIndex: number }) => {
27
+ const runtime = useExternalStoreRuntime({
28
+ messages,
29
+ convertMessage: (message) => message,
30
+ onNew: async () => {
31
+ throw new Error("This thread is read-only");
32
+ },
33
+ });
34
+
35
+ return (
36
+ <AssistantRuntimeProvider runtime={runtime}>
37
+ <MessageByIndexProvider index={0}>
38
+ <PartByIndexProvider index={partIndex}>
39
+ <MessagePartPrimitiveText testID="text" />
40
+ </PartByIndexProvider>
41
+ </MessageByIndexProvider>
42
+ </AssistantRuntimeProvider>
43
+ );
44
+ };
45
+
46
+ describe("MessagePartPrimitiveText", () => {
47
+ let container: HTMLDivElement;
48
+ let root: Root;
49
+
50
+ beforeEach(() => {
51
+ container = document.createElement("div");
52
+ document.body.appendChild(container);
53
+ root = createRoot(container);
54
+ });
55
+
56
+ afterEach(async () => {
57
+ await act(async () => root.unmount());
58
+ container.remove();
59
+ });
60
+
61
+ const mount = async (partIndex: number) => {
62
+ await act(async () => root.render(<App partIndex={partIndex} />));
63
+ return container.querySelector('[data-testid="text"]') as HTMLElement;
64
+ };
65
+
66
+ it("renders text from the current text part", async () => {
67
+ const text = await mount(0);
68
+ expect(text.textContent).toBe("Answer");
69
+ });
70
+
71
+ it("renders text from the current reasoning part", async () => {
72
+ const text = await mount(1);
73
+ expect(text.textContent).toBe("Thinking");
74
+ });
75
+
76
+ it("renders empty text for a non-text part", async () => {
77
+ const text = await mount(2);
78
+ expect(text.textContent).toBe("");
79
+ });
80
+ });
@@ -0,0 +1,36 @@
1
+ import {
2
+ type ComponentRef,
3
+ type ForwardRefExoticComponent,
4
+ forwardRef,
5
+ type RefAttributes,
6
+ } from "react";
7
+ import { Text, type TextProps } from "react-native";
8
+ import { useAuiState } from "@assistant-ui/store";
9
+
10
+ export namespace MessagePartPrimitiveText {
11
+ export type Element = ComponentRef<typeof Text>;
12
+ export type Props = Omit<TextProps, "children">;
13
+ }
14
+
15
+ export const MessagePartPrimitiveText: ForwardRefExoticComponent<
16
+ MessagePartPrimitiveText.Props &
17
+ RefAttributes<MessagePartPrimitiveText.Element>
18
+ > = forwardRef<
19
+ MessagePartPrimitiveText.Element,
20
+ MessagePartPrimitiveText.Props
21
+ >((props, forwardedRef) => {
22
+ const text = useAuiState((s) => {
23
+ const part = s.part;
24
+ return part.type === "text" || part.type === "reasoning" ? part.text : "";
25
+ });
26
+
27
+ return (
28
+ <Text {...props} ref={forwardedRef}>
29
+ {text}
30
+ </Text>
31
+ );
32
+ });
33
+
34
+ MessagePartPrimitiveText.displayName = "MessagePartPrimitive.Text";
35
+
36
+ export type MessagePartTextProps = MessagePartPrimitiveText.Props;
@@ -0,0 +1,10 @@
1
+ export {
2
+ MessagePartPrimitiveText as Text,
3
+ type MessagePartTextProps as TextProps,
4
+ } from "./messagePart/MessagePartText";
5
+ export {
6
+ MessagePartPrimitiveImage as Image,
7
+ type MessagePartImageProps as ImageProps,
8
+ } from "./messagePart/MessagePartImage";
9
+ export { MessagePartPrimitiveInProgress as InProgress } from "./messagePart/MessagePartInProgress";
10
+ export { PartPrimitiveMessages as Messages } from "@assistant-ui/core/react";