@assistant-ui/react 0.15.24 → 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 +3 -1
- package/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
- package/dist/hooks/useToolCallElapsed.js +8 -8
- package/dist/hooks/useToolCallElapsed.js.map +1 -1
- package/dist/index.d.ts +39 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- 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/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/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 +15 -1
- package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +5 -4
- 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/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/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 +55 -15
- 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/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/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/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 +195 -17
- 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 +9 -3
- 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 +12 -8
- 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/smooth/useSmooth.js +6 -6
- package/dist/utils/smooth/useSmooth.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 +12 -11
- package/src/hooks/useToolCallElapsed.test.tsx +37 -0
- package/src/hooks/useToolCallElapsed.ts +10 -7
- package/src/index.ts +47 -5
- 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/model-context/makeAssistantVisible.test.tsx +315 -5
- package/src/model-context/makeAssistantVisible.tsx +43 -7
- 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 +20 -2
- package/src/primitives/composer/ComposerRoot.tsx +6 -5
- package/src/primitives/composer/composerInputSelector.ts +4 -0
- 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/useComposerCancelWithFocus.ts +57 -0
- package/src/primitives/composer.ts +70 -15
- package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
- package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
- package/src/primitives/message/MessageRoot.test.tsx +125 -2
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
- package/src/primitives/thread/ThreadLoadEarlier.tsx +35 -0
- package/src/primitives/thread/ThreadMessages.tsx +1 -0
- package/src/primitives/thread/ThreadViewport.tsx +3 -3
- package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +922 -68
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +330 -56
- package/src/primitives/thread.ts +2 -0
- package/src/sandbox-host/SandboxHost.test.tsx +34 -0
- package/src/sandbox-host/SandboxHost.tsx +11 -12
- package/src/tests/toolCallTiming.test.tsx +19 -2
- 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.test.tsx +40 -0
- package/src/unstable/useMessageStallDetection.ts +14 -8
- 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/smooth/useSmooth.test.tsx +16 -5
- package/src/utils/smooth/useSmooth.ts +7 -7
- package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
- package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
type ChangeEvent,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
import {
|
|
12
|
+
mergeModelContexts,
|
|
13
|
+
type ModelContextProvider,
|
|
14
|
+
} from "@assistant-ui/core";
|
|
15
|
+
import type { Tool } from "assistant-stream";
|
|
11
16
|
|
|
12
17
|
const { registerMock, auiMock } = vi.hoisted(() => {
|
|
13
18
|
const register = vi.fn((_provider: unknown) => () => {});
|
|
@@ -30,10 +35,15 @@ type EditTool = {
|
|
|
30
35
|
execute: (args: { editId: string; value: string }) => Promise<unknown>;
|
|
31
36
|
};
|
|
32
37
|
|
|
33
|
-
type
|
|
38
|
+
type ClickTool = {
|
|
39
|
+
execute: (args: { clickId: string }) => Promise<unknown>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type VisibleToolsProvider = {
|
|
34
43
|
getModelContext: () => {
|
|
35
44
|
tools: {
|
|
36
|
-
edit
|
|
45
|
+
edit?: EditTool;
|
|
46
|
+
click?: ClickTool;
|
|
37
47
|
};
|
|
38
48
|
};
|
|
39
49
|
};
|
|
@@ -79,6 +89,36 @@ const ControlledTextarea = forwardRef<
|
|
|
79
89
|
);
|
|
80
90
|
});
|
|
81
91
|
|
|
92
|
+
const Button = forwardRef<HTMLButtonElement, ComponentProps<"button">>(
|
|
93
|
+
(props, ref) => <button {...props} ref={ref} />,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const SplitRefButton = forwardRef<HTMLButtonElement, ComponentProps<"div">>(
|
|
97
|
+
({ children, ...props }, ref) => (
|
|
98
|
+
<div {...props}>
|
|
99
|
+
<button ref={ref}>{children}</button>
|
|
100
|
+
</div>
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const getRegisteredTools = () => {
|
|
105
|
+
const provider = registerMock.mock.calls[0]![0] as VisibleToolsProvider;
|
|
106
|
+
return provider.getModelContext().tools;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const executeTool = (tool: Tool | undefined, args: Record<string, unknown>) => {
|
|
110
|
+
if (!tool?.execute) throw new Error("Missing executable tool");
|
|
111
|
+
return Promise.resolve(
|
|
112
|
+
tool.execute(args, {
|
|
113
|
+
toolCallId: "test-call",
|
|
114
|
+
abortSignal: new AbortController().signal,
|
|
115
|
+
human: async () => {
|
|
116
|
+
throw new Error("Unexpected human input request");
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
82
122
|
const runEditTool = async (
|
|
83
123
|
element: HTMLInputElement | HTMLTextAreaElement,
|
|
84
124
|
value: string,
|
|
@@ -86,8 +126,9 @@ const runEditTool = async (
|
|
|
86
126
|
const editId = element.dataset.editId;
|
|
87
127
|
if (editId === undefined) throw new Error("Missing edit id");
|
|
88
128
|
|
|
89
|
-
const
|
|
90
|
-
|
|
129
|
+
const edit = getRegisteredTools().edit;
|
|
130
|
+
if (!edit) throw new Error("Missing edit tool");
|
|
131
|
+
const task = edit.execute({
|
|
91
132
|
editId,
|
|
92
133
|
value,
|
|
93
134
|
});
|
|
@@ -95,7 +136,7 @@ const runEditTool = async (
|
|
|
95
136
|
await task;
|
|
96
137
|
};
|
|
97
138
|
|
|
98
|
-
describe("makeAssistantVisible
|
|
139
|
+
describe("makeAssistantVisible", () => {
|
|
99
140
|
beforeEach(() => {
|
|
100
141
|
registerMock.mockClear();
|
|
101
142
|
vi.stubGlobal("CSS", { escape: (value: string) => value });
|
|
@@ -143,4 +184,273 @@ describe("makeAssistantVisible editable components", () => {
|
|
|
143
184
|
expect(textarea.getAttribute("data-react-value")).toBe("updated");
|
|
144
185
|
expect(onChange).toHaveBeenCalledOnce();
|
|
145
186
|
});
|
|
187
|
+
|
|
188
|
+
it("preserves the default action settle delay", async () => {
|
|
189
|
+
const EditableInput = makeAssistantVisible(ControlledInput, {
|
|
190
|
+
editable: true,
|
|
191
|
+
});
|
|
192
|
+
const input = render(<EditableInput />).getByRole("textbox");
|
|
193
|
+
const editId = (input as HTMLInputElement).dataset.editId!;
|
|
194
|
+
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
|
195
|
+
|
|
196
|
+
expect(
|
|
197
|
+
(input as HTMLInputElement).dataset.actionSettleDelay,
|
|
198
|
+
).toBeUndefined();
|
|
199
|
+
|
|
200
|
+
const task = getRegisteredTools().edit!.execute({
|
|
201
|
+
editId,
|
|
202
|
+
value: "updated",
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 2000);
|
|
206
|
+
await vi.runAllTimersAsync();
|
|
207
|
+
await task;
|
|
208
|
+
setTimeoutSpy.mockRestore();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("uses the default when the rendered settle delay is invalid", async () => {
|
|
212
|
+
const ClickableButton = makeAssistantVisible(Button, {
|
|
213
|
+
clickable: true,
|
|
214
|
+
settleDelayMs: 25,
|
|
215
|
+
});
|
|
216
|
+
const button = render(<ClickableButton>Run</ClickableButton>).getByRole(
|
|
217
|
+
"button",
|
|
218
|
+
);
|
|
219
|
+
const clickId = (button as HTMLButtonElement).dataset.clickId!;
|
|
220
|
+
(button as HTMLButtonElement).dataset.actionSettleDelay = "invalid";
|
|
221
|
+
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
|
222
|
+
|
|
223
|
+
const task = getRegisteredTools().click!.execute({ clickId });
|
|
224
|
+
|
|
225
|
+
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 2000);
|
|
226
|
+
await vi.runAllTimersAsync();
|
|
227
|
+
await task;
|
|
228
|
+
setTimeoutSpy.mockRestore();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it.each(["", " "])(
|
|
232
|
+
"uses the default for a blank rendered settle delay of %j",
|
|
233
|
+
async (renderedDelay) => {
|
|
234
|
+
const VisibleInput = makeAssistantVisible(ControlledInput, {
|
|
235
|
+
clickable: true,
|
|
236
|
+
editable: true,
|
|
237
|
+
settleDelayMs: 25,
|
|
238
|
+
});
|
|
239
|
+
const input = render(<VisibleInput />).getByRole("textbox");
|
|
240
|
+
const clickId = (input as HTMLInputElement).dataset.clickId!;
|
|
241
|
+
const editId = (input as HTMLInputElement).dataset.editId!;
|
|
242
|
+
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
|
243
|
+
input.dataset.actionSettleDelay = renderedDelay;
|
|
244
|
+
|
|
245
|
+
const clickTask = getRegisteredTools().click!.execute({ clickId });
|
|
246
|
+
expect(setTimeoutSpy).toHaveBeenLastCalledWith(
|
|
247
|
+
expect.any(Function),
|
|
248
|
+
2000,
|
|
249
|
+
);
|
|
250
|
+
await vi.runAllTimersAsync();
|
|
251
|
+
await clickTask;
|
|
252
|
+
|
|
253
|
+
setTimeoutSpy.mockClear();
|
|
254
|
+
const editTask = getRegisteredTools().edit!.execute({
|
|
255
|
+
editId,
|
|
256
|
+
value: "updated",
|
|
257
|
+
});
|
|
258
|
+
expect(setTimeoutSpy).toHaveBeenLastCalledWith(
|
|
259
|
+
expect.any(Function),
|
|
260
|
+
2000,
|
|
261
|
+
);
|
|
262
|
+
await vi.runAllTimersAsync();
|
|
263
|
+
await editTask;
|
|
264
|
+
setTimeoutSpy.mockRestore();
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
it("uses the configured settle delay for click actions", async () => {
|
|
269
|
+
const ClickableButton = makeAssistantVisible(Button, {
|
|
270
|
+
clickable: true,
|
|
271
|
+
settleDelayMs: 25,
|
|
272
|
+
});
|
|
273
|
+
const onClick = vi.fn();
|
|
274
|
+
const button = render(
|
|
275
|
+
<ClickableButton onClick={onClick}>Run</ClickableButton>,
|
|
276
|
+
).getByRole("button");
|
|
277
|
+
const clickId = (button as HTMLButtonElement).dataset.clickId!;
|
|
278
|
+
const settled = vi.fn();
|
|
279
|
+
|
|
280
|
+
const task = getRegisteredTools().click!.execute({ clickId });
|
|
281
|
+
void task.then(settled);
|
|
282
|
+
|
|
283
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
284
|
+
await vi.advanceTimersByTimeAsync(24);
|
|
285
|
+
expect(settled).not.toHaveBeenCalled();
|
|
286
|
+
|
|
287
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
288
|
+
await task;
|
|
289
|
+
expect(settled).toHaveBeenCalledOnce();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("uses the action id when the forwarded ref targets a child", async () => {
|
|
293
|
+
const ClickableButton = makeAssistantVisible(SplitRefButton, {
|
|
294
|
+
clickable: true,
|
|
295
|
+
settleDelayMs: 25,
|
|
296
|
+
});
|
|
297
|
+
const onClick = vi.fn();
|
|
298
|
+
const view = render(
|
|
299
|
+
<ClickableButton data-testid="action" onClick={onClick}>
|
|
300
|
+
Run
|
|
301
|
+
</ClickableButton>,
|
|
302
|
+
);
|
|
303
|
+
const action = view.getByTestId("action");
|
|
304
|
+
const clickId = action.dataset.clickId!;
|
|
305
|
+
const settled = vi.fn();
|
|
306
|
+
|
|
307
|
+
const task = getRegisteredTools().click!.execute({ clickId });
|
|
308
|
+
void task.then(settled);
|
|
309
|
+
|
|
310
|
+
expect(onClick).toHaveBeenCalledOnce();
|
|
311
|
+
await vi.advanceTimersByTimeAsync(24);
|
|
312
|
+
expect(settled).not.toHaveBeenCalled();
|
|
313
|
+
|
|
314
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
315
|
+
await task;
|
|
316
|
+
expect(settled).toHaveBeenCalledOnce();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("shares tools across wrappers while keeping per-element delays", async () => {
|
|
320
|
+
const SlowButton = makeAssistantVisible(Button, {
|
|
321
|
+
clickable: true,
|
|
322
|
+
editable: true,
|
|
323
|
+
settleDelayMs: 25,
|
|
324
|
+
});
|
|
325
|
+
const ImmediateButton = makeAssistantVisible(Button, {
|
|
326
|
+
clickable: true,
|
|
327
|
+
editable: true,
|
|
328
|
+
settleDelayMs: 0,
|
|
329
|
+
});
|
|
330
|
+
const view = render(
|
|
331
|
+
<>
|
|
332
|
+
<SlowButton>Slow</SlowButton>
|
|
333
|
+
<ImmediateButton>Immediate</ImmediateButton>
|
|
334
|
+
</>,
|
|
335
|
+
);
|
|
336
|
+
const providers = registerMock.mock.calls.map(
|
|
337
|
+
([provider]) => provider as ModelContextProvider,
|
|
338
|
+
);
|
|
339
|
+
const mergedContext = mergeModelContexts(new Set(providers));
|
|
340
|
+
|
|
341
|
+
expect(mergedContext.tools?.click).toBe(
|
|
342
|
+
providers[0]!.getModelContext().tools?.click,
|
|
343
|
+
);
|
|
344
|
+
expect(mergedContext.tools?.click).toBe(
|
|
345
|
+
providers[1]!.getModelContext().tools?.click,
|
|
346
|
+
);
|
|
347
|
+
expect(mergedContext.tools?.edit).toBe(
|
|
348
|
+
providers[0]!.getModelContext().tools?.edit,
|
|
349
|
+
);
|
|
350
|
+
expect(mergedContext.tools?.edit).toBe(
|
|
351
|
+
providers[1]!.getModelContext().tools?.edit,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const [slowButton, immediateButton] = view.getAllByRole("button");
|
|
355
|
+
const click = mergedContext.tools!.click!;
|
|
356
|
+
const slowTask = executeTool(click, {
|
|
357
|
+
clickId: (slowButton as HTMLButtonElement).dataset.clickId!,
|
|
358
|
+
});
|
|
359
|
+
const immediateTask = executeTool(click, {
|
|
360
|
+
clickId: (immediateButton as HTMLButtonElement).dataset.clickId!,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
await immediateTask;
|
|
364
|
+
let slowSettled = false;
|
|
365
|
+
void slowTask.then(() => {
|
|
366
|
+
slowSettled = true;
|
|
367
|
+
});
|
|
368
|
+
await vi.advanceTimersByTimeAsync(24);
|
|
369
|
+
expect(slowSettled).toBe(false);
|
|
370
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
371
|
+
await slowTask;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("shares the edit tool across wrappers while keeping per-element delays", async () => {
|
|
375
|
+
const SlowInput = makeAssistantVisible(ControlledInput, {
|
|
376
|
+
editable: true,
|
|
377
|
+
settleDelayMs: 25,
|
|
378
|
+
});
|
|
379
|
+
const ImmediateInput = makeAssistantVisible(ControlledInput, {
|
|
380
|
+
editable: true,
|
|
381
|
+
settleDelayMs: 0,
|
|
382
|
+
});
|
|
383
|
+
const view = render(
|
|
384
|
+
<>
|
|
385
|
+
<SlowInput aria-label="Slow" />
|
|
386
|
+
<ImmediateInput aria-label="Immediate" />
|
|
387
|
+
</>,
|
|
388
|
+
);
|
|
389
|
+
const providers = registerMock.mock.calls.map(
|
|
390
|
+
([provider]) => provider as ModelContextProvider,
|
|
391
|
+
);
|
|
392
|
+
const mergedContext = mergeModelContexts(new Set(providers));
|
|
393
|
+
|
|
394
|
+
expect(mergedContext.tools?.edit).toBe(
|
|
395
|
+
providers[0]!.getModelContext().tools?.edit,
|
|
396
|
+
);
|
|
397
|
+
expect(mergedContext.tools?.edit).toBe(
|
|
398
|
+
providers[1]!.getModelContext().tools?.edit,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const slowInput = view.getByRole("textbox", { name: "Slow" });
|
|
402
|
+
const immediateInput = view.getByRole("textbox", { name: "Immediate" });
|
|
403
|
+
const edit = mergedContext.tools!.edit!;
|
|
404
|
+
const slowTask = executeTool(edit, {
|
|
405
|
+
editId: (slowInput as HTMLInputElement).dataset.editId!,
|
|
406
|
+
value: "slow update",
|
|
407
|
+
});
|
|
408
|
+
const immediateTask = executeTool(edit, {
|
|
409
|
+
editId: (immediateInput as HTMLInputElement).dataset.editId!,
|
|
410
|
+
value: "immediate update",
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
await immediateTask;
|
|
414
|
+
expect(immediateInput).toHaveProperty("value", "immediate update");
|
|
415
|
+
let slowSettled = false;
|
|
416
|
+
void slowTask.then(() => {
|
|
417
|
+
slowSettled = true;
|
|
418
|
+
});
|
|
419
|
+
await vi.advanceTimersByTimeAsync(24);
|
|
420
|
+
expect(slowSettled).toBe(false);
|
|
421
|
+
|
|
422
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
423
|
+
await slowTask;
|
|
424
|
+
expect(slowInput).toHaveProperty("value", "slow update");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("allows the action settle delay to be disabled", async () => {
|
|
428
|
+
const EditableInput = makeAssistantVisible(ControlledInput, {
|
|
429
|
+
editable: true,
|
|
430
|
+
settleDelayMs: 0,
|
|
431
|
+
});
|
|
432
|
+
const input = render(<EditableInput />).getByRole("textbox");
|
|
433
|
+
const editId = (input as HTMLInputElement).dataset.editId!;
|
|
434
|
+
const timersBefore = vi.getTimerCount();
|
|
435
|
+
|
|
436
|
+
const task = getRegisteredTools().edit!.execute({
|
|
437
|
+
editId,
|
|
438
|
+
value: "updated",
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
expect(vi.getTimerCount()).toBe(timersBefore);
|
|
442
|
+
await task;
|
|
443
|
+
expect(input).toHaveProperty("value", "updated");
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it.each([-1, Number.NaN, Number.POSITIVE_INFINITY, 2_147_483_648])(
|
|
447
|
+
"rejects an invalid action settle delay of %s",
|
|
448
|
+
(settleDelayMs) => {
|
|
449
|
+
expect(() =>
|
|
450
|
+
makeAssistantVisible(Button, { clickable: true, settleDelayMs }),
|
|
451
|
+
).toThrow(
|
|
452
|
+
new RangeError("settleDelayMs must be between 0 and 2147483647"),
|
|
453
|
+
);
|
|
454
|
+
},
|
|
455
|
+
);
|
|
146
456
|
});
|
|
@@ -15,6 +15,27 @@ import { useAui } from "@assistant-ui/store";
|
|
|
15
15
|
import { useComposedRefs } from "radix-ui/internal";
|
|
16
16
|
import { tool } from "@assistant-ui/core";
|
|
17
17
|
|
|
18
|
+
const DEFAULT_ACTION_SETTLE_DELAY_MS = 2000;
|
|
19
|
+
const MAX_ACTION_SETTLE_DELAY_MS = 2_147_483_647;
|
|
20
|
+
|
|
21
|
+
const waitForSettle = async (settleDelayMs: number) => {
|
|
22
|
+
const delay =
|
|
23
|
+
Number.isFinite(settleDelayMs) &&
|
|
24
|
+
settleDelayMs >= 0 &&
|
|
25
|
+
settleDelayMs <= MAX_ACTION_SETTLE_DELAY_MS
|
|
26
|
+
? settleDelayMs
|
|
27
|
+
: DEFAULT_ACTION_SETTLE_DELAY_MS;
|
|
28
|
+
if (delay === 0) return;
|
|
29
|
+
await new Promise<void>((resolve) => setTimeout(resolve, delay));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const readActionSettleDelay = (element: HTMLElement) => {
|
|
33
|
+
const value = element.dataset.actionSettleDelay;
|
|
34
|
+
return value === undefined || value.trim() === ""
|
|
35
|
+
? DEFAULT_ACTION_SETTLE_DELAY_MS
|
|
36
|
+
: Number(value);
|
|
37
|
+
};
|
|
38
|
+
|
|
18
39
|
const click = tool({
|
|
19
40
|
parameters: {
|
|
20
41
|
type: "object",
|
|
@@ -30,9 +51,7 @@ const click = tool({
|
|
|
30
51
|
const el = document.querySelector(`[data-click-id='${escapedClickId}']`);
|
|
31
52
|
if (el instanceof HTMLElement) {
|
|
32
53
|
el.click();
|
|
33
|
-
|
|
34
|
-
// todo make adjustable
|
|
35
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
54
|
+
await waitForSettle(readActionSettleDelay(el));
|
|
36
55
|
return {};
|
|
37
56
|
} else {
|
|
38
57
|
return "Element not found";
|
|
@@ -77,9 +96,7 @@ const edit = tool({
|
|
|
77
96
|
setNativeValue(el, value);
|
|
78
97
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
79
98
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
80
|
-
|
|
81
|
-
// todo make adjustable
|
|
82
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
99
|
+
await waitForSettle(readActionSettleDelay(el));
|
|
83
100
|
return {};
|
|
84
101
|
} else {
|
|
85
102
|
return "Element not found";
|
|
@@ -91,8 +108,23 @@ const ReadableContext = createContext<boolean>(false);
|
|
|
91
108
|
|
|
92
109
|
export const makeAssistantVisible = <T extends ComponentType<any>>(
|
|
93
110
|
Component: T,
|
|
94
|
-
config?: {
|
|
111
|
+
config?: {
|
|
112
|
+
clickable?: boolean | undefined;
|
|
113
|
+
editable?: boolean | undefined;
|
|
114
|
+
settleDelayMs?: number | undefined;
|
|
115
|
+
},
|
|
95
116
|
) => {
|
|
117
|
+
const settleDelayMs = config?.settleDelayMs ?? DEFAULT_ACTION_SETTLE_DELAY_MS;
|
|
118
|
+
if (
|
|
119
|
+
!Number.isFinite(settleDelayMs) ||
|
|
120
|
+
settleDelayMs < 0 ||
|
|
121
|
+
settleDelayMs > MAX_ACTION_SETTLE_DELAY_MS
|
|
122
|
+
) {
|
|
123
|
+
throw new RangeError(
|
|
124
|
+
`settleDelayMs must be between 0 and ${MAX_ACTION_SETTLE_DELAY_MS}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
const ReadableComponent = forwardRef(
|
|
97
129
|
(props: PropsWithoutRef<T>, outerRef: ForwardedRef<any>) => {
|
|
98
130
|
const isNestedReadable = useContext(ReadableContext);
|
|
@@ -127,6 +159,10 @@ export const makeAssistantVisible = <T extends ComponentType<any>>(
|
|
|
127
159
|
{...(props as any)}
|
|
128
160
|
{...(config?.clickable ? { "data-click-id": clickId } : {})}
|
|
129
161
|
{...(config?.editable ? { "data-edit-id": clickId } : {})}
|
|
162
|
+
{...(config?.settleDelayMs !== undefined &&
|
|
163
|
+
(config.clickable || config.editable)
|
|
164
|
+
? { "data-action-settle-delay": settleDelayMs }
|
|
165
|
+
: {})}
|
|
130
166
|
ref={ref}
|
|
131
167
|
/>
|
|
132
168
|
</ReadableContext.Provider>
|
|
@@ -7,6 +7,7 @@ import { useAuiEvent } from "@assistant-ui/store";
|
|
|
7
7
|
|
|
8
8
|
export namespace AssistantModalPrimitiveRoot {
|
|
9
9
|
export type Props = PopoverPrimitive.PopoverProps & {
|
|
10
|
+
/** @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release. */
|
|
10
11
|
unstable_openOnRunStart?: boolean | undefined;
|
|
11
12
|
};
|
|
12
13
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { AttachmentPrimitiveRoot as Root } from "./attachment/AttachmentRoot";
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
/** @deprecated Experimental since 2024-10-10. Not scheduled for removal; the API may change in any release. */
|
|
4
|
+
AttachmentPrimitiveThumb as unstable_Thumb,
|
|
5
|
+
} from "./attachment/AttachmentThumb";
|
|
3
6
|
export { AttachmentPrimitiveName as Name } from "./attachment/AttachmentName";
|
|
4
7
|
export { AttachmentPrimitiveRemove as Remove } from "./attachment/AttachmentRemove";
|