@assistant-ui/react-native 0.1.36 → 0.1.38
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 +3 -2
- package/dist/index.js +2 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -0
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.d.ts +2 -3
- package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.js +1 -0
- package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -0
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -0
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.d.ts +2 -3
- package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.js +1 -0
- package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.d.ts +2 -3
- package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -0
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts +2 -3
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.js +1 -0
- package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts +2 -3
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.js +1 -0
- package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts +2 -3
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -0
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts +2 -3
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +1 -0
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts +2 -3
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerCancel.js +1 -0
- package/dist/primitives/composer/ComposerCancel.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQueue.d.ts +2 -0
- package/dist/primitives/composer/ComposerQueue.js +2 -0
- package/dist/primitives/composer/ComposerQuote.d.ts +10 -0
- package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuote.js +38 -0
- package/dist/primitives/composer/ComposerQuote.js.map +1 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts +9 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.js +48 -0
- package/dist/primitives/composer/ComposerQuoteDismiss.js.map +1 -0
- package/dist/primitives/composer/ComposerQuoteText.d.ts +10 -0
- package/dist/primitives/composer/ComposerQuoteText.d.ts.map +1 -0
- package/dist/primitives/composer/ComposerQuoteText.js +40 -0
- package/dist/primitives/composer/ComposerQuoteText.js.map +1 -0
- package/dist/primitives/composer/ComposerSend.d.ts +2 -3
- package/dist/primitives/composer/ComposerSend.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerSend.js +1 -0
- package/dist/primitives/composer/ComposerSend.js.map +1 -1
- package/dist/primitives/composer.d.ts +6 -2
- package/dist/primitives/composer.js +9 -1
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/message/MessageIf.d.ts +3 -0
- package/dist/primitives/message/MessageIf.d.ts.map +1 -1
- package/dist/primitives/message/MessageIf.js +3 -0
- package/dist/primitives/message/MessageIf.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.d.ts +9 -0
- package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -0
- package/dist/primitives/queueItem/QueueItemRemove.js +48 -0
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.d.ts +9 -0
- package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.js +48 -0
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -0
- package/dist/primitives/queueItem/QueueItemText.d.ts +10 -0
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -0
- package/dist/primitives/queueItem/QueueItemText.js +45 -0
- package/dist/primitives/queueItem/QueueItemText.js.map +1 -0
- package/dist/primitives/queueItem.d.ts +9 -0
- package/dist/primitives/queueItem.d.ts.map +1 -0
- package/dist/primitives/queueItem.js +14 -0
- package/dist/primitives/queueItem.js.map +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts +2 -3
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.d.ts +4 -1
- package/dist/primitives/thread/ThreadEmpty.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadEmpty.js +12 -4
- package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.d.ts +3 -0
- package/dist/primitives/thread/ThreadIf.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadIf.js +5 -6
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +2 -2
- package/dist/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/primitives/thread/ThreadSuggestion.d.ts +2 -3
- package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadSuggestion.js +1 -0
- package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListItems.js +8 -3
- package/dist/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.d.ts +6 -4
- package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +46 -15
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.js +1 -0
- package/dist/primitives/threadListItem/ThreadListItemArchive.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.js +1 -0
- package/dist/primitives/threadListItem/ThreadListItemDelete.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts +6 -4
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +46 -15
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts +2 -3
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.js +1 -0
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.js.map +1 -1
- package/package.json +9 -8
- package/src/index.ts +2 -0
- package/src/primitives/accessibilityRole.test.tsx +335 -0
- package/src/primitives/actionBar/ActionBarCopy.tsx +1 -0
- package/src/primitives/actionBar/ActionBarEdit.tsx +6 -3
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
- package/src/primitives/actionBar/ActionBarReload.tsx +6 -3
- package/src/primitives/attachment/AttachmentRemove.tsx +11 -4
- package/src/primitives/branchPicker/BranchPickerNext.tsx +6 -3
- package/src/primitives/branchPicker/BranchPickerPrevious.tsx +6 -3
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.tsx +4 -4
- package/src/primitives/composer/ComposerAddAttachment.tsx +10 -4
- package/src/primitives/composer/ComposerCancel.tsx +6 -3
- package/src/primitives/composer/ComposerInput.tsx +17 -15
- package/src/primitives/composer/ComposerQueue.test.tsx +9 -0
- package/src/primitives/composer/ComposerQueue.tsx +1 -0
- package/src/primitives/composer/ComposerQuote.test.tsx +163 -0
- package/src/primitives/composer/ComposerQuote.tsx +17 -0
- package/src/primitives/composer/ComposerQuoteDismiss.tsx +31 -0
- package/src/primitives/composer/ComposerQuoteText.tsx +17 -0
- package/src/primitives/composer/ComposerSend.test.tsx +26 -0
- package/src/primitives/composer/ComposerSend.tsx +3 -3
- package/src/primitives/composer.ts +13 -0
- package/src/primitives/message/MessageIf.tsx +3 -0
- package/src/primitives/queueItem/QueueItem.test.tsx +132 -0
- package/src/primitives/queueItem/QueueItemRemove.tsx +31 -0
- package/src/primitives/queueItem/QueueItemSteer.tsx +31 -0
- package/src/primitives/queueItem/QueueItemText.tsx +22 -0
- package/src/primitives/queueItem.ts +12 -0
- package/src/primitives/suggestion/SuggestionTrigger.tsx +6 -3
- package/src/primitives/thread/ThreadEmpty.test.tsx +17 -3
- package/src/primitives/thread/ThreadEmpty.tsx +7 -6
- package/src/primitives/thread/ThreadIf.test.tsx +20 -20
- package/src/primitives/thread/ThreadIf.tsx +5 -8
- package/src/primitives/thread/ThreadMessages.test.tsx +18 -4
- package/src/primitives/thread/ThreadMessages.tsx +3 -2
- package/src/primitives/thread/ThreadSuggestion.tsx +6 -3
- package/src/primitives/threadList/ThreadListItems.test.tsx +166 -0
- package/src/primitives/threadList/ThreadListItems.tsx +6 -1
- package/src/primitives/threadList/ThreadListNew.test.tsx +118 -3
- package/src/primitives/threadList/ThreadListNew.tsx +28 -5
- package/src/primitives/threadListItem/ThreadListItemArchive.tsx +6 -4
- package/src/primitives/threadListItem/ThreadListItemDelete.tsx +10 -4
- package/src/primitives/threadListItem/ThreadListItemTrigger.test.tsx +74 -0
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +28 -5
- package/src/primitives/threadListItem/ThreadListItemUnarchive.tsx +10 -4
- package/dist/runtimes/RemoteThreadListHookInstanceManager.d.ts +0 -2
- package/dist/runtimes/RemoteThreadListHookInstanceManager.js +0 -2
- package/dist/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +0 -2
- package/dist/runtimes/RemoteThreadListThreadListRuntimeCore.js +0 -2
- package/src/runtimes/RemoteThreadListHookInstanceManager.tsx +0 -1
- package/src/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +0 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { Pressable, type PressableProps } from "react-native";
|
|
3
|
+
import { useAui } from "@assistant-ui/store";
|
|
4
|
+
|
|
5
|
+
export type QueueItemSteerProps = Omit<
|
|
6
|
+
PressableProps,
|
|
7
|
+
"onPress" | "children"
|
|
8
|
+
> & {
|
|
9
|
+
children: PressableProps["children"];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const QueueItemSteer = ({
|
|
13
|
+
children,
|
|
14
|
+
...pressableProps
|
|
15
|
+
}: QueueItemSteerProps) => {
|
|
16
|
+
const aui = useAui();
|
|
17
|
+
|
|
18
|
+
const handleSteer = useCallback(() => {
|
|
19
|
+
aui.queueItem.steer();
|
|
20
|
+
}, [aui]);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Pressable
|
|
24
|
+
onPress={handleSteer}
|
|
25
|
+
accessibilityRole="button"
|
|
26
|
+
{...pressableProps}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Pressable>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Text, type TextProps } from "react-native";
|
|
3
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
4
|
+
|
|
5
|
+
export type QueueItemTextProps = TextProps & {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const QueueItemText = ({
|
|
10
|
+
children,
|
|
11
|
+
...textProps
|
|
12
|
+
}: QueueItemTextProps) => {
|
|
13
|
+
const text = useAuiState((s) =>
|
|
14
|
+
// hosts on the pre-parts adapter shape may omit the field at runtime
|
|
15
|
+
(s.queueItem.parts ?? [])
|
|
16
|
+
.filter((part) => part.type === "text")
|
|
17
|
+
.map((part) => part.text)
|
|
18
|
+
.join("\n\n"),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return <Text {...textProps}>{children ?? text}</Text>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
QueueItemText as Text,
|
|
3
|
+
type QueueItemTextProps as TextProps,
|
|
4
|
+
} from "./queueItem/QueueItemText";
|
|
5
|
+
export {
|
|
6
|
+
QueueItemRemove as Remove,
|
|
7
|
+
type QueueItemRemoveProps as RemoveProps,
|
|
8
|
+
} from "./queueItem/QueueItemRemove";
|
|
9
|
+
export {
|
|
10
|
+
QueueItemSteer as Steer,
|
|
11
|
+
type QueueItemSteerProps as SteerProps,
|
|
12
|
+
} from "./queueItem/QueueItemSteer";
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { Pressable, type PressableProps } from "react-native";
|
|
3
2
|
import { useAuiState } from "@assistant-ui/store";
|
|
4
3
|
import { useSuggestionTrigger } from "@assistant-ui/core/react";
|
|
5
4
|
|
|
6
|
-
export type SuggestionTriggerProps = Omit<
|
|
7
|
-
|
|
5
|
+
export type SuggestionTriggerProps = Omit<
|
|
6
|
+
PressableProps,
|
|
7
|
+
"onPress" | "children"
|
|
8
|
+
> & {
|
|
9
|
+
children: PressableProps["children"];
|
|
8
10
|
/**
|
|
9
11
|
* When true, automatically sends the message.
|
|
10
12
|
* When false, replaces or appends the composer text with the suggestion.
|
|
@@ -36,6 +38,7 @@ export const SuggestionTrigger = ({
|
|
|
36
38
|
<Pressable
|
|
37
39
|
onPress={trigger}
|
|
38
40
|
disabled={disabledProp ?? disabled}
|
|
41
|
+
accessibilityRole="button"
|
|
39
42
|
{...pressableProps}
|
|
40
43
|
>
|
|
41
44
|
{children}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import { act } from "react";
|
|
2
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
4
|
import { Text } from "react-native";
|
|
4
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type * as Store from "@assistant-ui/store";
|
|
5
7
|
import { ThreadEmpty } from "./ThreadEmpty";
|
|
6
8
|
|
|
7
9
|
const h = vi.hoisted(() => ({
|
|
8
10
|
isEmpty: true,
|
|
9
11
|
}));
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
type ThreadSlice = { thread: { isEmpty: boolean } };
|
|
14
|
+
|
|
15
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
16
|
+
const actual = await importOriginal<typeof Store>();
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
AuiIf: ({
|
|
20
|
+
condition,
|
|
21
|
+
children,
|
|
22
|
+
}: {
|
|
23
|
+
condition: (s: ThreadSlice) => boolean;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}) => (condition({ thread: { isEmpty: h.isEmpty } }) ? children : null),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
14
28
|
|
|
15
29
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
16
30
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { AuiIf } from "@assistant-ui/store";
|
|
3
3
|
|
|
4
4
|
export type ThreadEmptyProps = {
|
|
5
5
|
children: ReactNode;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use `<AuiIf condition={(s) => s.thread.isEmpty} />` instead.
|
|
10
|
+
*/
|
|
11
|
+
export const ThreadEmpty = ({ children }: ThreadEmptyProps) => (
|
|
12
|
+
<AuiIf condition={(s) => s.thread.isEmpty}>{children}</AuiIf>
|
|
13
|
+
);
|
|
@@ -2,25 +2,29 @@ import { act } from "react";
|
|
|
2
2
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
3
|
import { Text } from "react-native";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type * as Store from "@assistant-ui/store";
|
|
5
6
|
import { ThreadIf } from "./ThreadIf";
|
|
6
7
|
|
|
7
8
|
const h = vi.hoisted(() => ({
|
|
8
|
-
thread: {
|
|
9
|
+
thread: { isEmpty: true, isRunning: false },
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
|
-
vi.mock("@assistant-ui/store", () =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
13
|
+
const actual = await importOriginal<typeof Store>();
|
|
14
|
+
return {
|
|
15
|
+
...actual,
|
|
16
|
+
useAuiState: <T,>(selector: (s: { thread: typeof h.thread }) => T) =>
|
|
17
|
+
selector({ thread: h.thread }),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
15
20
|
|
|
16
21
|
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
17
22
|
|
|
18
23
|
describe("ThreadIf", () => {
|
|
19
24
|
let container: HTMLDivElement;
|
|
20
25
|
let root: Root;
|
|
21
|
-
|
|
22
26
|
beforeEach(() => {
|
|
23
|
-
h.thread.
|
|
27
|
+
h.thread.isEmpty = true;
|
|
24
28
|
h.thread.isRunning = false;
|
|
25
29
|
|
|
26
30
|
container = document.createElement("div");
|
|
@@ -46,33 +50,29 @@ describe("ThreadIf", () => {
|
|
|
46
50
|
return container.querySelector('[data-testid="child"]');
|
|
47
51
|
};
|
|
48
52
|
|
|
49
|
-
const setMessages = (count: number) => {
|
|
50
|
-
h.thread.messages = Array.from({ length: count }, (_, i) => i);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
53
|
it("renders children when no guard is set", async () => {
|
|
54
|
-
|
|
54
|
+
h.thread.isEmpty = false;
|
|
55
55
|
expect(await mount()).not.toBeNull();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
describe("empty guard", () => {
|
|
59
59
|
it("renders children when empty:true matches an empty thread", async () => {
|
|
60
|
-
|
|
60
|
+
h.thread.isEmpty = true;
|
|
61
61
|
expect(await mount({ empty: true })).not.toBeNull();
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
it("hides children when empty:true but the thread
|
|
65
|
-
|
|
64
|
+
it("hides children when empty:true but the thread is not empty", async () => {
|
|
65
|
+
h.thread.isEmpty = false;
|
|
66
66
|
expect(await mount({ empty: true })).toBeNull();
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
it("renders children when empty:false matches a non-empty thread", async () => {
|
|
70
|
-
|
|
70
|
+
h.thread.isEmpty = false;
|
|
71
71
|
expect(await mount({ empty: false })).not.toBeNull();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it("hides children when empty:false but the thread is empty", async () => {
|
|
75
|
-
|
|
75
|
+
h.thread.isEmpty = true;
|
|
76
76
|
expect(await mount({ empty: false })).toBeNull();
|
|
77
77
|
});
|
|
78
78
|
});
|
|
@@ -101,19 +101,19 @@ describe("ThreadIf", () => {
|
|
|
101
101
|
|
|
102
102
|
describe("combined guards", () => {
|
|
103
103
|
it("renders children only when both empty and running match", async () => {
|
|
104
|
-
|
|
104
|
+
h.thread.isEmpty = true;
|
|
105
105
|
h.thread.isRunning = true;
|
|
106
106
|
expect(await mount({ empty: true, running: true })).not.toBeNull();
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
it("hides children when empty matches but running does not", async () => {
|
|
110
|
-
|
|
110
|
+
h.thread.isEmpty = true;
|
|
111
111
|
h.thread.isRunning = false;
|
|
112
112
|
expect(await mount({ empty: true, running: true })).toBeNull();
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
it("hides children when running matches but empty does not", async () => {
|
|
116
|
-
|
|
116
|
+
h.thread.isEmpty = false;
|
|
117
117
|
h.thread.isRunning = true;
|
|
118
118
|
expect(await mount({ empty: true, running: true })).toBeNull();
|
|
119
119
|
});
|
|
@@ -7,17 +7,14 @@ export type ThreadIfProps = {
|
|
|
7
7
|
running?: boolean | undefined;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use `<AuiIf condition={(s) => s.thread...} />` instead.
|
|
12
|
+
*/
|
|
10
13
|
export const ThreadIf = ({ children, empty, running }: ThreadIfProps) => {
|
|
11
14
|
const thread = useAuiState((s) => s.thread);
|
|
12
15
|
|
|
13
|
-
if (empty !== undefined)
|
|
14
|
-
|
|
15
|
-
if (empty !== isEmpty) return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (running !== undefined) {
|
|
19
|
-
if (running !== thread.isRunning) return null;
|
|
20
|
-
}
|
|
16
|
+
if (empty !== undefined && empty !== thread.isEmpty) return null;
|
|
17
|
+
if (running !== undefined && running !== thread.isRunning) return null;
|
|
21
18
|
|
|
22
19
|
return <>{children}</>;
|
|
23
20
|
};
|
|
@@ -273,6 +273,20 @@ describe("ThreadMessages", () => {
|
|
|
273
273
|
expect(container.querySelector('[data-testid="c-message"]')).toBeNull();
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
+
it("renders nothing for an editing system role with no system or Message component", async () => {
|
|
277
|
+
h.state.thread.messages = [{ id: "1", role: "system" }];
|
|
278
|
+
h.state.message.role = "system";
|
|
279
|
+
h.state.message.composer.isEditing = true;
|
|
280
|
+
const components = makeComponents() as Record<string, unknown>;
|
|
281
|
+
delete components.SystemEditComposer;
|
|
282
|
+
delete components.EditComposer;
|
|
283
|
+
delete components.SystemMessage;
|
|
284
|
+
delete components.Message;
|
|
285
|
+
await mount({ components: components as never });
|
|
286
|
+
expect(container.querySelector('[data-testid="c-system"]')).toBeNull();
|
|
287
|
+
expect(container.querySelector('[data-testid="c-message"]')).toBeNull();
|
|
288
|
+
});
|
|
289
|
+
|
|
276
290
|
it("throws for an unknown role", async () => {
|
|
277
291
|
h.state.thread.messages = [{ id: "1", role: "ghost" }];
|
|
278
292
|
h.state.message.role = "ghost";
|
|
@@ -354,7 +368,7 @@ describe("ThreadMessages", () => {
|
|
|
354
368
|
props.onContentSizeChange?.(0, 140);
|
|
355
369
|
});
|
|
356
370
|
await emit("thread.runStart");
|
|
357
|
-
await emit("
|
|
371
|
+
await emit("threads.selectionChanged");
|
|
358
372
|
|
|
359
373
|
expect(h.scrollToEnd).not.toHaveBeenCalled();
|
|
360
374
|
});
|
|
@@ -442,7 +456,7 @@ describe("ThreadMessages", () => {
|
|
|
442
456
|
const props = getFlatListProps();
|
|
443
457
|
h.scrollToEnd.mockClear();
|
|
444
458
|
|
|
445
|
-
await emit("
|
|
459
|
+
await emit("threads.selectionChanged");
|
|
446
460
|
expect(h.scrollToEnd).toHaveBeenCalledTimes(1);
|
|
447
461
|
|
|
448
462
|
await act(async () => {
|
|
@@ -560,7 +574,7 @@ describe("ThreadMessages", () => {
|
|
|
560
574
|
await mountFlatList({ components: messageComponents });
|
|
561
575
|
h.scrollToEnd.mockClear();
|
|
562
576
|
|
|
563
|
-
await emit("
|
|
577
|
+
await emit("threads.selectionChanged");
|
|
564
578
|
|
|
565
579
|
expect(h.scrollToEnd).toHaveBeenCalledWith({ animated: false });
|
|
566
580
|
});
|
|
@@ -573,7 +587,7 @@ describe("ThreadMessages", () => {
|
|
|
573
587
|
});
|
|
574
588
|
h.scrollToEnd.mockClear();
|
|
575
589
|
|
|
576
|
-
await emit("
|
|
590
|
+
await emit("threads.selectionChanged");
|
|
577
591
|
|
|
578
592
|
h.state.thread.messages = [
|
|
579
593
|
{ id: "2", role: "user" },
|
|
@@ -111,7 +111,8 @@ const getComponent = (
|
|
|
111
111
|
components.SystemEditComposer ??
|
|
112
112
|
components.EditComposer ??
|
|
113
113
|
components.SystemMessage ??
|
|
114
|
-
(components.Message as ComponentType)
|
|
114
|
+
(components.Message as ComponentType) ??
|
|
115
|
+
DEFAULT_SYSTEM_MESSAGE
|
|
115
116
|
);
|
|
116
117
|
} else {
|
|
117
118
|
return (
|
|
@@ -348,7 +349,7 @@ const useThreadMessagesFlatListAutoScroll = ({
|
|
|
348
349
|
scrollToBottom(true);
|
|
349
350
|
});
|
|
350
351
|
|
|
351
|
-
useAuiEvent("
|
|
352
|
+
useAuiEvent("threads.selectionChanged", () => {
|
|
352
353
|
if (!scrollToBottomOnThreadSwitch) return;
|
|
353
354
|
initializeScrollRequestedRef.current = false;
|
|
354
355
|
lastScrollEventYRef.current = 0;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { Pressable, type PressableProps } from "react-native";
|
|
3
2
|
import { useSuggestionTrigger } from "@assistant-ui/core/react";
|
|
4
3
|
|
|
5
|
-
export type ThreadSuggestionProps = Omit<
|
|
6
|
-
|
|
4
|
+
export type ThreadSuggestionProps = Omit<
|
|
5
|
+
PressableProps,
|
|
6
|
+
"onPress" | "children"
|
|
7
|
+
> & {
|
|
8
|
+
children: PressableProps["children"];
|
|
7
9
|
/** The suggestion prompt. */
|
|
8
10
|
prompt: string;
|
|
9
11
|
/**
|
|
@@ -37,6 +39,7 @@ export const ThreadSuggestion = ({
|
|
|
37
39
|
<Pressable
|
|
38
40
|
onPress={trigger}
|
|
39
41
|
disabled={disabledProp ?? disabled}
|
|
42
|
+
accessibilityRole="button"
|
|
40
43
|
{...pressableProps}
|
|
41
44
|
>
|
|
42
45
|
{children}
|
|
@@ -0,0 +1,166 @@
|
|
|
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 { Text } from "react-native";
|
|
5
|
+
import { ThreadListItems } from "./ThreadListItems";
|
|
6
|
+
|
|
7
|
+
const h = vi.hoisted(() => ({
|
|
8
|
+
state: { threads: { threadIds: [] as string[] } },
|
|
9
|
+
flatListProps: null as Record<string, unknown> | null,
|
|
10
|
+
providerProps: vi.fn(),
|
|
11
|
+
}));
|
|
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 FlatListMock = React.forwardRef(function FlatListMock(
|
|
18
|
+
props: Record<string, unknown>,
|
|
19
|
+
_ref,
|
|
20
|
+
) {
|
|
21
|
+
h.flatListProps = props;
|
|
22
|
+
|
|
23
|
+
const data = (props.data as unknown[]) ?? [];
|
|
24
|
+
const renderItem = props.renderItem as
|
|
25
|
+
| ((value: { item: unknown; index: number }) => React.ReactNode)
|
|
26
|
+
| undefined;
|
|
27
|
+
const keyExtractor = props.keyExtractor as
|
|
28
|
+
| ((item: unknown, index: number) => string)
|
|
29
|
+
| undefined;
|
|
30
|
+
|
|
31
|
+
return React.createElement(
|
|
32
|
+
"div",
|
|
33
|
+
{ "data-testid": "flatlist" },
|
|
34
|
+
data.map((item, index) =>
|
|
35
|
+
React.createElement(
|
|
36
|
+
"div",
|
|
37
|
+
{ key: keyExtractor?.(item, index) ?? index },
|
|
38
|
+
renderItem?.({ item, index }),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
...actual,
|
|
46
|
+
FlatList: FlatListMock,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
51
|
+
const actual = await importOriginal<typeof import("@assistant-ui/store")>();
|
|
52
|
+
return {
|
|
53
|
+
...actual,
|
|
54
|
+
useAuiState: <T,>(selector: (s: typeof h.state) => T) => selector(h.state),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => {
|
|
59
|
+
const actual =
|
|
60
|
+
await importOriginal<typeof import("@assistant-ui/core/react")>();
|
|
61
|
+
return {
|
|
62
|
+
...actual,
|
|
63
|
+
ThreadListItemByIndexProvider: ({
|
|
64
|
+
index,
|
|
65
|
+
archived,
|
|
66
|
+
children,
|
|
67
|
+
}: {
|
|
68
|
+
index: number;
|
|
69
|
+
archived: boolean;
|
|
70
|
+
children?: React.ReactNode;
|
|
71
|
+
}) => {
|
|
72
|
+
h.providerProps({ index, archived });
|
|
73
|
+
return <>{children}</>;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
79
|
+
|
|
80
|
+
describe("ThreadListItems", () => {
|
|
81
|
+
let container: HTMLDivElement;
|
|
82
|
+
let root: Root;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
h.state.threads.threadIds = [];
|
|
86
|
+
h.flatListProps = null;
|
|
87
|
+
h.providerProps.mockReset();
|
|
88
|
+
|
|
89
|
+
container = document.createElement("div");
|
|
90
|
+
document.body.appendChild(container);
|
|
91
|
+
root = createRoot(container);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
afterEach(async () => {
|
|
95
|
+
await act(async () => {
|
|
96
|
+
root.unmount();
|
|
97
|
+
});
|
|
98
|
+
container.remove();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const mount = async (
|
|
102
|
+
props: Partial<Parameters<typeof ThreadListItems>[0]> = {},
|
|
103
|
+
) => {
|
|
104
|
+
await act(async () => {
|
|
105
|
+
root.render(
|
|
106
|
+
<ThreadListItems
|
|
107
|
+
renderItem={({ threadId }) => <Text>{threadId}</Text>}
|
|
108
|
+
{...props}
|
|
109
|
+
/>,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
it("wraps each row in ThreadListItemByIndexProvider with its index", async () => {
|
|
115
|
+
h.state.threads.threadIds = ["t-1", "t-2", "t-3"];
|
|
116
|
+
|
|
117
|
+
await mount();
|
|
118
|
+
|
|
119
|
+
expect(h.providerProps.mock.calls.map((call) => call[0])).toEqual([
|
|
120
|
+
{ index: 0, archived: false },
|
|
121
|
+
{ index: 1, archived: false },
|
|
122
|
+
{ index: 2, archived: false },
|
|
123
|
+
]);
|
|
124
|
+
expect(container.textContent).toContain("t-1");
|
|
125
|
+
expect(container.textContent).toContain("t-3");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("passes threadId and index to renderItem", async () => {
|
|
129
|
+
const renderItem = vi.fn(
|
|
130
|
+
({ threadId }: { threadId: string; index: number }) => (
|
|
131
|
+
<Text>{threadId}</Text>
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
h.state.threads.threadIds = ["a", "b"];
|
|
135
|
+
|
|
136
|
+
await mount({ renderItem });
|
|
137
|
+
|
|
138
|
+
expect(renderItem.mock.calls.map((call) => call[0])).toEqual([
|
|
139
|
+
{ threadId: "a", index: 0 },
|
|
140
|
+
{ threadId: "b", index: 1 },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("keys rows by threadId via keyExtractor", async () => {
|
|
145
|
+
h.state.threads.threadIds = ["t-1", "t-2"];
|
|
146
|
+
|
|
147
|
+
await mount();
|
|
148
|
+
|
|
149
|
+
const keyExtractor = h.flatListProps?.keyExtractor as (
|
|
150
|
+
item: string,
|
|
151
|
+
index: number,
|
|
152
|
+
) => string;
|
|
153
|
+
expect(keyExtractor("t-1", 0)).toBe("t-1");
|
|
154
|
+
expect(keyExtractor("t-2", 1)).toBe("t-2");
|
|
155
|
+
expect(h.flatListProps?.data).toEqual(["t-1", "t-2"]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("forwards extra FlatList props", async () => {
|
|
159
|
+
h.state.threads.threadIds = ["t-1"];
|
|
160
|
+
|
|
161
|
+
await mount({ horizontal: true, testID: "thread-list" });
|
|
162
|
+
|
|
163
|
+
expect(h.flatListProps?.horizontal).toBe(true);
|
|
164
|
+
expect(h.flatListProps?.testID).toBe("thread-list");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ReactElement, useCallback } from "react";
|
|
2
2
|
import { FlatList, type FlatListProps } from "react-native";
|
|
3
3
|
import { useAuiState } from "@assistant-ui/store";
|
|
4
|
+
import { ThreadListItemByIndexProvider } from "@assistant-ui/core/react";
|
|
4
5
|
|
|
5
6
|
export type ThreadListItemsProps = Omit<
|
|
6
7
|
FlatListProps<string>,
|
|
@@ -17,7 +18,11 @@ export const ThreadListItems = ({
|
|
|
17
18
|
|
|
18
19
|
const renderFlatListItem = useCallback(
|
|
19
20
|
({ item, index }: { item: string; index: number }) => {
|
|
20
|
-
return
|
|
21
|
+
return (
|
|
22
|
+
<ThreadListItemByIndexProvider index={index} archived={false}>
|
|
23
|
+
{renderItem({ threadId: item, index })}
|
|
24
|
+
</ThreadListItemByIndexProvider>
|
|
25
|
+
);
|
|
21
26
|
},
|
|
22
27
|
[renderItem],
|
|
23
28
|
);
|