@assistant-ui/react 0.14.28 → 0.15.0
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/client/ExternalThread.d.ts +11 -1
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +762 -516
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.js +3 -3
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/context/index.d.ts +1 -8
- package/dist/context/index.js +1 -8
- package/dist/index.d.ts +1 -8
- package/dist/index.js +1 -8
- package/dist/legacy-runtime/cloud/auiV0.d.ts +12 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +1 -1
- package/dist/legacy-runtime/cloud/auiV0.js +2 -1
- package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +7 -6
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +30 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +11 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +1 -1
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +26 -25
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js +3 -3
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts +7 -6
- package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +10 -11
- package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +4 -3
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +4 -3
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +11 -5
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +2 -4
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +18 -16
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +1 -1
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerSend.js +1 -0
- package/dist/primitives/composer/ComposerSend.js.map +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +4 -3
- package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -3
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +67 -89
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js +3 -3
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +4 -3
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +4 -3
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +17 -15
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.js +1 -0
- package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
- package/dist/unstable/useComposerInput.js +2 -2
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +6 -6
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useMentionAdapter.js +1 -1
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -0
- package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
- package/dist/utils/hooks/useSizeHandle.js +1 -0
- package/dist/utils/hooks/useSizeHandle.js.map +1 -1
- package/dist/utils/smooth/SmoothContext.js +3 -3
- package/dist/utils/smooth/SmoothContext.js.map +1 -1
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +3 -3
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +9 -9
- package/src/client/ExternalThread.ts +345 -56
- package/src/client/InMemoryThreadList.ts +3 -3
- package/src/context/index.ts +0 -44
- package/src/legacy-runtime/cloud/auiV0.ts +14 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +4 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +37 -10
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +79 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +97 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +113 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +10 -2
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +185 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +21 -5
- package/src/mcp-apps/McpAppRenderer.tsx +1 -1
- package/src/model-context/makeAssistantVisible.test.tsx +1 -1
- package/src/model-context/makeAssistantVisible.tsx +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
- package/src/primitives/actionBar/useActionBarFloatStatus.ts +7 -5
- package/src/primitives/attachment/AttachmentRemove.ts +1 -1
- package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +1 -1
- package/src/primitives/composer/ComposerAddAttachment.test.tsx +108 -0
- package/src/primitives/composer/ComposerAddAttachment.ts +11 -5
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +5 -4
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +4 -3
- package/src/primitives/composer/ComposerInput.test.tsx +104 -7
- package/src/primitives/composer/ComposerInput.tsx +22 -20
- package/src/primitives/composer/ComposerQuote.tsx +1 -1
- package/src/primitives/composer/ComposerStopDictation.ts +1 -1
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +7 -9
- package/src/primitives/message/MessagePartsGrouped.tsx +6 -8
- package/src/primitives/message/MessageRoot.tsx +1 -1
- package/src/primitives/queueItem/QueueItemRemove.ts +1 -1
- package/src/primitives/queueItem/QueueItemSteer.ts +1 -1
- package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
- package/src/primitives/suggestion/SuggestionTrigger.ts +9 -9
- package/src/tests/DataRenderers.test.tsx +212 -0
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +1 -11
- package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +3 -3
- package/src/tests/external-thread-attachments.test.tsx +300 -11
- package/src/tests/external-thread-branches.test.tsx +10 -10
- package/src/tests/external-thread-parity.test.tsx +230 -0
- package/src/tests/generative-ui.test.tsx +1 -46
- package/src/tests/local-runtime-queue.test.tsx +29 -34
- package/src/tests/messagePartSwitchRace.test.tsx +46 -15
- package/src/tests/messagePartTypeMismatch.test.tsx +2 -2
- package/src/tests/threadMessageById.test.tsx +0 -7
- package/src/unstable/useComposerInput.test.tsx +2 -2
- package/src/unstable/useComposerInput.ts +2 -2
- package/src/unstable/useComposerInputHistory.test.tsx +3 -3
- package/src/unstable/useComposerInputHistory.ts +6 -6
- package/src/unstable/useMentionAdapter.ts +1 -1
- package/src/utils/smooth/SmoothContext.tsx +1 -1
- package/src/utils/smooth/useSmooth.test.tsx +1 -1
- package/src/utils/smooth/useSmooth.ts +8 -5
- package/dist/context/react/utils/createStateHookForRuntime.d.ts +0 -34
- package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +0 -1
- package/dist/context/react/utils/createStateHookForRuntime.js +0 -21
- package/dist/context/react/utils/createStateHookForRuntime.js.map +0 -1
- package/dist/legacy-runtime/hooks/AssistantContext.d.ts +0 -74
- package/dist/legacy-runtime/hooks/AssistantContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/AssistantContext.js +0 -29
- package/dist/legacy-runtime/hooks/AssistantContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +0 -1007
- package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/AttachmentContext.js +0 -47
- package/dist/legacy-runtime/hooks/AttachmentContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/ComposerContext.d.ts +0 -141
- package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/ComposerContext.js +0 -65
- package/dist/legacy-runtime/hooks/ComposerContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/MessageContext.d.ts +0 -155
- package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/MessageContext.js +0 -68
- package/dist/legacy-runtime/hooks/MessageContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +0 -44
- package/dist/legacy-runtime/hooks/MessagePartContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/MessagePartContext.js +0 -26
- package/dist/legacy-runtime/hooks/MessagePartContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadContext.d.ts +0 -139
- package/dist/legacy-runtime/hooks/ThreadContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadContext.js +0 -84
- package/dist/legacy-runtime/hooks/ThreadContext.js.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +0 -44
- package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts.map +0 -1
- package/dist/legacy-runtime/hooks/ThreadListItemContext.js +0 -26
- package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +0 -1
- package/src/context/react/utils/createStateHookForRuntime.ts +0 -82
- package/src/legacy-runtime/hooks/AssistantContext.ts +0 -67
- package/src/legacy-runtime/hooks/AttachmentContext.ts +0 -99
- package/src/legacy-runtime/hooks/ComposerContext.ts +0 -131
- package/src/legacy-runtime/hooks/MessageContext.ts +0 -127
- package/src/legacy-runtime/hooks/MessagePartContext.ts +0 -32
- package/src/legacy-runtime/hooks/ThreadContext.ts +0 -120
- package/src/legacy-runtime/hooks/ThreadListItemContext.ts +0 -36
- package/src/tests/BaseComposerRuntimeCore.test.ts +0 -621
- package/src/tests/BaseSubscribable.test.ts +0 -54
- package/src/tests/DataRenderers.test.ts +0 -355
- package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +0 -685
- package/src/tests/LocalThreadRuntimeCore.test.ts +0 -324
- package/src/tests/auiV0Encode.test.ts +0 -188
- package/src/tests/external-message-converter.test.ts +0 -346
- package/src/tests/shouldContinue.test.ts +0 -122
|
@@ -66,37 +66,6 @@ describe("MessagePrimitive.GenerativeUI (same-realm renderer)", () => {
|
|
|
66
66
|
expect(out).toContain("hello world");
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
it("throws GenerativeUIRenderError for unknown components", () => {
|
|
70
|
-
const spec: GenerativeUISpec = {
|
|
71
|
-
root: { component: "NotAllowed", props: {} },
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
expect(() =>
|
|
75
|
-
renderToStaticMarkup(
|
|
76
|
-
<GenerativeUIRender spec={spec} components={{ Card }} />,
|
|
77
|
-
),
|
|
78
|
-
).toThrow(GenerativeUIRenderError);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("uses Fallback when provided for unknown components", () => {
|
|
82
|
-
const spec: GenerativeUISpec = {
|
|
83
|
-
root: { component: "NotAllowed", props: { foo: 1 } },
|
|
84
|
-
};
|
|
85
|
-
const Fallback = ({ component }: any) => (
|
|
86
|
-
<span data-fallback={component}>missing</span>
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const out = renderToStaticMarkup(
|
|
90
|
-
<GenerativeUIRender
|
|
91
|
-
spec={spec}
|
|
92
|
-
components={{ Card }}
|
|
93
|
-
Fallback={Fallback}
|
|
94
|
-
/>,
|
|
95
|
-
);
|
|
96
|
-
expect(out).toContain('data-fallback="NotAllowed"');
|
|
97
|
-
expect(out).toContain("missing");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
69
|
it("handles empty / partial specs gracefully (stream-friendly)", () => {
|
|
101
70
|
const out1 = renderToStaticMarkup(
|
|
102
71
|
<GenerativeUIRender spec={{ root: [] }} components={{ Card }} />,
|
|
@@ -133,21 +102,7 @@ describe("MessagePrimitive.GenerativeUI (same-realm renderer)", () => {
|
|
|
133
102
|
}
|
|
134
103
|
});
|
|
135
104
|
|
|
136
|
-
it("
|
|
137
|
-
const spec: GenerativeUISpec = {
|
|
138
|
-
root: [
|
|
139
|
-
{ component: "Card", props: { title: "x" }, key: "stable-1" },
|
|
140
|
-
{ component: "Card", props: { title: "y" }, key: "stable-2" },
|
|
141
|
-
],
|
|
142
|
-
};
|
|
143
|
-
expect(() =>
|
|
144
|
-
renderToStaticMarkup(
|
|
145
|
-
<GenerativeUIRender spec={spec} components={{ Card }} />,
|
|
146
|
-
),
|
|
147
|
-
).not.toThrow();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("is a typed error subclass with the offending component name", () => {
|
|
105
|
+
it("throws a typed error naming the offending unknown component", () => {
|
|
151
106
|
try {
|
|
152
107
|
renderToStaticMarkup(
|
|
153
108
|
<GenerativeUIRender
|
|
@@ -27,8 +27,7 @@ const createCountingAdapter = () => {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const userTexts = (aui: ReturnType<typeof useAui>) =>
|
|
30
|
-
aui
|
|
31
|
-
.thread()
|
|
30
|
+
aui.thread
|
|
32
31
|
.getState()
|
|
33
32
|
.messages.filter((m) => m.role === "user")
|
|
34
33
|
.map((m) =>
|
|
@@ -57,8 +56,8 @@ const renderWithRuntime = (adapter: ChatModelAdapter, enableQueue: boolean) => {
|
|
|
57
56
|
|
|
58
57
|
const send = async (aui: ReturnType<typeof useAui>, text: string) => {
|
|
59
58
|
await act(async () => {
|
|
60
|
-
aui.thread
|
|
61
|
-
aui.thread
|
|
59
|
+
aui.thread.composer().setText(text);
|
|
60
|
+
aui.thread.composer().send();
|
|
62
61
|
await flush();
|
|
63
62
|
});
|
|
64
63
|
};
|
|
@@ -69,13 +68,12 @@ describe("local runtime message queue", () => {
|
|
|
69
68
|
const aui = renderWithRuntime(adapter, true);
|
|
70
69
|
|
|
71
70
|
await send(aui, "first");
|
|
72
|
-
expect(aui.thread
|
|
73
|
-
expect(aui.thread
|
|
71
|
+
expect(aui.thread.getState().isRunning).toBe(true);
|
|
72
|
+
expect(aui.thread.getState().capabilities.queue).toBe(true);
|
|
74
73
|
|
|
75
74
|
await send(aui, "second");
|
|
76
75
|
expect(
|
|
77
|
-
aui
|
|
78
|
-
.thread()
|
|
76
|
+
aui.thread
|
|
79
77
|
.composer()
|
|
80
78
|
.getState()
|
|
81
79
|
.queue.map((q) => q.prompt),
|
|
@@ -87,7 +85,7 @@ describe("local runtime message queue", () => {
|
|
|
87
85
|
await flush();
|
|
88
86
|
await flush();
|
|
89
87
|
});
|
|
90
|
-
expect(aui.thread
|
|
88
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
91
89
|
expect(userTexts(aui)).toContain("second");
|
|
92
90
|
});
|
|
93
91
|
|
|
@@ -100,7 +98,7 @@ describe("local runtime message queue", () => {
|
|
|
100
98
|
|
|
101
99
|
await send(aui, "a");
|
|
102
100
|
await send(aui, "b");
|
|
103
|
-
expect(aui.thread
|
|
101
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(2);
|
|
104
102
|
|
|
105
103
|
await act(async () => {
|
|
106
104
|
releases[0]!();
|
|
@@ -109,8 +107,7 @@ describe("local runtime message queue", () => {
|
|
|
109
107
|
});
|
|
110
108
|
expect(getRunCount()).toBe(2);
|
|
111
109
|
expect(
|
|
112
|
-
aui
|
|
113
|
-
.thread()
|
|
110
|
+
aui.thread
|
|
114
111
|
.composer()
|
|
115
112
|
.getState()
|
|
116
113
|
.queue.map((q) => q.prompt),
|
|
@@ -124,13 +121,13 @@ describe("local runtime message queue", () => {
|
|
|
124
121
|
await send(aui, "first");
|
|
125
122
|
await send(aui, "a");
|
|
126
123
|
await send(aui, "b");
|
|
127
|
-
expect(aui.thread
|
|
124
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(2);
|
|
128
125
|
|
|
129
126
|
await act(async () => {
|
|
130
|
-
aui.thread
|
|
127
|
+
aui.thread.composer().queueItem({ index: 0 }).remove();
|
|
131
128
|
await flush();
|
|
132
129
|
});
|
|
133
|
-
const queue = aui.thread
|
|
130
|
+
const queue = aui.thread.composer().getState().queue;
|
|
134
131
|
expect(queue).toHaveLength(1);
|
|
135
132
|
expect(queue[0]!.prompt).toBe("b");
|
|
136
133
|
});
|
|
@@ -142,15 +139,15 @@ describe("local runtime message queue", () => {
|
|
|
142
139
|
await send(aui, "first");
|
|
143
140
|
await send(aui, "a");
|
|
144
141
|
await send(aui, "b");
|
|
145
|
-
expect(aui.thread
|
|
142
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(2);
|
|
146
143
|
|
|
147
144
|
await act(async () => {
|
|
148
|
-
aui.thread
|
|
145
|
+
aui.thread.cancelRun();
|
|
149
146
|
await flush();
|
|
150
147
|
await flush();
|
|
151
148
|
});
|
|
152
149
|
|
|
153
|
-
expect(aui.thread
|
|
150
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
154
151
|
// cancelling must not start the next queued message
|
|
155
152
|
expect(getRunCount()).toBe(1);
|
|
156
153
|
});
|
|
@@ -168,11 +165,11 @@ describe("local runtime message queue", () => {
|
|
|
168
165
|
// start a second run and queue a message behind it
|
|
169
166
|
await send(aui, "second");
|
|
170
167
|
await send(aui, "queued");
|
|
171
|
-
expect(aui.thread
|
|
168
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(1);
|
|
172
169
|
|
|
173
170
|
// edit the first message while the run is in progress
|
|
174
171
|
await act(async () => {
|
|
175
|
-
const message = aui.thread
|
|
172
|
+
const message = aui.thread.message({ index: 0 });
|
|
176
173
|
message.composer().beginEdit();
|
|
177
174
|
message.composer().setText("edited");
|
|
178
175
|
message.composer().send();
|
|
@@ -180,7 +177,7 @@ describe("local runtime message queue", () => {
|
|
|
180
177
|
});
|
|
181
178
|
|
|
182
179
|
// the edit is applied (branches the thread) and the stale queue is cleared
|
|
183
|
-
expect(aui.thread
|
|
180
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
184
181
|
});
|
|
185
182
|
|
|
186
183
|
it("buffers a send during a regenerate instead of interrupting it", async () => {
|
|
@@ -196,22 +193,21 @@ describe("local runtime message queue", () => {
|
|
|
196
193
|
|
|
197
194
|
// regenerate the assistant message: a run started outside the queue
|
|
198
195
|
await act(async () => {
|
|
199
|
-
aui.thread
|
|
196
|
+
aui.thread.message({ index: 1 }).reload();
|
|
200
197
|
await flush();
|
|
201
198
|
});
|
|
202
199
|
expect(getRunCount()).toBe(2);
|
|
203
|
-
expect(aui.thread
|
|
200
|
+
expect(aui.thread.getState().isRunning).toBe(true);
|
|
204
201
|
|
|
205
202
|
// sending now must buffer, not interrupt the regenerate
|
|
206
203
|
await act(async () => {
|
|
207
|
-
aui.thread
|
|
208
|
-
aui.thread
|
|
204
|
+
aui.thread.composer().setText("Y");
|
|
205
|
+
aui.thread.composer().send();
|
|
209
206
|
await flush();
|
|
210
207
|
});
|
|
211
208
|
expect(getRunCount()).toBe(2);
|
|
212
209
|
expect(
|
|
213
|
-
aui
|
|
214
|
-
.thread()
|
|
210
|
+
aui.thread
|
|
215
211
|
.composer()
|
|
216
212
|
.getState()
|
|
217
213
|
.queue.map((q) => q.prompt),
|
|
@@ -239,7 +235,7 @@ describe("local runtime message queue", () => {
|
|
|
239
235
|
await send(aui, "first");
|
|
240
236
|
await send(aui, "a");
|
|
241
237
|
await send(aui, "b");
|
|
242
|
-
expect(aui.thread
|
|
238
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(2);
|
|
243
239
|
|
|
244
240
|
// run 1 settles -> "a" drains (run 2 throws) -> "b" drains (run 3)
|
|
245
241
|
await act(async () => {
|
|
@@ -248,18 +244,17 @@ describe("local runtime message queue", () => {
|
|
|
248
244
|
await flush();
|
|
249
245
|
});
|
|
250
246
|
expect(runCount).toBe(3);
|
|
251
|
-
expect(aui.thread
|
|
247
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
252
248
|
|
|
253
249
|
// "b" is running; a new send must buffer behind it, not interrupt it
|
|
254
250
|
await act(async () => {
|
|
255
|
-
aui.thread
|
|
256
|
-
aui.thread
|
|
251
|
+
aui.thread.composer().setText("c");
|
|
252
|
+
aui.thread.composer().send();
|
|
257
253
|
await flush();
|
|
258
254
|
});
|
|
259
255
|
expect(runCount).toBe(3);
|
|
260
256
|
expect(
|
|
261
|
-
aui
|
|
262
|
-
.thread()
|
|
257
|
+
aui.thread
|
|
263
258
|
.composer()
|
|
264
259
|
.getState()
|
|
265
260
|
.queue.map((q) => q.prompt),
|
|
@@ -269,7 +264,7 @@ describe("local runtime message queue", () => {
|
|
|
269
264
|
it("does not expose the queue capability when the flag is off", async () => {
|
|
270
265
|
const { adapter } = createCountingAdapter();
|
|
271
266
|
const aui = renderWithRuntime(adapter, false);
|
|
272
|
-
expect(aui.thread
|
|
267
|
+
expect(aui.thread.getState().capabilities.queue).toBe(false);
|
|
273
268
|
});
|
|
274
269
|
|
|
275
270
|
it("tears the queue down when the flag is toggled off at runtime", async () => {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { act, cleanup, render, waitFor } from "@testing-library/react";
|
|
4
4
|
import { flushSync } from "react-dom";
|
|
5
|
-
import { useEffect, useState, type FC } from "react";
|
|
6
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { Component, useEffect, useState, type FC, type ReactNode } from "react";
|
|
6
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
7
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
8
8
|
import { AssistantRuntimeProvider, PartByIndexProvider } from "../context";
|
|
9
9
|
import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
|
|
@@ -83,14 +83,35 @@ const ChildrenMessage: FC = () => (
|
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
const InvalidPartReader: FC = () => {
|
|
86
|
-
useAuiState((s) => s.part
|
|
87
|
-
return
|
|
86
|
+
const part = useAuiState((s) => s.part);
|
|
87
|
+
return (
|
|
88
|
+
<p data-testid="invalid-part">
|
|
89
|
+
{part.type === "text" ? part.text : part.type}
|
|
90
|
+
</p>
|
|
91
|
+
);
|
|
88
92
|
};
|
|
89
93
|
|
|
94
|
+
class PartErrorBoundary extends Component<
|
|
95
|
+
{ children: ReactNode },
|
|
96
|
+
{ error: Error | null }
|
|
97
|
+
> {
|
|
98
|
+
override state: { error: Error | null } = { error: null };
|
|
99
|
+
static getDerivedStateFromError(error: Error) {
|
|
100
|
+
return { error };
|
|
101
|
+
}
|
|
102
|
+
override render() {
|
|
103
|
+
if (this.state.error)
|
|
104
|
+
return <p data-testid="part-error">{this.state.error.message}</p>;
|
|
105
|
+
return this.props.children;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
const InvalidPartMessage: FC = () => (
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
110
|
+
<PartErrorBoundary>
|
|
111
|
+
<PartByIndexProvider index={2}>
|
|
112
|
+
<InvalidPartReader />
|
|
113
|
+
</PartByIndexProvider>
|
|
114
|
+
</PartErrorBoundary>
|
|
94
115
|
);
|
|
95
116
|
|
|
96
117
|
const App: FC<{ MessageComponent?: FC }> = ({ MessageComponent = Message }) => {
|
|
@@ -111,8 +132,8 @@ afterEach(() => {
|
|
|
111
132
|
harness.runtime = null;
|
|
112
133
|
});
|
|
113
134
|
|
|
114
|
-
describe("part hooks under a thread-switch race
|
|
115
|
-
it("
|
|
135
|
+
describe("part hooks under a thread-switch race", () => {
|
|
136
|
+
it("swaps every part when the incoming message has different part types", async () => {
|
|
116
137
|
let view!: ReturnType<typeof render>;
|
|
117
138
|
await act(async () => {
|
|
118
139
|
view = render(<App />);
|
|
@@ -148,11 +169,21 @@ describe("part hooks under a thread-switch race (#5118)", () => {
|
|
|
148
169
|
},
|
|
149
170
|
);
|
|
150
171
|
|
|
151
|
-
it("
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
172
|
+
it("throws to the error boundary for an index that was never valid", async () => {
|
|
173
|
+
const consoleError = vi
|
|
174
|
+
.spyOn(console, "error")
|
|
175
|
+
.mockImplementation(() => {});
|
|
176
|
+
try {
|
|
177
|
+
let view!: ReturnType<typeof render>;
|
|
178
|
+
await act(async () => {
|
|
179
|
+
view = render(<App MessageComponent={InvalidPartMessage} />);
|
|
180
|
+
});
|
|
181
|
+
expect(view.queryByTestId("invalid-part")).toBeNull();
|
|
182
|
+
expect(view.getByTestId("part-error").textContent).toContain(
|
|
183
|
+
"useClientLookup: index 2 out of bounds (length: 2)",
|
|
184
|
+
);
|
|
185
|
+
} finally {
|
|
186
|
+
consoleError.mockRestore();
|
|
187
|
+
}
|
|
157
188
|
});
|
|
158
189
|
});
|
|
@@ -69,7 +69,7 @@ const renderToolCallMessage = (renderPart: () => ReactNode) => {
|
|
|
69
69
|
);
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
describe("part hooks tolerate a transient part-type mismatch
|
|
72
|
+
describe("part hooks tolerate a transient part-type mismatch", () => {
|
|
73
73
|
it("MessagePartPrimitive.Text renders empty when the part is a tool-call", async () => {
|
|
74
74
|
renderToolCallMessage(() => (
|
|
75
75
|
<MessagePartPrimitive.Text data-testid="text" />
|
|
@@ -121,7 +121,7 @@ describe("part hooks tolerate a transient part-type mismatch (#5118)", () => {
|
|
|
121
121
|
expect(file.textContent).toBe("");
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
it("a real text part still renders its text
|
|
124
|
+
it("a real text part still renders its text", async () => {
|
|
125
125
|
const textMessage: ThreadMessageLike[] = [
|
|
126
126
|
{
|
|
127
127
|
role: "assistant",
|
|
@@ -215,10 +215,3 @@ describe("ThreadPrimitive.Unstable_MessageById", () => {
|
|
|
215
215
|
expect(screen.getByTestId("by-id").textContent).toBe("first");
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
|
-
|
|
219
|
-
describe("exports", () => {
|
|
220
|
-
it("exposes the unstable id-keyed surface", () => {
|
|
221
|
-
expect(typeof unstable_useThreadMessageIds).toBe("function");
|
|
222
|
-
expect(ThreadPrimitive.Unstable_MessageById).toBeDefined();
|
|
223
|
-
});
|
|
224
|
-
});
|
|
@@ -18,11 +18,11 @@ const flushTapSync = vi.fn((fn: () => void) => fn());
|
|
|
18
18
|
|
|
19
19
|
vi.mock("@assistant-ui/store", () => ({
|
|
20
20
|
useAui: () => ({
|
|
21
|
-
composer:
|
|
21
|
+
composer: {
|
|
22
22
|
getState: () => ({ isEditing: fixture.composer.isEditing }),
|
|
23
23
|
setText: composerSetText,
|
|
24
24
|
send: composerSend,
|
|
25
|
-
}
|
|
25
|
+
},
|
|
26
26
|
}),
|
|
27
27
|
useAuiState: (selector: (s: unknown) => unknown) =>
|
|
28
28
|
selector({
|
|
@@ -83,9 +83,9 @@ export function unstable_useComposerInput(
|
|
|
83
83
|
|
|
84
84
|
const setText = useCallback(
|
|
85
85
|
(text: string) => {
|
|
86
|
-
if (!aui.composer
|
|
86
|
+
if (!aui.composer.getState().isEditing) return;
|
|
87
87
|
flushTapSync(() => {
|
|
88
|
-
aui.composer
|
|
88
|
+
aui.composer.setText(text);
|
|
89
89
|
});
|
|
90
90
|
},
|
|
91
91
|
[aui],
|
|
@@ -12,11 +12,11 @@ const setText = vi.fn();
|
|
|
12
12
|
|
|
13
13
|
vi.mock("@assistant-ui/store", () => ({
|
|
14
14
|
useAui: () => ({
|
|
15
|
-
composer:
|
|
15
|
+
composer: {
|
|
16
16
|
getState: () => ({ type: fixture.composerType }),
|
|
17
17
|
setText,
|
|
18
|
-
}
|
|
19
|
-
thread:
|
|
18
|
+
},
|
|
19
|
+
thread: { getState: () => ({ messages: fixture.messages }) },
|
|
20
20
|
on: (_event: string, cb: () => void) => {
|
|
21
21
|
fixture.switchedToHandlers.push(cb);
|
|
22
22
|
return () => {};
|
|
@@ -70,7 +70,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
|
|
|
70
70
|
const browseRef = useRef<BrowseState | null>(null);
|
|
71
71
|
|
|
72
72
|
useEffect(() => {
|
|
73
|
-
if (aui.composer
|
|
73
|
+
if (aui.composer.getState().type !== "thread") return undefined;
|
|
74
74
|
|
|
75
75
|
return aui.on("threadListItem.switchedTo", () => {
|
|
76
76
|
browseRef.current = null;
|
|
@@ -84,7 +84,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
|
|
|
84
84
|
if (e.nativeEvent.isComposing) return;
|
|
85
85
|
if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
86
86
|
if (popoverCtx && popoverCtx.getActiveAria() !== null) return;
|
|
87
|
-
if (aui.composer
|
|
87
|
+
if (aui.composer.getState().type !== "thread") return;
|
|
88
88
|
|
|
89
89
|
const textarea = e.currentTarget;
|
|
90
90
|
const { selectionStart, selectionEnd, value } = textarea;
|
|
@@ -96,7 +96,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
|
|
|
96
96
|
const browse = browseRef.current;
|
|
97
97
|
|
|
98
98
|
const commitText = (text: string): void => {
|
|
99
|
-
flushTapSync(() => aui.composer
|
|
99
|
+
flushTapSync(() => aui.composer.setText(text));
|
|
100
100
|
// React's controlled-value commit restores the pre-recall caret;
|
|
101
101
|
// reposition after the commit, before paint.
|
|
102
102
|
requestAnimationFrame(() => {
|
|
@@ -124,13 +124,13 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
|
|
|
124
124
|
|
|
125
125
|
if (!browse) {
|
|
126
126
|
if (value.trim() !== "") return;
|
|
127
|
-
const history = deriveHistory(aui.thread
|
|
127
|
+
const history = deriveHistory(aui.thread.getState().messages);
|
|
128
128
|
if (history.length === 0) return;
|
|
129
129
|
recall(history, 0, value);
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
const history = deriveHistory(aui.thread
|
|
133
|
+
const history = deriveHistory(aui.thread.getState().messages);
|
|
134
134
|
const next = browse.cursor + 1;
|
|
135
135
|
if (next >= history.length) {
|
|
136
136
|
e.preventDefault();
|
|
@@ -150,7 +150,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
|
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const history = deriveHistory(aui.thread
|
|
153
|
+
const history = deriveHistory(aui.thread.getState().messages);
|
|
154
154
|
recall(history, next, browse.draftSnapshot);
|
|
155
155
|
},
|
|
156
156
|
[aui, popoverCtx],
|
|
@@ -107,7 +107,7 @@ export function unstable_useMentionAdapter(
|
|
|
107
107
|
const adapter = useMemo<Unstable_TriggerAdapter>(() => {
|
|
108
108
|
const getModelContextTools = (): Unstable_TriggerItem[] => {
|
|
109
109
|
if (!wantsTools) return [];
|
|
110
|
-
const ctx = aui.thread
|
|
110
|
+
const ctx = aui.thread.getModelContext();
|
|
111
111
|
const tools = ctx.tools;
|
|
112
112
|
if (!tools) return [];
|
|
113
113
|
const formatLabel = toolsConfig?.formatLabel;
|
|
@@ -38,7 +38,7 @@ export const SmoothContextProvider: FC<PropsWithChildren> = ({ children }) => {
|
|
|
38
38
|
const aui = useAui();
|
|
39
39
|
|
|
40
40
|
const [context] = useState(() =>
|
|
41
|
-
makeSmoothContext(aui.part
|
|
41
|
+
makeSmoothContext(aui.part.getState().status),
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
// do not wrap if there is an outer SmoothContextProvider
|
|
@@ -58,10 +58,13 @@ class TextStreamAnimator {
|
|
|
58
58
|
public maxCharsPerFrame: number = Infinity;
|
|
59
59
|
public minCommitMs: number = 0;
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
) {
|
|
61
|
+
public currentText: string;
|
|
62
|
+
private setText: (newText: string) => void;
|
|
63
|
+
|
|
64
|
+
constructor(currentText: string, setText: (newText: string) => void) {
|
|
65
|
+
this.currentText = currentText;
|
|
66
|
+
this.setText = setText;
|
|
67
|
+
}
|
|
65
68
|
|
|
66
69
|
start() {
|
|
67
70
|
if (this.animationFrameId !== null) return;
|
|
@@ -178,7 +181,7 @@ export const useSmooth = (
|
|
|
178
181
|
// of relying on a render-time proxy reference that may be stable
|
|
179
182
|
// across thread swaps.
|
|
180
183
|
const aui = useAui();
|
|
181
|
-
const part = useAuiState(() => aui.part
|
|
184
|
+
const part = useAuiState(() => aui.part);
|
|
182
185
|
const [prevPart, setPrevPart] = useState(part);
|
|
183
186
|
if (part !== prevPart || !text.startsWith(displayedText)) {
|
|
184
187
|
setPrevPart(part);
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { SubscribableRuntime } from "./useRuntimeState.js";
|
|
2
|
-
//#region src/context/react/utils/createStateHookForRuntime.d.ts
|
|
3
|
-
declare function createStateHookForRuntime<TState>(useRuntime: (options: {
|
|
4
|
-
optional: boolean | undefined;
|
|
5
|
-
}) => SubscribableRuntime<TState> | null): {
|
|
6
|
-
(): TState;
|
|
7
|
-
<TSelected>(selector: (state: TState) => TSelected): TSelected;
|
|
8
|
-
<TSelected>(selector: ((state: TState) => TSelected) | undefined): TSelected | TState;
|
|
9
|
-
(options: {
|
|
10
|
-
optional?: false | undefined;
|
|
11
|
-
}): TState;
|
|
12
|
-
(options: {
|
|
13
|
-
optional?: boolean | undefined;
|
|
14
|
-
}): TState | null;
|
|
15
|
-
<TSelected>(options: {
|
|
16
|
-
optional?: false | undefined;
|
|
17
|
-
selector: (state: TState) => TSelected;
|
|
18
|
-
}): TSelected;
|
|
19
|
-
<TSelected>(options: {
|
|
20
|
-
optional?: false | undefined;
|
|
21
|
-
selector: ((state: TState) => TSelected) | undefined;
|
|
22
|
-
}): TSelected | TState;
|
|
23
|
-
<TSelected>(options: {
|
|
24
|
-
optional?: boolean | undefined;
|
|
25
|
-
selector: (state: TState) => TSelected;
|
|
26
|
-
}): TSelected | null;
|
|
27
|
-
<TSelected>(options: {
|
|
28
|
-
optional?: boolean | undefined;
|
|
29
|
-
selector: ((state: TState) => TSelected) | undefined;
|
|
30
|
-
}): TSelected | TState | null;
|
|
31
|
-
};
|
|
32
|
-
//#endregion
|
|
33
|
-
export { createStateHookForRuntime };
|
|
34
|
-
//# sourceMappingURL=createStateHookForRuntime.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createStateHookForRuntime.d.ts","names":[],"sources":["../../../../src/context/react/utils/createStateHookForRuntime.ts"],"mappings":";;iBAKgB,0BAA0B,QACxC,aAAa;EACX;MACI,oBAAoB;MAGD;GAGH,WAAS,WAClB,OAAO,WAAW,YAC5B;GAGmB,WAAS,YACjB,OAAO,WAAW,yBAC7B,YAAY;GAGgB;IAAE;MAAiC;GAGnC;IAC7B;MACE;GAGkB,WAAS;IAC7B;IACA,WAAW,OAAO,WAAW;MAC3B;GAGkB,WAAS;IAC7B;IACA,YAAY,OAAO,WAAW;MAC5B,YAAY;GAGM,WAAS;IAC7B;IACA,WAAW,OAAO,WAAW;MAC3B;GAGkB,WAAS;IAC7B;IACA,YAAY,OAAO,WAAW;MAC5B,YAAY"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { useRuntimeStateInternal } from "./useRuntimeState.js";
|
|
2
|
-
//#region src/context/react/utils/createStateHookForRuntime.ts
|
|
3
|
-
function createStateHookForRuntime(useRuntime) {
|
|
4
|
-
function useStoreHook(param) {
|
|
5
|
-
let optional = false;
|
|
6
|
-
let selector;
|
|
7
|
-
if (typeof param === "function") selector = param;
|
|
8
|
-
else if (param) {
|
|
9
|
-
optional = !!param.optional;
|
|
10
|
-
selector = param.selector;
|
|
11
|
-
}
|
|
12
|
-
const store = useRuntime({ optional });
|
|
13
|
-
if (!store) return null;
|
|
14
|
-
return useRuntimeStateInternal(store, selector);
|
|
15
|
-
}
|
|
16
|
-
return useStoreHook;
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
export { createStateHookForRuntime };
|
|
20
|
-
|
|
21
|
-
//# sourceMappingURL=createStateHookForRuntime.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createStateHookForRuntime.js","names":["SubscribableRuntime","useRuntimeStateInternal","createStateHookForRuntime","useRuntime","options","optional","TState","useStoreHook","selector","state","TSelected","param","store"],"sources":["../../../../src/context/react/utils/createStateHookForRuntime.ts"],"sourcesContent":["import {\n type SubscribableRuntime,\n useRuntimeStateInternal,\n} from \"./useRuntimeState\";\n\nexport function createStateHookForRuntime<TState>(\n useRuntime: (options: {\n optional: boolean | undefined;\n }) => SubscribableRuntime<TState> | null,\n) {\n // empty\n function useStoreHook(): TState;\n\n // selector\n function useStoreHook<TSelected>(\n selector: (state: TState) => TSelected,\n ): TSelected;\n\n // selector?\n function useStoreHook<TSelected>(\n selector: ((state: TState) => TSelected) | undefined,\n ): TSelected | TState;\n\n // optional=false\n function useStoreHook(options: { optional?: false | undefined }): TState;\n\n // optional?\n function useStoreHook(options: {\n optional?: boolean | undefined;\n }): TState | null;\n\n // optional=false, selector\n function useStoreHook<TSelected>(options: {\n optional?: false | undefined;\n selector: (state: TState) => TSelected;\n }): TSelected;\n\n // optional=false, selector?\n function useStoreHook<TSelected>(options: {\n optional?: false | undefined;\n selector: ((state: TState) => TSelected) | undefined;\n }): TSelected | TState;\n\n // optional?, selector\n function useStoreHook<TSelected>(options: {\n optional?: boolean | undefined;\n selector: (state: TState) => TSelected;\n }): TSelected | null;\n\n // optional?, selector?\n function useStoreHook<TSelected>(options: {\n optional?: boolean | undefined;\n selector: ((state: TState) => TSelected) | undefined;\n }): TSelected | TState | null;\n\n function useStoreHook<TSelected>(\n param?:\n | ((state: TState) => TSelected)\n | {\n optional?: boolean | undefined;\n selector?: ((state: TState) => TSelected) | undefined;\n },\n ): TSelected | TState | null {\n let optional = false;\n let selector: ((state: TState) => TSelected) | undefined;\n\n if (typeof param === \"function\") {\n selector = param;\n } else if (param) {\n optional = !!param.optional;\n selector = param.selector;\n }\n\n const store = useRuntime({ optional });\n if (!store) return null;\n\n // it is ok to call useRuntimeStateInternal conditionally because it will never become null if its available\n return useRuntimeStateInternal(store, selector);\n }\n\n return useStoreHook;\n}\n"],"mappings":";;AAKA,SAAgBE,0BACdC,YAGA;CA8CA,SAASI,aACPI,OAM2B;EAC3B,IAAIN,WAAW;EACf,IAAIG;EAEJ,IAAI,OAAOG,UAAU,YACnBH,WAAWG;OACN,IAAIA,OAAO;GAChBN,WAAW,CAAC,CAACM,MAAMN;GACnBG,WAAWG,MAAMH;EACnB;EAEA,MAAMI,QAAQT,WAAW,EAAEE,SAAS,CAAC;EACrC,IAAI,CAACO,OAAO,OAAO;EAGnB,OAAOX,wBAAwBW,OAAOJ,QAAQ;CAChD;CAEA,OAAOD;AACT"}
|