@assistant-ui/react 0.15.13 → 0.15.15
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/hooks/useMessageQuote.js +1 -1
- package/dist/hooks/useToolCallElapsed.js +1 -1
- package/dist/index.d.ts +5 -8
- package/dist/index.js +7 -9
- package/dist/legacy-runtime/AssistantRuntimeProvider.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.js +39 -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.js +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/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.js +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/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/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.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js +41 -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.js +2 -0
- 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 +1 -1
- 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/smooth/SmoothContext.js +1 -1
- package/dist/utils/useToolArgsFieldStatus.js +1 -1
- package/package.json +9 -9
- package/src/assistant-transport.ts +94 -0
- package/src/index.ts +9 -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 +132 -0
- package/src/mcp-apps/bridge.ts +54 -30
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
- package/src/primitives/composer/ComposerInput.tsx +1 -1
- package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
- package/src/primitives/thread/ThreadRoot.tsx +30 -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 +29 -0
- package/src/sandbox-host/SandboxHost.tsx +2 -0
- 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 +87 -3
- 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 +100 -0
- package/src/unstable/useLiveCompletionAdapter.ts +41 -17
- package/dist/client/InMemoryThreadList.d.ts +0 -12
- package/dist/client/InMemoryThreadList.d.ts.map +0 -1
- package/dist/client/InMemoryThreadList.js +0 -365
- package/dist/client/InMemoryThreadList.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 -141
- 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 -241
- 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 -430
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -146
- 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
|
@@ -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
|
});
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { render, waitFor } from "@testing-library/react";
|
|
4
4
|
import type { FC } from "react";
|
|
5
|
-
import { describe, it, expect } from "vitest";
|
|
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 = () => {
|
|
@@ -29,7 +29,7 @@ const renderThreads = () => {
|
|
|
29
29
|
return { aui: () => captured.aui! };
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
describe("InMemoryThreadList
|
|
32
|
+
describe("InMemoryThreadList", () => {
|
|
33
33
|
it("falls back to a live thread when the switch target is deleted in the same tick", async () => {
|
|
34
34
|
const { aui } = renderThreads();
|
|
35
35
|
|
|
@@ -52,4 +52,88 @@ describe("InMemoryThreadList delete", () => {
|
|
|
52
52
|
expect(state.threadIds).not.toContain(newId);
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
|
+
|
|
56
|
+
it("creates unique IDs for threads created in the same millisecond", async () => {
|
|
57
|
+
const now = vi.spyOn(Date, "now").mockReturnValue(1_000);
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const { aui } = renderThreads();
|
|
61
|
+
aui().threads.switchToNewThread();
|
|
62
|
+
aui().threads.switchToNewThread();
|
|
63
|
+
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
const generatedIds = aui()
|
|
66
|
+
.threads.getState()
|
|
67
|
+
.threadIds.filter((id) => id !== "main");
|
|
68
|
+
expect(generatedIds).toHaveLength(2);
|
|
69
|
+
expect(new Set(generatedIds).size).toBe(2);
|
|
70
|
+
});
|
|
71
|
+
} finally {
|
|
72
|
+
now.mockRestore();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("InMemoryThreadList reloadMainThread", () => {
|
|
78
|
+
it("routes reloadMainThread to the main thread's refetch callback", async () => {
|
|
79
|
+
const refetched: string[] = [];
|
|
80
|
+
const captured: { aui?: ReturnType<typeof useAui> } = {};
|
|
81
|
+
const Capture: FC = () => {
|
|
82
|
+
captured.aui = useAui();
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
const App: FC = () => {
|
|
86
|
+
const aui = useAui({
|
|
87
|
+
threads: InMemoryThreadList({
|
|
88
|
+
thread: (threadId) =>
|
|
89
|
+
ExternalThread({
|
|
90
|
+
messages: [],
|
|
91
|
+
onRefetchThread: async () => {
|
|
92
|
+
refetched.push(threadId);
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
return (
|
|
98
|
+
<AuiProvider value={aui}>
|
|
99
|
+
<Capture />
|
|
100
|
+
</AuiProvider>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
render(<App />);
|
|
104
|
+
const aui = () => captured.aui!;
|
|
105
|
+
|
|
106
|
+
await waitFor(() => expect(captured.aui).toBeDefined());
|
|
107
|
+
await aui().threads.reloadMainThread();
|
|
108
|
+
expect(refetched).toEqual(["main"]);
|
|
109
|
+
|
|
110
|
+
aui().threads.switchToNewThread();
|
|
111
|
+
await waitFor(() =>
|
|
112
|
+
expect(aui().threads.getState().mainThreadId).not.toBe("main"),
|
|
113
|
+
);
|
|
114
|
+
const newId = aui().threads.getState().mainThreadId;
|
|
115
|
+
|
|
116
|
+
await aui().threads.reloadMainThread();
|
|
117
|
+
expect(refetched).toEqual(["main", newId]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("resolves reloadMainThread when the thread has no refetch callback", async () => {
|
|
121
|
+
const { aui } = renderThreads();
|
|
122
|
+
|
|
123
|
+
await waitFor(() => expect(aui()).toBeDefined());
|
|
124
|
+
await expect(aui().threads.reloadMainThread()).resolves.toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("InMemoryThreadList suggestions", () => {
|
|
129
|
+
it("derives the suggestions scope from the main thread", async () => {
|
|
130
|
+
const { aui } = renderThreads();
|
|
131
|
+
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
expect(aui().suggestions.getState()).toEqual({ suggestions: [] });
|
|
134
|
+
});
|
|
135
|
+
expect(aui().suggestions.getState()).toBe(
|
|
136
|
+
aui().thread.suggestions().getState(),
|
|
137
|
+
);
|
|
138
|
+
});
|
|
55
139
|
});
|
|
@@ -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,80 @@ 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
|
+
|
|
132
206
|
it("allows a failed query to be retried", async () => {
|
|
133
207
|
const fetcher = vi
|
|
134
208
|
.fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
|
|
@@ -156,6 +230,32 @@ describe("unstable_useLiveCompletionAdapter", () => {
|
|
|
156
230
|
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
157
231
|
});
|
|
158
232
|
|
|
233
|
+
it("allows a synchronously failed query to be retried", async () => {
|
|
234
|
+
const fetcher = vi
|
|
235
|
+
.fn<(query: string) => Promise<readonly Unstable_TriggerItem[]>>()
|
|
236
|
+
.mockImplementationOnce(() => {
|
|
237
|
+
throw new Error("invalid request configuration");
|
|
238
|
+
})
|
|
239
|
+
.mockResolvedValueOnce([item("alice")]);
|
|
240
|
+
const { result } = renderHook(() =>
|
|
241
|
+
unstable_useLiveCompletionAdapter({ fetcher, debounceMs: 0 }),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await act(async () => {
|
|
245
|
+
result.current.adapter.search!("alice");
|
|
246
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
247
|
+
});
|
|
248
|
+
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
249
|
+
expect(result.current.isLoading).toBe(false);
|
|
250
|
+
|
|
251
|
+
await act(async () => {
|
|
252
|
+
result.current.adapter.search!("alice");
|
|
253
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
254
|
+
});
|
|
255
|
+
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
256
|
+
expect(result.current.adapter.search!("alice")).toEqual([item("alice")]);
|
|
257
|
+
});
|
|
258
|
+
|
|
159
259
|
it("does not automatically retry when search runs during every render", async () => {
|
|
160
260
|
const fetcher = vi
|
|
161
261
|
.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();
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { ClientOutput } from "@assistant-ui/store";
|
|
2
|
-
import { ResourceElement } from "@assistant-ui/tap";
|
|
3
|
-
//#region src/client/InMemoryThreadList.d.ts
|
|
4
|
-
type InMemoryThreadListProps = {
|
|
5
|
-
thread: (threadId: string) => ResourceElement<ClientOutput<"thread">>;
|
|
6
|
-
onSwitchToThread?: (threadId: string) => void;
|
|
7
|
-
onSwitchToNewThread?: () => void;
|
|
8
|
-
};
|
|
9
|
-
declare const InMemoryThreadList: import("@assistant-ui/tap").Resource<ClientOutput<"threads">, [props: InMemoryThreadListProps]>;
|
|
10
|
-
//#endregion
|
|
11
|
-
export { InMemoryThreadList, InMemoryThreadListProps };
|
|
12
|
-
//# sourceMappingURL=InMemoryThreadList.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryThreadList.d.ts","names":[],"sources":["../../src/client/InMemoryThreadList.ts"],"mappings":";;;KAeY;EACV,SAAS,qBAAqB,gBAAgB;EAC9C,oBAAoB;EACpB;;cA+LW,gDAAkB,SAAA,0BAAA,OAAA"}
|