@assistant-ui/core 0.2.21 → 0.2.22
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/adapters/attachment.d.ts.map +1 -1
- package/dist/adapters/speech.d.ts.map +1 -1
- package/dist/adapters/speech.js +9 -2
- package/dist/adapters/speech.js.map +1 -1
- package/dist/adapters/voice.d.ts.map +1 -1
- package/dist/adapters/voice.js +13 -5
- package/dist/adapters/voice.js.map +1 -1
- package/dist/internal.d.ts +3 -1
- package/dist/internal.js +3 -1
- package/dist/model-context/frame/host.d.ts +1 -0
- package/dist/model-context/frame/host.d.ts.map +1 -1
- package/dist/model-context/frame/host.js +5 -0
- package/dist/model-context/frame/host.js.map +1 -1
- package/dist/model-context/frame/provider.d.ts.map +1 -1
- package/dist/model-context/frame/provider.js +1 -0
- package/dist/model-context/frame/provider.js.map +1 -1
- package/dist/model-context/interactable-composer-metadata.js.map +1 -1
- package/dist/react/AssistantProvider.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js +77 -50
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/client/Interactables.js.map +1 -1
- package/dist/react/client/Tools.d.ts.map +1 -1
- package/dist/react/client/interactable-model-context.d.ts.map +1 -1
- package/dist/react/model-context/useInteractable.js.map +1 -1
- package/dist/react/primitive-hooks/useActionBarCopy.js +13 -13
- package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
- package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts.map +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
- package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
- package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
- package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
- package/dist/react/primitives/message/MessageParts.d.ts +2 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +46 -36
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/primitives/part/PartMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
- package/dist/react/primitives/thread/ThreadSuggestions.js.map +1 -1
- package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +18 -30
- package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
- package/dist/react/providers/PartByIndexProvider.d.ts.map +1 -1
- package/dist/react/providers/PartByIndexProvider.js +18 -30
- package/dist/react/providers/PartByIndexProvider.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -0
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +9 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +49 -8
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +1 -1
- package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +2 -1
- package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
- package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
- package/dist/react/runtimes/external-message-converter.js +1 -1
- package/dist/react/runtimes/external-message-converter.js.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.d.ts +1 -0
- package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
- package/dist/react/runtimes/useLocalRuntime.js +9 -1
- package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
- package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
- package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.d.ts.map +1 -1
- package/dist/runtime/api/message-runtime.js +4 -4
- package/dist/runtime/api/message-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.js +2 -1
- package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +4 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +41 -14
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +2 -1
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-edit-composer-runtime-core.js +4 -2
- package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/default-thread-composer-runtime-core.js +2 -2
- package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
- package/dist/runtime/utils/message-repository.d.ts.map +1 -1
- package/dist/runtime/utils/thread-message-like.js +4 -1
- package/dist/runtime/utils/thread-message-like.js.map +1 -1
- package/dist/runtimes/external-store/external-store-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +5 -2
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +4 -4
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.js +22 -9
- package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +1 -2
- package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
- package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +1 -1
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +11 -3
- package/dist/subscribable/subscribable.js.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts +2 -1
- package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
- package/dist/tests/remote-thread-list-test-helpers.js +5 -2
- package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
- package/dist/types/attachment.d.ts +2 -1
- package/dist/types/attachment.d.ts.map +1 -1
- package/dist/types/attachment.js +6 -0
- package/dist/types/attachment.js.map +1 -0
- package/dist/types/message.d.ts +1 -0
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/message.js.map +1 -1
- package/dist/utils/data-url.d.ts +9 -0
- package/dist/utils/data-url.d.ts.map +1 -0
- package/dist/utils/data-url.js +14 -0
- package/dist/utils/data-url.js.map +1 -0
- package/dist/utils/notify-event-listeners.d.ts +6 -0
- package/dist/utils/notify-event-listeners.d.ts.map +1 -0
- package/dist/utils/notify-event-listeners.js +16 -0
- package/dist/utils/notify-event-listeners.js.map +1 -0
- package/package.json +10 -9
- package/src/adapters/speech.test.ts +88 -0
- package/src/adapters/speech.ts +17 -2
- package/src/adapters/voice.test.ts +130 -0
- package/src/adapters/voice.ts +26 -6
- package/src/internal.ts +5 -0
- package/src/model-context/frame/host.test.ts +100 -0
- package/src/model-context/frame/host.ts +10 -0
- package/src/model-context/frame/provider.test.ts +83 -0
- package/src/model-context/frame/provider.ts +1 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +82 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +94 -54
- package/src/react/primitive-hooks/useActionBarCopy.test.ts +79 -0
- package/src/react/primitive-hooks/useActionBarCopy.ts +4 -3
- package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -3
- package/src/react/primitives/message/MessageParts.tsx +48 -35
- package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +19 -2
- package/src/react/providers/PartByIndexProvider.tsx +16 -2
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +18 -0
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +1 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +48 -7
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +69 -0
- package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +1 -1
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +16 -0
- package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +2 -1
- package/src/react/runtimes/external-message-converter.test.tsx +21 -0
- package/src/react/runtimes/external-message-converter.ts +1 -1
- package/src/react/runtimes/useLocalRuntime.test.tsx +56 -0
- package/src/react/runtimes/useLocalRuntime.ts +13 -1
- package/src/runtime/api/message-runtime.test.ts +81 -0
- package/src/runtime/api/message-runtime.ts +4 -6
- package/src/runtime/api/thread-list-item-runtime.ts +6 -1
- package/src/runtime/base/base-composer-runtime-core.ts +77 -31
- package/src/runtime/base/base-thread-runtime-core.ts +2 -1
- package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -2
- package/src/runtime/base/default-thread-composer-runtime-core.ts +2 -2
- package/src/runtime/utils/thread-message-like.ts +1 -0
- package/src/runtimes/local/local-thread-runtime-core.test.ts +150 -1
- package/src/runtimes/local/local-thread-runtime-core.ts +4 -2
- package/src/runtimes/remote-thread-list/optimistic-state.test.ts +63 -0
- package/src/runtimes/remote-thread-list/optimistic-state.ts +49 -17
- package/src/runtimes/remote-thread-list/types.ts +1 -1
- package/src/runtimes/tool-invocations/EDGE_CASES.md +13 -19
- package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +127 -6
- package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +4 -2
- package/src/subscribable/subscribable.test.ts +58 -0
- package/src/subscribable/subscribable.ts +13 -3
- package/src/tests/OptimisticState-list-race.test.ts +33 -0
- package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-order.test.ts +223 -0
- package/src/tests/base-composer-runtime-core-addAttachment.test.ts +44 -0
- package/src/tests/base-composer-runtime-core-send.test.ts +433 -5
- package/src/tests/default-edit-composer-runtime-core.test.ts +43 -5
- package/src/tests/event-subscription-listener-errors.test.ts +205 -0
- package/src/tests/remote-thread-list-test-helpers.ts +14 -7
- package/src/tests/thread-message-like.test.ts +57 -0
- package/src/types/attachment.ts +5 -0
- package/src/types/message.ts +1 -0
- package/src/utils/data-url.test.ts +50 -0
- package/src/utils/data-url.ts +9 -0
- package/src/utils/notify-event-listeners.ts +30 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { renderHook } from "@testing-library/react";
|
|
4
|
+
import type { AssistantCloud } from "assistant-cloud";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { useAssistantCloudThreadHistoryAdapter } from "./AssistantCloudThreadHistoryAdapter";
|
|
7
|
+
|
|
8
|
+
const mocks = vi.hoisted(() => {
|
|
9
|
+
const threadListItem = {
|
|
10
|
+
getState: () => ({ remoteId: "thread-1" }),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
assistantClient: {
|
|
15
|
+
threadListItem: () => threadListItem,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => ({
|
|
21
|
+
...(await importOriginal<typeof import("@assistant-ui/store")>()),
|
|
22
|
+
useAui: () => mocks.assistantClient,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const makeCloud = () =>
|
|
26
|
+
({
|
|
27
|
+
threads: {
|
|
28
|
+
messages: {
|
|
29
|
+
list: vi.fn().mockResolvedValue({ messages: [] }),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}) as unknown as AssistantCloud;
|
|
33
|
+
|
|
34
|
+
describe("useAssistantCloudThreadHistoryAdapter", () => {
|
|
35
|
+
it("refreshes formatted persistence when the Cloud client changes", async () => {
|
|
36
|
+
const firstCloud = makeCloud();
|
|
37
|
+
const secondCloud = makeCloud();
|
|
38
|
+
const cloudRef = { current: firstCloud };
|
|
39
|
+
const { result } = renderHook(() =>
|
|
40
|
+
useAssistantCloudThreadHistoryAdapter(cloudRef),
|
|
41
|
+
);
|
|
42
|
+
const formatted = result.current.withFormat<
|
|
43
|
+
{ id: string },
|
|
44
|
+
Record<string, unknown>
|
|
45
|
+
>({
|
|
46
|
+
format: "test",
|
|
47
|
+
encode: ({ message }) => message,
|
|
48
|
+
decode: ({ parent_id, content }) => ({
|
|
49
|
+
parentId: parent_id,
|
|
50
|
+
message: content as { id: string },
|
|
51
|
+
}),
|
|
52
|
+
getId: (message) => message.id,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await formatted.load();
|
|
56
|
+
await formatted.load();
|
|
57
|
+
|
|
58
|
+
expect(firstCloud.threads.messages.list).toHaveBeenCalledTimes(2);
|
|
59
|
+
|
|
60
|
+
cloudRef.current = secondCloud;
|
|
61
|
+
await formatted.load();
|
|
62
|
+
|
|
63
|
+
expect(firstCloud.threads.messages.list).toHaveBeenCalledTimes(2);
|
|
64
|
+
expect(secondCloud.threads.messages.list).toHaveBeenCalledOnce();
|
|
65
|
+
expect(secondCloud.threads.messages.list).toHaveBeenCalledWith("thread-1", {
|
|
66
|
+
format: "test",
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -32,7 +32,7 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
|
|
|
32
32
|
if (!globalPersistence.has(key)) {
|
|
33
33
|
globalPersistence.set(
|
|
34
34
|
key,
|
|
35
|
-
new CloudMessagePersistence(this.cloudRef.current),
|
|
35
|
+
new CloudMessagePersistence(() => this.cloudRef.current),
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
return globalPersistence.get(key)!;
|
|
@@ -57,6 +57,22 @@ describe("CloudFileAttachmentAdapter", () => {
|
|
|
57
57
|
]);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
it("uploads when Web Crypto is unavailable", async () => {
|
|
61
|
+
vi.stubGlobal("crypto", undefined);
|
|
62
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true }));
|
|
63
|
+
const adapter = new CloudFileAttachmentAdapter(makeCloud());
|
|
64
|
+
|
|
65
|
+
const yields = await drain(adapter);
|
|
66
|
+
|
|
67
|
+
expect(yields).toHaveLength(2);
|
|
68
|
+
expect(yields[0]?.id).toBeTruthy();
|
|
69
|
+
expect(yields[1]?.id).toBe(yields[0]?.id);
|
|
70
|
+
expect(yields.at(-1)?.status).toEqual({
|
|
71
|
+
type: "requires-action",
|
|
72
|
+
reason: "composer-send",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
60
76
|
it("marks the attachment incomplete when the upload returns an HTTP error", async () => {
|
|
61
77
|
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
62
78
|
vi.stubGlobal(
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from "../../../types/attachment";
|
|
7
7
|
import type { ThreadUserMessagePart } from "../../../types/message";
|
|
8
8
|
import type { AttachmentAdapter } from "../../../adapters/attachment";
|
|
9
|
+
import { generateId } from "../../../utils/id";
|
|
9
10
|
|
|
10
11
|
const guessAttachmentType = (
|
|
11
12
|
contentType: string,
|
|
@@ -27,7 +28,7 @@ export class CloudFileAttachmentAdapter implements AttachmentAdapter {
|
|
|
27
28
|
}: {
|
|
28
29
|
file: File;
|
|
29
30
|
}): AsyncGenerator<PendingAttachment, void> {
|
|
30
|
-
const id =
|
|
31
|
+
const id = generateId();
|
|
31
32
|
const type = guessAttachmentType(file.type);
|
|
32
33
|
let attachment: PendingAttachment = {
|
|
33
34
|
id,
|
|
@@ -99,4 +99,25 @@ describe("useExternalMessageConverter", () => {
|
|
|
99
99
|
text: "HELLO",
|
|
100
100
|
});
|
|
101
101
|
});
|
|
102
|
+
|
|
103
|
+
it.each([
|
|
104
|
+
["false", false],
|
|
105
|
+
["zero", 0],
|
|
106
|
+
["an empty string", ""],
|
|
107
|
+
])("preserves %s assistant state", (_label, state) => {
|
|
108
|
+
const callback: useExternalMessageConverter.Callback<TestMessage> = (
|
|
109
|
+
message,
|
|
110
|
+
) => ({
|
|
111
|
+
role: message.role,
|
|
112
|
+
id: message.id,
|
|
113
|
+
content: [{ type: "text", text: message.text }],
|
|
114
|
+
...(message.role === "assistant"
|
|
115
|
+
? { metadata: { unstable_state: state } }
|
|
116
|
+
: undefined),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const { result } = renderConverter({ callback });
|
|
120
|
+
|
|
121
|
+
expect(result.current[1]?.metadata.unstable_state).toBe(state);
|
|
122
|
+
});
|
|
102
123
|
});
|
|
@@ -170,7 +170,7 @@ const joinExternalMessages = (
|
|
|
170
170
|
|
|
171
171
|
if (output.metadata) {
|
|
172
172
|
assistantMessage.metadata ??= {};
|
|
173
|
-
if (output.metadata.unstable_state) {
|
|
173
|
+
if (output.metadata.unstable_state !== undefined) {
|
|
174
174
|
assistantMessage.metadata.unstable_state =
|
|
175
175
|
output.metadata.unstable_state;
|
|
176
176
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { render, waitFor } from "@testing-library/react";
|
|
4
|
+
import { StrictMode } from "react";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { ChatModelAdapter } from "../../runtime/utils/chat-model-adapter";
|
|
7
|
+
import { AssistantRuntimeProvider } from "../AssistantRuntimeProvider";
|
|
8
|
+
import { useLocalRuntime } from "./useLocalRuntime";
|
|
9
|
+
|
|
10
|
+
const chatModel: ChatModelAdapter = {
|
|
11
|
+
run: async () => ({ content: [] }),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("useLocalRuntime", () => {
|
|
19
|
+
it("handles rejected history loads", async () => {
|
|
20
|
+
const error = new Error("history unavailable");
|
|
21
|
+
const consoleError = vi
|
|
22
|
+
.spyOn(console, "error")
|
|
23
|
+
.mockImplementation(() => {});
|
|
24
|
+
const history = {
|
|
25
|
+
load: vi.fn().mockRejectedValue(error),
|
|
26
|
+
append: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const App = () => {
|
|
30
|
+
const runtime = useLocalRuntime(chatModel, { adapters: { history } });
|
|
31
|
+
return (
|
|
32
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
33
|
+
<div />
|
|
34
|
+
</AssistantRuntimeProvider>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const renderApp = () => (
|
|
39
|
+
<StrictMode>
|
|
40
|
+
<App />
|
|
41
|
+
</StrictMode>
|
|
42
|
+
);
|
|
43
|
+
const { rerender } = render(renderApp());
|
|
44
|
+
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
47
|
+
"[assistant-ui] local thread history load failed:",
|
|
48
|
+
error,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
rerender(renderApp());
|
|
53
|
+
expect(history.load).toHaveBeenCalledTimes(1);
|
|
54
|
+
expect(consoleError).toHaveBeenCalledTimes(1);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -35,6 +35,7 @@ const useLocalThreadRuntime = (
|
|
|
35
35
|
const [runtime] = useState(() => new LocalRuntimeCore(opt, initialMessages));
|
|
36
36
|
|
|
37
37
|
const threadIdRef = useRef<string | undefined>(undefined);
|
|
38
|
+
const historyLoadPromiseRef = useRef<Promise<void> | undefined>(undefined);
|
|
38
39
|
threadIdRef.current = useAuiState((s) => s.threadListItem.remoteId);
|
|
39
40
|
|
|
40
41
|
useEffect(() => {
|
|
@@ -51,9 +52,20 @@ const useLocalThreadRuntime = (
|
|
|
51
52
|
|
|
52
53
|
useEffect(() => {
|
|
53
54
|
runtime.threads.getMainThreadRuntimeCore().__internal_setOptions(opt);
|
|
54
|
-
runtime.threads.getMainThreadRuntimeCore().__internal_load();
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const loadPromise = runtime.threads
|
|
59
|
+
.getMainThreadRuntimeCore()
|
|
60
|
+
.__internal_load();
|
|
61
|
+
if (historyLoadPromiseRef.current === loadPromise) return;
|
|
62
|
+
|
|
63
|
+
historyLoadPromiseRef.current = loadPromise;
|
|
64
|
+
void loadPromise.catch((error: unknown) => {
|
|
65
|
+
console.error("[assistant-ui] local thread history load failed:", error);
|
|
66
|
+
});
|
|
67
|
+
}, [runtime]);
|
|
68
|
+
|
|
57
69
|
useEffect(() => {
|
|
58
70
|
if (!modelContext) return undefined;
|
|
59
71
|
return runtime.registerModelContextProvider(modelContext);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { CompleteAttachment } from "../../types/attachment";
|
|
3
|
+
import type { ThreadRuntimeCoreBinding } from "./thread-runtime";
|
|
4
|
+
import {
|
|
5
|
+
MessageRuntimeImpl,
|
|
6
|
+
type MessageState,
|
|
7
|
+
type MessageStateBinding,
|
|
8
|
+
} from "./message-runtime";
|
|
9
|
+
|
|
10
|
+
const messagePath = {
|
|
11
|
+
ref: "threads.main.messages[0]",
|
|
12
|
+
threadSelector: { type: "main" as const },
|
|
13
|
+
messageSelector: { type: "index" as const, index: 0 },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const attachment: CompleteAttachment = {
|
|
17
|
+
id: "attachment-1",
|
|
18
|
+
type: "file",
|
|
19
|
+
name: "notes.txt",
|
|
20
|
+
contentType: "text/plain",
|
|
21
|
+
content: [{ type: "text", text: "notes" }],
|
|
22
|
+
status: { type: "complete" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const message: MessageState = {
|
|
26
|
+
id: "message-1",
|
|
27
|
+
role: "assistant",
|
|
28
|
+
createdAt: new Date(0),
|
|
29
|
+
content: [
|
|
30
|
+
{ type: "text", text: "Hello" },
|
|
31
|
+
{
|
|
32
|
+
type: "tool-call",
|
|
33
|
+
toolCallId: "call-1",
|
|
34
|
+
toolName: "weather",
|
|
35
|
+
args: {},
|
|
36
|
+
argsText: "{}",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
attachments: [attachment],
|
|
40
|
+
status: { type: "complete", reason: "stop" },
|
|
41
|
+
metadata: {
|
|
42
|
+
unstable_state: null,
|
|
43
|
+
unstable_annotations: [],
|
|
44
|
+
unstable_data: [],
|
|
45
|
+
steps: [],
|
|
46
|
+
custom: {},
|
|
47
|
+
},
|
|
48
|
+
parentId: null,
|
|
49
|
+
index: 0,
|
|
50
|
+
isLast: true,
|
|
51
|
+
branchNumber: 1,
|
|
52
|
+
branchCount: 1,
|
|
53
|
+
speech: undefined,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const messageBinding: MessageStateBinding = {
|
|
57
|
+
path: messagePath,
|
|
58
|
+
getState: () => message,
|
|
59
|
+
subscribe: () => () => {},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const threadBinding = {
|
|
63
|
+
subscribe: () => () => {},
|
|
64
|
+
} as unknown as ThreadRuntimeCoreBinding;
|
|
65
|
+
|
|
66
|
+
describe("MessageRuntimeImpl paths", () => {
|
|
67
|
+
it("appends nested selectors to the message path", () => {
|
|
68
|
+
const runtime = new MessageRuntimeImpl(messageBinding, threadBinding);
|
|
69
|
+
|
|
70
|
+
expect(runtime.composer.path.ref).toBe("threads.main.messages[0].composer");
|
|
71
|
+
expect(runtime.getMessagePartByIndex(0).path.ref).toBe(
|
|
72
|
+
"threads.main.messages[0].content[0]",
|
|
73
|
+
);
|
|
74
|
+
expect(runtime.getMessagePartByToolCallId("call-1").path.ref).toBe(
|
|
75
|
+
'threads.main.messages[0].content[toolCallId="call-1"]',
|
|
76
|
+
);
|
|
77
|
+
expect(runtime.getAttachmentByIndex(0).path.ref).toBe(
|
|
78
|
+
"threads.main.messages[0].attachments[0]",
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -142,7 +142,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
|
|
|
142
142
|
new NestedSubscriptionSubject({
|
|
143
143
|
path: {
|
|
144
144
|
...this.path,
|
|
145
|
-
ref: `${this.path.ref}
|
|
145
|
+
ref: `${this.path.ref}.composer`,
|
|
146
146
|
composerSource: "edit",
|
|
147
147
|
},
|
|
148
148
|
getState: this._getEditComposerRuntimeCore,
|
|
@@ -269,7 +269,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
|
|
|
269
269
|
new ShallowMemoizeSubject({
|
|
270
270
|
path: {
|
|
271
271
|
...this.path,
|
|
272
|
-
ref: `${this.path.ref}
|
|
272
|
+
ref: `${this.path.ref}.content[${idx}]`,
|
|
273
273
|
messagePartSelector: { type: "index", index: idx },
|
|
274
274
|
},
|
|
275
275
|
getState: () => {
|
|
@@ -287,9 +287,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
|
|
|
287
287
|
new ShallowMemoizeSubject({
|
|
288
288
|
path: {
|
|
289
289
|
...this.path,
|
|
290
|
-
ref:
|
|
291
|
-
this.path.ref +
|
|
292
|
-
`${this.path.ref}.content[toolCallId=${JSON.stringify(toolCallId)}]`,
|
|
290
|
+
ref: `${this.path.ref}.content[toolCallId=${JSON.stringify(toolCallId)}]`,
|
|
293
291
|
messagePartSelector: { type: "toolCallId", toolCallId },
|
|
294
292
|
},
|
|
295
293
|
getState: () => {
|
|
@@ -313,7 +311,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
|
|
|
313
311
|
new ShallowMemoizeSubject({
|
|
314
312
|
path: {
|
|
315
313
|
...this.path,
|
|
316
|
-
ref: `${this.path.ref}
|
|
314
|
+
ref: `${this.path.ref}.attachments[${idx}]`,
|
|
317
315
|
attachmentSource: "message",
|
|
318
316
|
attachmentSelector: { type: "index", index: idx },
|
|
319
317
|
},
|
|
@@ -2,6 +2,7 @@ import type { Unsubscribe } from "../../types/unsubscribe";
|
|
|
2
2
|
import type { SubscribableWithState } from "../../subscribable/subscribable";
|
|
3
3
|
import type { ThreadListItemRuntimePath } from "./paths";
|
|
4
4
|
import type { ThreadListRuntimeCoreBinding } from "./thread-list-runtime";
|
|
5
|
+
import { notifyEventListeners } from "../../utils/notify-event-listeners";
|
|
5
6
|
|
|
6
7
|
export type ThreadListItemEventPayload = {
|
|
7
8
|
/**
|
|
@@ -162,7 +163,11 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
|
|
|
162
163
|
|
|
163
164
|
if (event === "switchedTo" && !newIsMain) return;
|
|
164
165
|
if (event === "switchedAway" && newIsMain) return;
|
|
165
|
-
(
|
|
166
|
+
notifyEventListeners(
|
|
167
|
+
[callback as (payload?: unknown) => void],
|
|
168
|
+
{},
|
|
169
|
+
`Thread list item "${event}"`,
|
|
170
|
+
);
|
|
166
171
|
});
|
|
167
172
|
}
|
|
168
173
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
isCreateAttachment,
|
|
3
|
+
type Attachment,
|
|
4
|
+
type CompleteAttachment,
|
|
5
|
+
type CreateAttachment,
|
|
6
|
+
type PendingAttachment,
|
|
6
7
|
} from "../../types/attachment";
|
|
7
8
|
import type { MessageRole, AppendMessage } from "../../types/message";
|
|
8
9
|
import type { QuoteInfo } from "../../types/quote";
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
type QueueItemState,
|
|
29
30
|
} from "../../store/scopes/queue-item";
|
|
30
31
|
import { generateId } from "../../utils/id";
|
|
32
|
+
import { notifyEventListeners } from "../../utils/notify-event-listeners";
|
|
31
33
|
|
|
32
34
|
const isAttachmentComplete = (a: Attachment): a is CompleteAttachment =>
|
|
33
35
|
a.status.type === "complete";
|
|
@@ -133,6 +135,10 @@ export abstract class BaseComposerRuntimeCore
|
|
|
133
135
|
this._notifySubscribers();
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
protected _isSending = false;
|
|
139
|
+
private _removedDuringSend = new Set<string>();
|
|
140
|
+
private _sendGeneration = 0;
|
|
141
|
+
|
|
136
142
|
private _emptyTextAndAttachments() {
|
|
137
143
|
this._attachments = [];
|
|
138
144
|
this._text = "";
|
|
@@ -148,6 +154,14 @@ export abstract class BaseComposerRuntimeCore
|
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
public async reset() {
|
|
157
|
+
// A send whose adapter never settles must not brick the composer; reset is
|
|
158
|
+
// the escape hatch that releases the in-flight lock. Bumping the generation
|
|
159
|
+
// invalidates that send entirely so a late-settling upload can neither
|
|
160
|
+
// append the discarded draft nor touch a newer send's lock.
|
|
161
|
+
this._sendGeneration++;
|
|
162
|
+
this._isSending = false;
|
|
163
|
+
this._removedDuringSend.clear();
|
|
164
|
+
|
|
151
165
|
if (
|
|
152
166
|
this._attachments.length === 0 &&
|
|
153
167
|
this._text === "" &&
|
|
@@ -175,7 +189,7 @@ export abstract class BaseComposerRuntimeCore
|
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
public async send(options?: SendOptions) {
|
|
178
|
-
if (!this.canSend) return;
|
|
192
|
+
if (!this.canSend || this._isSending) return;
|
|
179
193
|
|
|
180
194
|
if (this._dictationSession) {
|
|
181
195
|
this._dictationSession.cancel();
|
|
@@ -183,47 +197,75 @@ export abstract class BaseComposerRuntimeCore
|
|
|
183
197
|
}
|
|
184
198
|
|
|
185
199
|
const adapter = this.getAttachmentAdapter();
|
|
186
|
-
const attachments
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const result = await adapter.send(a);
|
|
193
|
-
return result as CompleteAttachment;
|
|
194
|
-
}),
|
|
195
|
-
)
|
|
196
|
-
: [];
|
|
200
|
+
const attachmentTasks = this.attachments.map(async (a) => {
|
|
201
|
+
if (isAttachmentComplete(a)) return a;
|
|
202
|
+
if (!adapter) throw new Error("Attachments are not supported");
|
|
203
|
+
const result = await adapter.send(a);
|
|
204
|
+
return result as CompleteAttachment;
|
|
205
|
+
});
|
|
197
206
|
|
|
198
207
|
const originalAttachments = this.attachments;
|
|
199
208
|
const text = this.text;
|
|
200
209
|
const quote = this._quote;
|
|
201
210
|
this._quote = undefined;
|
|
202
|
-
this.
|
|
211
|
+
this._text = "";
|
|
212
|
+
this._isSending = true;
|
|
213
|
+
const generation = ++this._sendGeneration;
|
|
214
|
+
this._notifySubscribers();
|
|
203
215
|
|
|
204
|
-
let resolvedAttachments:
|
|
216
|
+
let resolvedAttachments: CompleteAttachment[];
|
|
205
217
|
try {
|
|
206
|
-
resolvedAttachments = await
|
|
218
|
+
resolvedAttachments = await Promise.all(attachmentTasks);
|
|
207
219
|
} catch (e) {
|
|
208
|
-
if (
|
|
209
|
-
this.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
220
|
+
if (generation === this._sendGeneration) {
|
|
221
|
+
if (!this.text.trim() && this._quote === undefined) {
|
|
222
|
+
this._text = text;
|
|
223
|
+
this._quote = quote;
|
|
224
|
+
this._notifySubscribers();
|
|
225
|
+
}
|
|
226
|
+
// Promise.all rejects on the first failure, but sibling uploads from
|
|
227
|
+
// this batch keep running; the send rejects immediately while the
|
|
228
|
+
// retry lock is held until they settle, or a retry could re-send
|
|
229
|
+
// attachments that are still in flight.
|
|
230
|
+
void Promise.allSettled(attachmentTasks).then(() => {
|
|
231
|
+
if (generation !== this._sendGeneration) return;
|
|
232
|
+
this._removedDuringSend.clear();
|
|
233
|
+
this._isSending = false;
|
|
234
|
+
this._notifySubscribers();
|
|
235
|
+
});
|
|
213
236
|
}
|
|
214
237
|
throw e;
|
|
215
238
|
}
|
|
216
239
|
|
|
240
|
+
// A reset during the upload discarded this send's draft; the settled
|
|
241
|
+
// uploads must not append it or touch the lock a newer send may own.
|
|
242
|
+
if (generation !== this._sendGeneration) return;
|
|
243
|
+
|
|
244
|
+
// Drop by id, not wholesale: the user may have added or removed chips while
|
|
245
|
+
// the upload was in flight.
|
|
246
|
+
const sentIds = new Set(originalAttachments.map((a) => a.id));
|
|
247
|
+
this._attachments = this._attachments.filter((a) => !sentIds.has(a.id));
|
|
248
|
+
this._isSending = false;
|
|
249
|
+
this._notifySubscribers();
|
|
250
|
+
|
|
251
|
+
// An attachment removed mid-upload can't be cancelled, but it can still be
|
|
252
|
+
// dropped from the outgoing message instead of silently being sent anyway.
|
|
253
|
+
const finalAttachments = resolvedAttachments.filter(
|
|
254
|
+
(a) => !this._removedDuringSend.has(a.id),
|
|
255
|
+
);
|
|
256
|
+
this._removedDuringSend.clear();
|
|
257
|
+
|
|
217
258
|
const message: Omit<AppendMessage, "parentId" | "sourceId"> = {
|
|
218
259
|
createdAt: new Date(),
|
|
219
260
|
role: this.role,
|
|
220
261
|
content: text ? [{ type: "text", text }] : [],
|
|
221
|
-
attachments:
|
|
262
|
+
attachments: finalAttachments,
|
|
222
263
|
runConfig: this.runConfig,
|
|
223
264
|
metadata: { custom: { ...(quote ? { quote } : {}) } },
|
|
224
265
|
};
|
|
225
266
|
|
|
226
|
-
this.handleSend(message, options);
|
|
267
|
+
const sendTask = this.handleSend(message, options);
|
|
268
|
+
if (sendTask) void sendTask.catch(() => {});
|
|
227
269
|
this._notifyEventSubscribers("send", {});
|
|
228
270
|
}
|
|
229
271
|
|
|
@@ -241,11 +283,11 @@ export abstract class BaseComposerRuntimeCore
|
|
|
241
283
|
protected abstract handleSend(
|
|
242
284
|
message: Omit<AppendMessage, "parentId" | "sourceId">,
|
|
243
285
|
options?: SendOptions,
|
|
244
|
-
): void
|
|
286
|
+
): void | Promise<void>;
|
|
245
287
|
protected abstract handleCancel(): void;
|
|
246
288
|
|
|
247
289
|
async addAttachment(fileOrAttachment: File | CreateAttachment) {
|
|
248
|
-
if (
|
|
290
|
+
if (isCreateAttachment(fileOrAttachment)) {
|
|
249
291
|
const adapter = this.getAttachmentAdapter();
|
|
250
292
|
if (
|
|
251
293
|
adapter &&
|
|
@@ -392,12 +434,16 @@ export abstract class BaseComposerRuntimeCore
|
|
|
392
434
|
if (index === -1) throw new Error("Attachment not found");
|
|
393
435
|
const attachment = this._attachments[index]!;
|
|
394
436
|
|
|
437
|
+
// A send in flight may already be uploading this attachment; the upload
|
|
438
|
+
// can't be cancelled, so mark it to be dropped from the outgoing message
|
|
439
|
+
// before any await gives the upload a chance to settle first.
|
|
440
|
+
if (this._isSending) this._removedDuringSend.add(attachmentId);
|
|
441
|
+
|
|
395
442
|
if (!isAttachmentComplete(attachment)) {
|
|
396
443
|
const adapter = this.getAttachmentAdapter();
|
|
397
444
|
if (!adapter) throw new Error("Attachments are not supported");
|
|
398
445
|
await adapter.remove(attachment);
|
|
399
446
|
}
|
|
400
|
-
|
|
401
447
|
this._attachments = this._attachments.filter((a) => a.id !== attachmentId);
|
|
402
448
|
this._notifySubscribers();
|
|
403
449
|
}
|
|
@@ -563,7 +609,7 @@ export abstract class BaseComposerRuntimeCore
|
|
|
563
609
|
const subscribers = this._eventSubscribers.get(event);
|
|
564
610
|
if (!subscribers) return;
|
|
565
611
|
|
|
566
|
-
|
|
612
|
+
notifyEventListeners(subscribers, payload, `Composer runtime "${event}"`);
|
|
567
613
|
}
|
|
568
614
|
|
|
569
615
|
public unstable_on<E extends ComposerRuntimeEventType>(
|
|
@@ -34,6 +34,7 @@ import type { FeedbackAdapter } from "../../adapters/feedback";
|
|
|
34
34
|
import type { AttachmentAdapter } from "../../adapters/attachment";
|
|
35
35
|
import type { RealtimeVoiceAdapter } from "../../adapters/voice";
|
|
36
36
|
import type { ThreadMessageLike } from "../utils/thread-message-like";
|
|
37
|
+
import { notifyEventListeners } from "../../utils/notify-event-listeners";
|
|
37
38
|
|
|
38
39
|
type BaseThreadAdapters = {
|
|
39
40
|
speech?: SpeechSynthesisAdapter | undefined;
|
|
@@ -182,7 +183,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
|
|
|
182
183
|
const subscribers = this._eventSubscribers.get(event);
|
|
183
184
|
if (!subscribers) return;
|
|
184
185
|
|
|
185
|
-
|
|
186
|
+
notifyEventListeners(subscribers, payload, `Thread runtime "${event}"`);
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
public subscribe(callback: () => void): Unsubscribe {
|
|
@@ -15,7 +15,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
public get canSend() {
|
|
18
|
-
return !this.isEmpty;
|
|
18
|
+
return !this.isEmpty && !this._isSending;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
protected getAttachmentAdapter() {
|
|
@@ -80,6 +80,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
|
|
|
80
80
|
message: Omit<AppendMessage, "parentId" | "sourceId">,
|
|
81
81
|
options?: SendOptions,
|
|
82
82
|
) {
|
|
83
|
+
let appendTask: void | Promise<void> = undefined;
|
|
83
84
|
const text = getThreadMessageText(message as AppendMessage);
|
|
84
85
|
const attachmentsChanged = !attachmentsEqual(
|
|
85
86
|
message.attachments ?? [],
|
|
@@ -114,7 +115,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
|
|
|
114
115
|
message,
|
|
115
116
|
composerMetadata,
|
|
116
117
|
);
|
|
117
|
-
this.runtime.append({
|
|
118
|
+
appendTask = this.runtime.append({
|
|
118
119
|
...enriched,
|
|
119
120
|
content,
|
|
120
121
|
parentId: this._parentId,
|
|
@@ -124,6 +125,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
this.handleCancel();
|
|
128
|
+
return appendTask;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
public handleCancel() {
|
|
@@ -23,7 +23,7 @@ export class DefaultThreadComposerRuntimeCore
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
public get canSend() {
|
|
26
|
-
return !this.isEmpty && !this.runtime.isSendDisabled;
|
|
26
|
+
return !this.isEmpty && !this.runtime.isSendDisabled && !this._isSending;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
public override get queue(): readonly QueueItemState[] {
|
|
@@ -94,7 +94,7 @@ export class DefaultThreadComposerRuntimeCore
|
|
|
94
94
|
);
|
|
95
95
|
const enriched = this.enrichWithComposerMetadata(message, composerMetadata);
|
|
96
96
|
|
|
97
|
-
this.runtime.append({
|
|
97
|
+
return this.runtime.append({
|
|
98
98
|
...(enriched as AppendMessage),
|
|
99
99
|
parentId: this.runtime.messages.at(-1)?.id ?? null,
|
|
100
100
|
sourceId: null,
|