@assistant-ui/react 0.15.16 → 0.15.18
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/README.md +4 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +4 -2
- package/dist/mcp-apps/McpAppRenderer.d.ts +36 -9
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +108 -57
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +20 -17
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +229 -115
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/bridge.js +2 -3
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/mcp-apps/index.d.ts +3 -3
- package/dist/model-context/frame/useAssistantFrameHost.d.ts +1 -1
- package/dist/model-context/frame/useAssistantFrameHost.js +10 -11
- package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +3 -3
- package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +3 -3
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +3 -3
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
- package/dist/primitives/actionBarMore/scope.d.ts +2 -2
- package/dist/primitives/actionBarMore/scope.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/scope.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
- package/dist/primitives/assistantModal/scope.d.ts +2 -2
- package/dist/primitives/assistantModal/scope.d.ts.map +1 -1
- package/dist/primitives/assistantModal/scope.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts +1 -2
- package/dist/primitives/composer/ComposerInputPluginContext.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.js +1 -6
- package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +5 -5
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +3 -3
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts +5 -0
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +157 -130
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +46 -15
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js +3 -3
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +4 -4
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +40 -10
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js +4 -4
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -0
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +63 -61
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts +2 -0
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts +21 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/detectTrigger.js +17 -3
- package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
- package/dist/primitives/composer/trigger/index.d.ts +3 -1
- package/dist/primitives/composer/trigger/index.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/index.js.map +1 -1
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts +6 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.d.ts.map +1 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js +9 -0
- package/dist/primitives/composer/trigger/matchesTriggerItemQuery.js.map +1 -0
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +3 -4
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +15 -14
- package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +3 -5
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +18 -19
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/composer.d.ts +3 -2
- package/dist/primitives/composer.js.map +1 -1
- package/dist/primitives/error/ErrorMessage.js.map +1 -1
- package/dist/primitives/message/MessageIf.js.map +1 -1
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js +4 -4
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +3 -3
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +3 -3
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadIf.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +3 -3
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js +3 -3
- package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +4 -4
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +3 -3
- package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
- package/dist/primitives/threadListFocusGroup.d.ts +2 -1
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
- package/dist/primitives/threadListFocusGroup.js +2 -2
- package/dist/primitives/threadListFocusGroup.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js +3 -3
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -2
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
- package/dist/primitives/threadListItemMore/scope.d.ts +2 -2
- package/dist/primitives/threadListItemMore/scope.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/scope.js.map +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +148 -72
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +3 -1
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +9 -3
- package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
- package/dist/unstable/useMentionAdapter.js +50 -43
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useModelContextSnapshot.d.ts +34 -0
- package/dist/unstable/useModelContextSnapshot.d.ts.map +1 -0
- package/dist/unstable/useModelContextSnapshot.js +61 -0
- package/dist/unstable/useModelContextSnapshot.js.map +1 -0
- package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +37 -29
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts +14 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.d.ts.map +1 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js +85 -0
- package/dist/unstable/webmcp/WebMcpRegistrationResource.js.map +1 -0
- package/dist/unstable/webmcp/convertTools.d.ts +21 -0
- package/dist/unstable/webmcp/convertTools.d.ts.map +1 -0
- package/dist/unstable/webmcp/convertTools.js +110 -0
- package/dist/unstable/webmcp/convertTools.js.map +1 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts +21 -0
- package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js +105 -0
- package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -0
- package/dist/unstable/webmcp/webmcp-host.d.ts +39 -0
- package/dist/unstable/webmcp/webmcp-host.d.ts.map +1 -0
- package/dist/unstable/webmcp/webmcp-host.js +29 -0
- package/dist/unstable/webmcp/webmcp-host.js.map +1 -0
- package/dist/utils/Primitive.d.ts +6 -6
- package/dist/utils/Primitive.js +2 -2
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/createActionButton.js +3 -3
- package/dist/utils/createActionButton.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -1
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
- package/dist/utils/smooth/SmoothContext.d.ts +24 -0
- package/dist/utils/smooth/SmoothContext.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +1 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +14 -22
- package/src/index.ts +22 -1
- package/src/mcp-apps/McpAppRenderer.test.tsx +386 -9
- package/src/mcp-apps/McpAppRenderer.tsx +171 -69
- package/src/mcp-apps/McpAppsRemoteHost.concurrent.test.tsx +215 -0
- package/src/mcp-apps/McpAppsRemoteHost.ts +28 -12
- package/src/mcp-apps/app-frame.test.tsx +349 -0
- package/src/mcp-apps/app-frame.tsx +68 -9
- package/src/mcp-apps/bridge.ts +7 -2
- package/src/mcp-apps/index.ts +6 -1
- package/src/model-context/frame/AssistantFrame.test.ts +3 -3
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
- package/src/model-context/frame/useAssistantFrameHost.ts +2 -2
- package/src/model-context/makeAssistantVisible.tsx +1 -1
- package/src/primitives/actionBar/ActionBarCopy.tsx +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackNegative.tsx +1 -1
- package/src/primitives/actionBar/ActionBarFeedbackPositive.tsx +1 -1
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +1 -1
- package/src/primitives/actionBarMore/scope.ts +2 -2
- package/src/primitives/assistantModal/AssistantModalContent.tsx +1 -1
- package/src/primitives/assistantModal/scope.ts +2 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
- package/src/primitives/composer/ComposerInput.test.tsx +2 -1
- package/src/primitives/composer/ComposerInput.tsx +3 -3
- package/src/primitives/composer/ComposerInputPluginContext.tsx +0 -10
- package/src/primitives/composer/ComposerQuote.tsx +1 -1
- package/src/primitives/composer/ComposerRoot.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopover.tsx +16 -1
- package/src/primitives/composer/trigger/TriggerPopoverAction.tsx +4 -2
- package/src/primitives/composer/trigger/TriggerPopoverBack.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverBehavior.test.tsx +109 -0
- package/src/primitives/composer/trigger/TriggerPopoverCategories.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverDirective.tsx +4 -2
- package/src/primitives/composer/trigger/TriggerPopoverItems.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverResource.ts +4 -1
- package/src/primitives/composer/trigger/TriggerPopoverRootContext.tsx +2 -0
- package/src/primitives/composer/trigger/detectTrigger.test.ts +88 -5
- package/src/primitives/composer/trigger/detectTrigger.ts +50 -9
- package/src/primitives/composer/trigger/index.ts +1 -0
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.test.ts +52 -0
- package/src/primitives/composer/trigger/matchesTriggerItemQuery.ts +13 -0
- package/src/primitives/composer/trigger/triggerDetectionResource.ts +10 -7
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +3 -10
- package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +15 -3
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +1 -4
- package/src/primitives/composer.ts +1 -0
- package/src/primitives/message/MessageRoot.tsx +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +1 -1
- package/src/primitives/thread/ThreadViewport.tsx +1 -1
- package/src/primitives/thread/ThreadViewportFooter.tsx +1 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -7
- package/src/primitives/threadList/ThreadListNew.tsx +1 -1
- package/src/primitives/threadListFocusGroup.ts +2 -2
- package/src/primitives/threadListItem/ThreadListItemRoot.tsx +1 -1
- package/src/primitives/threadListItem/ThreadListItemTrigger.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreContent.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreRoot.tsx +1 -1
- package/src/primitives/threadListItemMore/ThreadListItemMoreTrigger.tsx +2 -2
- package/src/primitives/threadListItemMore/scope.ts +2 -2
- package/src/sandbox-host/SandboxHost.test.tsx +165 -3
- package/src/sandbox-host/SandboxHost.tsx +42 -9
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +31 -1
- package/src/unstable/useComposerInputHistory.test.tsx +2 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +60 -1
- package/src/unstable/useLiveCompletionAdapter.ts +4 -1
- package/src/unstable/useMentionAdapter.test.tsx +337 -0
- package/src/unstable/useMentionAdapter.ts +94 -57
- package/src/unstable/useModelContextSnapshot.test.tsx +132 -0
- package/src/unstable/useModelContextSnapshot.ts +67 -0
- package/src/unstable/useSlashCommandAdapter.test.tsx +141 -0
- package/src/unstable/useSlashCommandAdapter.ts +47 -19
- package/src/unstable/webmcp/WebMcpRegistrationResource.ts +92 -0
- package/src/unstable/webmcp/__tests__/webmcp.fake.tsx +168 -0
- package/src/unstable/webmcp/convertTools.test.ts +393 -0
- package/src/unstable/webmcp/convertTools.ts +180 -0
- package/src/unstable/webmcp/useWebMcpProvider.test.tsx +466 -0
- package/src/unstable/webmcp/useWebMcpProvider.ts +150 -0
- package/src/unstable/webmcp/webmcp-host.test.ts +142 -0
- package/src/unstable/webmcp/webmcp-host.ts +83 -0
- package/src/utils/Primitive.tsx +2 -2
- package/src/utils/createActionButton.tsx +1 -1
- package/src/utils/hooks/useOnResizeContent.ts +1 -1
- package/src/utils/hooks/useOnScrollToBottom.ts +1 -1
- package/src/utils/smooth/useSmooth.ts +1 -1
- package/dist/legacy-runtime/runtime/MessagePartRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/MessagePartRuntime.js +0 -2
- package/dist/legacy-runtime/runtime/MessageRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/MessageRuntime.js +0 -2
- package/dist/legacy-runtime/runtime/ThreadListRuntime.d.ts +0 -3
- package/dist/legacy-runtime/runtime/ThreadListRuntime.js +0 -2
- package/dist/utils/invokeCallbackSafely.d.ts +0 -5
- package/dist/utils/invokeCallbackSafely.d.ts.map +0 -1
- package/dist/utils/invokeCallbackSafely.js +0 -15
- package/dist/utils/invokeCallbackSafely.js.map +0 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +0 -15
- package/dist/utils/useToolArgsFieldStatus.d.ts.map +0 -1
- package/dist/utils/useToolArgsFieldStatus.js +0 -23
- package/dist/utils/useToolArgsFieldStatus.js.map +0 -1
- package/src/legacy-runtime/runtime/MessagePartRuntime.ts +0 -2
- package/src/legacy-runtime/runtime/MessageRuntime.ts +0 -3
- package/src/legacy-runtime/runtime/ThreadListRuntime.ts +0 -4
- package/src/utils/invokeCallbackSafely.ts +0 -11
- package/src/utils/useToolArgsFieldStatus.ts +0 -18
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Unstable_TriggerItem } from "@assistant-ui/core";
|
|
2
|
+
|
|
3
|
+
export function matchesTriggerItemQuery(
|
|
4
|
+
item: Unstable_TriggerItem,
|
|
5
|
+
lowerQuery: string,
|
|
6
|
+
): boolean {
|
|
7
|
+
if (!lowerQuery) return true;
|
|
8
|
+
return (
|
|
9
|
+
item.id.toLowerCase().includes(lowerQuery) ||
|
|
10
|
+
item.label.toLowerCase().includes(lowerQuery) ||
|
|
11
|
+
(item.description?.toLowerCase().includes(lowerQuery) ?? false)
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
2
|
import { resource } from "@assistant-ui/tap";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
detectTrigger,
|
|
5
|
+
type TriggerMatch,
|
|
6
|
+
type TriggerMatcher,
|
|
7
|
+
} from "./detectTrigger";
|
|
4
8
|
|
|
5
9
|
/** Detected trigger position within the composer text. */
|
|
6
|
-
export type DetectedTrigger =
|
|
7
|
-
readonly offset: number;
|
|
8
|
-
readonly query: string;
|
|
9
|
-
};
|
|
10
|
+
export type DetectedTrigger = TriggerMatch;
|
|
10
11
|
|
|
11
12
|
export type TriggerDetectionResourceOutput = {
|
|
12
13
|
/** Detected trigger (or `null` when inactive). */
|
|
@@ -21,16 +22,18 @@ export type TriggerDetectionResourceOutput = {
|
|
|
21
22
|
const useTriggerDetectionResource = ({
|
|
22
23
|
text,
|
|
23
24
|
triggerChar,
|
|
25
|
+
matcher,
|
|
24
26
|
}: {
|
|
25
27
|
text: string;
|
|
26
28
|
triggerChar: string;
|
|
29
|
+
matcher?: TriggerMatcher | undefined;
|
|
27
30
|
}): TriggerDetectionResourceOutput => {
|
|
28
31
|
const [cursorPosition, setCursorPosition] = useState(text.length);
|
|
29
32
|
|
|
30
33
|
const trigger = useMemo(() => {
|
|
31
34
|
const pos = Math.min(cursorPosition, text.length);
|
|
32
|
-
return detectTrigger(text, triggerChar, pos);
|
|
33
|
-
}, [cursorPosition, text, triggerChar]);
|
|
35
|
+
return detectTrigger(text, triggerChar, pos, matcher);
|
|
36
|
+
}, [cursorPosition, matcher, text, triggerChar]);
|
|
34
37
|
|
|
35
38
|
const query = trigger?.query ?? "";
|
|
36
39
|
|
|
@@ -5,14 +5,7 @@ import type {
|
|
|
5
5
|
Unstable_TriggerCategory,
|
|
6
6
|
Unstable_TriggerItem,
|
|
7
7
|
} from "@assistant-ui/core";
|
|
8
|
-
|
|
9
|
-
function matchesQuery(item: Unstable_TriggerItem, lower: string): boolean {
|
|
10
|
-
return (
|
|
11
|
-
item.id.toLowerCase().includes(lower) ||
|
|
12
|
-
item.label.toLowerCase().includes(lower) ||
|
|
13
|
-
(item.description?.toLowerCase().includes(lower) ?? false)
|
|
14
|
-
);
|
|
15
|
-
}
|
|
8
|
+
import { matchesTriggerItemQuery } from "./matchesTriggerItemQuery";
|
|
16
9
|
|
|
17
10
|
export type TriggerNavigationResourceOutput = {
|
|
18
11
|
/** Filtered categories visible in the list (empty in search mode). */
|
|
@@ -76,7 +69,7 @@ const useTriggerNavigationResource = ({
|
|
|
76
69
|
const lower = query.toLowerCase();
|
|
77
70
|
for (const cat of categories) {
|
|
78
71
|
for (const item of adapter.categoryItems(cat.id)) {
|
|
79
|
-
if (
|
|
72
|
+
if (matchesTriggerItemQuery(item, lower)) {
|
|
80
73
|
all.push(item);
|
|
81
74
|
}
|
|
82
75
|
}
|
|
@@ -97,7 +90,7 @@ const useTriggerNavigationResource = ({
|
|
|
97
90
|
if (isSearchMode) return searchResults ?? [];
|
|
98
91
|
if (!query) return allItems;
|
|
99
92
|
const lower = query.toLowerCase();
|
|
100
|
-
return allItems.filter((item) =>
|
|
93
|
+
return allItems.filter((item) => matchesTriggerItemQuery(item, lower));
|
|
101
94
|
}, [allItems, query, isSearchMode, searchResults]);
|
|
102
95
|
|
|
103
96
|
const navigableList = useMemo(() => {
|
|
@@ -45,7 +45,7 @@ const render = ({
|
|
|
45
45
|
}: {
|
|
46
46
|
behavior: TriggerBehavior;
|
|
47
47
|
text: string;
|
|
48
|
-
trigger: { offset: number; query: string };
|
|
48
|
+
trigger: { offset: number; query: string; endOffset: number };
|
|
49
49
|
}) => {
|
|
50
50
|
const textRef = { value: text };
|
|
51
51
|
const { aui, setText } = makeAui(textRef);
|
|
@@ -72,7 +72,7 @@ describe("TriggerSelectionResource", () => {
|
|
|
72
72
|
const { root, setText, setCursorPosition, onSelected } = render({
|
|
73
73
|
behavior: { kind: "directive", formatter },
|
|
74
74
|
text: "@file tail",
|
|
75
|
-
trigger: { offset: 0, query: "file" },
|
|
75
|
+
trigger: { offset: 0, query: "file", endOffset: 5 },
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
root.getValue().selectItem(item);
|
|
@@ -91,7 +91,7 @@ describe("TriggerSelectionResource", () => {
|
|
|
91
91
|
removeOnExecute: true,
|
|
92
92
|
},
|
|
93
93
|
text: "before @file tail",
|
|
94
|
-
trigger: { offset: 7, query: "file" },
|
|
94
|
+
trigger: { offset: 7, query: "file", endOffset: 12 },
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
root.getValue().selectItem(item);
|
|
@@ -132,4 +132,16 @@ describe("TriggerSelectionResource", () => {
|
|
|
132
132
|
expect(setText).toHaveBeenCalledWith("@file.txt ");
|
|
133
133
|
expect(textRef.value).toBe("@file.txt ");
|
|
134
134
|
});
|
|
135
|
+
|
|
136
|
+
it("replaces the matcher endOffset even when query is shorter", () => {
|
|
137
|
+
const { root, setText } = render({
|
|
138
|
+
behavior: { kind: "directive", formatter },
|
|
139
|
+
text: "hello @Example UK tail",
|
|
140
|
+
trigger: { offset: 6, query: "example", endOffset: 17 },
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
root.getValue().selectItem(item);
|
|
144
|
+
|
|
145
|
+
expect(setText).toHaveBeenCalledWith("hello @file.txt tail");
|
|
146
|
+
});
|
|
135
147
|
});
|
|
@@ -37,7 +37,6 @@ const useTriggerSelectionResource = ({
|
|
|
37
37
|
behavior,
|
|
38
38
|
trigger,
|
|
39
39
|
aui,
|
|
40
|
-
triggerChar,
|
|
41
40
|
setCursorPosition,
|
|
42
41
|
onSelected,
|
|
43
42
|
}: {
|
|
@@ -74,9 +73,7 @@ const useTriggerSelectionResource = ({
|
|
|
74
73
|
|
|
75
74
|
const currentText = aui.composer.getState().text;
|
|
76
75
|
const before = currentText.slice(0, trigger.offset);
|
|
77
|
-
const after = currentText.slice(
|
|
78
|
-
trigger.offset + triggerChar.length + trigger.query.length,
|
|
79
|
-
);
|
|
76
|
+
const after = currentText.slice(trigger.endOffset);
|
|
80
77
|
|
|
81
78
|
const insertDirective = () => {
|
|
82
79
|
const directive = behavior.formatter.serialize(item);
|
|
@@ -29,3 +29,4 @@ export { useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScop
|
|
|
29
29
|
export { useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers } from "./composer/trigger";
|
|
30
30
|
export { useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional } from "./composer/trigger";
|
|
31
31
|
export type { RegisteredTrigger as Unstable_RegisteredTrigger } from "./composer/trigger";
|
|
32
|
+
export type { TriggerMatcher as Unstable_TriggerMatcher } from "./composer/trigger";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "react";
|
|
11
11
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
12
12
|
import { useManagedRef } from "../../utils/hooks/useManagedRef";
|
|
13
|
-
import { useComposedRefs } from "
|
|
13
|
+
import { useComposedRefs } from "radix-ui/internal";
|
|
14
14
|
import {
|
|
15
15
|
useThreadViewport,
|
|
16
16
|
useThreadViewportStore,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useComposedRefs } from "
|
|
3
|
+
import { useComposedRefs } from "radix-ui/internal";
|
|
4
4
|
import { useCallback, useLayoutEffect, useRef, type RefCallback } from "react";
|
|
5
5
|
import { useAuiEvent, useAuiState } from "@assistant-ui/store";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
isUserScrollUp,
|
|
8
|
+
isViewportAtBottom,
|
|
9
|
+
viewportOverflows,
|
|
10
|
+
} from "@assistant-ui/store/client";
|
|
7
11
|
import { useOnResizeContent } from "../../utils/hooks/useOnResizeContent";
|
|
8
12
|
import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom";
|
|
9
13
|
import { useManagedRef } from "../../utils/hooks/useManagedRef";
|
|
@@ -114,9 +118,7 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
114
118
|
if (!div) return;
|
|
115
119
|
|
|
116
120
|
const isAtBottom = threadViewportStore.getState().isAtBottom;
|
|
117
|
-
const newIsAtBottom =
|
|
118
|
-
Math.abs(div.scrollHeight - div.scrollTop - div.clientHeight) <= 1 ||
|
|
119
|
-
div.scrollHeight <= div.clientHeight;
|
|
121
|
+
const newIsAtBottom = isViewportAtBottom(div);
|
|
120
122
|
|
|
121
123
|
const isInFlightDownwardScroll =
|
|
122
124
|
!newIsAtBottom && lastScrollTop.current < div.scrollTop;
|
|
@@ -135,8 +137,7 @@ export const useThreadViewportAutoScroll = <TElement extends HTMLElement>({
|
|
|
135
137
|
if (newIsAtBottom) {
|
|
136
138
|
// newIsAtBottom is ambiguous when the viewport doesn't overflow —
|
|
137
139
|
// keep intent alive until content can actually scroll
|
|
138
|
-
|
|
139
|
-
if (viewportOverflows) {
|
|
140
|
+
if (viewportOverflows(div)) {
|
|
140
141
|
scrollingToBottomBehaviorRef.current = null;
|
|
141
142
|
}
|
|
142
143
|
if (autoScroll) followBottomRef.current = true;
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
} from "../../utils/createActionButton";
|
|
7
7
|
import { forwardRef } from "react";
|
|
8
8
|
import { Primitive } from "../../utils/Primitive";
|
|
9
|
-
import { composeEventHandlers } from "
|
|
9
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
10
10
|
import { useAuiState } from "@assistant-ui/store";
|
|
11
11
|
import { useThreadListNew as useThreadListNewBehavior } from "@assistant-ui/core/react";
|
|
12
12
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Collection } from "radix-ui/internal";
|
|
4
4
|
import { createContext, useContext, type RefObject } from "react";
|
|
5
5
|
|
|
6
6
|
const [collection, useThreadListCollection] =
|
|
7
|
-
createCollection<HTMLButtonElement>("ThreadList");
|
|
7
|
+
Collection.createCollection<HTMLButtonElement>("ThreadList");
|
|
8
8
|
|
|
9
9
|
export const ThreadListCollection = collection;
|
|
10
10
|
export { useThreadListCollection };
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
} from "react";
|
|
12
12
|
import { useAuiState } from "@assistant-ui/store";
|
|
13
|
-
import { composeEventHandlers } from "
|
|
13
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
14
14
|
import { Direction } from "radix-ui";
|
|
15
15
|
import {
|
|
16
16
|
ThreadListItemFocusProvider,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
} from "react";
|
|
8
8
|
import { Direction, DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
9
|
-
import { composeEventHandlers } from "
|
|
9
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
10
10
|
import type { WithRenderPropProps } from "../../utils/Primitive";
|
|
11
11
|
import { DropdownMenuRenderContent } from "../dropdownMenuRenderPrimitives";
|
|
12
12
|
import { useThreadListItemFocus } from "../threadListFocusGroup";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useContext,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
11
|
-
import { useControllableState } from "
|
|
11
|
+
import { useControllableState } from "radix-ui/internal";
|
|
12
12
|
import { type ScopedProps, useDropdownMenuScope } from "./scope";
|
|
13
13
|
|
|
14
14
|
const ThreadListItemMoreSetOpenContext = createContext<
|
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
Direction,
|
|
6
6
|
type DropdownMenu as DropdownMenuPrimitive,
|
|
7
7
|
} from "radix-ui";
|
|
8
|
-
import { useComposedRefs } from "
|
|
9
|
-
import { composeEventHandlers } from "
|
|
8
|
+
import { useComposedRefs } from "radix-ui/internal";
|
|
9
|
+
import { composeEventHandlers } from "radix-ui/internal";
|
|
10
10
|
import type { WithRenderPropProps } from "../../utils/Primitive";
|
|
11
11
|
import { DropdownMenuRenderTrigger } from "../dropdownMenuRenderPrimitives";
|
|
12
12
|
import { useThreadListItemFocus } from "../threadListFocusGroup";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Context } from "radix-ui/internal";
|
|
3
3
|
|
|
4
4
|
export const useDropdownMenuScope: ReturnType<
|
|
5
5
|
typeof DropdownMenuPrimitive.createDropdownMenuScope
|
|
6
6
|
> = DropdownMenuPrimitive.createDropdownMenuScope();
|
|
7
7
|
|
|
8
|
-
export type ScopedProps<P> = P & { __scopeThreadListItemMore?: Scope };
|
|
8
|
+
export type ScopedProps<P> = P & { __scopeThreadListItemMore?: Context.Scope };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import { act } from "react";
|
|
2
|
+
import { act, startTransition, Suspense } from "react";
|
|
3
3
|
import { createRoot, type Root } from "react-dom/client";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
|
|
6
6
|
const { renderHtmlMock } = vi.hoisted(() => ({ renderHtmlMock: vi.fn() }));
|
|
7
7
|
|
|
8
|
-
vi.mock("safe-content-frame", () => ({
|
|
8
|
+
vi.mock("safe-content-frame", async (importOriginal) => ({
|
|
9
|
+
...(await importOriginal<typeof import("safe-content-frame")>()),
|
|
9
10
|
SafeContentFrame: class {
|
|
10
11
|
renderHtml = renderHtmlMock;
|
|
11
12
|
},
|
|
@@ -34,7 +35,7 @@ function fakeRendered() {
|
|
|
34
35
|
origin: "https://fake.scf.test",
|
|
35
36
|
sendMessage: vi.fn(),
|
|
36
37
|
dispose: vi.fn(),
|
|
37
|
-
fullyLoadedPromiseWithTimeout: vi.fn(),
|
|
38
|
+
fullyLoadedPromiseWithTimeout: vi.fn(() => new Promise<void>(() => {})),
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -140,6 +141,123 @@ describe("SandboxHost", () => {
|
|
|
140
141
|
expect(onMessage).toHaveBeenCalledTimes(1);
|
|
141
142
|
});
|
|
142
143
|
|
|
144
|
+
it("reports a frame that never finishes loading through onError", async () => {
|
|
145
|
+
const rendered = fakeRendered();
|
|
146
|
+
rendered.fullyLoadedPromiseWithTimeout.mockImplementation(() =>
|
|
147
|
+
Promise.reject(
|
|
148
|
+
Object.assign(new Error("Failed to load shim: https://fake.scf.test"), {
|
|
149
|
+
code: "shim-unavailable",
|
|
150
|
+
}),
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
renderHtmlMock.mockResolvedValue(rendered);
|
|
154
|
+
const onError = vi.fn();
|
|
155
|
+
|
|
156
|
+
await act(async () => {
|
|
157
|
+
root.render(
|
|
158
|
+
<SandboxHost
|
|
159
|
+
content={{ html: "" }}
|
|
160
|
+
contentKey="k"
|
|
161
|
+
createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
|
|
162
|
+
onError={onError}
|
|
163
|
+
/>,
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
await flush();
|
|
167
|
+
|
|
168
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
169
|
+
expect(onError.mock.calls[0]![0]).toBeInstanceOf(Error);
|
|
170
|
+
expect(onError.mock.calls[0]![0]).toMatchObject({
|
|
171
|
+
code: "shim-unavailable",
|
|
172
|
+
message: "Failed to load shim: https://fake.scf.test",
|
|
173
|
+
});
|
|
174
|
+
expect(rendered.dispose).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("stays silent when the shim started and the render is merely slow", async () => {
|
|
178
|
+
const rendered = fakeRendered();
|
|
179
|
+
rendered.fullyLoadedPromiseWithTimeout.mockImplementation(() =>
|
|
180
|
+
Promise.reject(
|
|
181
|
+
Object.assign(new Error("Timeout"), { code: "render-timeout" }),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
renderHtmlMock.mockResolvedValue(rendered);
|
|
185
|
+
const onError = vi.fn();
|
|
186
|
+
|
|
187
|
+
await act(async () => {
|
|
188
|
+
root.render(
|
|
189
|
+
<SandboxHost
|
|
190
|
+
content={{ html: "" }}
|
|
191
|
+
contentKey="k"
|
|
192
|
+
createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
|
|
193
|
+
onError={onError}
|
|
194
|
+
/>,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
await flush();
|
|
198
|
+
|
|
199
|
+
expect(rendered.fullyLoadedPromiseWithTimeout).toHaveBeenCalled();
|
|
200
|
+
expect(onError).not.toHaveBeenCalled();
|
|
201
|
+
expect(rendered.dispose).not.toHaveBeenCalled();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("reports a load failure that carries no shim code", async () => {
|
|
205
|
+
const rendered = fakeRendered();
|
|
206
|
+
rendered.fullyLoadedPromiseWithTimeout.mockImplementation(() =>
|
|
207
|
+
Promise.reject(new Error("Failed to load iframe")),
|
|
208
|
+
);
|
|
209
|
+
renderHtmlMock.mockResolvedValue(rendered);
|
|
210
|
+
const onError = vi.fn();
|
|
211
|
+
|
|
212
|
+
await act(async () => {
|
|
213
|
+
root.render(
|
|
214
|
+
<SandboxHost
|
|
215
|
+
content={{ html: "" }}
|
|
216
|
+
contentKey="k"
|
|
217
|
+
createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
|
|
218
|
+
onError={onError}
|
|
219
|
+
/>,
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
await flush();
|
|
223
|
+
|
|
224
|
+
expect(onError).toHaveBeenCalledTimes(1);
|
|
225
|
+
expect(onError.mock.calls[0]![0].message).toBe("Failed to load iframe");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("does not report a load failure after unmount", async () => {
|
|
229
|
+
const rendered = fakeRendered();
|
|
230
|
+
let rejectLoad: (error: Error) => void;
|
|
231
|
+
rendered.fullyLoadedPromiseWithTimeout.mockReturnValue(
|
|
232
|
+
new Promise<void>((_, reject) => {
|
|
233
|
+
rejectLoad = reject;
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
renderHtmlMock.mockResolvedValue(rendered);
|
|
237
|
+
const onError = vi.fn();
|
|
238
|
+
|
|
239
|
+
await act(async () => {
|
|
240
|
+
root.render(
|
|
241
|
+
<SandboxHost
|
|
242
|
+
content={{ html: "" }}
|
|
243
|
+
contentKey="k"
|
|
244
|
+
createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
|
|
245
|
+
onError={onError}
|
|
246
|
+
/>,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
await flush();
|
|
250
|
+
|
|
251
|
+
await act(async () => {
|
|
252
|
+
root.unmount();
|
|
253
|
+
});
|
|
254
|
+
rejectLoad!(new Error("Timeout"));
|
|
255
|
+
await flush();
|
|
256
|
+
|
|
257
|
+
expect(rendered.fullyLoadedPromiseWithTimeout).toHaveBeenCalled();
|
|
258
|
+
expect(onError).not.toHaveBeenCalled();
|
|
259
|
+
});
|
|
260
|
+
|
|
143
261
|
it("clamps the bridge-reported height to maxHeight and ignores invalid values", async () => {
|
|
144
262
|
const rendered = fakeRendered();
|
|
145
263
|
renderHtmlMock.mockResolvedValue(rendered);
|
|
@@ -318,4 +436,48 @@ describe("SandboxHost", () => {
|
|
|
318
436
|
});
|
|
319
437
|
expect(rendered.dispose).toHaveBeenCalledTimes(1);
|
|
320
438
|
});
|
|
439
|
+
|
|
440
|
+
it("keeps bridge options scoped to committed renders", async () => {
|
|
441
|
+
let resolveRender!: (frame: ReturnType<typeof fakeRendered>) => void;
|
|
442
|
+
renderHtmlMock.mockReturnValue(
|
|
443
|
+
new Promise((resolve) => {
|
|
444
|
+
resolveRender = resolve;
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
const rendered = fakeRendered();
|
|
448
|
+
const bridge = { onMessage: vi.fn(), dispose: vi.fn() };
|
|
449
|
+
const createBridgeA = vi.fn(() => bridge);
|
|
450
|
+
const createBridgeB = vi.fn(() => bridge);
|
|
451
|
+
const interruptedRender = vi.fn();
|
|
452
|
+
const pending = new Promise<never>(() => {});
|
|
453
|
+
const Block = () => {
|
|
454
|
+
interruptedRender();
|
|
455
|
+
throw pending;
|
|
456
|
+
};
|
|
457
|
+
const view = (createBridge: typeof createBridgeA, blocked: boolean) => (
|
|
458
|
+
<Suspense fallback={null}>
|
|
459
|
+
<SandboxHost
|
|
460
|
+
content={{ html: "" }}
|
|
461
|
+
contentKey="k"
|
|
462
|
+
createBridge={createBridge}
|
|
463
|
+
/>
|
|
464
|
+
{blocked ? <Block /> : null}
|
|
465
|
+
</Suspense>
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
await act(async () => {
|
|
469
|
+
root.render(view(createBridgeA, false));
|
|
470
|
+
});
|
|
471
|
+
act(() => {
|
|
472
|
+
startTransition(() => root.render(view(createBridgeB, true)));
|
|
473
|
+
});
|
|
474
|
+
await vi.waitFor(() => expect(interruptedRender).toHaveBeenCalled());
|
|
475
|
+
await act(async () => {
|
|
476
|
+
resolveRender(rendered);
|
|
477
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(createBridgeA).toHaveBeenCalledTimes(1);
|
|
481
|
+
expect(createBridgeB).not.toHaveBeenCalled();
|
|
482
|
+
});
|
|
321
483
|
});
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { type CSSProperties, useEffect, useRef, useState } from "react";
|
|
4
3
|
import {
|
|
4
|
+
type CSSProperties,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
isShimLoadError,
|
|
5
12
|
type RenderedFrame,
|
|
6
13
|
SafeContentFrame,
|
|
7
14
|
type SandboxOption,
|
|
8
15
|
} from "safe-content-frame";
|
|
9
|
-
import {
|
|
16
|
+
import { invokeUserCallback } from "@assistant-ui/core/internal";
|
|
10
17
|
|
|
11
18
|
const DEFAULT_PRODUCT = "assistant-ui-sandbox";
|
|
12
19
|
const DEFAULT_MAX_HEIGHT = 800;
|
|
20
|
+
const LOAD_TIMEOUT_MS = 10_000;
|
|
13
21
|
|
|
14
22
|
export type SandboxHostConfig = {
|
|
15
23
|
sandbox?: SandboxOption[];
|
|
@@ -81,8 +89,15 @@ export function SandboxHost({
|
|
|
81
89
|
undefined,
|
|
82
90
|
);
|
|
83
91
|
|
|
84
|
-
const liveRef = useRef<LiveSnapshot>(
|
|
85
|
-
|
|
92
|
+
const liveRef = useRef<LiveSnapshot>({
|
|
93
|
+
content,
|
|
94
|
+
sandbox,
|
|
95
|
+
createBridge,
|
|
96
|
+
onError,
|
|
97
|
+
});
|
|
98
|
+
useLayoutEffect(() => {
|
|
99
|
+
liveRef.current = { content, sandbox, createBridge, onError };
|
|
100
|
+
}, [content, sandbox, createBridge, onError]);
|
|
86
101
|
|
|
87
102
|
useEffect(() => {
|
|
88
103
|
const container = containerRef.current;
|
|
@@ -95,6 +110,16 @@ export function SandboxHost({
|
|
|
95
110
|
|
|
96
111
|
const { content: liveContent, sandbox: sb } = liveRef.current;
|
|
97
112
|
|
|
113
|
+
const reportError = (err: unknown) => {
|
|
114
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
115
|
+
invokeUserCallback(
|
|
116
|
+
"assistant-ui",
|
|
117
|
+
"SandboxHost onError",
|
|
118
|
+
liveRef.current.onError?.bind(liveRef.current),
|
|
119
|
+
error,
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
98
123
|
const scf = new SafeContentFrame(sb?.product ?? DEFAULT_PRODUCT, {
|
|
99
124
|
...(sb?.sandbox !== undefined && { sandbox: sb.sandbox }),
|
|
100
125
|
...(sb?.useShadowDom !== undefined && { useShadowDom: sb.useShadowDom }),
|
|
@@ -146,16 +171,24 @@ export function SandboxHost({
|
|
|
146
171
|
bridge?.onMessage(event);
|
|
147
172
|
};
|
|
148
173
|
window.addEventListener("message", onMessage);
|
|
174
|
+
|
|
175
|
+
// renderHtml resolves at iframe load, which a shim that was never
|
|
176
|
+
// served also reaches, so a completed render is only known once the
|
|
177
|
+
// frame says so. render-timeout is excluded because the shim is
|
|
178
|
+
// running and the guest may still paint, and reporting it would leave
|
|
179
|
+
// a host that renders an error state showing one over a frame that
|
|
180
|
+
// recovers.
|
|
181
|
+
rendered.fullyLoadedPromiseWithTimeout(LOAD_TIMEOUT_MS).catch((err) => {
|
|
182
|
+
if (cancelled) return;
|
|
183
|
+
if (isShimLoadError(err) && err.code === "render-timeout") return;
|
|
184
|
+
reportError(err);
|
|
185
|
+
});
|
|
149
186
|
})
|
|
150
187
|
.catch((err) => {
|
|
151
188
|
if (cancelled) return;
|
|
152
189
|
frame?.dispose();
|
|
153
190
|
frame = null;
|
|
154
|
-
|
|
155
|
-
invokeCallbackSafely(
|
|
156
|
-
() => liveRef.current.onError?.(error),
|
|
157
|
-
"SandboxHost onError",
|
|
158
|
-
);
|
|
191
|
+
reportError(err);
|
|
159
192
|
});
|
|
160
193
|
|
|
161
194
|
return () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
3
|
import { act, render, waitFor } from "@testing-library/react";
|
|
4
|
-
import type
|
|
4
|
+
import { useState, type FC, type PropsWithChildren } from "react";
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import {
|
|
7
7
|
RuntimeAdapterProvider,
|
|
@@ -107,6 +107,36 @@ describe("RemoteThreadListAdapter.unstable_Provider", () => {
|
|
|
107
107
|
expect(capture.adapters?.history).toBe(dummyHistory);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
it("picks up a post-mount useAdapters bag", async () => {
|
|
111
|
+
const capture: { adapters: CapturedAdapters } = { adapters: null };
|
|
112
|
+
const firstHistory: ThreadHistoryAdapter = {
|
|
113
|
+
load: async () => ({ messages: [] }),
|
|
114
|
+
append: async () => {},
|
|
115
|
+
};
|
|
116
|
+
const secondHistory: ThreadHistoryAdapter = {
|
|
117
|
+
load: async () => ({ messages: [] }),
|
|
118
|
+
append: async () => {},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
let bump: (() => void) | undefined;
|
|
122
|
+
const adapter = makeAdapter({
|
|
123
|
+
unstable_useAdapters: function useTestAdapters() {
|
|
124
|
+
const [swapped, setSwapped] = useState(false);
|
|
125
|
+
bump = () => setSwapped(true);
|
|
126
|
+
return { history: swapped ? secondHistory : firstHistory };
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await renderAndWaitForBinder(adapter, capture);
|
|
131
|
+
expect(capture.adapters?.history).toBe(firstHistory);
|
|
132
|
+
|
|
133
|
+
await act(async () => {
|
|
134
|
+
bump!();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(capture.adapters?.history).toBe(secondHistory);
|
|
138
|
+
});
|
|
139
|
+
|
|
110
140
|
it("picks up a swapped Provider on re-render", async () => {
|
|
111
141
|
const capture: { adapters: CapturedAdapters } = { adapters: null };
|
|
112
142
|
const firstHistory: ThreadHistoryAdapter = {
|