@assistant-ui/react 0.15.25 → 0.15.26
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.map +1 -1
- package/dist/hooks/useToolCallElapsed.js +3 -2
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +3 -2
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +56 -46
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/csp.d.ts +4 -0
- package/dist/mcp-apps/csp.d.ts.map +1 -0
- package/dist/mcp-apps/csp.js +93 -0
- package/dist/mcp-apps/csp.js.map +1 -0
- package/dist/mcp-apps/types.d.ts +1 -0
- package/dist/mcp-apps/types.d.ts.map +1 -1
- package/dist/mcp-apps/types.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts +1 -0
- package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/attachment.d.ts +3 -1
- package/dist/primitives/attachment.d.ts.map +1 -1
- package/dist/primitives/attachment.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts +5 -1
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerCancel.js +14 -1
- package/dist/primitives/composer/ComposerCancel.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.d.ts +14 -0
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +4 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +24 -19
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/composerInputSelector.d.ts +3 -0
- package/dist/primitives/composer/composerInputSelector.d.ts.map +1 -0
- package/dist/primitives/composer/composerInputSelector.js +7 -0
- package/dist/primitives/composer/composerInputSelector.js.map +1 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.d.ts +2 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.d.ts.map +1 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.js +42 -0
- package/dist/primitives/composer/useComposerCancelWithFocus.js.map +1 -0
- package/dist/primitives/composer.d.ts +35 -14
- package/dist/primitives/composer.d.ts.map +1 -1
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts +8 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +58 -278
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -2
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +4 -3
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +2 -2
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +90 -21
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +3 -2
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +7 -4
- package/dist/unstable/useComposerInput.d.ts.map +1 -1
- package/dist/unstable/useComposerInput.js +4 -4
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.d.ts +3 -2
- package/dist/unstable/useComposerInputHistory.d.ts.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +2 -2
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.d.ts +3 -2
- package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +8 -7
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +12 -3
- package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
- package/dist/unstable/useMentionAdapter.js +2 -2
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useMessageStallDetection.d.ts +4 -2
- package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
- package/dist/unstable/useMessageStallDetection.js +2 -2
- package/dist/unstable/useMessageStallDetection.js.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.d.ts +5 -2
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +5 -4
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts +4 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
- package/dist/unstable/webmcp/useWebMcpProvider.js +2 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
- package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
- package/dist/utils/getSelectionMessageId.js +31 -8
- package/dist/utils/getSelectionMessageId.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +2 -22
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/utils/useIsomorphicLayoutEffect.d.ts.map +1 -0
- package/dist/utils/useIsomorphicLayoutEffect.js +6 -0
- package/dist/utils/useIsomorphicLayoutEffect.js.map +1 -0
- package/package.json +10 -10
- package/src/hooks/useToolCallElapsed.test.tsx +4 -2
- package/src/hooks/useToolCallElapsed.ts +3 -2
- package/src/index.ts +19 -2
- package/src/mcp-apps/McpAppRenderer.tsx +3 -9
- package/src/mcp-apps/app-frame.test.tsx +38 -0
- package/src/mcp-apps/app-frame.tsx +5 -8
- package/src/mcp-apps/csp.test.ts +112 -0
- package/src/mcp-apps/csp.ts +124 -0
- package/src/mcp-apps/types.ts +1 -0
- package/src/model-context/frame/AssistantFrame.test.ts +45 -0
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -0
- package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -0
- package/src/primitives/attachment.ts +4 -1
- package/src/primitives/composer/ComposerCancel.focus.test.tsx +307 -0
- package/src/primitives/composer/ComposerCancel.ts +8 -1
- package/src/primitives/composer/ComposerInput.tsx +19 -1
- package/src/primitives/composer/ComposerRoot.tsx +6 -5
- package/src/primitives/composer/composerInputSelector.ts +4 -0
- package/src/primitives/composer/useComposerCancelWithFocus.ts +57 -0
- package/src/primitives/composer.ts +56 -21
- package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
- package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
- package/src/primitives/thread/ThreadViewport.tsx +3 -3
- package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +666 -63
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +201 -73
- package/src/sandbox-host/SandboxHost.tsx +3 -8
- package/src/unstable/useComposerInput.ts +7 -4
- package/src/unstable/useComposerInputHistory.ts +3 -2
- package/src/unstable/useLiveCompletionAdapter.ts +9 -14
- package/src/unstable/useMentionAdapter.ts +12 -3
- package/src/unstable/useMessageStallDetection.ts +4 -2
- package/src/unstable/useSlashCommandAdapter.ts +8 -4
- package/src/unstable/webmcp/useWebMcpProvider.ts +4 -0
- package/src/utils/getSelectionMessageId.test.ts +231 -0
- package/src/utils/getSelectionMessageId.ts +45 -7
- package/src/utils/hooks/useOnResizeContent.ts +2 -29
- package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
- package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
|
@@ -12,10 +12,12 @@ import { describe, expect, it } from "vitest";
|
|
|
12
12
|
import type { ThreadMessageLike } from "@assistant-ui/core";
|
|
13
13
|
import {
|
|
14
14
|
AssistantRuntimeProvider,
|
|
15
|
+
Tools,
|
|
15
16
|
useAssistantDataUI,
|
|
16
17
|
useExternalStoreRuntime,
|
|
17
18
|
} from "@assistant-ui/core/react";
|
|
18
|
-
import { useAui } from "@assistant-ui/store";
|
|
19
|
+
import { AuiConfig, useAui } from "@assistant-ui/store";
|
|
20
|
+
import { resource } from "@assistant-ui/tap";
|
|
19
21
|
import { ThreadPrimitiveMessageByIndex } from "../thread/ThreadMessages";
|
|
20
22
|
import {
|
|
21
23
|
type MessagePrimitiveUnstable_PartsGrouped,
|
|
@@ -48,6 +50,11 @@ const partsMessage =
|
|
|
48
50
|
const Named = () => <b>named</b>;
|
|
49
51
|
const Fallback = () => <i>fallback</i>;
|
|
50
52
|
const GlobalFallback = () => <b>global-fallback</b>;
|
|
53
|
+
const Mcp = () => <b>mcp</b>;
|
|
54
|
+
const McpApp = resource(function McpApp() {
|
|
55
|
+
return { render: Mcp };
|
|
56
|
+
});
|
|
57
|
+
const mcpConfig = AuiConfig({ tools: Tools({ mcpApp: McpApp() }) });
|
|
51
58
|
|
|
52
59
|
const RegisterFallbackDataUI: FC<{ render: typeof GlobalFallback }> = ({
|
|
53
60
|
render,
|
|
@@ -69,11 +76,13 @@ const Example = ({
|
|
|
69
76
|
content,
|
|
70
77
|
Message: MessageComponent = Message,
|
|
71
78
|
extra,
|
|
79
|
+
config,
|
|
72
80
|
isRunning = false,
|
|
73
81
|
}: {
|
|
74
82
|
content: ThreadMessageLike["content"];
|
|
75
83
|
Message?: FC;
|
|
76
84
|
extra?: ReactNode;
|
|
85
|
+
config?: AuiConfig;
|
|
77
86
|
isRunning?: boolean;
|
|
78
87
|
}) => {
|
|
79
88
|
const messages: ThreadMessageLike[] = [
|
|
@@ -86,7 +95,10 @@ const Example = ({
|
|
|
86
95
|
onNew: async () => {},
|
|
87
96
|
});
|
|
88
97
|
return (
|
|
89
|
-
<AssistantRuntimeProvider
|
|
98
|
+
<AssistantRuntimeProvider
|
|
99
|
+
runtime={runtime}
|
|
100
|
+
{...(config === undefined ? {} : { config })}
|
|
101
|
+
>
|
|
90
102
|
{extra}
|
|
91
103
|
<ThreadPrimitiveMessageByIndex
|
|
92
104
|
index={0}
|
|
@@ -439,6 +451,17 @@ describe("MessagePrimitive.Unstable_PartsGroupedByParentId", () => {
|
|
|
439
451
|
},
|
|
440
452
|
);
|
|
441
453
|
|
|
454
|
+
it("falls back to the default text component for an undefined slot", () => {
|
|
455
|
+
render(
|
|
456
|
+
<Example
|
|
457
|
+
content={[{ type: "text", text: "hello" }]}
|
|
458
|
+
Message={partsMessage({ Text: undefined })}
|
|
459
|
+
/>,
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
expect(screen.getByText("hello")).toBeTruthy();
|
|
463
|
+
});
|
|
464
|
+
|
|
442
465
|
it("renders tool and data UIs registered under an inherited name", () => {
|
|
443
466
|
const { container } = render(
|
|
444
467
|
<Example
|
|
@@ -461,6 +484,87 @@ describe("MessagePrimitive.Unstable_PartsGroupedByParentId", () => {
|
|
|
461
484
|
expect(container.innerHTML).toBe("<b>named</b><b>named</b>");
|
|
462
485
|
});
|
|
463
486
|
|
|
487
|
+
it("renders tools.mcpApp for a tool call with a ui:// resource", () => {
|
|
488
|
+
const { container } = render(
|
|
489
|
+
<Example
|
|
490
|
+
content={[
|
|
491
|
+
{
|
|
492
|
+
type: "tool-call",
|
|
493
|
+
toolCallId: "call",
|
|
494
|
+
toolName: "show_chart",
|
|
495
|
+
args: {},
|
|
496
|
+
mcp: { app: { resourceUri: "ui://chart" } },
|
|
497
|
+
},
|
|
498
|
+
]}
|
|
499
|
+
Message={partsMessage({ tools: { Fallback } })}
|
|
500
|
+
config={mcpConfig}
|
|
501
|
+
/>,
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
expect(container.innerHTML).toBe("<b>mcp</b>");
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("prefers an inline by_name component over tools.mcpApp", () => {
|
|
508
|
+
const { container } = render(
|
|
509
|
+
<Example
|
|
510
|
+
content={[
|
|
511
|
+
{
|
|
512
|
+
type: "tool-call",
|
|
513
|
+
toolCallId: "call",
|
|
514
|
+
toolName: "show_chart",
|
|
515
|
+
args: {},
|
|
516
|
+
mcp: { app: { resourceUri: "ui://chart" } },
|
|
517
|
+
},
|
|
518
|
+
]}
|
|
519
|
+
Message={partsMessage({
|
|
520
|
+
tools: { by_name: { show_chart: Named }, Fallback },
|
|
521
|
+
})}
|
|
522
|
+
config={mcpConfig}
|
|
523
|
+
/>,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
expect(container.innerHTML).toBe("<b>named</b>");
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("uses inline Fallback when the tool call has no ui:// resource", () => {
|
|
530
|
+
const { container } = render(
|
|
531
|
+
<Example
|
|
532
|
+
content={[
|
|
533
|
+
{
|
|
534
|
+
type: "tool-call",
|
|
535
|
+
toolCallId: "call",
|
|
536
|
+
toolName: "show_chart",
|
|
537
|
+
args: {},
|
|
538
|
+
},
|
|
539
|
+
]}
|
|
540
|
+
Message={partsMessage({ tools: { Fallback } })}
|
|
541
|
+
config={mcpConfig}
|
|
542
|
+
/>,
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
expect(container.innerHTML).toBe("<i>fallback</i>");
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("uses inline Fallback when the resource URI is not an MCP App URI", () => {
|
|
549
|
+
const { container } = render(
|
|
550
|
+
<Example
|
|
551
|
+
content={[
|
|
552
|
+
{
|
|
553
|
+
type: "tool-call",
|
|
554
|
+
toolCallId: "call",
|
|
555
|
+
toolName: "show_chart",
|
|
556
|
+
args: {},
|
|
557
|
+
mcp: { app: { resourceUri: "https://example.com/chart" } },
|
|
558
|
+
},
|
|
559
|
+
]}
|
|
560
|
+
Message={partsMessage({ tools: { Fallback } })}
|
|
561
|
+
config={mcpConfig}
|
|
562
|
+
/>,
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
expect(container.innerHTML).toBe("<i>fallback</i>");
|
|
566
|
+
});
|
|
567
|
+
|
|
464
568
|
it("uses dataRenderers.fallbacks[0] before inline data.Fallback", async () => {
|
|
465
569
|
const { container } = render(
|
|
466
570
|
<Example
|
|
@@ -7,23 +7,23 @@ import {
|
|
|
7
7
|
type PropsWithChildren,
|
|
8
8
|
useMemo,
|
|
9
9
|
} from "react";
|
|
10
|
-
import { useAuiState
|
|
10
|
+
import { useAuiState } from "@assistant-ui/store";
|
|
11
11
|
import { PartByIndexProvider } from "../../context/providers/PartByIndexProvider";
|
|
12
12
|
import { TextMessagePartProvider } from "../../context/providers/TextMessagePartProvider";
|
|
13
13
|
import { MessagePartPrimitiveText } from "../messagePart/MessagePartText";
|
|
14
14
|
import { MessagePartPrimitiveImage } from "../messagePart/MessagePartImage";
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
EmptyMessagePartComponent,
|
|
20
|
-
TextMessagePartComponent,
|
|
21
|
-
ImageMessagePartComponent,
|
|
22
|
-
SourceMessagePartComponent,
|
|
23
|
-
ToolCallMessagePartComponent,
|
|
24
|
-
ToolCallMessagePartProps,
|
|
25
|
-
FileMessagePartComponent,
|
|
26
|
-
ReasoningMessagePartComponent,
|
|
15
|
+
import {
|
|
16
|
+
MessagePartComponent,
|
|
17
|
+
type Unstable_AudioMessagePartComponent,
|
|
18
|
+
type DataMessagePartComponent,
|
|
19
|
+
type EmptyMessagePartComponent,
|
|
20
|
+
type TextMessagePartComponent,
|
|
21
|
+
type ImageMessagePartComponent,
|
|
22
|
+
type SourceMessagePartComponent,
|
|
23
|
+
type ToolCallMessagePartComponent,
|
|
24
|
+
type ToolCallMessagePartProps,
|
|
25
|
+
type FileMessagePartComponent,
|
|
26
|
+
type ReasoningMessagePartComponent,
|
|
27
27
|
} from "@assistant-ui/core/react";
|
|
28
28
|
import { MessagePartPrimitiveInProgress } from "../messagePart/MessagePartInProgress";
|
|
29
29
|
import type { MessagePartStatus } from "@assistant-ui/core";
|
|
@@ -86,6 +86,13 @@ const useMessagePartsGrouped = (
|
|
|
86
86
|
return { groups, partKeys: getMessagePartKeys(parts) };
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* @deprecated Prefer `<MessagePrimitive.GroupedParts>` for adjacent
|
|
91
|
+
* grouping — it dispatches all rendering through one `switch (part.type)`
|
|
92
|
+
* and supports nested group paths. Keep this primitive only for
|
|
93
|
+
* non-adjacent clustering (e.g., gathering parts with the same parent-id
|
|
94
|
+
* across the message).
|
|
95
|
+
*/
|
|
89
96
|
export namespace MessagePrimitiveUnstable_PartsGrouped {
|
|
90
97
|
export type Props = {
|
|
91
98
|
/**
|
|
@@ -231,34 +238,6 @@ export namespace MessagePrimitiveUnstable_PartsGrouped {
|
|
|
231
238
|
};
|
|
232
239
|
}
|
|
233
240
|
|
|
234
|
-
const ToolUIDisplay = ({
|
|
235
|
-
Fallback,
|
|
236
|
-
...props
|
|
237
|
-
}: {
|
|
238
|
-
Fallback: ToolCallMessagePartComponent | undefined;
|
|
239
|
-
} & ToolCallMessagePartProps) => {
|
|
240
|
-
const Render = useAuiState(
|
|
241
|
-
(s) => s.tools.toolUIs[props.toolName]?.[0]?.render ?? Fallback,
|
|
242
|
-
);
|
|
243
|
-
if (!Render) return null;
|
|
244
|
-
return <Render {...props} />;
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const DataUIDisplay = ({
|
|
248
|
-
Fallback,
|
|
249
|
-
...props
|
|
250
|
-
}: {
|
|
251
|
-
Fallback: DataMessagePartComponent | undefined;
|
|
252
|
-
} & DataMessagePartProps) => {
|
|
253
|
-
const Render = useAuiState((s) => {
|
|
254
|
-
const named = s.dataRenderers.renderers[props.name]?.[0];
|
|
255
|
-
if (named) return named;
|
|
256
|
-
return s.dataRenderers.fallbacks[0] ?? Fallback;
|
|
257
|
-
});
|
|
258
|
-
if (!Render) return null;
|
|
259
|
-
return <Render {...props} />;
|
|
260
|
-
};
|
|
261
|
-
|
|
262
241
|
const defaultComponents = {
|
|
263
242
|
Text: () => (
|
|
264
243
|
<p style={{ whiteSpace: "pre-line" }}>
|
|
@@ -280,104 +259,17 @@ type MessagePartComponentProps = {
|
|
|
280
259
|
components: MessagePrimitiveUnstable_PartsGrouped.Props["components"];
|
|
281
260
|
};
|
|
282
261
|
|
|
283
|
-
const MessagePartComponent: FC<MessagePartComponentProps> = ({
|
|
284
|
-
components: {
|
|
285
|
-
Text = defaultComponents.Text,
|
|
286
|
-
Reasoning = defaultComponents.Reasoning,
|
|
287
|
-
Image = defaultComponents.Image,
|
|
288
|
-
Source = defaultComponents.Source,
|
|
289
|
-
File = defaultComponents.File,
|
|
290
|
-
Unstable_Audio: Audio = defaultComponents.Unstable_Audio,
|
|
291
|
-
tools = {},
|
|
292
|
-
data,
|
|
293
|
-
} = {},
|
|
294
|
-
}) => {
|
|
295
|
-
const aui = useAui();
|
|
296
|
-
const part = useAuiState((s) => s.part);
|
|
297
|
-
|
|
298
|
-
const type = part.type;
|
|
299
|
-
if (type === "tool-call") {
|
|
300
|
-
const addResult = aui.part.addToolResult;
|
|
301
|
-
const resume = aui.part.resumeToolCall;
|
|
302
|
-
const respondToApproval = aui.part.respondToToolApproval;
|
|
303
|
-
const unstable_recordInteraction = aui.part.unstable_recordInteraction;
|
|
304
|
-
if ("Override" in tools)
|
|
305
|
-
return (
|
|
306
|
-
<tools.Override
|
|
307
|
-
{...part}
|
|
308
|
-
addResult={addResult}
|
|
309
|
-
resume={resume}
|
|
310
|
-
respondToApproval={respondToApproval}
|
|
311
|
-
{...(unstable_recordInteraction && { unstable_recordInteraction })}
|
|
312
|
-
/>
|
|
313
|
-
);
|
|
314
|
-
const Tool =
|
|
315
|
-
(tools.by_name && Object.hasOwn(tools.by_name, part.toolName)
|
|
316
|
-
? tools.by_name[part.toolName]
|
|
317
|
-
: undefined) ?? tools.Fallback;
|
|
318
|
-
return (
|
|
319
|
-
<ToolUIDisplay
|
|
320
|
-
{...part}
|
|
321
|
-
Fallback={Tool}
|
|
322
|
-
addResult={addResult}
|
|
323
|
-
resume={resume}
|
|
324
|
-
respondToApproval={respondToApproval}
|
|
325
|
-
{...(unstable_recordInteraction && { unstable_recordInteraction })}
|
|
326
|
-
/>
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (part.status?.type === "requires-action")
|
|
331
|
-
throw new Error("Encountered unexpected requires-action status");
|
|
332
|
-
|
|
333
|
-
switch (type) {
|
|
334
|
-
case "text":
|
|
335
|
-
return <Text {...part} />;
|
|
336
|
-
|
|
337
|
-
case "reasoning":
|
|
338
|
-
return <Reasoning {...part} />;
|
|
339
|
-
|
|
340
|
-
case "source":
|
|
341
|
-
return <Source {...part} />;
|
|
342
|
-
|
|
343
|
-
case "image":
|
|
344
|
-
return <Image {...part} />;
|
|
345
|
-
|
|
346
|
-
case "file":
|
|
347
|
-
return <File {...part} />;
|
|
348
|
-
|
|
349
|
-
case "audio":
|
|
350
|
-
return <Audio {...part} />;
|
|
351
|
-
|
|
352
|
-
case "data": {
|
|
353
|
-
const Data =
|
|
354
|
-
(data?.by_name && Object.hasOwn(data.by_name, part.name)
|
|
355
|
-
? data.by_name[part.name]
|
|
356
|
-
: undefined) ?? data?.Fallback;
|
|
357
|
-
return <DataUIDisplay {...part} Fallback={Data} />;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
default:
|
|
361
|
-
console.warn(`Unknown message part type: ${type}`);
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
};
|
|
365
|
-
|
|
366
262
|
type MessagePartProps = {
|
|
367
263
|
partIndex: number;
|
|
368
264
|
components: MessagePrimitiveUnstable_PartsGrouped.Props["components"];
|
|
369
265
|
};
|
|
370
266
|
|
|
371
|
-
const
|
|
372
|
-
|
|
267
|
+
const MessagePart = memo(
|
|
268
|
+
({ partIndex, components }: MessagePartProps) => (
|
|
373
269
|
<PartByIndexProvider index={partIndex}>
|
|
374
270
|
<MessagePartComponent components={components} />
|
|
375
271
|
</PartByIndexProvider>
|
|
376
|
-
)
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
const MessagePart = memo(
|
|
380
|
-
MessagePartImpl,
|
|
272
|
+
),
|
|
381
273
|
(prev, next) =>
|
|
382
274
|
prev.partIndex === next.partIndex &&
|
|
383
275
|
prev.components?.Text === next.components?.Text &&
|
|
@@ -387,8 +279,7 @@ const MessagePart = memo(
|
|
|
387
279
|
prev.components?.File === next.components?.File &&
|
|
388
280
|
prev.components?.Unstable_Audio === next.components?.Unstable_Audio &&
|
|
389
281
|
prev.components?.tools === next.components?.tools &&
|
|
390
|
-
prev.components?.data === next.components?.data
|
|
391
|
-
prev.components?.Group === next.components?.Group,
|
|
282
|
+
prev.components?.data === next.components?.data,
|
|
392
283
|
);
|
|
393
284
|
|
|
394
285
|
const EmptyPartFallback: FC<{
|
|
@@ -473,6 +364,17 @@ export const MessagePrimitiveUnstable_PartsGrouped: FC<
|
|
|
473
364
|
return <EmptyParts components={components} />;
|
|
474
365
|
}
|
|
475
366
|
|
|
367
|
+
const leafComponents = {
|
|
368
|
+
...components,
|
|
369
|
+
Text: components?.Text ?? defaultComponents.Text,
|
|
370
|
+
Reasoning: components?.Reasoning ?? defaultComponents.Reasoning,
|
|
371
|
+
Image: components?.Image ?? defaultComponents.Image,
|
|
372
|
+
Source: components?.Source ?? defaultComponents.Source,
|
|
373
|
+
File: components?.File ?? defaultComponents.File,
|
|
374
|
+
Unstable_Audio:
|
|
375
|
+
components?.Unstable_Audio ?? defaultComponents.Unstable_Audio,
|
|
376
|
+
};
|
|
377
|
+
|
|
476
378
|
return messageGroups.map((group, groupIndex) => {
|
|
477
379
|
const GroupComponent = components?.Group ?? defaultComponents.Group;
|
|
478
380
|
const identity = partKeys[group.indices[0]!];
|
|
@@ -491,7 +393,7 @@ export const MessagePrimitiveUnstable_PartsGrouped: FC<
|
|
|
491
393
|
<MessagePart
|
|
492
394
|
key={partKeys[partIndex]}
|
|
493
395
|
partIndex={partIndex}
|
|
494
|
-
components={
|
|
396
|
+
components={leafComponents}
|
|
495
397
|
/>
|
|
496
398
|
))}
|
|
497
399
|
</GroupComponent>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @vitest-environment jsdom */
|
|
2
2
|
import type { MouseEvent, ReactNode } from "react";
|
|
3
|
-
import { fireEvent, render } from "@testing-library/react";
|
|
3
|
+
import { act, fireEvent, render } from "@testing-library/react";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import type * as StoreModule from "@assistant-ui/store";
|
|
6
6
|
import { ThreadPrimitiveRoot } from "../thread/ThreadRoot";
|
|
@@ -96,14 +96,6 @@ describe("SelectionToolbarPrimitiveRoot onMouseDown composition", () => {
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
describe("SelectionToolbarPrimitiveRoot selection changes", () => {
|
|
99
|
-
it("opens from a selectionchange event without a mouse or key event", () => {
|
|
100
|
-
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
101
|
-
|
|
102
|
-
fireEvent(document, new Event("selectionchange"));
|
|
103
|
-
|
|
104
|
-
expect(document.querySelector('[data-testid="toolbar"]')).not.toBeNull();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
99
|
it("closes an open toolbar when the selection collapses", () => {
|
|
108
100
|
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
109
101
|
fireEvent(document, new Event("selectionchange"));
|
|
@@ -282,17 +274,33 @@ describe("SelectionToolbarPrimitiveRoot frame cleanup", () => {
|
|
|
282
274
|
expect(frames).toHaveLength(2);
|
|
283
275
|
});
|
|
284
276
|
|
|
285
|
-
it("
|
|
277
|
+
it("shows the toolbar when the window blurs during a drag", () => {
|
|
286
278
|
const { frames } = deferFrames();
|
|
287
|
-
render(<SelectionToolbarPrimitiveRoot />);
|
|
279
|
+
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
288
280
|
|
|
289
281
|
fireEvent.mouseDown(document);
|
|
290
282
|
fireEvent(document, new Event("selectionchange"));
|
|
291
283
|
expect(frames).toHaveLength(0);
|
|
292
284
|
|
|
293
285
|
fireEvent.blur(window);
|
|
294
|
-
fireEvent(document, new Event("selectionchange"));
|
|
295
286
|
expect(frames).toHaveLength(1);
|
|
287
|
+
|
|
288
|
+
act(() => frames[0]?.(0));
|
|
289
|
+
|
|
290
|
+
expect(document.querySelector('[data-testid="toolbar"]')).not.toBeNull();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("does not reopen a toolbar hidden by scroll on an unrelated window blur", () => {
|
|
294
|
+
render(<SelectionToolbarPrimitiveRoot data-testid="toolbar" />);
|
|
295
|
+
|
|
296
|
+
fireEvent(document, new Event("selectionchange"));
|
|
297
|
+
expect(document.querySelector('[data-testid="toolbar"]')).not.toBeNull();
|
|
298
|
+
|
|
299
|
+
fireEvent.scroll(document);
|
|
300
|
+
expect(document.querySelector('[data-testid="toolbar"]')).toBeNull();
|
|
301
|
+
|
|
302
|
+
fireEvent.blur(window);
|
|
303
|
+
expect(document.querySelector('[data-testid="toolbar"]')).toBeNull();
|
|
296
304
|
});
|
|
297
305
|
|
|
298
306
|
it("recovers when a context menu consumes the mouseup of a press", () => {
|
|
@@ -134,12 +134,16 @@ export const SelectionToolbarPrimitiveRoot = forwardRef<
|
|
|
134
134
|
isMouseDragging = false;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
const handleWindowBlur = () => {
|
|
138
|
+
if (isMouseDragging) handleMouseUp();
|
|
139
|
+
};
|
|
140
|
+
|
|
137
141
|
document.addEventListener("mousedown", handleMouseDown, true);
|
|
138
142
|
document.addEventListener("mouseup", handleMouseUp, true);
|
|
139
143
|
document.addEventListener("dragend", handleMouseUp, true);
|
|
140
144
|
// A context menu can consume the mouseup of the press that opened it.
|
|
141
145
|
document.addEventListener("contextmenu", handleMouseCancel, true);
|
|
142
|
-
window.addEventListener("blur",
|
|
146
|
+
window.addEventListener("blur", handleWindowBlur);
|
|
143
147
|
document.addEventListener("selectionchange", handleSelectionChange);
|
|
144
148
|
document.addEventListener("scroll", handleScroll, true);
|
|
145
149
|
|
|
@@ -149,7 +153,7 @@ export const SelectionToolbarPrimitiveRoot = forwardRef<
|
|
|
149
153
|
document.removeEventListener("mouseup", handleMouseUp, true);
|
|
150
154
|
document.removeEventListener("dragend", handleMouseUp, true);
|
|
151
155
|
document.removeEventListener("contextmenu", handleMouseCancel, true);
|
|
152
|
-
window.removeEventListener("blur",
|
|
156
|
+
window.removeEventListener("blur", handleWindowBlur);
|
|
153
157
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
154
158
|
document.removeEventListener("scroll", handleScroll, true);
|
|
155
159
|
};
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
forwardRef,
|
|
8
8
|
type ComponentPropsWithoutRef,
|
|
9
9
|
useCallback,
|
|
10
|
-
useLayoutEffect,
|
|
11
10
|
useMemo,
|
|
12
11
|
} from "react";
|
|
13
12
|
import { useAuiState } from "@assistant-ui/store";
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
useThreadViewportStore,
|
|
21
20
|
} from "../../context/react/ThreadViewportContext";
|
|
22
21
|
import { useTopAnchorReserve } from "./topAnchor/useTopAnchorReserve";
|
|
22
|
+
import { useIsomorphicLayoutEffect } from "../../utils/useIsomorphicLayoutEffect";
|
|
23
23
|
import {
|
|
24
24
|
getActiveTopAnchorAnchorId,
|
|
25
25
|
getActiveTopAnchorTargetId,
|
|
@@ -124,13 +124,13 @@ const useTopAnchorTurn = (enabled: boolean) => {
|
|
|
124
124
|
isTopAnchorTurnValid(topAnchorTurn, s.thread.messages),
|
|
125
125
|
);
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
useIsomorphicLayoutEffect(() => {
|
|
128
128
|
if (!topAnchorTurn || topAnchorTurnIsValid) return;
|
|
129
129
|
|
|
130
130
|
threadViewportStore.getState().setTopAnchorTurn(null);
|
|
131
131
|
}, [threadViewportStore, topAnchorTurn, topAnchorTurnIsValid]);
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
useIsomorphicLayoutEffect(() => {
|
|
134
134
|
if (!activeTurn) return;
|
|
135
135
|
|
|
136
136
|
const state = threadViewportStore.getState();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useIsomorphicLayoutEffect } from "../../../utils/useIsomorphicLayoutEffect";
|
|
4
4
|
import { useThreadViewportStore } from "../../../context/react/ThreadViewportContext";
|
|
5
5
|
import { mountTopAnchorReserve } from "./mountTopAnchorReserve";
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ import { mountTopAnchorReserve } from "./mountTopAnchorReserve";
|
|
|
12
12
|
export const useTopAnchorReserve = (enabled: boolean) => {
|
|
13
13
|
const threadViewportStore = useThreadViewportStore();
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
useIsomorphicLayoutEffect(() => {
|
|
16
16
|
if (!enabled) return;
|
|
17
17
|
return mountTopAnchorReserve(threadViewportStore);
|
|
18
18
|
}, [enabled, threadViewportStore]);
|