@assistant-ui/react-native 0.1.40 → 0.1.42
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.
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/primitives/composer/ComposerAddAttachment.d.ts +3 -5
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +2 -4
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/message/MessageContent.d.ts.map +1 -1
- package/dist/primitives/message/MessageContent.js +1 -1
- package/dist/primitives/message/MessageContent.js.map +1 -1
- package/dist/primitives/message/MessageParts.js +2 -14
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +186 -152
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/package.json +9 -8
- package/src/index.ts +29 -0
- package/src/primitives/composer/ComposerAddAttachment.tsx +3 -8
- package/src/primitives/message/MessageContent.runtime.test.tsx +81 -0
- package/src/primitives/message/MessageContent.test.tsx +6 -1
- package/src/primitives/message/MessageContent.tsx +13 -7
- package/src/primitives/message/MessageParts.test.tsx +116 -0
- package/src/primitives/message/MessageParts.tsx +1 -24
- package/src/primitives/thread/ThreadMessages.test.tsx +256 -53
- package/src/primitives/thread/ThreadMessages.tsx +67 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-native",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
4
4
|
"description": "React Native bindings for assistant-ui",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant",
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
],
|
|
32
32
|
"sideEffects": false,
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@assistant-ui/core": "^0.3.
|
|
35
|
-
"@assistant-ui/store": "^0.3.
|
|
36
|
-
"@assistant-ui/tap": "^0.9.
|
|
37
|
-
"assistant-
|
|
34
|
+
"@assistant-ui/core": "^0.3.19",
|
|
35
|
+
"@assistant-ui/store": "^0.3.13",
|
|
36
|
+
"@assistant-ui/tap": "^0.9.17",
|
|
37
|
+
"assistant-cloud": "^0.2.1",
|
|
38
|
+
"assistant-stream": "^0.3.43"
|
|
38
39
|
},
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"@types/react": "*",
|
|
@@ -47,16 +48,16 @@
|
|
|
47
48
|
}
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
51
|
+
"@assistant-ui/x-buildutils": "0.0.27",
|
|
51
52
|
"@babel/core": "^7.29.7",
|
|
52
53
|
"@types/react": "^19.2.18",
|
|
53
|
-
"@types/react-dom": "^19.2.
|
|
54
|
+
"@types/react-dom": "^19.2.7",
|
|
54
55
|
"jsdom": "^30.0.1",
|
|
55
56
|
"react": "^19.2.8",
|
|
56
57
|
"react-dom": "^19.2.8",
|
|
57
58
|
"react-native": "^0.87.1",
|
|
58
59
|
"react-native-web": "^0.21.2",
|
|
59
|
-
"vitest": "^
|
|
60
|
+
"vitest": "^5.0.0"
|
|
60
61
|
},
|
|
61
62
|
"publishConfig": {
|
|
62
63
|
"access": "public",
|
package/src/index.ts
CHANGED
|
@@ -94,6 +94,7 @@ export type {
|
|
|
94
94
|
AttachmentState,
|
|
95
95
|
ThreadListItemState,
|
|
96
96
|
QueueItemState,
|
|
97
|
+
TaskState,
|
|
97
98
|
} from "@assistant-ui/core/store";
|
|
98
99
|
|
|
99
100
|
// Store hooks and components
|
|
@@ -127,6 +128,34 @@ export {
|
|
|
127
128
|
type LocalRuntimeOptions,
|
|
128
129
|
} from "./runtimes/useLocalRuntime";
|
|
129
130
|
export { useRemoteThreadListRuntime } from "./runtimes/useRemoteThreadListRuntime";
|
|
131
|
+
export {
|
|
132
|
+
getExternalStoreMessages,
|
|
133
|
+
bindExternalStoreMessage,
|
|
134
|
+
pickExternalStoreSharedOptions,
|
|
135
|
+
createMessageQueue,
|
|
136
|
+
MessageNotSentError,
|
|
137
|
+
isMessageNotSentError,
|
|
138
|
+
type ExternalStoreAdapter,
|
|
139
|
+
type ExternalStoreMessageConverter,
|
|
140
|
+
type ExternalStoreSharedOptions,
|
|
141
|
+
type ExternalStoreThreadListAdapter,
|
|
142
|
+
type ExternalStoreThreadData,
|
|
143
|
+
type ExternalStoreBranchChange,
|
|
144
|
+
type ExternalThreadQueueAdapter,
|
|
145
|
+
type ExternalThreadBranchAdapter,
|
|
146
|
+
type MessageQueueDriver,
|
|
147
|
+
type MessageQueueController,
|
|
148
|
+
} from "@assistant-ui/core";
|
|
149
|
+
export {
|
|
150
|
+
useExternalStoreRuntime,
|
|
151
|
+
useExternalStoreSharedOptions,
|
|
152
|
+
useExternalMessageConverter,
|
|
153
|
+
convertExternalMessages as unstable_convertExternalMessages,
|
|
154
|
+
createExternalMessageConversionCache as unstable_createExternalMessageConversionCache,
|
|
155
|
+
createMessageConverter as unstable_createMessageConverter,
|
|
156
|
+
type ExternalMessageConversionCache as Unstable_ExternalMessageConversionCache,
|
|
157
|
+
type JoinStrategy,
|
|
158
|
+
} from "@assistant-ui/core/react";
|
|
130
159
|
|
|
131
160
|
// Primitives
|
|
132
161
|
export * as ThreadPrimitive from "./primitives/thread";
|
|
@@ -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
|
|
9
|
+
* A button for adding attachments. It is disabled while the composer cannot accept attachments.
|
|
13
10
|
*
|
|
14
|
-
*
|
|
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,81 @@
|
|
|
1
|
+
import { act } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import type { MessagePartState, ThreadMessageLike } from "@assistant-ui/core";
|
|
5
|
+
import {
|
|
6
|
+
AssistantRuntimeProvider,
|
|
7
|
+
MessageByIndexProvider,
|
|
8
|
+
useAssistantDataUI,
|
|
9
|
+
useAssistantToolUI,
|
|
10
|
+
useExternalStoreRuntime,
|
|
11
|
+
} from "@assistant-ui/core/react";
|
|
12
|
+
import { MessageContent } from "./MessageContent";
|
|
13
|
+
|
|
14
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
15
|
+
|
|
16
|
+
describe("MessageContent with a runtime", () => {
|
|
17
|
+
it.each(["tool-call", "data"] as const)(
|
|
18
|
+
"renders derived status for registered %s UIs",
|
|
19
|
+
async (type) => {
|
|
20
|
+
const Status = ({ status }: Pick<MessagePartState, "status">) => (
|
|
21
|
+
<span>{status.type}</span>
|
|
22
|
+
);
|
|
23
|
+
const Registrations = () => {
|
|
24
|
+
useAssistantToolUI({ toolName: "search", render: Status });
|
|
25
|
+
useAssistantDataUI({ name: "chart", render: Status });
|
|
26
|
+
return <MessageContent />;
|
|
27
|
+
};
|
|
28
|
+
const App = ({ message }: { message: ThreadMessageLike }) => {
|
|
29
|
+
const runtime = useExternalStoreRuntime({
|
|
30
|
+
messages: [message],
|
|
31
|
+
convertMessage: (value) => value,
|
|
32
|
+
onNew: async () => {},
|
|
33
|
+
});
|
|
34
|
+
return (
|
|
35
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
36
|
+
<MessageByIndexProvider index={0}>
|
|
37
|
+
<Registrations />
|
|
38
|
+
</MessageByIndexProvider>
|
|
39
|
+
</AssistantRuntimeProvider>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
const container = document.createElement("div");
|
|
43
|
+
const root = createRoot(container);
|
|
44
|
+
try {
|
|
45
|
+
for (const status of [
|
|
46
|
+
"running",
|
|
47
|
+
"requires-action",
|
|
48
|
+
"complete",
|
|
49
|
+
] as const) {
|
|
50
|
+
if (type === "data" && status === "requires-action") continue;
|
|
51
|
+
const message: ThreadMessageLike = {
|
|
52
|
+
id: "m1",
|
|
53
|
+
role: "assistant",
|
|
54
|
+
status:
|
|
55
|
+
status === "running"
|
|
56
|
+
? { type: status }
|
|
57
|
+
: status === "complete"
|
|
58
|
+
? { type: status, reason: "stop" }
|
|
59
|
+
: { type: status, reason: "tool-calls" },
|
|
60
|
+
content: [
|
|
61
|
+
type === "data"
|
|
62
|
+
? { type, name: "chart", data: { value: 1 } }
|
|
63
|
+
: {
|
|
64
|
+
type,
|
|
65
|
+
toolName: "search",
|
|
66
|
+
toolCallId: "c1",
|
|
67
|
+
args: {},
|
|
68
|
+
argsText: "{}",
|
|
69
|
+
...(status === "complete" ? { result: "done" } : {}),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
await act(async () => root.render(<App message={message} />));
|
|
74
|
+
expect(container.textContent).toBe(status);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
await act(async () => root.unmount());
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
});
|
|
@@ -10,7 +10,12 @@ const h = vi.hoisted(() => ({
|
|
|
10
10
|
resumeToolCall: vi.fn(),
|
|
11
11
|
respondToToolApproval: vi.fn(),
|
|
12
12
|
state: {
|
|
13
|
-
message: {
|
|
13
|
+
message: {
|
|
14
|
+
content: [] as AnyPart[],
|
|
15
|
+
get parts() {
|
|
16
|
+
return this.content;
|
|
17
|
+
},
|
|
18
|
+
},
|
|
14
19
|
tools: { toolUIs: {} as Record<string, unknown> },
|
|
15
20
|
dataRenderers: { renderers: {} as Record<string, unknown> },
|
|
16
21
|
},
|
|
@@ -3,8 +3,7 @@ import { Text } from "react-native";
|
|
|
3
3
|
import type {
|
|
4
4
|
ThreadUserMessagePart,
|
|
5
5
|
ThreadAssistantMessagePart,
|
|
6
|
-
|
|
7
|
-
DataMessagePart,
|
|
6
|
+
MessagePartState,
|
|
8
7
|
} from "@assistant-ui/core";
|
|
9
8
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
10
9
|
import type {
|
|
@@ -13,6 +12,7 @@ import type {
|
|
|
13
12
|
} from "@assistant-ui/core/react";
|
|
14
13
|
|
|
15
14
|
type MessageContentPart = ThreadUserMessagePart | ThreadAssistantMessagePart;
|
|
15
|
+
type MessageContentStatePart = MessagePartState;
|
|
16
16
|
|
|
17
17
|
export type MessageContentProps = {
|
|
18
18
|
renderText?: (props: {
|
|
@@ -59,9 +59,12 @@ const ToolUIDisplay = ({
|
|
|
59
59
|
index,
|
|
60
60
|
}: {
|
|
61
61
|
Fallback:
|
|
62
|
-
| ((props: {
|
|
62
|
+
| ((props: {
|
|
63
|
+
part: Extract<MessageContentPart, { type: "tool-call" }>;
|
|
64
|
+
index: number;
|
|
65
|
+
}) => ReactElement)
|
|
63
66
|
| undefined;
|
|
64
|
-
part:
|
|
67
|
+
part: Extract<MessageContentStatePart, { type: "tool-call" }>;
|
|
65
68
|
index: number;
|
|
66
69
|
}) => {
|
|
67
70
|
const aui = useAui();
|
|
@@ -91,9 +94,12 @@ const DataUIDisplay = ({
|
|
|
91
94
|
index,
|
|
92
95
|
}: {
|
|
93
96
|
Fallback:
|
|
94
|
-
| ((props: {
|
|
97
|
+
| ((props: {
|
|
98
|
+
part: Extract<MessageContentPart, { type: "data" }>;
|
|
99
|
+
index: number;
|
|
100
|
+
}) => ReactElement)
|
|
95
101
|
| undefined;
|
|
96
|
-
part:
|
|
102
|
+
part: Extract<MessageContentStatePart, { type: "data" }>;
|
|
97
103
|
index: number;
|
|
98
104
|
}) => {
|
|
99
105
|
const Render = useAuiState((s) => {
|
|
@@ -115,7 +121,7 @@ export const MessageContent = ({
|
|
|
115
121
|
renderFile,
|
|
116
122
|
renderData,
|
|
117
123
|
}: MessageContentProps) => {
|
|
118
|
-
const content = useAuiState((s) => s.message.
|
|
124
|
+
const content = useAuiState((s) => s.message.parts);
|
|
119
125
|
|
|
120
126
|
return (
|
|
121
127
|
<>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { act } from "react";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
import { Text } from "react-native";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import type { ThreadMessageLike } from "@assistant-ui/core";
|
|
6
|
+
import {
|
|
7
|
+
AssistantRuntimeProvider,
|
|
8
|
+
MessageByIndexProvider,
|
|
9
|
+
useExternalStoreRuntime,
|
|
10
|
+
} from "@assistant-ui/core/react";
|
|
11
|
+
import { MessagePrimitiveParts } from "./MessageParts";
|
|
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
|
+
{
|
|
22
|
+
type: "generative-ui",
|
|
23
|
+
spec: {
|
|
24
|
+
root: [
|
|
25
|
+
{ component: "Card", props: { title: "Result" } },
|
|
26
|
+
{ component: "Unknown" },
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{ type: "data", name: "chart", data: 42 },
|
|
31
|
+
{ type: "data", name: "other", data: 7 },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const components = {
|
|
37
|
+
generativeUI: {
|
|
38
|
+
components: {
|
|
39
|
+
Card: ({ title }: { title: string }) => <Text>{title}</Text>,
|
|
40
|
+
},
|
|
41
|
+
Fallback: ({ component }: { component: string }) => (
|
|
42
|
+
<Text>Unavailable: {component}</Text>
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
data: {
|
|
46
|
+
by_name: {
|
|
47
|
+
chart: ({ data }: { data: unknown }) => (
|
|
48
|
+
<Text>Chart: {String(data)}</Text>
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
Fallback: ({ data }: { data: unknown }) => (
|
|
52
|
+
<Text>Data: {String(data)}</Text>
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const App = (props: MessagePrimitiveParts.Props) => {
|
|
58
|
+
const runtime = useExternalStoreRuntime({
|
|
59
|
+
messages,
|
|
60
|
+
convertMessage: (message) => message,
|
|
61
|
+
onNew: async () => {
|
|
62
|
+
throw new Error("This thread is read-only");
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return (
|
|
66
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
67
|
+
<MessageByIndexProvider index={0}>
|
|
68
|
+
<MessagePrimitiveParts {...props} />
|
|
69
|
+
</MessageByIndexProvider>
|
|
70
|
+
</AssistantRuntimeProvider>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const ChainOfThought = () => <Text>Thought group</Text>;
|
|
75
|
+
|
|
76
|
+
describe("MessagePrimitiveParts", () => {
|
|
77
|
+
let container: HTMLDivElement;
|
|
78
|
+
let root: Root;
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
container = document.createElement("div");
|
|
82
|
+
document.body.appendChild(container);
|
|
83
|
+
root = createRoot(container);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(async () => {
|
|
87
|
+
await act(async () => root.unmount());
|
|
88
|
+
container.remove();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("renders generative UI and its fallback beside native text", async () => {
|
|
92
|
+
await act(async () => root.render(<App components={components} />));
|
|
93
|
+
expect(container.textContent).toBe(
|
|
94
|
+
"AnswerResultUnavailable: UnknownChart: 42Data: 7",
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("keeps data and generative UI when chain-of-thought grouping changes", async () => {
|
|
99
|
+
await act(async () => root.render(<App components={components} />));
|
|
100
|
+
expect(container.textContent).toBe(
|
|
101
|
+
"AnswerResultUnavailable: UnknownChart: 42Data: 7",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
await act(async () =>
|
|
105
|
+
root.render(<App components={{ ...components, ChainOfThought }} />),
|
|
106
|
+
);
|
|
107
|
+
expect(container.textContent).toBe(
|
|
108
|
+
"AnswerThought groupResultUnavailable: UnknownChart: 42Data: 7",
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
await act(async () => root.render(<App components={components} />));
|
|
112
|
+
expect(container.textContent).toBe(
|
|
113
|
+
"AnswerResultUnavailable: UnknownChart: 42Data: 7",
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -30,30 +30,7 @@ export const MessagePrimitiveParts: FC<MessagePrimitiveParts.Props> = (
|
|
|
30
30
|
|
|
31
31
|
const { components, ...rest } = props;
|
|
32
32
|
const merged = components
|
|
33
|
-
? {
|
|
34
|
-
Text: components.Text ?? rnDefaultComponents.Text,
|
|
35
|
-
Image: components.Image ?? messagePartsDefaultComponents.Image,
|
|
36
|
-
Reasoning:
|
|
37
|
-
components.Reasoning ?? messagePartsDefaultComponents.Reasoning,
|
|
38
|
-
Source: components.Source ?? messagePartsDefaultComponents.Source,
|
|
39
|
-
File: components.File ?? messagePartsDefaultComponents.File,
|
|
40
|
-
Unstable_Audio:
|
|
41
|
-
components.Unstable_Audio ??
|
|
42
|
-
messagePartsDefaultComponents.Unstable_Audio,
|
|
43
|
-
...("ChainOfThought" in components
|
|
44
|
-
? { ChainOfThought: components.ChainOfThought }
|
|
45
|
-
: {
|
|
46
|
-
tools: components.tools,
|
|
47
|
-
data: components.data,
|
|
48
|
-
ToolGroup:
|
|
49
|
-
components.ToolGroup ?? messagePartsDefaultComponents.ToolGroup,
|
|
50
|
-
ReasoningGroup:
|
|
51
|
-
components.ReasoningGroup ??
|
|
52
|
-
messagePartsDefaultComponents.ReasoningGroup,
|
|
53
|
-
}),
|
|
54
|
-
Empty: components.Empty,
|
|
55
|
-
Quote: components.Quote,
|
|
56
|
-
}
|
|
33
|
+
? { ...components, Text: components.Text ?? rnDefaultComponents.Text }
|
|
57
34
|
: rnDefaultComponents;
|
|
58
35
|
|
|
59
36
|
return <MessagePrimitivePartsBase components={merged as any} {...rest} />;
|