@assistant-ui/core 0.1.15 → 0.1.17
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/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
- package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/primitives/message/MessageGroupedParts.d.ts +104 -0
- package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -0
- package/dist/react/primitives/message/MessageGroupedParts.js +74 -0
- package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -0
- package/dist/react/primitives/message/MessageParts.d.ts +14 -1
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +55 -35
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -3
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +26 -5
- package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -2
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +6 -5
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/utils/groupParts.d.ts +49 -0
- package/dist/react/utils/groupParts.d.ts.map +1 -0
- package/dist/react/utils/groupParts.js +97 -0
- package/dist/react/utils/groupParts.js.map +1 -0
- package/dist/runtime/api/bindings.d.ts +1 -0
- package/dist/runtime/api/bindings.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.d.ts +7 -5
- package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
- package/dist/runtime/api/composer-runtime.js +1 -1
- package/dist/runtime/api/composer-runtime.js.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.d.ts +18 -3
- package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-list-item-runtime.js +1 -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-list-runtime.js +1 -0
- package/dist/runtime/api/thread-list-runtime.js.map +1 -1
- package/dist/runtime/api/thread-runtime.d.ts +5 -5
- package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
- package/dist/runtime/api/thread-runtime.js +1 -1
- package/dist/runtime/api/thread-runtime.js.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +4 -3
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +54 -26
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.d.ts +3 -3
- package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-thread-runtime-core.js +11 -11
- package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +28 -2
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +37 -2
- package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js +2 -2
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +2 -2
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +3 -0
- package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
- package/dist/runtimes/remote-thread-list/types.d.ts +13 -1
- package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
- package/dist/store/runtime-clients/composer-runtime-client.js +5 -6
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/scopes/composer.d.ts +11 -1
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/thread-list-item.d.ts +11 -0
- package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
- package/dist/store/scopes/thread.d.ts +17 -0
- package/dist/store/scopes/thread.d.ts.map +1 -1
- package/dist/subscribable/subscribable.d.ts +4 -4
- package/dist/subscribable/subscribable.d.ts.map +1 -1
- package/dist/subscribable/subscribable.js +4 -4
- package/dist/subscribable/subscribable.js.map +1 -1
- package/package.json +21 -9
- package/src/index.ts +10 -0
- package/src/react/adapters/LocalStorageThreadListAdapter.tsx +2 -0
- package/src/react/index.ts +2 -0
- package/src/react/primitives/message/MessageGroupedParts.tsx +186 -0
- package/src/react/primitives/message/MessageParts.tsx +101 -49
- package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +40 -7
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +6 -12
- package/src/react/utils/groupParts.ts +152 -0
- package/src/runtime/api/bindings.ts +1 -0
- package/src/runtime/api/composer-runtime.ts +14 -11
- package/src/runtime/api/thread-list-item-runtime.ts +28 -6
- package/src/runtime/api/thread-list-runtime.ts +1 -0
- package/src/runtime/api/thread-runtime.ts +11 -7
- package/src/runtime/base/base-composer-runtime-core.ts +99 -35
- package/src/runtime/base/base-thread-runtime-core.ts +21 -12
- package/src/runtime/interfaces/composer-runtime-core.ts +39 -7
- package/src/runtime/interfaces/thread-list-runtime-core.ts +1 -0
- package/src/runtime/interfaces/thread-runtime-core.ts +44 -6
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +2 -2
- package/src/runtimes/local/local-thread-runtime-core.ts +2 -2
- package/src/runtimes/remote-thread-list/remote-thread-state.ts +3 -0
- package/src/runtimes/remote-thread-list/types.ts +13 -1
- package/src/store/runtime-clients/composer-runtime-client.ts +5 -9
- package/src/store/scopes/composer.ts +11 -0
- package/src/store/scopes/thread-list-item.ts +11 -0
- package/src/store/scopes/thread.ts +17 -0
- package/src/subscribable/subscribable.ts +10 -7
- package/src/tests/RemoteThreadListThreadListRuntimeCore-custom-metadata.test.ts +123 -0
- package/src/tests/base-composer-runtime-core-addAttachment.test.ts +217 -0
- package/src/tests/groupParts.test.ts +114 -0
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FC,
|
|
3
|
+
type RefObject,
|
|
3
4
|
useCallback,
|
|
4
5
|
useRef,
|
|
5
6
|
useEffect,
|
|
7
|
+
useLayoutEffect,
|
|
6
8
|
memo,
|
|
7
9
|
type PropsWithChildren,
|
|
8
10
|
type ComponentType,
|
|
9
11
|
useMemo,
|
|
12
|
+
Fragment,
|
|
10
13
|
} from "react";
|
|
11
14
|
import { type UseBoundStore, type StoreApi, create } from "zustand";
|
|
12
15
|
import { useAui } from "@assistant-ui/store";
|
|
@@ -23,6 +26,15 @@ type RemoteThreadListHook = () => AssistantRuntime;
|
|
|
23
26
|
type RemoteThreadListHookInstance = {
|
|
24
27
|
runtime?: ThreadRuntimeCore;
|
|
25
28
|
};
|
|
29
|
+
|
|
30
|
+
const ProviderRenderDetector: FC<{
|
|
31
|
+
detectorRef: RefObject<boolean>;
|
|
32
|
+
}> = ({ detectorRef }) => {
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
detectorRef.current = true;
|
|
35
|
+
}, [detectorRef]);
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
26
38
|
export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
27
39
|
private useRuntimeHook: UseBoundStore<
|
|
28
40
|
StoreApi<{ useRuntime: RemoteThreadListHook }>
|
|
@@ -82,9 +94,11 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
// Rendered outside the user's Provider so deferred `children` cannot strand the binding.
|
|
98
|
+
private _RuntimeBinder: FC<PropsWithChildren<{ threadId: string }>> = ({
|
|
99
|
+
threadId,
|
|
100
|
+
children,
|
|
101
|
+
}) => {
|
|
88
102
|
const { useRuntime } = this.useRuntimeHook();
|
|
89
103
|
const runtime = useRuntime();
|
|
90
104
|
|
|
@@ -138,7 +152,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
138
152
|
});
|
|
139
153
|
}, [runtime, aui]);
|
|
140
154
|
|
|
141
|
-
return
|
|
155
|
+
return <>{children}</>;
|
|
142
156
|
};
|
|
143
157
|
|
|
144
158
|
private _OuterActiveThreadProvider: FC<{
|
|
@@ -150,11 +164,30 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
|
|
|
150
164
|
[threadId],
|
|
151
165
|
);
|
|
152
166
|
|
|
167
|
+
const detectorRef = useRef(false);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (process.env.NODE_ENV !== "production" && Provider !== Fragment) {
|
|
170
|
+
const id = setTimeout(() => {
|
|
171
|
+
if (!detectorRef.current) {
|
|
172
|
+
console.warn(
|
|
173
|
+
"RemoteThreadListAdapter.unstable_Provider did not render its `children` synchronously. " +
|
|
174
|
+
"Render `children` on first commit; deferring them behind a loading state, Suspense boundary, " +
|
|
175
|
+
"or `useEffect` gate leaves thread context unavailable to downstream consumers.",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}, 100);
|
|
179
|
+
return () => clearTimeout(id);
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}, [Provider]);
|
|
183
|
+
|
|
153
184
|
return (
|
|
154
185
|
<ThreadListItemRuntimeProvider runtime={runtime}>
|
|
155
|
-
<
|
|
156
|
-
<
|
|
157
|
-
|
|
186
|
+
<this._RuntimeBinder threadId={threadId}>
|
|
187
|
+
<Provider>
|
|
188
|
+
<ProviderRenderDetector detectorRef={detectorRef} />
|
|
189
|
+
</Provider>
|
|
190
|
+
</this._RuntimeBinder>
|
|
158
191
|
</ThreadListItemRuntimeProvider>
|
|
159
192
|
);
|
|
160
193
|
});
|
|
@@ -15,14 +15,7 @@ import {
|
|
|
15
15
|
} from "../../runtimes/remote-thread-list/remote-thread-state";
|
|
16
16
|
import type { RemoteThreadListOptions } from "../../runtimes/remote-thread-list/types";
|
|
17
17
|
import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
|
|
18
|
-
import {
|
|
19
|
-
type ComponentType,
|
|
20
|
-
type FC,
|
|
21
|
-
Fragment,
|
|
22
|
-
type PropsWithChildren,
|
|
23
|
-
useEffect,
|
|
24
|
-
useId,
|
|
25
|
-
} from "react";
|
|
18
|
+
import { type FC, Fragment, useEffect, useId } from "react";
|
|
26
19
|
import { create } from "zustand";
|
|
27
20
|
import { AssistantMessageStream } from "assistant-stream";
|
|
28
21
|
import type { ModelContextProvider } from "../../model-context/types";
|
|
@@ -98,6 +91,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
98
91
|
externalId: thread.externalId,
|
|
99
92
|
status: thread.status,
|
|
100
93
|
title: thread.title,
|
|
94
|
+
custom: thread.custom,
|
|
101
95
|
initializeTask: Promise.resolve({
|
|
102
96
|
remoteId: thread.remoteId,
|
|
103
97
|
externalId: thread.externalId,
|
|
@@ -150,8 +144,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
150
144
|
this,
|
|
151
145
|
);
|
|
152
146
|
this.useProvider = create(() => ({
|
|
153
|
-
Provider:
|
|
154
|
-
Fragment) as ComponentType<PropsWithChildren>,
|
|
147
|
+
Provider: options.adapter.unstable_Provider ?? Fragment,
|
|
155
148
|
}));
|
|
156
149
|
this.__internal_setOptions(options);
|
|
157
150
|
this.switchToNewThread();
|
|
@@ -165,8 +158,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
165
158
|
|
|
166
159
|
this._options = options;
|
|
167
160
|
|
|
168
|
-
const Provider =
|
|
169
|
-
Fragment) as ComponentType<PropsWithChildren>;
|
|
161
|
+
const Provider = options.adapter.unstable_Provider ?? Fragment;
|
|
170
162
|
if (Provider !== this.useProvider.getState().Provider) {
|
|
171
163
|
this.useProvider.setState({ Provider }, true);
|
|
172
164
|
}
|
|
@@ -250,6 +242,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
250
242
|
externalId: remoteMetadata.externalId,
|
|
251
243
|
status: remoteMetadata.status,
|
|
252
244
|
title: remoteMetadata.title,
|
|
245
|
+
custom: remoteMetadata.custom,
|
|
253
246
|
} as RemoteThreadData,
|
|
254
247
|
};
|
|
255
248
|
|
|
@@ -335,6 +328,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
335
328
|
remoteId: undefined,
|
|
336
329
|
externalId: undefined,
|
|
337
330
|
title: undefined,
|
|
331
|
+
custom: undefined,
|
|
338
332
|
} satisfies RemoteThreadData,
|
|
339
333
|
},
|
|
340
334
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarchical adjacent-coalescing grouping for message parts.
|
|
3
|
+
*
|
|
4
|
+
* Given a group path per part (from `groupBy`), builds a tree of group
|
|
5
|
+
* nodes wrapping individual parts. Adjacent parts sharing a path prefix
|
|
6
|
+
* coalesce into the same group; ungrouped parts are direct children of
|
|
7
|
+
* the root.
|
|
8
|
+
*
|
|
9
|
+
* Each node gets a structural `nodeKey` built from sibling indices
|
|
10
|
+
* (`"0.1.0"`), stable under append-only streaming.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Public group key type. Group keys must be prefixed with `group-` so
|
|
15
|
+
* that a unified `switch (part.type)` in the renderer can distinguish
|
|
16
|
+
* a group key (e.g. `"group-thought"`) from a real part type
|
|
17
|
+
* (`"text"`, `"tool-call"`).
|
|
18
|
+
*/
|
|
19
|
+
export type GroupKey<TKey extends `group-${string}` = `group-${string}`> =
|
|
20
|
+
| TKey
|
|
21
|
+
| readonly TKey[]
|
|
22
|
+
| null
|
|
23
|
+
| undefined;
|
|
24
|
+
|
|
25
|
+
export type GroupNode = GroupNodeGroup | GroupNodePart;
|
|
26
|
+
|
|
27
|
+
export interface GroupNodeGroup {
|
|
28
|
+
readonly type: "group";
|
|
29
|
+
/** Current-level group key (last segment of the path). */
|
|
30
|
+
readonly key: string;
|
|
31
|
+
/** Structural React key: sibling-index path, e.g. `"0.1.0"`. */
|
|
32
|
+
readonly nodeKey: string;
|
|
33
|
+
/** Indices of parts in this subtree, in order. */
|
|
34
|
+
readonly indices: readonly number[];
|
|
35
|
+
readonly children: readonly GroupNode[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GroupNodePart {
|
|
39
|
+
readonly type: "part";
|
|
40
|
+
/** Index of the part in the message. */
|
|
41
|
+
readonly index: number;
|
|
42
|
+
/** Structural React key: sibling-index path within parent. */
|
|
43
|
+
readonly nodeKey: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const EMPTY_PATH: readonly string[] = Object.freeze([]);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalize a `groupBy` return value to a path array.
|
|
50
|
+
* `null`/`undefined`/`[]` → `[]` (ungrouped).
|
|
51
|
+
* `"foo"` → `["foo"]`. Arrays pass through.
|
|
52
|
+
*/
|
|
53
|
+
export const normalizeGroupKey = (key: GroupKey): readonly string[] => {
|
|
54
|
+
if (key == null) return EMPTY_PATH;
|
|
55
|
+
if (typeof key === "string") return [key];
|
|
56
|
+
return key;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
interface BuildFrame {
|
|
60
|
+
key: string;
|
|
61
|
+
nodeKey: string;
|
|
62
|
+
indices: number[];
|
|
63
|
+
children: GroupNode[];
|
|
64
|
+
nextChildIdx: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const makeChildNodeKey = (parent: BuildFrame): string => {
|
|
68
|
+
const idx = parent.nextChildIdx++;
|
|
69
|
+
return parent.nodeKey === "" ? String(idx) : `${parent.nodeKey}.${idx}`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build the group tree from an array of normalized group paths.
|
|
74
|
+
* `paths[i]` is the path for part `i`. The output tree contains one
|
|
75
|
+
* `part` node per part and one `group` node per coalesced run.
|
|
76
|
+
*/
|
|
77
|
+
export const buildGroupTree = (
|
|
78
|
+
paths: readonly (readonly string[])[],
|
|
79
|
+
): readonly GroupNode[] => {
|
|
80
|
+
const root: BuildFrame = {
|
|
81
|
+
key: "",
|
|
82
|
+
nodeKey: "",
|
|
83
|
+
indices: [],
|
|
84
|
+
children: [],
|
|
85
|
+
nextChildIdx: 0,
|
|
86
|
+
};
|
|
87
|
+
const stack: BuildFrame[] = [root];
|
|
88
|
+
|
|
89
|
+
const closeTop = (): void => {
|
|
90
|
+
const closing = stack.pop()!;
|
|
91
|
+
const parent = stack[stack.length - 1]!;
|
|
92
|
+
parent.children.push({
|
|
93
|
+
type: "group",
|
|
94
|
+
key: closing.key,
|
|
95
|
+
nodeKey: closing.nodeKey,
|
|
96
|
+
indices: closing.indices,
|
|
97
|
+
children: closing.children,
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < paths.length; i++) {
|
|
102
|
+
const path = paths[i]!;
|
|
103
|
+
|
|
104
|
+
// Find the longest prefix shared between currently-open groups
|
|
105
|
+
// (excluding root) and this part's path.
|
|
106
|
+
let common = 0;
|
|
107
|
+
while (
|
|
108
|
+
common < stack.length - 1 &&
|
|
109
|
+
common < path.length &&
|
|
110
|
+
stack[common + 1]!.key === path[common]
|
|
111
|
+
) {
|
|
112
|
+
common++;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Close groups not on this path.
|
|
116
|
+
while (stack.length - 1 > common) {
|
|
117
|
+
closeTop();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Open new groups down to the part's depth.
|
|
121
|
+
while (stack.length - 1 < path.length) {
|
|
122
|
+
const parent = stack[stack.length - 1]!;
|
|
123
|
+
stack.push({
|
|
124
|
+
key: path[stack.length - 1]!,
|
|
125
|
+
nodeKey: makeChildNodeKey(parent),
|
|
126
|
+
indices: [],
|
|
127
|
+
children: [],
|
|
128
|
+
nextChildIdx: 0,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Push this part as a leaf in the deepest open group (or root).
|
|
133
|
+
const top = stack[stack.length - 1]!;
|
|
134
|
+
top.children.push({
|
|
135
|
+
type: "part",
|
|
136
|
+
index: i,
|
|
137
|
+
nodeKey: makeChildNodeKey(top),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Record the part index in every open ancestor group.
|
|
141
|
+
for (let s = 1; s < stack.length; s++) {
|
|
142
|
+
stack[s]!.indices.push(i);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Close any still-open groups.
|
|
147
|
+
while (stack.length > 1) {
|
|
148
|
+
closeTop();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return root.children;
|
|
152
|
+
};
|
|
@@ -47,4 +47,5 @@ export type ThreadListItemState = {
|
|
|
47
47
|
readonly externalId: string | undefined;
|
|
48
48
|
readonly status: import("../interfaces/thread-list-runtime-core").ThreadListItemStatus;
|
|
49
49
|
readonly title?: string | undefined;
|
|
50
|
+
readonly custom?: Record<string, unknown> | undefined;
|
|
50
51
|
};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
SKIP_UPDATE,
|
|
13
13
|
} from "../../subscribable/subscribable";
|
|
14
14
|
import type {
|
|
15
|
+
ComposerRuntimeEventCallback,
|
|
15
16
|
ComposerRuntimeEventType,
|
|
16
17
|
DictationState,
|
|
17
18
|
EditComposerRuntimeCore,
|
|
@@ -136,8 +137,10 @@ export type ComposerRuntime = {
|
|
|
136
137
|
/**
|
|
137
138
|
* Add an attachment to the composer. Accepts either a standard File object
|
|
138
139
|
* (processed through the AttachmentAdapter) or a CreateAttachment descriptor
|
|
139
|
-
* for external-source attachments (URLs, API data, CMS references)
|
|
140
|
-
*
|
|
140
|
+
* for external-source attachments (URLs, API data, CMS references). External
|
|
141
|
+
* descriptors bypass the adapter's `add()` step but still respect
|
|
142
|
+
* `adapter.accept` when an adapter is configured; without an adapter they
|
|
143
|
+
* are added as-is.
|
|
141
144
|
* @param fileOrAttachment The file or attachment descriptor to add.
|
|
142
145
|
*/
|
|
143
146
|
addAttachment(fileOrAttachment: File | CreateAttachment): Promise<void>;
|
|
@@ -220,9 +223,9 @@ export type ComposerRuntime = {
|
|
|
220
223
|
/**
|
|
221
224
|
* @deprecated This API is still under active development and might change without notice.
|
|
222
225
|
*/
|
|
223
|
-
unstable_on(
|
|
224
|
-
event:
|
|
225
|
-
callback:
|
|
226
|
+
unstable_on<E extends ComposerRuntimeEventType>(
|
|
227
|
+
event: E,
|
|
228
|
+
callback: ComposerRuntimeEventCallback<E>,
|
|
226
229
|
): Unsubscribe;
|
|
227
230
|
};
|
|
228
231
|
|
|
@@ -330,19 +333,19 @@ export abstract class ComposerRuntimeImpl implements ComposerRuntime {
|
|
|
330
333
|
EventSubscriptionSubject<ComposerRuntimeEventType>
|
|
331
334
|
>();
|
|
332
335
|
|
|
333
|
-
public unstable_on(
|
|
334
|
-
event:
|
|
335
|
-
callback:
|
|
336
|
+
public unstable_on<E extends ComposerRuntimeEventType>(
|
|
337
|
+
event: E,
|
|
338
|
+
callback: ComposerRuntimeEventCallback<E>,
|
|
336
339
|
): Unsubscribe {
|
|
337
340
|
let subject = this._eventSubscriptionSubjects.get(event);
|
|
338
341
|
if (!subject) {
|
|
339
|
-
subject = new EventSubscriptionSubject({
|
|
340
|
-
event
|
|
342
|
+
subject = new EventSubscriptionSubject<ComposerRuntimeEventType>({
|
|
343
|
+
event,
|
|
341
344
|
binding: this._core,
|
|
342
345
|
});
|
|
343
346
|
this._eventSubscriptionSubjects.set(event, subject);
|
|
344
347
|
}
|
|
345
|
-
return subject.subscribe(callback);
|
|
348
|
+
return subject.subscribe(callback as (payload?: unknown) => void);
|
|
346
349
|
}
|
|
347
350
|
|
|
348
351
|
public abstract getAttachmentByIndex(idx: number): AttachmentRuntime;
|
|
@@ -3,7 +3,26 @@ import type { SubscribableWithState } from "../../subscribable/subscribable";
|
|
|
3
3
|
import type { ThreadListItemRuntimePath } from "./paths";
|
|
4
4
|
import type { ThreadListRuntimeCoreBinding } from "./thread-list-runtime";
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type ThreadListItemEventPayload = {
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated State-derivable. Compare `s.threads.mainThreadId` against the
|
|
9
|
+
* item's `s.threadListItem.id` via `useAuiState` instead. Kept for backward
|
|
10
|
+
* compatibility.
|
|
11
|
+
*/
|
|
12
|
+
switchedTo: Record<string, never>;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated State-derivable. Compare `s.threads.mainThreadId` against the
|
|
15
|
+
* item's `s.threadListItem.id` via `useAuiState` instead. Kept for backward
|
|
16
|
+
* compatibility.
|
|
17
|
+
*/
|
|
18
|
+
switchedAway: Record<string, never>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ThreadListItemEventType = keyof ThreadListItemEventPayload;
|
|
22
|
+
|
|
23
|
+
export type ThreadListItemEventCallback<E extends ThreadListItemEventType> = (
|
|
24
|
+
payload: ThreadListItemEventPayload[E],
|
|
25
|
+
) => void;
|
|
7
26
|
|
|
8
27
|
import type { ThreadListItemState } from "./bindings";
|
|
9
28
|
import type { ThreadListItemStatus } from "../interfaces/thread-list-runtime-core";
|
|
@@ -27,9 +46,9 @@ export type ThreadListItemRuntime = {
|
|
|
27
46
|
|
|
28
47
|
subscribe(callback: () => void): Unsubscribe;
|
|
29
48
|
|
|
30
|
-
unstable_on(
|
|
31
|
-
event:
|
|
32
|
-
callback:
|
|
49
|
+
unstable_on<E extends ThreadListItemEventType>(
|
|
50
|
+
event: E,
|
|
51
|
+
callback: ThreadListItemEventCallback<E>,
|
|
33
52
|
): Unsubscribe;
|
|
34
53
|
|
|
35
54
|
__internal_getRuntime(): ThreadListItemRuntime;
|
|
@@ -112,7 +131,10 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
|
|
|
112
131
|
return this._threadListBinding.generateTitle(state.id);
|
|
113
132
|
}
|
|
114
133
|
|
|
115
|
-
public unstable_on
|
|
134
|
+
public unstable_on<E extends ThreadListItemEventType>(
|
|
135
|
+
event: E,
|
|
136
|
+
callback: ThreadListItemEventCallback<E>,
|
|
137
|
+
) {
|
|
116
138
|
let prevIsMain = this._core.getState().isMain;
|
|
117
139
|
let prevThreadId = this._core.getState().id;
|
|
118
140
|
return this.subscribe(() => {
|
|
@@ -125,7 +147,7 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
|
|
|
125
147
|
|
|
126
148
|
if (event === "switchedTo" && !newIsMain) return;
|
|
127
149
|
if (event === "switchedAway" && newIsMain) return;
|
|
128
|
-
callback();
|
|
150
|
+
(callback as (payload?: unknown) => void)({});
|
|
129
151
|
});
|
|
130
152
|
}
|
|
131
153
|
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ThreadRuntimeCore,
|
|
5
5
|
SpeechState,
|
|
6
6
|
VoiceSessionState,
|
|
7
|
+
ThreadRuntimeEventCallback,
|
|
7
8
|
ThreadRuntimeEventType,
|
|
8
9
|
StartRunConfig,
|
|
9
10
|
ResumeRunConfig,
|
|
@@ -329,7 +330,10 @@ export type ThreadRuntime = {
|
|
|
329
330
|
muteVoice(): void;
|
|
330
331
|
unmuteVoice(): void;
|
|
331
332
|
|
|
332
|
-
unstable_on
|
|
333
|
+
unstable_on<E extends ThreadRuntimeEventType>(
|
|
334
|
+
event: E,
|
|
335
|
+
callback: ThreadRuntimeEventCallback<E>,
|
|
336
|
+
): Unsubscribe;
|
|
333
337
|
};
|
|
334
338
|
|
|
335
339
|
export class ThreadRuntimeImpl implements ThreadRuntime {
|
|
@@ -601,18 +605,18 @@ export class ThreadRuntimeImpl implements ThreadRuntime {
|
|
|
601
605
|
EventSubscriptionSubject<ThreadRuntimeEventType>
|
|
602
606
|
>();
|
|
603
607
|
|
|
604
|
-
public unstable_on(
|
|
605
|
-
event:
|
|
606
|
-
callback:
|
|
608
|
+
public unstable_on<E extends ThreadRuntimeEventType>(
|
|
609
|
+
event: E,
|
|
610
|
+
callback: ThreadRuntimeEventCallback<E>,
|
|
607
611
|
): Unsubscribe {
|
|
608
612
|
let subject = this._eventSubscriptionSubjects.get(event);
|
|
609
613
|
if (!subject) {
|
|
610
|
-
subject = new EventSubscriptionSubject({
|
|
611
|
-
event
|
|
614
|
+
subject = new EventSubscriptionSubject<ThreadRuntimeEventType>({
|
|
615
|
+
event,
|
|
612
616
|
binding: this._threadBinding,
|
|
613
617
|
});
|
|
614
618
|
this._eventSubscriptionSubjects.set(event, subject);
|
|
615
619
|
}
|
|
616
|
-
return subject.subscribe(callback);
|
|
620
|
+
return subject.subscribe(callback as (payload?: unknown) => void);
|
|
617
621
|
}
|
|
618
622
|
}
|
|
@@ -14,7 +14,10 @@ import {
|
|
|
14
14
|
fileMatchesAccept,
|
|
15
15
|
} from "../../adapters/attachment";
|
|
16
16
|
import type {
|
|
17
|
+
AttachmentAddErrorReason,
|
|
17
18
|
ComposerRuntimeCore,
|
|
19
|
+
ComposerRuntimeEventCallback,
|
|
20
|
+
ComposerRuntimeEventPayload,
|
|
18
21
|
ComposerRuntimeEventType,
|
|
19
22
|
DictationState,
|
|
20
23
|
SendOptions,
|
|
@@ -189,7 +192,7 @@ export abstract class BaseComposerRuntimeCore
|
|
|
189
192
|
};
|
|
190
193
|
|
|
191
194
|
this.handleSend(message, options);
|
|
192
|
-
this._notifyEventSubscribers("send");
|
|
195
|
+
this._notifyEventSubscribers("send", {});
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
public cancel() {
|
|
@@ -204,6 +207,28 @@ export abstract class BaseComposerRuntimeCore
|
|
|
204
207
|
|
|
205
208
|
async addAttachment(fileOrAttachment: File | CreateAttachment) {
|
|
206
209
|
if (!(fileOrAttachment instanceof File)) {
|
|
210
|
+
const adapter = this.getAttachmentAdapter();
|
|
211
|
+
if (
|
|
212
|
+
adapter &&
|
|
213
|
+
!fileMatchesAccept(
|
|
214
|
+
{
|
|
215
|
+
name: fileOrAttachment.name,
|
|
216
|
+
type: fileOrAttachment.contentType ?? "",
|
|
217
|
+
},
|
|
218
|
+
adapter.accept,
|
|
219
|
+
)
|
|
220
|
+
) {
|
|
221
|
+
const message = `File type ${fileOrAttachment.contentType || "unknown"} is not accepted. Accepted types: ${adapter.accept}`;
|
|
222
|
+
const err = new Error(message);
|
|
223
|
+
this._safeEmitAttachmentAddError(
|
|
224
|
+
"not-accepted",
|
|
225
|
+
message,
|
|
226
|
+
undefined,
|
|
227
|
+
err,
|
|
228
|
+
);
|
|
229
|
+
throw err;
|
|
230
|
+
}
|
|
231
|
+
|
|
207
232
|
const a: CompleteAttachment = {
|
|
208
233
|
id: fileOrAttachment.id ?? generateId(),
|
|
209
234
|
type: fileOrAttachment.type ?? "document",
|
|
@@ -213,25 +238,11 @@ export abstract class BaseComposerRuntimeCore
|
|
|
213
238
|
status: { type: "complete" },
|
|
214
239
|
};
|
|
215
240
|
this._attachments = [...this._attachments, a];
|
|
216
|
-
this._notifyEventSubscribers("attachmentAdd");
|
|
217
241
|
this._notifySubscribers();
|
|
242
|
+
this._notifyEventSubscribers("attachmentAdd", {});
|
|
218
243
|
return;
|
|
219
244
|
}
|
|
220
245
|
|
|
221
|
-
const adapter = this.getAttachmentAdapter();
|
|
222
|
-
if (!adapter) throw new Error("Attachments are not supported");
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
!fileMatchesAccept(
|
|
226
|
-
{ name: fileOrAttachment.name, type: fileOrAttachment.type },
|
|
227
|
-
adapter.accept,
|
|
228
|
-
)
|
|
229
|
-
) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`File type ${fileOrAttachment.type || "unknown"} is not accepted. Accepted types: ${adapter.accept}`,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
246
|
const upsertAttachment = (a: PendingAttachment) => {
|
|
236
247
|
const idx = this._attachments.findIndex(
|
|
237
248
|
(attachment) => attachment.id === a.id,
|
|
@@ -249,6 +260,26 @@ export abstract class BaseComposerRuntimeCore
|
|
|
249
260
|
this._notifySubscribers();
|
|
250
261
|
};
|
|
251
262
|
|
|
263
|
+
const adapter = this.getAttachmentAdapter();
|
|
264
|
+
if (!adapter) {
|
|
265
|
+
const message = "Attachments are not supported";
|
|
266
|
+
const err = new Error(message);
|
|
267
|
+
this._safeEmitAttachmentAddError("no-adapter", message, undefined, err);
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
!fileMatchesAccept(
|
|
273
|
+
{ name: fileOrAttachment.name, type: fileOrAttachment.type },
|
|
274
|
+
adapter.accept,
|
|
275
|
+
)
|
|
276
|
+
) {
|
|
277
|
+
const message = `File type ${fileOrAttachment.type || "unknown"} is not accepted. Accepted types: ${adapter.accept}`;
|
|
278
|
+
const err = new Error(message);
|
|
279
|
+
this._safeEmitAttachmentAddError("not-accepted", message, undefined, err);
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
|
|
252
283
|
let lastAttachment: PendingAttachment | undefined;
|
|
253
284
|
try {
|
|
254
285
|
const promiseOrGenerator = adapter.add({ file: fileOrAttachment });
|
|
@@ -268,20 +299,48 @@ export abstract class BaseComposerRuntimeCore
|
|
|
268
299
|
status: { type: "incomplete", reason: "error" },
|
|
269
300
|
});
|
|
270
301
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
302
|
+
this._safeEmitAttachmentAddError(
|
|
303
|
+
"adapter-error",
|
|
304
|
+
e instanceof Error ? e.message : String(e),
|
|
305
|
+
lastAttachment?.id,
|
|
306
|
+
e instanceof Error ? e : undefined,
|
|
307
|
+
);
|
|
276
308
|
throw e;
|
|
277
309
|
}
|
|
278
310
|
|
|
279
311
|
const hasError =
|
|
280
312
|
lastAttachment?.status.type === "incomplete" &&
|
|
281
313
|
lastAttachment.status.reason === "error";
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
314
|
+
if (hasError) {
|
|
315
|
+
this._safeEmitAttachmentAddError(
|
|
316
|
+
"adapter-error",
|
|
317
|
+
"Attachment upload did not complete successfully.",
|
|
318
|
+
lastAttachment?.id,
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
this._notifyEventSubscribers("attachmentAdd", {});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private _safeEmitAttachmentAddError(
|
|
326
|
+
reason: AttachmentAddErrorReason,
|
|
327
|
+
message: string,
|
|
328
|
+
attachmentId?: string,
|
|
329
|
+
error?: Error,
|
|
330
|
+
) {
|
|
331
|
+
try {
|
|
332
|
+
this._notifyEventSubscribers("attachmentAddError", {
|
|
333
|
+
reason,
|
|
334
|
+
message,
|
|
335
|
+
...(attachmentId !== undefined && { attachmentId }),
|
|
336
|
+
...(error !== undefined && { error }),
|
|
337
|
+
});
|
|
338
|
+
} catch (subscriberError) {
|
|
339
|
+
console.error(
|
|
340
|
+
"[assistant-ui] attachmentAddError subscriber threw:",
|
|
341
|
+
subscriberError,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
285
344
|
}
|
|
286
345
|
|
|
287
346
|
async removeAttachment(attachmentId: string) {
|
|
@@ -450,28 +509,33 @@ export abstract class BaseComposerRuntimeCore
|
|
|
450
509
|
|
|
451
510
|
private _eventSubscribers = new Map<
|
|
452
511
|
ComposerRuntimeEventType,
|
|
453
|
-
Set<() => void>
|
|
512
|
+
Set<(payload?: unknown) => void>
|
|
454
513
|
>();
|
|
455
514
|
|
|
456
|
-
protected _notifyEventSubscribers
|
|
515
|
+
protected _notifyEventSubscribers<E extends ComposerRuntimeEventType>(
|
|
516
|
+
event: E,
|
|
517
|
+
payload: ComposerRuntimeEventPayload[E],
|
|
518
|
+
) {
|
|
457
519
|
const subscribers = this._eventSubscribers.get(event);
|
|
458
520
|
if (!subscribers) return;
|
|
459
521
|
|
|
460
|
-
for (const callback of subscribers) callback();
|
|
522
|
+
for (const callback of subscribers) callback(payload);
|
|
461
523
|
}
|
|
462
524
|
|
|
463
|
-
public unstable_on
|
|
464
|
-
|
|
525
|
+
public unstable_on<E extends ComposerRuntimeEventType>(
|
|
526
|
+
event: E,
|
|
527
|
+
callback: ComposerRuntimeEventCallback<E>,
|
|
528
|
+
) {
|
|
529
|
+
const wrapped = callback as (payload?: unknown) => void;
|
|
530
|
+
let subscribers = this._eventSubscribers.get(event);
|
|
465
531
|
if (!subscribers) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
subscribers.add(callback);
|
|
532
|
+
subscribers = new Set();
|
|
533
|
+
this._eventSubscribers.set(event, subscribers);
|
|
469
534
|
}
|
|
535
|
+
subscribers.add(wrapped);
|
|
470
536
|
|
|
471
537
|
return () => {
|
|
472
|
-
|
|
473
|
-
if (!subscribers) return;
|
|
474
|
-
subscribers.delete(callback);
|
|
538
|
+
this._eventSubscribers.get(event)?.delete(wrapped);
|
|
475
539
|
};
|
|
476
540
|
}
|
|
477
541
|
}
|