@assistant-ui/react 0.14.27 → 0.14.29
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 +758 -513
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/context/react/utils/createContextStoreHook.d.ts.map +1 -1
- package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
- 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.d.ts.map +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 +8 -4
- 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.d.ts.map +1 -1
- package/dist/model-context/makeAssistantVisible.d.ts.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +7 -1
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarEdit.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarReload.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js +3 -3
- package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarSpeak.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.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/actionBarMore/ActionBarMoreContent.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreItem.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.d.ts.map +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalAnchor.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.d.ts.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
- package/dist/primitives/assistantModal/AssistantModalTrigger.d.ts.map +1 -1
- package/dist/primitives/assistantModal/popoverRenderPrimitives.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -0
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRoot.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerNext.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerPrevious.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.d.ts.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -0
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +8 -2
- 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 +1 -3
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerDictate.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +6 -4
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.d.ts +5 -0
- package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +86 -40
- package/dist/primitives/composer/ComposerRoot.js.map +1 -1
- package/dist/primitives/composer/ComposerSend.d.ts.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.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -0
- package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverBack.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
- package/dist/primitives/composer/trigger/detectTrigger.d.ts +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/dropdownMenuRenderPrimitives.d.ts.map +1 -1
- package/dist/primitives/error/ErrorMessage.d.ts.map +1 -1
- package/dist/primitives/error/ErrorMessage.js.map +1 -1
- package/dist/primitives/error/ErrorRoot.d.ts.map +1 -1
- package/dist/primitives/message/MessageParts.js.map +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartData.d.ts +3 -2
- package/dist/primitives/messagePart/useMessagePartData.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartData.js +3 -2
- package/dist/primitives/messagePart/useMessagePartData.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartFile.d.ts +5 -4
- package/dist/primitives/messagePart/useMessagePartFile.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartFile.js +11 -3
- package/dist/primitives/messagePart/useMessagePartFile.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartImage.d.ts +5 -4
- package/dist/primitives/messagePart/useMessagePartImage.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartImage.js +10 -3
- package/dist/primitives/messagePart/useMessagePartImage.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartReasoning.d.ts +5 -4
- package/dist/primitives/messagePart/useMessagePartReasoning.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartReasoning.js +10 -3
- package/dist/primitives/messagePart/useMessagePartReasoning.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartSource.d.ts +6 -4
- package/dist/primitives/messagePart/useMessagePartSource.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartSource.js +12 -3
- package/dist/primitives/messagePart/useMessagePartSource.js.map +1 -1
- package/dist/primitives/messagePart/useMessagePartText.d.ts +6 -5
- package/dist/primitives/messagePart/useMessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/useMessagePartText.js +10 -3
- package/dist/primitives/messagePart/useMessagePartText.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -0
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -0
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -0
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.js +1 -0
- package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
- package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +53 -19
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/createReserveObservers.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.d.ts +4 -0
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js +14 -1
- package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js.map +1 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.d.ts +13 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.d.ts.map +1 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js +14 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +2 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListLoadMore.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.d.ts.map +1 -1
- package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
- package/dist/primitives/threadListFocusGroup.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemArchive.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemDelete.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.d.ts.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
- package/dist/primitives/threadListItem/ThreadListItemUnarchive.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.d.ts.map +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.d.ts.map +1 -1
- package/dist/tests/setup.d.ts +1 -1
- package/dist/utils/Primitive.d.ts.map +1 -1
- package/dist/utils/Primitive.js.map +1 -1
- package/dist/utils/createActionButton.d.ts.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/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +2 -2
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +20 -20
- package/src/client/ExternalThread.ts +341 -53
- 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 +189 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +20 -4
- package/src/model-context/makeAssistantVisible.test.tsx +146 -0
- package/src/model-context/makeAssistantVisible.tsx +18 -1
- package/src/primitives/actionBar/useActionBarFloatStatus.ts +7 -5
- package/src/primitives/composer/ComposerAddAttachment.test.tsx +108 -0
- package/src/primitives/composer/ComposerAddAttachment.ts +10 -4
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +3 -2
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +3 -2
- package/src/primitives/composer/ComposerInput.test.tsx +98 -1
- package/src/primitives/composer/ComposerInput.tsx +10 -8
- package/src/primitives/composer/ComposerRoot.test.tsx +226 -5
- package/src/primitives/composer/ComposerRoot.tsx +50 -1
- package/src/primitives/messagePart/useMessagePartData.ts +3 -2
- package/src/primitives/messagePart/useMessagePartFile.ts +19 -7
- package/src/primitives/messagePart/useMessagePartImage.ts +18 -7
- package/src/primitives/messagePart/useMessagePartReasoning.ts +15 -6
- package/src/primitives/messagePart/useMessagePartSource.ts +20 -7
- package/src/primitives/messagePart/useMessagePartText.ts +15 -5
- package/src/primitives/thread/ThreadViewport.tsx +15 -0
- package/src/primitives/thread/topAnchor/mountTopAnchorReserve.test.ts +243 -0
- package/src/primitives/thread/topAnchor/mountTopAnchorReserve.ts +28 -7
- package/src/primitives/thread/topAnchor/topAnchorTurn.test.ts +69 -0
- package/src/primitives/thread/topAnchor/topAnchorTurn.ts +29 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +86 -1
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +6 -1
- package/src/tests/DataRenderers.test.tsx +212 -0
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +1 -11
- package/src/tests/external-thread-attachments.test.tsx +372 -0
- package/src/tests/external-thread-parity.test.tsx +232 -0
- package/src/tests/generative-ui.test.tsx +1 -46
- package/src/tests/messagePartSwitchRace.test.tsx +172 -0
- package/src/tests/messagePartTypeMismatch.test.tsx +146 -0
- package/src/tests/threadMessageById.test.tsx +0 -7
- package/src/utils/smooth/useSmooth.ts +7 -4
- package/src/tests/BaseComposerRuntimeCore.test.ts +0 -612
- 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
package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, render, waitFor } from "@testing-library/react";
|
|
4
|
+
import type { FC } from "react";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { useAui } from "@assistant-ui/store";
|
|
7
|
+
import { AssistantRuntimeProvider } from "../../../context";
|
|
8
|
+
import {
|
|
9
|
+
useAssistantTransportRuntime,
|
|
10
|
+
useAssistantTransportSendCommand,
|
|
11
|
+
} from "./useAssistantTransportRuntime";
|
|
12
|
+
import type {
|
|
13
|
+
AssistantTransportCommand,
|
|
14
|
+
AssistantTransportOptions,
|
|
15
|
+
AssistantTransportStateConverter,
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
const converter: AssistantTransportStateConverter<unknown> = (
|
|
19
|
+
_state,
|
|
20
|
+
meta,
|
|
21
|
+
) => ({
|
|
22
|
+
messages: [],
|
|
23
|
+
isRunning: meta.isSending,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const createMessageCommand = (text: string): AssistantTransportCommand => ({
|
|
27
|
+
type: "add-message",
|
|
28
|
+
message: {
|
|
29
|
+
role: "user",
|
|
30
|
+
parts: [{ type: "text", text }],
|
|
31
|
+
},
|
|
32
|
+
parentId: null,
|
|
33
|
+
sourceId: null,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const createStreamResponse = () => {
|
|
37
|
+
let controller!: ReadableStreamDefaultController<Uint8Array>;
|
|
38
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
39
|
+
start(c) {
|
|
40
|
+
controller = c;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
response: new Response(stream, { status: 200 }),
|
|
45
|
+
close: () => controller.close(),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type StreamResponse = ReturnType<typeof createStreamResponse>;
|
|
50
|
+
|
|
51
|
+
type RecordedRequest = {
|
|
52
|
+
url: string;
|
|
53
|
+
init: RequestInit;
|
|
54
|
+
body: Record<string, any>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const installFetch = () => {
|
|
58
|
+
const requests: RecordedRequest[] = [];
|
|
59
|
+
const servers: StreamResponse[] = [];
|
|
60
|
+
|
|
61
|
+
vi.stubGlobal(
|
|
62
|
+
"fetch",
|
|
63
|
+
async (url: RequestInfo | URL, init: RequestInit = {}) => {
|
|
64
|
+
requests.push({
|
|
65
|
+
url: String(url),
|
|
66
|
+
init,
|
|
67
|
+
body: JSON.parse(init.body as string),
|
|
68
|
+
});
|
|
69
|
+
const server = createStreamResponse();
|
|
70
|
+
servers.push(server);
|
|
71
|
+
return server.response;
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return { requests, servers };
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const mountRuntime = (
|
|
79
|
+
options?: Partial<AssistantTransportOptions<unknown>>,
|
|
80
|
+
) => {
|
|
81
|
+
const captured: {
|
|
82
|
+
aui?: ReturnType<typeof useAui>;
|
|
83
|
+
sendCommand?: (command: AssistantTransportCommand) => void;
|
|
84
|
+
} = {};
|
|
85
|
+
const Capture: FC = () => {
|
|
86
|
+
captured.aui = useAui();
|
|
87
|
+
captured.sendCommand = useAssistantTransportSendCommand();
|
|
88
|
+
return null;
|
|
89
|
+
};
|
|
90
|
+
const App: FC = () => {
|
|
91
|
+
const runtime = useAssistantTransportRuntime({
|
|
92
|
+
initialState: {},
|
|
93
|
+
api: "https://example.com/api",
|
|
94
|
+
headers: {},
|
|
95
|
+
converter,
|
|
96
|
+
...options,
|
|
97
|
+
});
|
|
98
|
+
return (
|
|
99
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
100
|
+
<Capture />
|
|
101
|
+
</AssistantRuntimeProvider>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
const utils = render(<App />);
|
|
105
|
+
return {
|
|
106
|
+
aui: () => captured.aui!,
|
|
107
|
+
sendCommand: (command: AssistantTransportCommand) =>
|
|
108
|
+
captured.sendCommand!(command),
|
|
109
|
+
...utils,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
vi.unstubAllGlobals();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("useAssistantTransportRuntime", () => {
|
|
118
|
+
it("no-ops a follow-up run that finds an empty queue", async () => {
|
|
119
|
+
const fetchMock = installFetch();
|
|
120
|
+
const onError = vi.fn();
|
|
121
|
+
const { aui, sendCommand } = mountRuntime({ onError });
|
|
122
|
+
await waitFor(() =>
|
|
123
|
+
expect(
|
|
124
|
+
(aui().thread().getState().extras as { sendCommand?: unknown })
|
|
125
|
+
?.sendCommand,
|
|
126
|
+
).toBeTypeOf("function"),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Two synchronous sends coalesce into one request plus one follow-up run.
|
|
130
|
+
act(() => {
|
|
131
|
+
sendCommand(createMessageCommand("a"));
|
|
132
|
+
sendCommand(createMessageCommand("b"));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
|
|
136
|
+
expect(
|
|
137
|
+
fetchMock.requests[0]!.body["commands"].map((c: any) => c.type),
|
|
138
|
+
).toEqual(["add-message", "add-message"]);
|
|
139
|
+
|
|
140
|
+
act(() => fetchMock.servers[0]!.close());
|
|
141
|
+
|
|
142
|
+
await waitFor(() =>
|
|
143
|
+
expect(aui().thread().getState().isRunning).toBe(false),
|
|
144
|
+
);
|
|
145
|
+
await act(async () => {});
|
|
146
|
+
expect(onError).not.toHaveBeenCalled();
|
|
147
|
+
expect(fetchMock.requests).toHaveLength(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("flushes commands enqueued during a resume run in a follow-up run", async () => {
|
|
151
|
+
const fetchMock = installFetch();
|
|
152
|
+
const { aui, sendCommand } = mountRuntime({
|
|
153
|
+
resumeApi: "https://example.com/resume",
|
|
154
|
+
});
|
|
155
|
+
await waitFor(() =>
|
|
156
|
+
expect(
|
|
157
|
+
(aui().thread().getState().extras as { sendCommand?: unknown })
|
|
158
|
+
?.sendCommand,
|
|
159
|
+
).toBeTypeOf("function"),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
act(() => sendCommand(createMessageCommand("a")));
|
|
163
|
+
await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
|
|
164
|
+
|
|
165
|
+
// Both land while the first run is active and coalesce into one follow-up.
|
|
166
|
+
act(() => {
|
|
167
|
+
sendCommand(createMessageCommand("b"));
|
|
168
|
+
aui().thread().resumeRun({ parentId: null });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
act(() => fetchMock.servers[0]!.close());
|
|
172
|
+
await waitFor(() => expect(fetchMock.requests).toHaveLength(2));
|
|
173
|
+
expect(fetchMock.requests[1]!.url).toBe("https://example.com/resume");
|
|
174
|
+
expect(fetchMock.requests[1]!.body["commands"]).toEqual([]);
|
|
175
|
+
|
|
176
|
+
// "b" coalesced into the resume run and must not starve in the queue.
|
|
177
|
+
act(() => fetchMock.servers[1]!.close());
|
|
178
|
+
await waitFor(() => expect(fetchMock.requests).toHaveLength(3));
|
|
179
|
+
expect(fetchMock.requests[2]!.url).toBe("https://example.com/api");
|
|
180
|
+
expect(fetchMock.requests[2]!.body["commands"]).toEqual([
|
|
181
|
+
createMessageCommand("b"),
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
act(() => fetchMock.servers[2]!.close());
|
|
185
|
+
await waitFor(() =>
|
|
186
|
+
expect(aui().thread().getState().isRunning).toBe(false),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
});
|
package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts
CHANGED
|
@@ -135,8 +135,13 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
135
135
|
resumeFlagRef.current = false;
|
|
136
136
|
setIsReplaying(false);
|
|
137
137
|
const commands: QueuedCommand[] = isResume ? [] : commandQueue.flush();
|
|
138
|
-
if (commands.length === 0 && !isResume)
|
|
139
|
-
|
|
138
|
+
if (commands.length === 0 && !isResume) return;
|
|
139
|
+
|
|
140
|
+
// The flushed batch consumes the parentId; read it alongside the flush
|
|
141
|
+
// (before any awaits) so a mid-run append keeps its own value. Resume
|
|
142
|
+
// runs send no commands, so they neither send nor consume it.
|
|
143
|
+
const parentId = isResume ? undefined : parentIdRef.current;
|
|
144
|
+
if (!isResume) parentIdRef.current = undefined;
|
|
140
145
|
|
|
141
146
|
const headers = await createRequestHeaders(options.headers);
|
|
142
147
|
const bodyValue =
|
|
@@ -151,8 +156,8 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
151
156
|
system: context.system,
|
|
152
157
|
tools: context.tools ? toToolsJSONSchema(context.tools) : undefined,
|
|
153
158
|
threadId,
|
|
154
|
-
...(
|
|
155
|
-
parentId
|
|
159
|
+
...(parentId !== undefined && {
|
|
160
|
+
parentId,
|
|
156
161
|
}),
|
|
157
162
|
// nested (new format, aligned with AssistantChatTransport)
|
|
158
163
|
callSettings: context.callSettings,
|
|
@@ -232,6 +237,17 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
232
237
|
if (err) {
|
|
233
238
|
throw new Error(err);
|
|
234
239
|
}
|
|
240
|
+
|
|
241
|
+
// A successful run confirms delivery even when no state-changing
|
|
242
|
+
// chunk was observed.
|
|
243
|
+
if (!markedDelivered) {
|
|
244
|
+
commandQueue.markDelivered();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// commands that coalesced into this resume run must not starve
|
|
248
|
+
if (isResume && commandQueue.state.queued.length > 0) {
|
|
249
|
+
runManager.schedule();
|
|
250
|
+
}
|
|
235
251
|
},
|
|
236
252
|
onFinish: options.onFinish,
|
|
237
253
|
onCancel: () => {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { act, cleanup, render } from "@testing-library/react";
|
|
4
|
+
import {
|
|
5
|
+
forwardRef,
|
|
6
|
+
useState,
|
|
7
|
+
type ComponentProps,
|
|
8
|
+
type ChangeEvent,
|
|
9
|
+
} from "react";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
|
|
12
|
+
const { registerMock, auiMock } = vi.hoisted(() => {
|
|
13
|
+
const register = vi.fn((_provider: unknown) => () => {});
|
|
14
|
+
return {
|
|
15
|
+
registerMock: register,
|
|
16
|
+
auiMock: {
|
|
17
|
+
modelContext: () => ({ register }),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
23
|
+
...(await importOriginal()),
|
|
24
|
+
useAui: () => auiMock,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
import { makeAssistantVisible } from "./makeAssistantVisible";
|
|
28
|
+
|
|
29
|
+
type EditTool = {
|
|
30
|
+
execute: (args: { editId: string; value: string }) => Promise<unknown>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ModelContextProvider = {
|
|
34
|
+
getModelContext: () => {
|
|
35
|
+
tools: {
|
|
36
|
+
edit: EditTool;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const ControlledInput = forwardRef<HTMLInputElement, ComponentProps<"input">>(
|
|
42
|
+
({ onChange, ...props }, ref) => {
|
|
43
|
+
const [value, setValue] = useState("initial");
|
|
44
|
+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
45
|
+
setValue(event.target.value);
|
|
46
|
+
onChange?.(event);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<input
|
|
51
|
+
{...props}
|
|
52
|
+
ref={ref}
|
|
53
|
+
value={value}
|
|
54
|
+
data-react-value={value}
|
|
55
|
+
onChange={handleChange}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const ControlledTextarea = forwardRef<
|
|
62
|
+
HTMLTextAreaElement,
|
|
63
|
+
ComponentProps<"textarea">
|
|
64
|
+
>(({ onChange, ...props }, ref) => {
|
|
65
|
+
const [value, setValue] = useState("initial");
|
|
66
|
+
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
67
|
+
setValue(event.target.value);
|
|
68
|
+
onChange?.(event);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<textarea
|
|
73
|
+
{...props}
|
|
74
|
+
ref={ref}
|
|
75
|
+
value={value}
|
|
76
|
+
data-react-value={value}
|
|
77
|
+
onChange={handleChange}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const runEditTool = async (
|
|
83
|
+
element: HTMLInputElement | HTMLTextAreaElement,
|
|
84
|
+
value: string,
|
|
85
|
+
) => {
|
|
86
|
+
const editId = element.dataset.editId;
|
|
87
|
+
if (editId === undefined) throw new Error("Missing edit id");
|
|
88
|
+
|
|
89
|
+
const provider = registerMock.mock.calls[0]![0] as ModelContextProvider;
|
|
90
|
+
const task = provider.getModelContext().tools.edit.execute({
|
|
91
|
+
editId,
|
|
92
|
+
value,
|
|
93
|
+
});
|
|
94
|
+
await vi.runAllTimersAsync();
|
|
95
|
+
await task;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
describe("makeAssistantVisible editable components", () => {
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
registerMock.mockClear();
|
|
101
|
+
vi.stubGlobal("CSS", { escape: (value: string) => value });
|
|
102
|
+
vi.useFakeTimers();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
cleanup();
|
|
107
|
+
vi.unstubAllGlobals();
|
|
108
|
+
vi.useRealTimers();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("updates controlled input state through the edit tool", async () => {
|
|
112
|
+
const EditableInput = makeAssistantVisible(ControlledInput, {
|
|
113
|
+
editable: true,
|
|
114
|
+
});
|
|
115
|
+
const onChange = vi.fn();
|
|
116
|
+
const input = render(<EditableInput onChange={onChange} />).getByRole(
|
|
117
|
+
"textbox",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await act(async () => {
|
|
121
|
+
await runEditTool(input as HTMLInputElement, "updated");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(input).toHaveProperty("value", "updated");
|
|
125
|
+
expect(input.getAttribute("data-react-value")).toBe("updated");
|
|
126
|
+
expect(onChange).toHaveBeenCalledOnce();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("updates controlled textarea state through the edit tool", async () => {
|
|
130
|
+
const EditableTextarea = makeAssistantVisible(ControlledTextarea, {
|
|
131
|
+
editable: true,
|
|
132
|
+
});
|
|
133
|
+
const onChange = vi.fn();
|
|
134
|
+
const textarea = render(<EditableTextarea onChange={onChange} />).getByRole(
|
|
135
|
+
"textbox",
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
await act(async () => {
|
|
139
|
+
await runEditTool(textarea as HTMLTextAreaElement, "updated");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(textarea).toHaveProperty("value", "updated");
|
|
143
|
+
expect(textarea.getAttribute("data-react-value")).toBe("updated");
|
|
144
|
+
expect(onChange).toHaveBeenCalledOnce();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -40,6 +40,23 @@ const click = tool({
|
|
|
40
40
|
},
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
const setNativeValue = (
|
|
44
|
+
element: HTMLInputElement | HTMLTextAreaElement,
|
|
45
|
+
value: string,
|
|
46
|
+
) => {
|
|
47
|
+
const prototype =
|
|
48
|
+
element instanceof HTMLInputElement
|
|
49
|
+
? HTMLInputElement.prototype
|
|
50
|
+
: HTMLTextAreaElement.prototype;
|
|
51
|
+
const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
|
|
52
|
+
|
|
53
|
+
if (setter) {
|
|
54
|
+
setter.call(element, value);
|
|
55
|
+
} else {
|
|
56
|
+
element.value = value;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
43
60
|
const edit = tool({
|
|
44
61
|
parameters: {
|
|
45
62
|
type: "object",
|
|
@@ -57,7 +74,7 @@ const edit = tool({
|
|
|
57
74
|
const escapedEditId = CSS.escape(editId);
|
|
58
75
|
const el = document.querySelector(`[data-edit-id='${escapedEditId}']`);
|
|
59
76
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
60
|
-
el
|
|
77
|
+
setNativeValue(el, value);
|
|
61
78
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
62
79
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
63
80
|
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useAuiState } from "@assistant-ui/store";
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
Hidden
|
|
7
|
-
Floating
|
|
8
|
-
Normal
|
|
9
|
-
}
|
|
5
|
+
export const HideAndFloatStatus = {
|
|
6
|
+
Hidden: "hidden",
|
|
7
|
+
Floating: "floating",
|
|
8
|
+
Normal: "normal",
|
|
9
|
+
} as const;
|
|
10
|
+
export type HideAndFloatStatus =
|
|
11
|
+
(typeof HideAndFloatStatus)[keyof typeof HideAndFloatStatus];
|
|
10
12
|
|
|
11
13
|
export type UseActionBarFloatStatusProps = {
|
|
12
14
|
hideWhenRunning?: boolean | undefined;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { act } from "react";
|
|
5
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
6
|
+
import type * as CoreReact from "@assistant-ui/core/react";
|
|
7
|
+
import type * as AssistantStore from "@assistant-ui/store";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { ComposerPrimitiveAddAttachment } from "./ComposerAddAttachment";
|
|
10
|
+
|
|
11
|
+
const { addAttachment } = vi.hoisted(() => ({
|
|
12
|
+
addAttachment: vi.fn<(file: File) => Promise<void>>(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
16
|
+
|
|
17
|
+
vi.mock("@assistant-ui/core/react", async (importOriginal) => {
|
|
18
|
+
const actual = await importOriginal<typeof CoreReact>();
|
|
19
|
+
return {
|
|
20
|
+
...actual,
|
|
21
|
+
useComposerAddAttachment: () => ({
|
|
22
|
+
disabled: false,
|
|
23
|
+
addAttachment,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
29
|
+
const actual = await importOriginal<typeof AssistantStore>();
|
|
30
|
+
return {
|
|
31
|
+
...actual,
|
|
32
|
+
useAui: () => ({
|
|
33
|
+
composer: () => ({
|
|
34
|
+
getState: () => ({ attachmentAccept: "*" }),
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("ComposerPrimitiveAddAttachment", () => {
|
|
41
|
+
let container: HTMLDivElement;
|
|
42
|
+
let root: Root;
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
addAttachment.mockReset();
|
|
46
|
+
vi.spyOn(HTMLInputElement.prototype, "click").mockImplementation(() => {});
|
|
47
|
+
|
|
48
|
+
container = document.createElement("div");
|
|
49
|
+
document.body.appendChild(container);
|
|
50
|
+
root = createRoot(container);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterEach(async () => {
|
|
54
|
+
await act(async () => {
|
|
55
|
+
root.unmount();
|
|
56
|
+
});
|
|
57
|
+
document.querySelectorAll('input[type="file"]').forEach((input) => {
|
|
58
|
+
input.remove();
|
|
59
|
+
});
|
|
60
|
+
container.remove();
|
|
61
|
+
vi.restoreAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("handles rejected file picker attachments and continues processing", async () => {
|
|
65
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
66
|
+
|
|
67
|
+
addAttachment
|
|
68
|
+
.mockRejectedValueOnce(new Error("unsupported file"))
|
|
69
|
+
.mockResolvedValueOnce(undefined);
|
|
70
|
+
|
|
71
|
+
await act(async () => {
|
|
72
|
+
root.render(
|
|
73
|
+
<ComposerPrimitiveAddAttachment>
|
|
74
|
+
Add attachment
|
|
75
|
+
</ComposerPrimitiveAddAttachment>,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const button = container.querySelector("button");
|
|
80
|
+
expect(button).not.toBeNull();
|
|
81
|
+
|
|
82
|
+
await act(async () => {
|
|
83
|
+
button!.click();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const input =
|
|
87
|
+
document.querySelector<HTMLInputElement>('input[type="file"]');
|
|
88
|
+
expect(input).not.toBeNull();
|
|
89
|
+
|
|
90
|
+
const files = [
|
|
91
|
+
new File(["a"], "unsupported.bin"),
|
|
92
|
+
new File(["b"], "supported.txt", { type: "text/plain" }),
|
|
93
|
+
];
|
|
94
|
+
Object.defineProperty(input, "files", {
|
|
95
|
+
configurable: true,
|
|
96
|
+
value: files,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = input!.onchange?.call(input, {
|
|
100
|
+
target: input,
|
|
101
|
+
} as unknown as Event);
|
|
102
|
+
|
|
103
|
+
await expect(Promise.resolve(result)).resolves.toBeUndefined();
|
|
104
|
+
expect(addAttachment).toHaveBeenCalledTimes(2);
|
|
105
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
106
|
+
expect(input!.isConnected).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -31,14 +31,20 @@ const useComposerAddAttachment = ({
|
|
|
31
31
|
|
|
32
32
|
document.body.appendChild(input);
|
|
33
33
|
|
|
34
|
-
input.onchange = (e) => {
|
|
34
|
+
input.onchange = async (e) => {
|
|
35
35
|
const fileList = (e.target as HTMLInputElement).files;
|
|
36
36
|
if (!fileList) return;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
|
|
38
|
+
const attachmentPromises = Array.from(fileList, async (file) => {
|
|
39
|
+
try {
|
|
40
|
+
await addAttachment(file);
|
|
41
|
+
} catch {
|
|
42
|
+
// The composer runtime emits composer.attachmentAddError before rejecting.
|
|
43
|
+
}
|
|
44
|
+
});
|
|
40
45
|
|
|
41
46
|
document.body.removeChild(input);
|
|
47
|
+
await Promise.all(attachmentPromises);
|
|
42
48
|
};
|
|
43
49
|
|
|
44
50
|
input.oncancel = () => {
|
|
@@ -95,7 +95,7 @@ describe("ComposerPrimitiveAttachmentDropzone", () => {
|
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it("continues processing other files when one attachment fails", async () => {
|
|
98
|
+
it("continues processing other files when one attachment fails without console.error", async () => {
|
|
99
99
|
const error = new Error("upload failed");
|
|
100
100
|
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
101
101
|
|
|
@@ -120,6 +120,7 @@ describe("ComposerPrimitiveAttachmentDropzone", () => {
|
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
expect(addAttachment).toHaveBeenCalledTimes(3);
|
|
123
|
-
expect(errorSpy).
|
|
123
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
124
|
+
errorSpy.mockRestore();
|
|
124
125
|
});
|
|
125
126
|
});
|
|
@@ -71,8 +71,9 @@ export const ComposerPrimitiveAttachmentDropzone = forwardRef<
|
|
|
71
71
|
files.map(async (file) => {
|
|
72
72
|
try {
|
|
73
73
|
await aui.composer().addAttachment(file);
|
|
74
|
-
} catch
|
|
75
|
-
|
|
74
|
+
} catch {
|
|
75
|
+
// The composer runtime emits composer.attachmentAddError before rejecting;
|
|
76
|
+
// the readonly and empty-thread stubs only throw.
|
|
76
77
|
}
|
|
77
78
|
}),
|
|
78
79
|
);
|