@assistant-ui/react 0.15.14 → 0.15.16
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/assistant-transport.d.ts +32 -0
- package/dist/assistant-transport.d.ts.map +1 -0
- package/dist/assistant-transport.js +27 -0
- package/dist/assistant-transport.js.map +1 -0
- package/dist/context/providers/MessageProvider.js +1 -1
- package/dist/devtools/DevToolsHooks.d.ts +0 -2
- package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
- package/dist/devtools/DevToolsHooks.js +10 -12
- package/dist/devtools/DevToolsHooks.js.map +1 -1
- package/dist/hooks/useMessageQuote.js +1 -1
- package/dist/hooks/useToolCallElapsed.js +1 -1
- package/dist/index.d.ts +4 -7
- package/dist/index.js +6 -8
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +18 -2
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/bridge.d.ts.map +1 -1
- package/dist/mcp-apps/bridge.js +33 -24
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +5 -2
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +24 -37
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
- package/dist/primitives/attachment/AttachmentName.js +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerCount.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js +20 -15
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.d.ts +2 -2
- package/dist/primitives/composer/ComposerDictationTranscript.js +3 -3
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +2 -2
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +1 -1
- package/dist/primitives/composer/ComposerRoot.js +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +24 -20
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/composer/useComposerInputState.js +1 -1
- package/dist/primitives/message/MessageIf.js +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +1 -1
- package/dist/primitives/message/MessageRoot.js +1 -1
- package/dist/primitives/message.js +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
- package/dist/primitives/queueItem/QueueItemText.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
- package/dist/primitives/thread/ThreadIf.js +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts +3 -0
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js +44 -9
- package/dist/primitives/thread/ThreadRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +2 -13
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +23 -8
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +6 -1
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +2 -2
- package/dist/unstable/useComposerInput.js +1 -1
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +2 -2
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.d.ts +5 -0
- package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +16 -3
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +1 -1
- package/dist/utils/invokeCallbackSafely.d.ts +5 -0
- package/dist/utils/invokeCallbackSafely.d.ts.map +1 -0
- package/dist/utils/invokeCallbackSafely.js +15 -0
- package/dist/utils/invokeCallbackSafely.js.map +1 -0
- package/dist/utils/smooth/SmoothContext.d.ts +6 -6
- package/dist/utils/smooth/SmoothContext.js +1 -1
- package/dist/utils/smooth/useSmooth.d.ts +3 -0
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +16 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/dist/utils/useToolArgsFieldStatus.js +1 -1
- package/package.json +10 -10
- package/src/assistant-transport.ts +94 -0
- package/src/devtools/DevToolsHooks.test.ts +40 -0
- package/src/devtools/DevToolsHooks.ts +10 -15
- package/src/index.ts +8 -4
- package/src/mcp-apps/McpAppsRemoteHost.test.ts +29 -3
- package/src/mcp-apps/McpAppsRemoteHost.ts +42 -7
- package/src/mcp-apps/bridge.test.ts +153 -0
- package/src/mcp-apps/bridge.ts +42 -30
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.test.tsx +67 -0
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +3 -1
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
- package/src/primitives/branchPicker/BranchPickerRoot.tsx +5 -3
- package/src/primitives/composer/ComposerDictationTranscript.tsx +2 -2
- package/src/primitives/composer/ComposerInput.tsx +1 -1
- package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +135 -0
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +2 -0
- package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
- package/src/primitives/thread/ThreadRoot.tsx +33 -0
- package/src/primitives/thread/ThreadViewport.tsx +1 -8
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +152 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -19
- package/src/sandbox-host/SandboxHost.test.tsx +90 -0
- package/src/sandbox-host/SandboxHost.tsx +8 -2
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +24 -3
- package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +1 -2
- package/src/tests/augmentations.test.ts +49 -0
- package/src/tests/in-memory-thread-list.test.tsx +1 -1
- package/src/tests/threadMessageById.test.tsx +61 -1
- package/src/unstable/useComposerInput.ts +2 -2
- package/src/unstable/useComposerInputHistory.test.tsx +4 -4
- package/src/unstable/useComposerInputHistory.ts +1 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +157 -0
- package/src/unstable/useLiveCompletionAdapter.ts +41 -17
- package/src/utils/invokeCallbackSafely.ts +11 -0
- package/src/utils/smooth/useSmooth.test.tsx +110 -0
- package/src/utils/smooth/useSmooth.ts +20 -6
- package/dist/client/InMemoryThreadList.d.ts +0 -12
- package/dist/client/InMemoryThreadList.d.ts.map +0 -1
- package/dist/client/InMemoryThreadList.js +0 -374
- package/dist/client/InMemoryThreadList.js.map +0 -1
- package/dist/context/react/utils/ensureBinding.d.ts +0 -5
- package/dist/context/react/utils/ensureBinding.d.ts.map +0 -1
- package/dist/context/react/utils/ensureBinding.js +0 -24
- package/dist/context/react/utils/ensureBinding.js.map +0 -1
- package/dist/context/react/utils/useRuntimeState.d.ts +0 -13
- package/dist/context/react/utils/useRuntimeState.d.ts.map +0 -1
- package/dist/context/react/utils/useRuntimeState.js +0 -33
- package/dist/context/react/utils/useRuntimeState.js.map +0 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts +0 -119
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +0 -1
- package/dist/legacy-runtime/cloud/auiV0.js +0 -137
- package/dist/legacy-runtime/cloud/auiV0.js.map +0 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts +0 -16
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +0 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +0 -36
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +0 -17
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +0 -55
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +0 -11
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +0 -165
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +0 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +0 -81
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +0 -126
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +0 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +0 -14
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +0 -290
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +0 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +0 -25
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +0 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +0 -27
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +0 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +0 -2
- package/src/client/InMemoryThreadList.ts +0 -247
- package/src/context/react/utils/ensureBinding.ts +0 -46
- package/src/context/react/utils/useRuntimeState.ts +0 -46
- package/src/legacy-runtime/cloud/auiV0.ts +0 -331
- package/src/legacy-runtime/cloud/useCloudThreadListRuntime.ts +0 -31
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +0 -68
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +0 -455
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -172
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +0 -113
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +0 -79
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +0 -97
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +0 -307
- package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +0 -170
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +0 -141
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +0 -585
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +0 -476
- package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +0 -20
- package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +0 -9
- package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +0 -1
|
@@ -228,4 +228,94 @@ describe("SandboxHost", () => {
|
|
|
228
228
|
expect(onError).toHaveBeenCalledTimes(1);
|
|
229
229
|
expect(onError.mock.calls[0]![0].message).toBe("boom");
|
|
230
230
|
});
|
|
231
|
+
|
|
232
|
+
it("does not report render failures after unmount", async () => {
|
|
233
|
+
let rejectRender!: (error: Error) => void;
|
|
234
|
+
renderHtmlMock.mockReturnValue(
|
|
235
|
+
new Promise((_, reject) => {
|
|
236
|
+
rejectRender = reject;
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
const onError = vi.fn();
|
|
240
|
+
|
|
241
|
+
await act(async () => {
|
|
242
|
+
root.render(
|
|
243
|
+
<SandboxHost
|
|
244
|
+
content={{ html: "" }}
|
|
245
|
+
contentKey="k"
|
|
246
|
+
createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
|
|
247
|
+
onError={onError}
|
|
248
|
+
/>,
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await act(async () => {
|
|
253
|
+
root.unmount();
|
|
254
|
+
});
|
|
255
|
+
rejectRender(new Error("late failure"));
|
|
256
|
+
await flush();
|
|
257
|
+
|
|
258
|
+
expect(onError).not.toHaveBeenCalled();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("contains failures thrown by onError", async () => {
|
|
262
|
+
const renderError = new Error("render failed");
|
|
263
|
+
const callbackError = new Error("error callback failed");
|
|
264
|
+
renderHtmlMock.mockRejectedValue(renderError);
|
|
265
|
+
const consoleError = vi
|
|
266
|
+
.spyOn(console, "error")
|
|
267
|
+
.mockImplementation(() => {});
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await act(async () => {
|
|
271
|
+
root.render(
|
|
272
|
+
<SandboxHost
|
|
273
|
+
content={{ html: "" }}
|
|
274
|
+
contentKey="k"
|
|
275
|
+
createBridge={() => ({ onMessage: vi.fn(), dispose: vi.fn() })}
|
|
276
|
+
onError={() => {
|
|
277
|
+
throw callbackError;
|
|
278
|
+
}}
|
|
279
|
+
/>,
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
await flush();
|
|
283
|
+
|
|
284
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
285
|
+
"[assistant-ui] SandboxHost onError callback threw an error",
|
|
286
|
+
callbackError,
|
|
287
|
+
);
|
|
288
|
+
} finally {
|
|
289
|
+
consoleError.mockRestore();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("disposes the rendered frame when bridge creation fails", async () => {
|
|
294
|
+
const rendered = fakeRendered();
|
|
295
|
+
renderHtmlMock.mockResolvedValue(rendered);
|
|
296
|
+
const error = new Error("bridge failed");
|
|
297
|
+
const onError = vi.fn();
|
|
298
|
+
|
|
299
|
+
await act(async () => {
|
|
300
|
+
root.render(
|
|
301
|
+
<SandboxHost
|
|
302
|
+
content={{ html: "" }}
|
|
303
|
+
contentKey="k"
|
|
304
|
+
createBridge={() => {
|
|
305
|
+
throw error;
|
|
306
|
+
}}
|
|
307
|
+
onError={onError}
|
|
308
|
+
/>,
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
await flush();
|
|
312
|
+
|
|
313
|
+
expect(onError).toHaveBeenCalledWith(error);
|
|
314
|
+
expect(rendered.dispose).toHaveBeenCalledTimes(1);
|
|
315
|
+
|
|
316
|
+
await act(async () => {
|
|
317
|
+
root.unmount();
|
|
318
|
+
});
|
|
319
|
+
expect(rendered.dispose).toHaveBeenCalledTimes(1);
|
|
320
|
+
});
|
|
231
321
|
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SafeContentFrame,
|
|
7
7
|
type SandboxOption,
|
|
8
8
|
} from "safe-content-frame";
|
|
9
|
+
import { invokeCallbackSafely } from "../utils/invokeCallbackSafely";
|
|
9
10
|
|
|
10
11
|
const DEFAULT_PRODUCT = "assistant-ui-sandbox";
|
|
11
12
|
const DEFAULT_MAX_HEIGHT = 800;
|
|
@@ -147,8 +148,13 @@ export function SandboxHost({
|
|
|
147
148
|
window.addEventListener("message", onMessage);
|
|
148
149
|
})
|
|
149
150
|
.catch((err) => {
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
if (cancelled) return;
|
|
152
|
+
frame?.dispose();
|
|
153
|
+
frame = null;
|
|
154
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
155
|
+
invokeCallbackSafely(
|
|
156
|
+
() => liveRef.current.onError?.(error),
|
|
157
|
+
"SandboxHost onError",
|
|
152
158
|
);
|
|
153
159
|
});
|
|
154
160
|
|
|
@@ -26,9 +26,14 @@ const dummyHistory: ThreadHistoryAdapter = {
|
|
|
26
26
|
append: async () => {},
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const makeRuntimeHook = (capture: {
|
|
29
|
+
const makeRuntimeHook = (capture: {
|
|
30
|
+
adapters: CapturedAdapters;
|
|
31
|
+
calls?: CapturedAdapters[];
|
|
32
|
+
}) =>
|
|
30
33
|
function useTestRuntimeHook() {
|
|
31
|
-
|
|
34
|
+
const adapters = useRuntimeAdapters();
|
|
35
|
+
capture.calls?.push(adapters);
|
|
36
|
+
capture.adapters = adapters;
|
|
32
37
|
return useLocalRuntime(noOpAdapter);
|
|
33
38
|
};
|
|
34
39
|
|
|
@@ -68,12 +73,16 @@ const wrapInRuntimeAdapterProvider = (
|
|
|
68
73
|
|
|
69
74
|
describe("RemoteThreadListAdapter.unstable_Provider", () => {
|
|
70
75
|
it("makes Provider context visible to the runtime hook while preserving outer modelContext", async () => {
|
|
71
|
-
const capture: { adapters: CapturedAdapters } = {
|
|
76
|
+
const capture: { adapters: CapturedAdapters; calls: CapturedAdapters[] } = {
|
|
77
|
+
adapters: null,
|
|
78
|
+
calls: [],
|
|
79
|
+
};
|
|
72
80
|
const adapter = makeAdapter({
|
|
73
81
|
unstable_Provider: wrapInRuntimeAdapterProvider(dummyHistory),
|
|
74
82
|
});
|
|
75
83
|
await renderAndWaitForBinder(adapter, capture);
|
|
76
84
|
|
|
85
|
+
expect(capture.calls[0]?.history).toBe(dummyHistory);
|
|
77
86
|
expect(capture.adapters?.history).toBe(dummyHistory);
|
|
78
87
|
expect(capture.adapters?.modelContext).toBeDefined();
|
|
79
88
|
});
|
|
@@ -86,6 +95,18 @@ describe("RemoteThreadListAdapter.unstable_Provider", () => {
|
|
|
86
95
|
expect(capture.adapters?.history).toBeUndefined();
|
|
87
96
|
});
|
|
88
97
|
|
|
98
|
+
it("makes useAdapters context visible when no Provider is supplied", async () => {
|
|
99
|
+
const capture: { adapters: CapturedAdapters } = { adapters: null };
|
|
100
|
+
const adapter = makeAdapter({
|
|
101
|
+
unstable_useAdapters: function useTestAdapters() {
|
|
102
|
+
return { history: dummyHistory };
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
await renderAndWaitForBinder(adapter, capture);
|
|
106
|
+
|
|
107
|
+
expect(capture.adapters?.history).toBe(dummyHistory);
|
|
108
|
+
});
|
|
109
|
+
|
|
89
110
|
it("picks up a swapped Provider on re-render", async () => {
|
|
90
111
|
const capture: { adapters: CapturedAdapters } = { adapters: null };
|
|
91
112
|
const firstHistory: ThreadHistoryAdapter = {
|
|
@@ -86,8 +86,7 @@ describe("threads.reloadMainThread", () => {
|
|
|
86
86
|
await runtime.threads.reloadMainThread();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
await waitFor(() => expect(mounts.count).toBe(beforeReload + 1));
|
|
89
|
+
await waitFor(() => expect(mounts.count).toBeGreaterThan(beforeReload));
|
|
91
90
|
});
|
|
92
91
|
|
|
93
92
|
it("keeps a thread runtime readable across the remount", async () => {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, expectTypeOf, it } from "vitest";
|
|
2
2
|
import type { UserCommands, UserExternalState } from "../augmentations";
|
|
3
|
+
import type {
|
|
4
|
+
AssistantTransportCommand,
|
|
5
|
+
useAssistantTransportRuntime,
|
|
6
|
+
useAssistantTransportSendCommand,
|
|
7
|
+
useAssistantTransportState,
|
|
8
|
+
} from "../index";
|
|
3
9
|
|
|
4
10
|
declare module "../augmentations" {
|
|
5
11
|
namespace Assistant {
|
|
@@ -36,4 +42,47 @@ describe("Assistant augmentations", () => {
|
|
|
36
42
|
expectTypeOf<{ type: "core-command" }>().toExtend<UserCommands>();
|
|
37
43
|
expectTypeOf<{ fromCore: boolean }>().toExtend<UserExternalState>();
|
|
38
44
|
});
|
|
45
|
+
|
|
46
|
+
it("react-side contributions reach the public transport types", () => {
|
|
47
|
+
expectTypeOf<{
|
|
48
|
+
type: "react-command";
|
|
49
|
+
data: number;
|
|
50
|
+
}>().toExtend<AssistantTransportCommand>();
|
|
51
|
+
expectTypeOf<{ fromReact: boolean }>().toExtend<
|
|
52
|
+
ReturnType<typeof useAssistantTransportState>
|
|
53
|
+
>();
|
|
54
|
+
expectTypeOf<{
|
|
55
|
+
type: "react-command";
|
|
56
|
+
data: number;
|
|
57
|
+
}>().toExtend<
|
|
58
|
+
Parameters<ReturnType<typeof useAssistantTransportSendCommand>>[0]
|
|
59
|
+
>();
|
|
60
|
+
type RuntimeOptions = Parameters<typeof useAssistantTransportRuntime>[0];
|
|
61
|
+
expectTypeOf<{
|
|
62
|
+
type: "react-command";
|
|
63
|
+
data: number;
|
|
64
|
+
}>().toExtend<
|
|
65
|
+
Parameters<RuntimeOptions["converter"]>[1]["pendingCommands"][number]
|
|
66
|
+
>();
|
|
67
|
+
expectTypeOf<{
|
|
68
|
+
type: "react-command";
|
|
69
|
+
data: number;
|
|
70
|
+
}>().toExtend<
|
|
71
|
+
Parameters<NonNullable<RuntimeOptions["onError"]>>[1]["commands"][number]
|
|
72
|
+
>();
|
|
73
|
+
expectTypeOf<{
|
|
74
|
+
type: "react-command";
|
|
75
|
+
data: number;
|
|
76
|
+
}>().toExtend<
|
|
77
|
+
Parameters<NonNullable<RuntimeOptions["onCancel"]>>[0]["commands"][number]
|
|
78
|
+
>();
|
|
79
|
+
expectTypeOf<{
|
|
80
|
+
type: "react-command";
|
|
81
|
+
data: number;
|
|
82
|
+
}>().toExtend<
|
|
83
|
+
Parameters<
|
|
84
|
+
NonNullable<RuntimeOptions["prepareSendCommandsRequest"]>
|
|
85
|
+
>[0]["commands"][number]
|
|
86
|
+
>();
|
|
87
|
+
});
|
|
39
88
|
});
|
|
@@ -4,7 +4,7 @@ import { render, waitFor } from "@testing-library/react";
|
|
|
4
4
|
import type { FC } from "react";
|
|
5
5
|
import { describe, it, expect, vi } from "vitest";
|
|
6
6
|
import { useAui, AuiProvider } from "@assistant-ui/store";
|
|
7
|
-
import { InMemoryThreadList } from "
|
|
7
|
+
import { InMemoryThreadList } from "@assistant-ui/core/store";
|
|
8
8
|
import { ExternalThread } from "../index";
|
|
9
9
|
|
|
10
10
|
const renderThreads = () => {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { act, render, screen } from "@testing-library/react";
|
|
4
4
|
import { useState, type FC, type PropsWithChildren } from "react";
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { useAui } from "@assistant-ui/store";
|
|
6
7
|
import {
|
|
7
8
|
AssistantRuntimeProvider,
|
|
8
9
|
useExternalStoreRuntime,
|
|
@@ -13,7 +14,11 @@ import * as ThreadPrimitive from "../primitives/thread";
|
|
|
13
14
|
import * as MessagePrimitive from "../primitives/message";
|
|
14
15
|
import * as MessagePartPrimitive from "../primitives/messagePart";
|
|
15
16
|
|
|
16
|
-
type Msg = {
|
|
17
|
+
type Msg = {
|
|
18
|
+
id: string;
|
|
19
|
+
role: "user" | "assistant" | "system";
|
|
20
|
+
text: string;
|
|
21
|
+
};
|
|
17
22
|
|
|
18
23
|
const convertMessage = (m: Msg): ThreadMessageLike => ({
|
|
19
24
|
id: m.id,
|
|
@@ -39,6 +44,7 @@ const Provider: FC<PropsWithChildren<{ initial: Msg[] }>> = ({
|
|
|
39
44
|
messages,
|
|
40
45
|
convertMessage,
|
|
41
46
|
onNew: async () => {},
|
|
47
|
+
onEdit: async () => {},
|
|
42
48
|
});
|
|
43
49
|
return (
|
|
44
50
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
@@ -47,6 +53,19 @@ const Provider: FC<PropsWithChildren<{ initial: Msg[] }>> = ({
|
|
|
47
53
|
);
|
|
48
54
|
};
|
|
49
55
|
|
|
56
|
+
let beginEdit: (() => void) | undefined;
|
|
57
|
+
|
|
58
|
+
const CaptureBeginEdit: FC = () => {
|
|
59
|
+
const aui = useAui();
|
|
60
|
+
beginEdit = () => aui.thread.message({ index: 0 }).composer().beginEdit();
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const SYSTEM_COMPONENTS = {
|
|
65
|
+
UserMessage: () => <span>user</span>,
|
|
66
|
+
AssistantMessage: () => <span>assistant</span>,
|
|
67
|
+
};
|
|
68
|
+
|
|
50
69
|
let lastIds: readonly string[] | undefined;
|
|
51
70
|
const idIdentities: (readonly string[])[] = [];
|
|
52
71
|
|
|
@@ -215,3 +234,44 @@ describe("ThreadPrimitive.Unstable_MessageById", () => {
|
|
|
215
234
|
expect(screen.getByTestId("by-id").textContent).toBe("first");
|
|
216
235
|
});
|
|
217
236
|
});
|
|
237
|
+
|
|
238
|
+
describe("system message editing fallback", () => {
|
|
239
|
+
it.each([
|
|
240
|
+
[
|
|
241
|
+
"Messages",
|
|
242
|
+
() => <ThreadPrimitive.Messages components={SYSTEM_COMPONENTS} />,
|
|
243
|
+
],
|
|
244
|
+
[
|
|
245
|
+
"MessageByIndex",
|
|
246
|
+
() => (
|
|
247
|
+
<ThreadPrimitive.MessageByIndex
|
|
248
|
+
index={0}
|
|
249
|
+
components={SYSTEM_COMPONENTS}
|
|
250
|
+
/>
|
|
251
|
+
),
|
|
252
|
+
],
|
|
253
|
+
[
|
|
254
|
+
"Unstable_MessageById",
|
|
255
|
+
() => (
|
|
256
|
+
<ThreadPrimitive.Unstable_MessageById
|
|
257
|
+
messageId="s1"
|
|
258
|
+
components={SYSTEM_COMPONENTS}
|
|
259
|
+
/>
|
|
260
|
+
),
|
|
261
|
+
],
|
|
262
|
+
])(
|
|
263
|
+
"renders nothing for a system message in edit mode through %s",
|
|
264
|
+
async (_, renderMessage) => {
|
|
265
|
+
const view = render(
|
|
266
|
+
<Provider initial={[{ id: "s1", role: "system", text: "sys" }]}>
|
|
267
|
+
<CaptureBeginEdit />
|
|
268
|
+
{renderMessage()}
|
|
269
|
+
</Provider>,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
await act(async () => beginEdit!());
|
|
273
|
+
|
|
274
|
+
expect(view.container.textContent).toBe("");
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
});
|
|
@@ -26,8 +26,8 @@ export type Unstable_ComposerInput = {
|
|
|
26
26
|
value: string;
|
|
27
27
|
/**
|
|
28
28
|
* Writes `text` into the composer, mirroring `ComposerPrimitive.Input`:
|
|
29
|
-
* a no-op unless the composer is editing
|
|
30
|
-
*
|
|
29
|
+
* a no-op unless the composer is editing; the controlled value stays in
|
|
30
|
+
* sync within the same tick.
|
|
31
31
|
*/
|
|
32
32
|
setText(text: string): void;
|
|
33
33
|
/**
|
|
@@ -6,7 +6,7 @@ const fixture = {
|
|
|
6
6
|
messages: [] as { role: string; content: { type: string; text: string }[] }[],
|
|
7
7
|
composerType: "thread",
|
|
8
8
|
activeAria: null as object | null,
|
|
9
|
-
|
|
9
|
+
selectionChangedHandlers: [] as (() => void)[],
|
|
10
10
|
};
|
|
11
11
|
const setText = vi.fn();
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ vi.mock("@assistant-ui/store", () => ({
|
|
|
18
18
|
},
|
|
19
19
|
thread: { getState: () => ({ messages: fixture.messages }) },
|
|
20
20
|
on: (_event: string, cb: () => void) => {
|
|
21
|
-
fixture.
|
|
21
|
+
fixture.selectionChangedHandlers.push(cb);
|
|
22
22
|
return () => {};
|
|
23
23
|
},
|
|
24
24
|
}),
|
|
@@ -71,7 +71,7 @@ beforeEach(() => {
|
|
|
71
71
|
fixture.messages = [user("first"), assistant("reply"), user("second")];
|
|
72
72
|
fixture.composerType = "thread";
|
|
73
73
|
fixture.activeAria = null;
|
|
74
|
-
fixture.
|
|
74
|
+
fixture.selectionChangedHandlers = [];
|
|
75
75
|
setText.mockClear();
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -192,7 +192,7 @@ describe("unstable_useComposerInputHistory", () => {
|
|
|
192
192
|
it("resets browsing when the thread switches", () => {
|
|
193
193
|
const { textarea } = setup("");
|
|
194
194
|
arrow(textarea, "ArrowUp");
|
|
195
|
-
fixture.
|
|
195
|
+
fixture.selectionChangedHandlers.forEach((cb) => cb());
|
|
196
196
|
textarea.value = "second";
|
|
197
197
|
const notPrevented = arrow(textarea, "ArrowDown");
|
|
198
198
|
expect(notPrevented).toBe(true);
|
|
@@ -72,7 +72,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
|
|
|
72
72
|
useEffect(() => {
|
|
73
73
|
if (aui.composer.getState().type !== "thread") return undefined;
|
|
74
74
|
|
|
75
|
-
return aui.on("
|
|
75
|
+
return aui.on("threads.selectionChanged", () => {
|
|
76
76
|
browseRef.current = null;
|
|
77
77
|
});
|
|
78
78
|
}, [aui]);
|
|
@@ -129,6 +129,137 @@ describe("unstable_useLiveCompletionAdapter", () => {
|
|
|
129
129
|
expect(result.current.adapter.search!("ab")).toEqual([item("ab")]);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
it("refreshes cached results when the fetcher cache key changes", async () => {
|
|
133
|
+
const fetcherA = vi.fn(async () => [item("workspace-a")]);
|
|
134
|
+
const fetcherB = vi.fn(async () => [item("workspace-b")]);
|
|
135
|
+
const { result, rerender } = renderHook(
|
|
136
|
+
({ fetcher, cacheKey }) =>
|
|
137
|
+
unstable_useLiveCompletionAdapter({
|
|
138
|
+
fetcher,
|
|
139
|
+
cacheKey,
|
|
140
|
+
debounceMs: 0,
|
|
141
|
+
}),
|
|
142
|
+
{ initialProps: { fetcher: fetcherA, cacheKey: "workspace-a" } },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
await act(async () => {
|
|
146
|
+
result.current.adapter.search!("alice");
|
|
147
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
148
|
+
});
|
|
149
|
+
expect(result.current.adapter.search!("alice")).toEqual([
|
|
150
|
+
item("workspace-a"),
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
await act(async () => {
|
|
154
|
+
rerender({ fetcher: fetcherB, cacheKey: "workspace-b" });
|
|
155
|
+
});
|
|
156
|
+
expect(result.current.adapter.search!("alice")).toEqual([]);
|
|
157
|
+
|
|
158
|
+
await act(async () => {
|
|
159
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
160
|
+
});
|
|
161
|
+
expect(fetcherB).toHaveBeenCalledWith("alice");
|
|
162
|
+
expect(result.current.adapter.search!("alice")).toEqual([
|
|
163
|
+
item("workspace-b"),
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("drops pending results after the fetcher cache key changes", async () => {
|
|
168
|
+
let resolveA!: (items: readonly Unstable_TriggerItem[]) => void;
|
|
169
|
+
const fetcherA = vi.fn(
|
|
170
|
+
() =>
|
|
171
|
+
new Promise<readonly Unstable_TriggerItem[]>((resolve) => {
|
|
172
|
+
resolveA = resolve;
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
const fetcherB = vi.fn(async () => [item("workspace-b")]);
|
|
176
|
+
const { result, rerender } = renderHook(
|
|
177
|
+
({ fetcher, cacheKey }) =>
|
|
178
|
+
unstable_useLiveCompletionAdapter({
|
|
179
|
+
fetcher,
|
|
180
|
+
cacheKey,
|
|
181
|
+
debounceMs: 0,
|
|
182
|
+
}),
|
|
183
|
+
{ initialProps: { fetcher: fetcherA, cacheKey: "workspace-a" } },
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await act(async () => {
|
|
187
|
+
result.current.adapter.search!("alice");
|
|
188
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
189
|
+
});
|
|
190
|
+
await act(async () => {
|
|
191
|
+
rerender({ fetcher: fetcherB, cacheKey: "workspace-b" });
|
|
192
|
+
});
|
|
193
|
+
await act(async () => {
|
|
194
|
+
result.current.adapter.search!("alice");
|
|
195
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
196
|
+
});
|
|
197
|
+
await act(async () => {
|
|
198
|
+
resolveA([item("workspace-a")]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result.current.adapter.search!("alice")).toEqual([
|
|
202
|
+
item("workspace-b"),
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("keeps cached results when only the fetcher identity changes", async () => {
|
|
207
|
+
const first = vi.fn(async () => [item("alice")]);
|
|
208
|
+
const second = vi.fn(async () => [item("bob")]);
|
|
209
|
+
const { result, rerender } = renderHook(
|
|
210
|
+
({ fetcher }) =>
|
|
211
|
+
unstable_useLiveCompletionAdapter({
|
|
212
|
+
fetcher,
|
|
213
|
+
cacheKey: "workspace-a",
|
|
214
|
+
debounceMs: 0,
|
|
215
|
+
}),
|
|
216
|
+
{ initialProps: { fetcher: first } },
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await act(async () => {
|
|
220
|
+
result.current.adapter.search!("alice");
|
|
221
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
222
|
+
});
|
|
223
|
+
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
224
|
+
|
|
225
|
+
await act(async () => {
|
|
226
|
+
rerender({ fetcher: second });
|
|
227
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
228
|
+
});
|
|
229
|
+
expect(second).not.toHaveBeenCalled();
|
|
230
|
+
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("drops a pending result when only the cache key changes", async () => {
|
|
234
|
+
let resolveFirst!: (items: readonly Unstable_TriggerItem[]) => void;
|
|
235
|
+
const first = new Promise<readonly Unstable_TriggerItem[]>((resolve) => {
|
|
236
|
+
resolveFirst = resolve;
|
|
237
|
+
});
|
|
238
|
+
const fetcher = vi.fn(() => first);
|
|
239
|
+
const { result, rerender } = renderHook(
|
|
240
|
+
({ cacheKey }) =>
|
|
241
|
+
unstable_useLiveCompletionAdapter({ fetcher, cacheKey, debounceMs: 0 }),
|
|
242
|
+
{ initialProps: { cacheKey: "workspace-a" } },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await act(async () => {
|
|
246
|
+
result.current.adapter.search!("alice");
|
|
247
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
248
|
+
});
|
|
249
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
250
|
+
|
|
251
|
+
await act(async () => {
|
|
252
|
+
rerender({ cacheKey: "workspace-b" });
|
|
253
|
+
});
|
|
254
|
+
await act(async () => {
|
|
255
|
+
resolveFirst([item("workspace-a")]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(result.current.isLoading).toBe(false);
|
|
259
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
260
|
+
expect(result.current.adapter.search!("alice")).toEqual([]);
|
|
261
|
+
});
|
|
262
|
+
|
|
132
263
|
it("allows a failed query to be retried", async () => {
|
|
133
264
|
const fetcher = vi
|
|
134
265
|
.fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
|
|
@@ -156,6 +287,32 @@ describe("unstable_useLiveCompletionAdapter", () => {
|
|
|
156
287
|
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
157
288
|
});
|
|
158
289
|
|
|
290
|
+
it("allows a synchronously failed query to be retried", async () => {
|
|
291
|
+
const fetcher = vi
|
|
292
|
+
.fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
|
|
293
|
+
.mockImplementationOnce(() => {
|
|
294
|
+
throw new Error("invalid request configuration");
|
|
295
|
+
})
|
|
296
|
+
.mockResolvedValueOnce([item("alice")]);
|
|
297
|
+
const { result } = renderHook(() =>
|
|
298
|
+
unstable_useLiveCompletionAdapter({ fetcher, debounceMs: 0 }),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
await act(async () => {
|
|
302
|
+
result.current.adapter.search!("alice");
|
|
303
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
304
|
+
});
|
|
305
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
306
|
+
expect(result.current.isLoading).toBe(false);
|
|
307
|
+
|
|
308
|
+
await act(async () => {
|
|
309
|
+
result.current.adapter.search!("alice");
|
|
310
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
311
|
+
});
|
|
312
|
+
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
313
|
+
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
314
|
+
});
|
|
315
|
+
|
|
159
316
|
it("does not automatically retry when search runs during every render", async () => {
|
|
160
317
|
const fetcher = vi
|
|
161
318
|
.fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
4
11
|
import type {
|
|
5
12
|
Unstable_TriggerAdapter,
|
|
6
13
|
Unstable_TriggerItem,
|
|
@@ -13,6 +20,11 @@ export type Unstable_UseLiveCompletionAdapterOptions = {
|
|
|
13
20
|
* next render.
|
|
14
21
|
*/
|
|
15
22
|
readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Identifies the fetcher's data source. Change this when switching accounts,
|
|
25
|
+
* workspaces, or another boundary that should invalidate cached results.
|
|
26
|
+
*/
|
|
27
|
+
readonly cacheKey?: string | number | undefined;
|
|
16
28
|
/** Debounce applied before a fetch fires, in milliseconds. @default 60 */
|
|
17
29
|
readonly debounceMs?: number | undefined;
|
|
18
30
|
/** When `false`, no fetch is scheduled and the adapter stays empty. @default true */
|
|
@@ -52,7 +64,7 @@ const NO_QUERY = "\u0000";
|
|
|
52
64
|
export function unstable_useLiveCompletionAdapter(
|
|
53
65
|
options: Unstable_UseLiveCompletionAdapterOptions,
|
|
54
66
|
): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {
|
|
55
|
-
const { fetcher, debounceMs = 60, enabled = true } = options;
|
|
67
|
+
const { fetcher, cacheKey, debounceMs = 60, enabled = true } = options;
|
|
56
68
|
|
|
57
69
|
const [state, setState] = useState<{
|
|
58
70
|
query: string;
|
|
@@ -99,21 +111,23 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
99
111
|
setIsLoading(true);
|
|
100
112
|
timerRef.current = setTimeout(() => {
|
|
101
113
|
timerRef.current = null;
|
|
102
|
-
|
|
103
|
-
(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
Promise.resolve()
|
|
115
|
+
.then(() => fetcherRef.current(query))
|
|
116
|
+
.then(
|
|
117
|
+
(items) => {
|
|
118
|
+
if (token !== tokenRef.current) return;
|
|
119
|
+
pendingRetryQueryRef.current = null;
|
|
120
|
+
setState({ query, items, failed: false });
|
|
121
|
+
setIsLoading(false);
|
|
122
|
+
},
|
|
123
|
+
() => {
|
|
124
|
+
if (token !== tokenRef.current) return;
|
|
125
|
+
pendingQueryRef.current = null;
|
|
126
|
+
pendingRetryQueryRef.current = null;
|
|
127
|
+
setState({ query, items: [], failed: true });
|
|
128
|
+
setIsLoading(false);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
117
131
|
}, debounceMs);
|
|
118
132
|
},
|
|
119
133
|
[enabled, debounceMs, cancelTimer, rearmPendingRetry],
|
|
@@ -127,6 +141,16 @@ export function unstable_useLiveCompletionAdapter(
|
|
|
127
141
|
setIsLoading(false);
|
|
128
142
|
}, [cancelTimer, rearmPendingRetry]);
|
|
129
143
|
|
|
144
|
+
const cacheKeyRef = useRef(cacheKey);
|
|
145
|
+
useLayoutEffect(() => {
|
|
146
|
+
if (cacheKeyRef.current === cacheKey) return;
|
|
147
|
+
cacheKeyRef.current = cacheKey;
|
|
148
|
+
invalidatePending();
|
|
149
|
+
retryableQueryRef.current = null;
|
|
150
|
+
pendingRetryQueryRef.current = null;
|
|
151
|
+
setState({ query: NO_QUERY, items: [], failed: false });
|
|
152
|
+
}, [cacheKey, invalidatePending]);
|
|
153
|
+
|
|
130
154
|
useEffect(() => {
|
|
131
155
|
if (enabled) return;
|
|
132
156
|
invalidatePending();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const invokeCallbackSafely = (invoke: () => unknown, name: string) => {
|
|
2
|
+
const reportFailure = (error: unknown) => {
|
|
3
|
+
console.error(`[assistant-ui] ${name} callback threw an error`, error);
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
void Promise.resolve(invoke()).catch(reportFailure);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
reportFailure(error);
|
|
10
|
+
}
|
|
11
|
+
};
|