@assistant-ui/react 0.15.24 → 0.15.25
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/hooks/useToolCallElapsed.d.ts +3 -1
- package/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
- package/dist/hooks/useToolCallElapsed.js +7 -8
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/model-context/makeAssistantVisible.d.ts +1 -0
- package/dist/model-context/makeAssistantVisible.d.ts.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +31 -12
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -1
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts +11 -11
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +62 -60
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.d.ts +8 -8
- package/dist/primitives/composer/trigger/TriggerPopoverAction.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +6 -6
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts +3 -3
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.d.ts +9 -9
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +7 -7
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts +5 -5
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +8 -8
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +8 -8
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/index.d.ts +1 -1
- package/dist/primitives/composer.d.ts +20 -1
- package/dist/primitives/composer.d.ts.map +1 -1
- package/dist/primitives/composer.js +8 -1
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/thread/ThreadLoadEarlier.d.ts +19 -0
- package/dist/primitives/thread/ThreadLoadEarlier.d.ts.map +1 -0
- package/dist/primitives/thread/ThreadLoadEarlier.js +36 -0
- package/dist/primitives/thread/ThreadLoadEarlier.js.map +1 -0
- package/dist/primitives/thread/ThreadMessages.d.ts +1 -1
- package/dist/primitives/thread/ThreadMessages.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadMessages.js +2 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +111 -2
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/thread.d.ts +2 -0
- package/dist/primitives/thread.d.ts.map +1 -1
- package/dist/primitives/thread.js +5 -2
- package/dist/primitives/thread.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +6 -1
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +10 -6
- package/dist/unstable/useMessageStallDetection.js.map +1 -1
- package/dist/utils/smooth/useSmooth.js +6 -6
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +5 -4
- package/src/hooks/useToolCallElapsed.test.tsx +35 -0
- package/src/hooks/useToolCallElapsed.ts +9 -7
- package/src/index.ts +28 -3
- package/src/model-context/makeAssistantVisible.test.tsx +315 -5
- package/src/model-context/makeAssistantVisible.tsx +43 -7
- package/src/primitives/composer/ComposerInput.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopover.tsx +11 -11
- package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +10 -13
- package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +2 -2
- package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +11 -14
- package/src/primitives/composer/trigger/TriggerPopoverExports.test.ts +61 -0
- package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +3 -3
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +8 -8
- package/src/primitives/composer.ts +21 -1
- package/src/primitives/message/MessageRoot.test.tsx +125 -2
- package/src/primitives/thread/ThreadLoadEarlier.tsx +35 -0
- package/src/primitives/thread/ThreadMessages.tsx +1 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +256 -5
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +147 -1
- package/src/primitives/thread.ts +2 -0
- package/src/sandbox-host/SandboxHost.test.tsx +34 -0
- package/src/sandbox-host/SandboxHost.tsx +8 -4
- package/src/tests/toolCallTiming.test.tsx +19 -2
- package/src/unstable/useMessageStallDetection.test.tsx +40 -0
- package/src/unstable/useMessageStallDetection.ts +10 -6
- package/src/utils/smooth/useSmooth.test.tsx +16 -5
- package/src/utils/smooth/useSmooth.ts +7 -7
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
Unstable_TriggerItem,
|
|
6
|
-
} from "@assistant-ui/core";
|
|
7
|
-
import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
3
|
+
import type { DirectiveFormatter, TriggerItem } from "@assistant-ui/core";
|
|
4
|
+
import { defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
8
5
|
import { useEffect, useInsertionEffect, useRef, type FC } from "react";
|
|
9
6
|
import { useTriggerBehaviorRegistration } from "./TriggerPopover";
|
|
10
7
|
import type { TriggerBehavior } from "./triggerSelectionResource";
|
|
11
8
|
|
|
12
9
|
export namespace ComposerPrimitiveTriggerPopoverDirective {
|
|
13
10
|
export type Props = {
|
|
14
|
-
/** Defaults to `
|
|
15
|
-
readonly formatter?:
|
|
11
|
+
/** Defaults to `defaultDirectiveFormatter`. */
|
|
12
|
+
readonly formatter?: DirectiveFormatter | undefined;
|
|
16
13
|
/** Fires after an item has been inserted into the composer. */
|
|
17
|
-
readonly onInserted?: ((item:
|
|
14
|
+
readonly onInserted?: ((item: TriggerItem) => void) | undefined;
|
|
18
15
|
};
|
|
19
16
|
}
|
|
20
17
|
|
|
@@ -22,16 +19,16 @@ export namespace ComposerPrimitiveTriggerPopoverDirective {
|
|
|
22
19
|
* Configures a `<TriggerPopover>` to insert a directive chip when an item is
|
|
23
20
|
* selected. Render exactly one behavior sub-primitive per `<TriggerPopover>`.
|
|
24
21
|
*
|
|
25
|
-
* Exposed as `ComposerPrimitive.
|
|
22
|
+
* Exposed as `ComposerPrimitive.TriggerPopover.Directive`.
|
|
26
23
|
*
|
|
27
24
|
* @example
|
|
28
25
|
* ```tsx
|
|
29
|
-
* <ComposerPrimitive.
|
|
30
|
-
* <ComposerPrimitive.
|
|
31
|
-
* formatter={
|
|
26
|
+
* <ComposerPrimitive.TriggerPopover char="@" adapter={mentionAdapter}>
|
|
27
|
+
* <ComposerPrimitive.TriggerPopover.Directive
|
|
28
|
+
* formatter={defaultDirectiveFormatter}
|
|
32
29
|
* onInserted={(item) => track("mention", item.id)}
|
|
33
30
|
* />
|
|
34
|
-
* </ComposerPrimitive.
|
|
31
|
+
* </ComposerPrimitive.TriggerPopover>
|
|
35
32
|
* ```
|
|
36
33
|
*/
|
|
37
34
|
export const ComposerPrimitiveTriggerPopoverDirective: FC<
|
|
@@ -46,7 +43,7 @@ export const ComposerPrimitiveTriggerPopoverDirective: FC<
|
|
|
46
43
|
useEffect(() => {
|
|
47
44
|
const behavior: TriggerBehavior = {
|
|
48
45
|
kind: "directive",
|
|
49
|
-
formatter: formatter ??
|
|
46
|
+
formatter: formatter ?? defaultDirectiveFormatter,
|
|
50
47
|
onInserted: (item) => onInsertedRef.current?.(item),
|
|
51
48
|
};
|
|
52
49
|
return register(behavior);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
+
import * as ComposerPrimitive from "../../composer";
|
|
3
|
+
import {
|
|
4
|
+
defaultDirectiveFormatter,
|
|
5
|
+
unstable_defaultDirectiveFormatter,
|
|
6
|
+
type DirectiveFormatter,
|
|
7
|
+
type TriggerAdapter,
|
|
8
|
+
type TriggerCategory,
|
|
9
|
+
type TriggerItem,
|
|
10
|
+
type Unstable_DirectiveFormatter,
|
|
11
|
+
type Unstable_TriggerBehavior,
|
|
12
|
+
type Unstable_TriggerItem,
|
|
13
|
+
} from "../../../index";
|
|
14
|
+
import type { TriggerBehavior } from "./triggerSelectionResource";
|
|
15
|
+
|
|
16
|
+
describe("stable trigger popover exports", () => {
|
|
17
|
+
it("keeps every old composer member as an alias of its stable member", () => {
|
|
18
|
+
expect(ComposerPrimitive.Unstable_TriggerPopover).toBe(
|
|
19
|
+
ComposerPrimitive.TriggerPopover,
|
|
20
|
+
);
|
|
21
|
+
expect(ComposerPrimitive.Unstable_TriggerPopover.Directive).toBe(
|
|
22
|
+
ComposerPrimitive.TriggerPopover.Directive,
|
|
23
|
+
);
|
|
24
|
+
expect(ComposerPrimitive.Unstable_TriggerPopover.Action).toBe(
|
|
25
|
+
ComposerPrimitive.TriggerPopover.Action,
|
|
26
|
+
);
|
|
27
|
+
expect(ComposerPrimitive.Unstable_TriggerPopoverRoot).toBe(
|
|
28
|
+
ComposerPrimitive.TriggerPopoverRoot,
|
|
29
|
+
);
|
|
30
|
+
expect(ComposerPrimitive.Unstable_TriggerPopoverCategories).toBe(
|
|
31
|
+
ComposerPrimitive.TriggerPopoverCategories,
|
|
32
|
+
);
|
|
33
|
+
expect(ComposerPrimitive.Unstable_TriggerPopoverCategoryItem).toBe(
|
|
34
|
+
ComposerPrimitive.TriggerPopoverCategoryItem,
|
|
35
|
+
);
|
|
36
|
+
expect(ComposerPrimitive.Unstable_TriggerPopoverItems).toBe(
|
|
37
|
+
ComposerPrimitive.TriggerPopoverItems,
|
|
38
|
+
);
|
|
39
|
+
expect(ComposerPrimitive.Unstable_TriggerPopoverItem).toBe(
|
|
40
|
+
ComposerPrimitive.TriggerPopoverItem,
|
|
41
|
+
);
|
|
42
|
+
expect(ComposerPrimitive.Unstable_TriggerPopoverBack).toBe(
|
|
43
|
+
ComposerPrimitive.TriggerPopoverBack,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("keeps the old members' namespaced prop types", () => {
|
|
48
|
+
expectTypeOf<ComposerPrimitive.Unstable_TriggerPopoverItem.Props>().toEqualTypeOf<ComposerPrimitive.TriggerPopoverItem.Props>();
|
|
49
|
+
expectTypeOf<ComposerPrimitive.Unstable_TriggerPopoverBack.Element>().toEqualTypeOf<ComposerPrimitive.TriggerPopoverBack.Element>();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("exports the stable adapter, item, category and formatter names beside the old ones", () => {
|
|
53
|
+
expect(unstable_defaultDirectiveFormatter).toBe(defaultDirectiveFormatter);
|
|
54
|
+
expectTypeOf<Unstable_TriggerItem>().toEqualTypeOf<TriggerItem>();
|
|
55
|
+
expectTypeOf<Unstable_DirectiveFormatter>().toEqualTypeOf<DirectiveFormatter>();
|
|
56
|
+
expectTypeOf<TriggerAdapter["categories"]>().returns.toEqualTypeOf<
|
|
57
|
+
readonly TriggerCategory[]
|
|
58
|
+
>();
|
|
59
|
+
expectTypeOf<Unstable_TriggerBehavior>().toEqualTypeOf<TriggerBehavior>();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "react";
|
|
11
11
|
import { composeEventHandlers } from "radix-ui/internal";
|
|
12
12
|
import { useTriggerPopoverScopeContext } from "./TriggerPopover";
|
|
13
|
-
import type {
|
|
13
|
+
import type { TriggerItem } from "@assistant-ui/core";
|
|
14
14
|
|
|
15
15
|
export namespace ComposerPrimitiveTriggerPopoverItems {
|
|
16
16
|
export type Element = ComponentRef<typeof Primitive.div>;
|
|
@@ -18,7 +18,7 @@ export namespace ComposerPrimitiveTriggerPopoverItems {
|
|
|
18
18
|
ComponentPropsWithoutRef<typeof Primitive.div>,
|
|
19
19
|
"children"
|
|
20
20
|
> & {
|
|
21
|
-
children: (items: readonly
|
|
21
|
+
children: (items: readonly TriggerItem[]) => ReactNode;
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -53,7 +53,7 @@ ComposerPrimitiveTriggerPopoverItems.displayName =
|
|
|
53
53
|
export namespace ComposerPrimitiveTriggerPopoverItem {
|
|
54
54
|
export type Element = ComponentRef<typeof Primitive.button>;
|
|
55
55
|
export type Props = ComponentPropsWithoutRef<typeof Primitive.button> & {
|
|
56
|
-
item:
|
|
56
|
+
item: TriggerItem;
|
|
57
57
|
index?: number | undefined;
|
|
58
58
|
};
|
|
59
59
|
}
|
|
@@ -294,21 +294,21 @@ const TriggerPopoverRootInner: FC<
|
|
|
294
294
|
*
|
|
295
295
|
* @example
|
|
296
296
|
* ```tsx
|
|
297
|
-
* <ComposerPrimitive.
|
|
298
|
-
* <ComposerPrimitive.
|
|
299
|
-
* <ComposerPrimitive.
|
|
297
|
+
* <ComposerPrimitive.TriggerPopoverRoot>
|
|
298
|
+
* <ComposerPrimitive.TriggerPopover char="@" adapter={mentionAdapter}>
|
|
299
|
+
* <ComposerPrimitive.TriggerPopover.Directive formatter={formatter} />
|
|
300
300
|
* ...
|
|
301
|
-
* </ComposerPrimitive.
|
|
301
|
+
* </ComposerPrimitive.TriggerPopover>
|
|
302
302
|
*
|
|
303
|
-
* <ComposerPrimitive.
|
|
304
|
-
* <ComposerPrimitive.
|
|
303
|
+
* <ComposerPrimitive.TriggerPopover char="/" adapter={slashAdapter}>
|
|
304
|
+
* <ComposerPrimitive.TriggerPopover.Action onExecute={handler} />
|
|
305
305
|
* ...
|
|
306
|
-
* </ComposerPrimitive.
|
|
306
|
+
* </ComposerPrimitive.TriggerPopover>
|
|
307
307
|
*
|
|
308
308
|
* <ComposerPrimitive.Root>
|
|
309
309
|
* <ComposerPrimitive.Input />
|
|
310
310
|
* </ComposerPrimitive.Root>
|
|
311
|
-
* </ComposerPrimitive.
|
|
311
|
+
* </ComposerPrimitive.TriggerPopoverRoot>
|
|
312
312
|
* ```
|
|
313
313
|
*/
|
|
314
314
|
export const ComposerPrimitiveTriggerPopoverRoot: FC<
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TriggerMatch, TriggerMatcher } from "./composer/trigger";
|
|
2
|
+
|
|
1
3
|
export { ComposerPrimitiveRoot as Root } from "./composer/ComposerRoot";
|
|
2
4
|
export { ComposerPrimitiveInput as Input } from "./composer/ComposerInput";
|
|
3
5
|
export { ComposerPrimitiveSend as Send } from "./composer/ComposerSend";
|
|
@@ -15,12 +17,26 @@ export { ComposerPrimitiveQuoteText as QuoteText } from "./composer/ComposerQuot
|
|
|
15
17
|
export { ComposerPrimitiveQuoteDismiss as QuoteDismiss } from "./composer/ComposerQuote";
|
|
16
18
|
export { ComposerPrimitiveQueue as Queue } from "./composer/ComposerQueue";
|
|
17
19
|
|
|
20
|
+
export { ComposerPrimitiveTriggerPopover as TriggerPopover } from "./composer/trigger";
|
|
21
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopover` instead. */
|
|
18
22
|
export { ComposerPrimitiveTriggerPopover as Unstable_TriggerPopover } from "./composer/trigger";
|
|
23
|
+
export { ComposerPrimitiveTriggerPopoverRoot as TriggerPopoverRoot } from "./composer/trigger";
|
|
24
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopoverRoot` instead. */
|
|
19
25
|
export { ComposerPrimitiveTriggerPopoverRoot as Unstable_TriggerPopoverRoot } from "./composer/trigger";
|
|
26
|
+
export { ComposerPrimitiveTriggerPopoverCategories as TriggerPopoverCategories } from "./composer/trigger";
|
|
27
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopoverCategories` instead. */
|
|
20
28
|
export { ComposerPrimitiveTriggerPopoverCategories as Unstable_TriggerPopoverCategories } from "./composer/trigger";
|
|
29
|
+
export { ComposerPrimitiveTriggerPopoverCategoryItem as TriggerPopoverCategoryItem } from "./composer/trigger";
|
|
30
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopoverCategoryItem` instead. */
|
|
21
31
|
export { ComposerPrimitiveTriggerPopoverCategoryItem as Unstable_TriggerPopoverCategoryItem } from "./composer/trigger";
|
|
32
|
+
export { ComposerPrimitiveTriggerPopoverItems as TriggerPopoverItems } from "./composer/trigger";
|
|
33
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopoverItems` instead. */
|
|
22
34
|
export { ComposerPrimitiveTriggerPopoverItems as Unstable_TriggerPopoverItems } from "./composer/trigger";
|
|
35
|
+
export { ComposerPrimitiveTriggerPopoverItem as TriggerPopoverItem } from "./composer/trigger";
|
|
36
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopoverItem` instead. */
|
|
23
37
|
export { ComposerPrimitiveTriggerPopoverItem as Unstable_TriggerPopoverItem } from "./composer/trigger";
|
|
38
|
+
export { ComposerPrimitiveTriggerPopoverBack as TriggerPopoverBack } from "./composer/trigger";
|
|
39
|
+
/** @deprecated Use `ComposerPrimitive.TriggerPopoverBack` instead. */
|
|
24
40
|
export { ComposerPrimitiveTriggerPopoverBack as Unstable_TriggerPopoverBack } from "./composer/trigger";
|
|
25
41
|
export { useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext } from "./composer/trigger";
|
|
26
42
|
export { useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional } from "./composer/trigger";
|
|
@@ -29,4 +45,8 @@ export { useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScop
|
|
|
29
45
|
export { useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers } from "./composer/trigger";
|
|
30
46
|
export { useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional } from "./composer/trigger";
|
|
31
47
|
export type { RegisteredTrigger as Unstable_RegisteredTrigger } from "./composer/trigger";
|
|
32
|
-
export type { TriggerMatcher
|
|
48
|
+
export type { TriggerMatch, TriggerMatcher } from "./composer/trigger";
|
|
49
|
+
/** @deprecated Use `TriggerMatcher` instead. */
|
|
50
|
+
export type Unstable_TriggerMatcher = TriggerMatcher;
|
|
51
|
+
/** @deprecated Use `TriggerMatch` instead. */
|
|
52
|
+
export type Unstable_TriggerMatch = TriggerMatch;
|
|
@@ -2,14 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
import { act, render } from "@testing-library/react";
|
|
4
4
|
import { createContext, useContext } from "react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { hydrateRoot } from "react-dom/client";
|
|
7
|
+
import { renderToString } from "react-dom/server";
|
|
5
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
ChatModelAdapter,
|
|
11
|
+
ThreadMessageLike,
|
|
12
|
+
ThreadMessage,
|
|
13
|
+
} from "@assistant-ui/core";
|
|
7
14
|
import {
|
|
8
15
|
AssistantRuntimeProvider,
|
|
16
|
+
ThreadListItemRuntimeProvider,
|
|
9
17
|
useExternalStoreRuntime,
|
|
18
|
+
useLocalRuntime,
|
|
10
19
|
} from "@assistant-ui/core/react";
|
|
11
20
|
import { useAuiState } from "@assistant-ui/store";
|
|
12
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
ThreadPrimitiveMessageByIndex,
|
|
23
|
+
ThreadPrimitiveMessages,
|
|
24
|
+
} from "../thread/ThreadMessages";
|
|
13
25
|
import { ThreadPrimitiveRoot } from "../thread/ThreadRoot";
|
|
14
26
|
import { ThreadPrimitiveViewport } from "../thread/ThreadViewport";
|
|
15
27
|
import { MessagePrimitiveRoot } from "./MessageRoot";
|
|
@@ -22,6 +34,14 @@ const messages: ThreadMessageLike[] = [
|
|
|
22
34
|
},
|
|
23
35
|
];
|
|
24
36
|
|
|
37
|
+
const initialMessages: readonly ThreadMessageLike[] = [
|
|
38
|
+
{ role: "user", content: "Hydrated message" },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const chatModel: ChatModelAdapter = {
|
|
42
|
+
run: async () => ({ content: [] }),
|
|
43
|
+
};
|
|
44
|
+
|
|
25
45
|
class TestResizeObserver {
|
|
26
46
|
observe() {}
|
|
27
47
|
unobserve() {}
|
|
@@ -62,6 +82,64 @@ const Example = ({ visible = true }: { visible?: boolean }) => {
|
|
|
62
82
|
);
|
|
63
83
|
};
|
|
64
84
|
|
|
85
|
+
const HydrationMessage = () => (
|
|
86
|
+
<MessagePrimitiveRoot data-testid="hydration-message" />
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const LocalThread = ({
|
|
90
|
+
onRuntime,
|
|
91
|
+
turnAnchor,
|
|
92
|
+
}: {
|
|
93
|
+
onRuntime: (runtime: ReturnType<typeof useLocalRuntime>) => void;
|
|
94
|
+
turnAnchor: "bottom" | "top";
|
|
95
|
+
}) => {
|
|
96
|
+
const runtime = useLocalRuntime(chatModel, { initialMessages });
|
|
97
|
+
onRuntime(runtime);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
101
|
+
<ThreadPrimitiveRoot>
|
|
102
|
+
<ThreadPrimitiveViewport
|
|
103
|
+
scrollToBottomOnInitialize={false}
|
|
104
|
+
turnAnchor={turnAnchor}
|
|
105
|
+
>
|
|
106
|
+
<ThreadPrimitiveMessages components={{ Message: HydrationMessage }} />
|
|
107
|
+
</ThreadPrimitiveViewport>
|
|
108
|
+
</ThreadPrimitiveRoot>
|
|
109
|
+
</AssistantRuntimeProvider>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const NestedLocalRuntime = ({
|
|
114
|
+
onRuntime,
|
|
115
|
+
turnAnchor,
|
|
116
|
+
}: {
|
|
117
|
+
onRuntime: (runtime: ReturnType<typeof useLocalRuntime>) => void;
|
|
118
|
+
turnAnchor: "bottom" | "top";
|
|
119
|
+
}) => {
|
|
120
|
+
const host = useExternalStoreRuntime<ThreadMessage>({
|
|
121
|
+
messages: [],
|
|
122
|
+
onNew: async () => {},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<AssistantRuntimeProvider runtime={host}>
|
|
127
|
+
<ThreadListItemRuntimeProvider runtime={host.threads.mainItem}>
|
|
128
|
+
<LocalThread onRuntime={onRuntime} turnAnchor={turnAnchor} />
|
|
129
|
+
</ThreadListItemRuntimeProvider>
|
|
130
|
+
</AssistantRuntimeProvider>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const renderOnServer = (node: ReactNode) => {
|
|
135
|
+
vi.stubGlobal("document", undefined);
|
|
136
|
+
try {
|
|
137
|
+
return renderToString(node);
|
|
138
|
+
} finally {
|
|
139
|
+
vi.unstubAllGlobals();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
65
143
|
beforeEach(() => {
|
|
66
144
|
vi.stubGlobal("ResizeObserver", TestResizeObserver);
|
|
67
145
|
});
|
|
@@ -82,6 +160,51 @@ const mockHoveredElement = () => {
|
|
|
82
160
|
};
|
|
83
161
|
|
|
84
162
|
describe("MessagePrimitiveRoot", () => {
|
|
163
|
+
it.each(["bottom", "top"] as const)(
|
|
164
|
+
"hydrates nested local message ids with %s anchoring",
|
|
165
|
+
async (turnAnchor) => {
|
|
166
|
+
let serverRuntime: ReturnType<typeof useLocalRuntime> | undefined;
|
|
167
|
+
const html = renderOnServer(
|
|
168
|
+
<NestedLocalRuntime
|
|
169
|
+
onRuntime={(runtime) => (serverRuntime = runtime)}
|
|
170
|
+
turnAnchor={turnAnchor}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
const serverMessageId = serverRuntime!.thread.getState().messages[0]!.id;
|
|
174
|
+
const container = document.createElement("div");
|
|
175
|
+
container.innerHTML = html;
|
|
176
|
+
vi.stubGlobal("ResizeObserver", TestResizeObserver);
|
|
177
|
+
|
|
178
|
+
let clientRuntime: ReturnType<typeof useLocalRuntime> | undefined;
|
|
179
|
+
const errors = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
180
|
+
let root: ReturnType<typeof hydrateRoot> | undefined;
|
|
181
|
+
await act(async () => {
|
|
182
|
+
root = hydrateRoot(
|
|
183
|
+
container,
|
|
184
|
+
<NestedLocalRuntime
|
|
185
|
+
onRuntime={(runtime) => (clientRuntime = runtime)}
|
|
186
|
+
turnAnchor={turnAnchor}
|
|
187
|
+
/>,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const clientMessageId = clientRuntime!.thread.getState().messages[0]!.id;
|
|
192
|
+
const domMessageId = container
|
|
193
|
+
.querySelector("[data-testid='hydration-message']")!
|
|
194
|
+
.getAttribute("data-message-id");
|
|
195
|
+
|
|
196
|
+
expect(clientMessageId).toBe(serverMessageId);
|
|
197
|
+
expect(domMessageId).toBe(clientMessageId);
|
|
198
|
+
expect(
|
|
199
|
+
errors.mock.calls.some((args) =>
|
|
200
|
+
args.some((value) => String(value).includes("data-message-id")),
|
|
201
|
+
),
|
|
202
|
+
).toBe(false);
|
|
203
|
+
|
|
204
|
+
await act(async () => root?.unmount());
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
85
208
|
it("synchronizes hover state while mounted", async () => {
|
|
86
209
|
mockHoveredElement();
|
|
87
210
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
5
|
+
import {
|
|
6
|
+
type ActionButtonElement,
|
|
7
|
+
type ActionButtonProps,
|
|
8
|
+
createActionButton,
|
|
9
|
+
} from "../../utils/createActionButton";
|
|
10
|
+
|
|
11
|
+
const useThreadLoadEarlier = () => {
|
|
12
|
+
const aui = useAui();
|
|
13
|
+
const hasEarlier = useAuiState((s) => s.thread.hasEarlier);
|
|
14
|
+
const loadEarlier = useCallback(() => {
|
|
15
|
+
void aui.thread.loadEarlier();
|
|
16
|
+
}, [aui]);
|
|
17
|
+
if (!hasEarlier) return null;
|
|
18
|
+
return loadEarlier;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export namespace ThreadPrimitiveLoadEarlier {
|
|
22
|
+
export type Element = ActionButtonElement;
|
|
23
|
+
export type Props = ActionButtonProps<typeof useThreadLoadEarlier>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A button that loads the page before the first loaded message. Disabled
|
|
28
|
+
* while the runtime reports no earlier messages. A press while a page is
|
|
29
|
+
* loading joins that load, so the button stays enabled and keeps keyboard
|
|
30
|
+
* focus until the page lands; read `thread.isLoadingEarlier` to show progress.
|
|
31
|
+
*/
|
|
32
|
+
export const ThreadPrimitiveLoadEarlier = createActionButton(
|
|
33
|
+
"ThreadPrimitive.LoadEarlier",
|
|
34
|
+
useThreadLoadEarlier,
|
|
35
|
+
);
|