@assistant-ui/core 0.1.7 → 0.1.9
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 +4 -0
- package/dist/adapters/attachment.d.ts.map +1 -1
- package/dist/adapters/attachment.js +1 -1
- package/dist/adapters/attachment.js.map +1 -1
- package/dist/adapters/index.d.ts +10 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mention.d.ts +24 -0
- package/dist/adapters/mention.d.ts.map +1 -0
- package/dist/adapters/mention.js +42 -0
- package/dist/adapters/mention.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/react/RuntimeAdapter.js +5 -6
- package/dist/react/RuntimeAdapter.js.map +1 -1
- package/dist/react/client/Interactables.d.ts +3 -0
- package/dist/react/client/Interactables.d.ts.map +1 -0
- package/dist/react/client/Interactables.js +173 -0
- package/dist/react/client/Interactables.js.map +1 -0
- package/dist/react/client/Tools.js +5 -6
- package/dist/react/client/Tools.js.map +1 -1
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/model-context/makeInteractable.d.ts +10 -0
- package/dist/react/model-context/makeInteractable.d.ts.map +1 -0
- package/dist/react/model-context/makeInteractable.js +10 -0
- package/dist/react/model-context/makeInteractable.js.map +1 -0
- package/dist/react/model-context/useInteractable.d.ts +16 -0
- package/dist/react/model-context/useInteractable.d.ts.map +1 -0
- package/dist/react/model-context/useInteractable.js +36 -0
- package/dist/react/model-context/useInteractable.js.map +1 -0
- package/dist/react/primitive-hooks/useComposerSend.d.ts +2 -1
- package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
- package/dist/react/primitive-hooks/useComposerSend.js +5 -3
- package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
- package/dist/react/primitives/composer/ComposerQueue.d.ts +31 -0
- package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -0
- package/dist/react/primitives/composer/ComposerQueue.js +30 -0
- package/dist/react/primitives/composer/ComposerQueue.js.map +1 -0
- package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
- package/dist/react/primitives/message/MessageParts.js +2 -0
- package/dist/react/primitives/message/MessageParts.js.map +1 -1
- package/dist/react/providers/QueueItemByIndexProvider.d.ts +6 -0
- package/dist/react/providers/QueueItemByIndexProvider.d.ts.map +1 -0
- package/dist/react/providers/QueueItemByIndexProvider.js +13 -0
- package/dist/react/providers/QueueItemByIndexProvider.js.map +1 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +1 -0
- package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
- package/dist/react/types/scopes/interactables.d.ts +39 -0
- package/dist/react/types/scopes/interactables.d.ts.map +1 -0
- package/dist/react/types/scopes/interactables.js +2 -0
- package/dist/react/types/scopes/interactables.js.map +1 -0
- package/dist/react/types/store-augmentation.d.ts +2 -0
- package/dist/react/types/store-augmentation.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
- package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/base/base-composer-runtime-core.js +33 -8
- package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
- package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
- package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -0
- package/dist/runtime/interfaces/thread-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 +2 -0
- package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
- package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
- package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -0
- package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -0
- package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +9 -0
- package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
- package/dist/runtimes/remote-thread-list/optimistic-state.js +20 -0
- package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
- package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
- package/dist/store/clients/no-op-composer-client.js +4 -0
- package/dist/store/clients/no-op-composer-client.js.map +1 -1
- package/dist/store/clients/runtime-adapter.d.ts +1 -1
- package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
- package/dist/store/clients/runtime-adapter.js +19 -26
- package/dist/store/clients/runtime-adapter.js.map +1 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js.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 +16 -5
- package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
- package/dist/store/scope-registration.d.ts +2 -0
- package/dist/store/scope-registration.d.ts.map +1 -1
- package/dist/store/scopes/composer.d.ts +25 -1
- package/dist/store/scopes/composer.d.ts.map +1 -1
- package/dist/store/scopes/queue-item.d.ts +20 -0
- package/dist/store/scopes/queue-item.d.ts.map +1 -0
- package/dist/store/scopes/queue-item.js +2 -0
- package/dist/store/scopes/queue-item.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mention.d.ts +32 -0
- package/dist/types/mention.d.ts.map +1 -0
- package/dist/types/mention.js +2 -0
- package/dist/types/mention.js.map +1 -0
- package/package.json +11 -11
- package/src/adapters/attachment.ts +1 -1
- package/src/adapters/index.ts +34 -0
- package/src/adapters/mention.ts +77 -0
- package/src/index.ts +11 -0
- package/src/react/RuntimeAdapter.ts +5 -7
- package/src/react/client/Interactables.ts +233 -0
- package/src/react/client/Tools.ts +5 -6
- package/src/react/index.ts +24 -0
- package/src/react/model-context/makeInteractable.ts +21 -0
- package/src/react/model-context/useInteractable.ts +73 -0
- package/src/react/primitive-hooks/useComposerSend.ts +11 -4
- package/src/react/primitives/composer/ComposerQueue.tsx +58 -0
- package/src/react/primitives/message/MessageParts.tsx +2 -0
- package/src/react/providers/QueueItemByIndexProvider.tsx +21 -0
- package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +1 -0
- package/src/react/types/scopes/interactables.ts +44 -0
- package/src/react/types/store-augmentation.ts +2 -0
- package/src/runtime/base/base-composer-runtime-core.ts +45 -9
- package/src/runtime/interfaces/composer-runtime-core.ts +4 -1
- package/src/runtime/interfaces/thread-runtime-core.ts +1 -0
- package/src/runtimes/external-store/external-store-thread-runtime-core.ts +2 -0
- package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
- package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -0
- package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -0
- package/src/runtimes/remote-thread-list/optimistic-state.ts +27 -0
- package/src/store/clients/no-op-composer-client.ts +4 -0
- package/src/store/clients/runtime-adapter.ts +20 -31
- package/src/store/index.ts +7 -0
- package/src/store/runtime-clients/composer-runtime-client.ts +22 -7
- package/src/store/scope-registration.ts +2 -0
- package/src/store/scopes/composer.ts +26 -1
- package/src/store/scopes/queue-item.ts +20 -0
- package/src/tests/OptimisticState-list-race.test.ts +256 -0
- package/src/tests/mention-formatter.test.ts +112 -0
- package/src/types/index.ts +47 -0
- package/src/types/mention.ts +50 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resource,
|
|
3
|
+
tapState,
|
|
4
|
+
tapEffect,
|
|
5
|
+
tapCallback,
|
|
6
|
+
tapRef,
|
|
7
|
+
tapMemo,
|
|
8
|
+
} from "@assistant-ui/tap";
|
|
9
|
+
import {
|
|
10
|
+
tapAssistantClientRef,
|
|
11
|
+
type ClientOutput,
|
|
12
|
+
attachTransformScopes,
|
|
13
|
+
} from "@assistant-ui/store";
|
|
14
|
+
import type {
|
|
15
|
+
InteractablesState,
|
|
16
|
+
InteractableDefinition,
|
|
17
|
+
InteractableRegistration,
|
|
18
|
+
InteractableStateSchema,
|
|
19
|
+
} from "../types/scopes/interactables";
|
|
20
|
+
import { toJSONSchema, toPartialJSONSchema } from "assistant-stream";
|
|
21
|
+
import type { Tool } from "assistant-stream";
|
|
22
|
+
import { ModelContext } from "../../store";
|
|
23
|
+
|
|
24
|
+
function shallowMerge(prev: unknown, partial: unknown): unknown {
|
|
25
|
+
if (
|
|
26
|
+
typeof prev !== "object" ||
|
|
27
|
+
prev === null ||
|
|
28
|
+
typeof partial !== "object" ||
|
|
29
|
+
partial === null ||
|
|
30
|
+
Array.isArray(prev) ||
|
|
31
|
+
Array.isArray(partial)
|
|
32
|
+
) {
|
|
33
|
+
return partial;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
...(prev as Record<string, unknown>),
|
|
37
|
+
...(partial as Record<string, unknown>),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Interactables = resource((): ClientOutput<"interactables"> => {
|
|
42
|
+
const [state, setState] = tapState<InteractablesState>(() => ({
|
|
43
|
+
definitions: {},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
const clientRef = tapAssistantClientRef();
|
|
47
|
+
|
|
48
|
+
const stateRef = tapRef(state);
|
|
49
|
+
tapEffect(() => {
|
|
50
|
+
stateRef.current = state;
|
|
51
|
+
}, [state]);
|
|
52
|
+
|
|
53
|
+
const subscribersRef = tapRef(new Set<() => void>());
|
|
54
|
+
|
|
55
|
+
const partialSchemaCacheRef = tapRef(
|
|
56
|
+
new Map<string, InteractableStateSchema>(),
|
|
57
|
+
);
|
|
58
|
+
const detachedStateRef = tapRef(new Map<string, unknown>());
|
|
59
|
+
|
|
60
|
+
const setDefState = tapCallback(
|
|
61
|
+
(id: string, updater: (prev: unknown) => unknown) => {
|
|
62
|
+
setState((prev) => {
|
|
63
|
+
const existing = prev.definitions[id];
|
|
64
|
+
if (!existing) return prev;
|
|
65
|
+
return {
|
|
66
|
+
...prev,
|
|
67
|
+
definitions: {
|
|
68
|
+
...prev.definitions,
|
|
69
|
+
[id]: { ...existing, state: updater(existing.state) },
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
[],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const setDefSelected = tapCallback((id: string, selected: boolean) => {
|
|
78
|
+
setState((prev) => {
|
|
79
|
+
const existing = prev.definitions[id];
|
|
80
|
+
if (!existing) return prev;
|
|
81
|
+
return {
|
|
82
|
+
...prev,
|
|
83
|
+
definitions: {
|
|
84
|
+
...prev.definitions,
|
|
85
|
+
[id]: { ...existing, selected },
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
const provider = tapMemo(
|
|
92
|
+
() => ({
|
|
93
|
+
getModelContext: () => {
|
|
94
|
+
const defs = stateRef.current.definitions;
|
|
95
|
+
const entries = Object.values(defs);
|
|
96
|
+
if (entries.length === 0) return {};
|
|
97
|
+
|
|
98
|
+
const byName = new Map<string, InteractableDefinition[]>();
|
|
99
|
+
for (const def of entries) {
|
|
100
|
+
const list = byName.get(def.name) ?? [];
|
|
101
|
+
list.push(def);
|
|
102
|
+
byName.set(def.name, list);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const systemParts: string[] = [];
|
|
106
|
+
const tools: Record<string, Tool<any, any>> = {};
|
|
107
|
+
|
|
108
|
+
for (const [name, instances] of byName) {
|
|
109
|
+
const isMulti = instances.length > 1;
|
|
110
|
+
|
|
111
|
+
for (const def of instances) {
|
|
112
|
+
const selectedTag = def.selected ? " (SELECTED)" : "";
|
|
113
|
+
const idTag = isMulti ? ` [id="${def.id}"]` : "";
|
|
114
|
+
|
|
115
|
+
systemParts.push(
|
|
116
|
+
`Interactable component "${name}"${idTag}${selectedTag} (${def.description}). Current state: ${JSON.stringify(def.state)}`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
120
|
+
const safeId = def.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
121
|
+
const toolName = isMulti
|
|
122
|
+
? `update_${safeName}_${safeId}`
|
|
123
|
+
: `update_${safeName}`;
|
|
124
|
+
|
|
125
|
+
const partialSchema =
|
|
126
|
+
partialSchemaCacheRef.current.get(def.id) ?? def.stateSchema;
|
|
127
|
+
|
|
128
|
+
tools[toolName] = {
|
|
129
|
+
type: "frontend" as const,
|
|
130
|
+
description: `Update the state of interactable component "${name}"${isMulti ? ` (id: ${def.id})` : ""}. Only include the fields you want to change; omitted fields keep their current values. ${def.description}`,
|
|
131
|
+
parameters: partialSchema,
|
|
132
|
+
streamCall: async (reader) => {
|
|
133
|
+
try {
|
|
134
|
+
for await (const partialArgs of reader.args.streamValues()) {
|
|
135
|
+
setDefState(def.id, (prev) =>
|
|
136
|
+
shallowMerge(prev, partialArgs),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// Non-fatal: execute handles the final state
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
execute: async (partialState: unknown) => {
|
|
144
|
+
setDefState(def.id, (prev) => shallowMerge(prev, partialState));
|
|
145
|
+
return { success: true };
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { system: systemParts.join("\n"), tools };
|
|
152
|
+
},
|
|
153
|
+
subscribe: (callback: () => void) => {
|
|
154
|
+
subscribersRef.current.add(callback);
|
|
155
|
+
return () => {
|
|
156
|
+
subscribersRef.current.delete(callback);
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
[setDefState],
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: state dep triggers notification
|
|
164
|
+
tapEffect(() => {
|
|
165
|
+
for (const cb of subscribersRef.current) {
|
|
166
|
+
cb();
|
|
167
|
+
}
|
|
168
|
+
}, [state]);
|
|
169
|
+
|
|
170
|
+
tapEffect(() => {
|
|
171
|
+
return clientRef.current!.modelContext().register(provider);
|
|
172
|
+
}, [clientRef, provider]);
|
|
173
|
+
|
|
174
|
+
const register = tapCallback((def: InteractableRegistration) => {
|
|
175
|
+
try {
|
|
176
|
+
const jsonSchema = toJSONSchema(def.stateSchema);
|
|
177
|
+
partialSchemaCacheRef.current.set(
|
|
178
|
+
def.id,
|
|
179
|
+
toPartialJSONSchema(jsonSchema),
|
|
180
|
+
);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Fall back to the raw schema; partial updates may not work correctly
|
|
183
|
+
console.warn(
|
|
184
|
+
`[Interactables] Failed to create partial schema for "${def.name}". The update tool will require all fields.`,
|
|
185
|
+
e,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const detached = detachedStateRef.current.get(def.id);
|
|
190
|
+
detachedStateRef.current.delete(def.id);
|
|
191
|
+
|
|
192
|
+
setState((prev) => ({
|
|
193
|
+
...prev,
|
|
194
|
+
definitions: {
|
|
195
|
+
...prev.definitions,
|
|
196
|
+
[def.id]: {
|
|
197
|
+
id: def.id,
|
|
198
|
+
name: def.name,
|
|
199
|
+
description: def.description,
|
|
200
|
+
stateSchema: def.stateSchema,
|
|
201
|
+
state:
|
|
202
|
+
prev.definitions[def.id]?.state ?? detached ?? def.initialState,
|
|
203
|
+
selected: def.selected,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
}));
|
|
207
|
+
|
|
208
|
+
return () => {
|
|
209
|
+
setState((prev) => {
|
|
210
|
+
const existing = prev.definitions[def.id];
|
|
211
|
+
if (existing) {
|
|
212
|
+
detachedStateRef.current.set(def.id, existing.state);
|
|
213
|
+
}
|
|
214
|
+
partialSchemaCacheRef.current.delete(def.id);
|
|
215
|
+
const { [def.id]: _, ...rest } = prev.definitions;
|
|
216
|
+
return { ...prev, definitions: rest };
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
}, []);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
getState: () => state,
|
|
223
|
+
register,
|
|
224
|
+
setState: setDefState,
|
|
225
|
+
setSelected: setDefSelected,
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
attachTransformScopes(Interactables, (scopes, parent) => {
|
|
230
|
+
if (!scopes.modelContext && parent.modelContext.source === null) {
|
|
231
|
+
scopes.modelContext = ModelContext();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
@@ -89,9 +89,8 @@ export const Tools = resource(
|
|
|
89
89
|
},
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
attachTransformScopes(Tools, (scopes, parent) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}));
|
|
92
|
+
attachTransformScopes(Tools, (scopes, parent) => {
|
|
93
|
+
if (!scopes.modelContext && parent.modelContext.source === null) {
|
|
94
|
+
scopes.modelContext = ModelContext();
|
|
95
|
+
}
|
|
96
|
+
});
|
package/src/react/index.ts
CHANGED
|
@@ -28,10 +28,21 @@ export {
|
|
|
28
28
|
} from "./model-context/useAssistantDataUI";
|
|
29
29
|
export { useInlineRender } from "./model-context/useInlineRender";
|
|
30
30
|
export type { Toolkit, ToolDefinition } from "./model-context/toolbox";
|
|
31
|
+
export {
|
|
32
|
+
useInteractable,
|
|
33
|
+
type UseInteractableConfig,
|
|
34
|
+
type UseInteractableMetadata,
|
|
35
|
+
} from "./model-context/useInteractable";
|
|
36
|
+
export {
|
|
37
|
+
makeInteractable,
|
|
38
|
+
type InteractableConfig,
|
|
39
|
+
type AssistantInteractable,
|
|
40
|
+
} from "./model-context/makeInteractable";
|
|
31
41
|
|
|
32
42
|
// client
|
|
33
43
|
export { Tools } from "./client/Tools";
|
|
34
44
|
export { DataRenderers } from "./client/DataRenderers";
|
|
45
|
+
export { Interactables } from "./client/Interactables";
|
|
35
46
|
|
|
36
47
|
// types
|
|
37
48
|
export type {
|
|
@@ -68,6 +79,14 @@ export type {
|
|
|
68
79
|
DataRenderersMethods,
|
|
69
80
|
DataRenderersClientSchema,
|
|
70
81
|
} from "./types/scopes/dataRenderers";
|
|
82
|
+
export type {
|
|
83
|
+
InteractableStateSchema,
|
|
84
|
+
InteractablesState,
|
|
85
|
+
InteractableDefinition,
|
|
86
|
+
InteractableRegistration,
|
|
87
|
+
InteractablesMethods,
|
|
88
|
+
InteractablesClientSchema,
|
|
89
|
+
} from "./types/scopes/interactables";
|
|
71
90
|
|
|
72
91
|
// providers
|
|
73
92
|
export {
|
|
@@ -85,6 +104,10 @@ export {
|
|
|
85
104
|
SuggestionByIndexProvider,
|
|
86
105
|
type SuggestionByIndexProviderProps,
|
|
87
106
|
} from "./providers/SuggestionByIndexProvider";
|
|
107
|
+
export {
|
|
108
|
+
QueueItemByIndexProvider,
|
|
109
|
+
type QueueItemByIndexProviderProps,
|
|
110
|
+
} from "./providers/QueueItemByIndexProvider";
|
|
88
111
|
export { ReadonlyThreadProvider } from "./providers/ReadonlyThreadProvider";
|
|
89
112
|
|
|
90
113
|
// RuntimeAdapter
|
|
@@ -144,6 +167,7 @@ export {
|
|
|
144
167
|
ComposerPrimitiveAttachments,
|
|
145
168
|
ComposerPrimitiveAttachmentByIndex,
|
|
146
169
|
} from "./primitives/composer/ComposerAttachments";
|
|
170
|
+
export { ComposerPrimitiveQueue } from "./primitives/composer/ComposerQueue";
|
|
147
171
|
export {
|
|
148
172
|
ThreadListPrimitiveItems,
|
|
149
173
|
ThreadListPrimitiveItemByIndex,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { useInteractable, type UseInteractableConfig } from "./useInteractable";
|
|
3
|
+
|
|
4
|
+
export type InteractableConfig<TState> = UseInteractableConfig<TState> & {
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type AssistantInteractable = FC & {
|
|
9
|
+
unstable_interactable: InteractableConfig<any>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const makeInteractable = <TState>(
|
|
13
|
+
config: InteractableConfig<TState>,
|
|
14
|
+
) => {
|
|
15
|
+
const Interactable: AssistantInteractable = () => {
|
|
16
|
+
useInteractable(config.name, config);
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
Interactable.unstable_interactable = config;
|
|
20
|
+
return Interactable;
|
|
21
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useCallback, useId, useRef } from "react";
|
|
2
|
+
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
3
|
+
import type { InteractableStateSchema } from "../types/scopes/interactables";
|
|
4
|
+
|
|
5
|
+
export type UseInteractableConfig<TState> = {
|
|
6
|
+
description: string;
|
|
7
|
+
stateSchema: InteractableStateSchema;
|
|
8
|
+
initialState: TState;
|
|
9
|
+
id?: string;
|
|
10
|
+
selected?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type StateUpdater<TState> = TState | ((prev: TState) => TState);
|
|
14
|
+
|
|
15
|
+
export type UseInteractableMetadata = {
|
|
16
|
+
id: string;
|
|
17
|
+
setSelected: (selected: boolean) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const useInteractable = <TState>(
|
|
21
|
+
name: string,
|
|
22
|
+
config: UseInteractableConfig<TState>,
|
|
23
|
+
): [
|
|
24
|
+
TState,
|
|
25
|
+
(updater: StateUpdater<TState>) => void,
|
|
26
|
+
UseInteractableMetadata,
|
|
27
|
+
] => {
|
|
28
|
+
const aui = useAui();
|
|
29
|
+
|
|
30
|
+
const autoId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
31
|
+
const id = config.id ?? autoId;
|
|
32
|
+
|
|
33
|
+
const stateSchemaRef = useRef(config.stateSchema);
|
|
34
|
+
stateSchemaRef.current = config.stateSchema;
|
|
35
|
+
const initialStateRef = useRef(config.initialState);
|
|
36
|
+
initialStateRef.current = config.initialState;
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
return aui.interactables().register({
|
|
40
|
+
id,
|
|
41
|
+
name,
|
|
42
|
+
description: config.description,
|
|
43
|
+
stateSchema: stateSchemaRef.current,
|
|
44
|
+
initialState: initialStateRef.current,
|
|
45
|
+
selected: config.selected,
|
|
46
|
+
});
|
|
47
|
+
}, [aui, id, name, config.description, config.selected]);
|
|
48
|
+
|
|
49
|
+
const state =
|
|
50
|
+
(useAuiState((s) => s.interactables.definitions[id]?.state) as TState) ??
|
|
51
|
+
config.initialState;
|
|
52
|
+
|
|
53
|
+
const setState = useCallback(
|
|
54
|
+
(updater: StateUpdater<TState>) => {
|
|
55
|
+
aui.interactables().setState(id, (prev) => {
|
|
56
|
+
if (typeof updater === "function") {
|
|
57
|
+
return (updater as (prev: TState) => TState)(prev as TState);
|
|
58
|
+
}
|
|
59
|
+
return updater;
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
[aui, id],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const setSelected = useCallback(
|
|
66
|
+
(selected: boolean) => {
|
|
67
|
+
aui.interactables().setSelected(id, selected);
|
|
68
|
+
},
|
|
69
|
+
[aui, id],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return [state, setState, { id, setSelected }];
|
|
73
|
+
};
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
3
|
+
import type { ComposerSendOptions } from "../../store/scopes/composer";
|
|
3
4
|
|
|
4
5
|
export const useComposerSend = () => {
|
|
5
6
|
const aui = useAui();
|
|
6
7
|
const disabled = useAuiState(
|
|
7
|
-
(s) =>
|
|
8
|
+
(s) =>
|
|
9
|
+
(s.thread.isRunning && !s.thread.capabilities.queue) ||
|
|
10
|
+
!s.composer.isEditing ||
|
|
11
|
+
s.composer.isEmpty,
|
|
8
12
|
);
|
|
9
13
|
|
|
10
|
-
const send = useCallback(
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
const send = useCallback(
|
|
15
|
+
(opts?: ComposerSendOptions) => {
|
|
16
|
+
aui.composer().send(opts);
|
|
17
|
+
},
|
|
18
|
+
[aui],
|
|
19
|
+
);
|
|
13
20
|
|
|
14
21
|
return { send, disabled };
|
|
15
22
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type FC, type ReactNode, memo, useMemo } from "react";
|
|
2
|
+
import { RenderChildrenWithAccessor, useAuiState } from "@assistant-ui/store";
|
|
3
|
+
import type { QueueItemState } from "../../../store/scopes/queue-item";
|
|
4
|
+
import { QueueItemByIndexProvider } from "../../providers/QueueItemByIndexProvider";
|
|
5
|
+
|
|
6
|
+
export namespace ComposerPrimitiveQueue {
|
|
7
|
+
export type Props = {
|
|
8
|
+
/** Render function called for each queue item. Receives the queue item state. */
|
|
9
|
+
children: (value: { queueItem: QueueItemState }) => ReactNode;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ComposerPrimitiveQueueInner: FC<{
|
|
14
|
+
children: (value: { queueItem: QueueItemState }) => ReactNode;
|
|
15
|
+
}> = ({ children }) => {
|
|
16
|
+
const queue = useAuiState((s) => s.composer.queue.length);
|
|
17
|
+
|
|
18
|
+
return useMemo(
|
|
19
|
+
() =>
|
|
20
|
+
Array.from({ length: queue }, (_, index) => (
|
|
21
|
+
<QueueItemByIndexProvider key={index} index={index}>
|
|
22
|
+
<RenderChildrenWithAccessor
|
|
23
|
+
getItemState={(aui) =>
|
|
24
|
+
aui.composer().queueItem({ index }).getState()
|
|
25
|
+
}
|
|
26
|
+
>
|
|
27
|
+
{(getItem) =>
|
|
28
|
+
children({
|
|
29
|
+
get queueItem() {
|
|
30
|
+
return getItem();
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
</RenderChildrenWithAccessor>
|
|
35
|
+
</QueueItemByIndexProvider>
|
|
36
|
+
)),
|
|
37
|
+
[queue, children],
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Renders all queue items in the composer.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <ComposerPrimitive.Queue>
|
|
47
|
+
* {({ queueItem }) => (
|
|
48
|
+
* <div>
|
|
49
|
+
* <QueueItemPrimitive.Text />
|
|
50
|
+
* <QueueItemPrimitive.Steer>Run Now</QueueItemPrimitive.Steer>
|
|
51
|
+
* </div>
|
|
52
|
+
* )}
|
|
53
|
+
* </ComposerPrimitive.Queue>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export const ComposerPrimitiveQueue = memo(ComposerPrimitiveQueueInner);
|
|
57
|
+
|
|
58
|
+
ComposerPrimitiveQueue.displayName = "ComposerPrimitive.Queue";
|
|
@@ -440,6 +440,8 @@ const EmptyPartsImpl: FC<MessagePartComponentProps> = ({ components }) => {
|
|
|
440
440
|
|
|
441
441
|
if (components?.Empty) return <components.Empty status={status} />;
|
|
442
442
|
|
|
443
|
+
if (status.type !== "running") return null;
|
|
444
|
+
|
|
443
445
|
return (
|
|
444
446
|
<EmptyPartFallback
|
|
445
447
|
status={status}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FC, type PropsWithChildren } from "react";
|
|
2
|
+
import { AuiProvider, Derived, useAui } from "@assistant-ui/store";
|
|
3
|
+
|
|
4
|
+
export type QueueItemByIndexProviderProps = PropsWithChildren<{
|
|
5
|
+
index: number;
|
|
6
|
+
}>;
|
|
7
|
+
|
|
8
|
+
export const QueueItemByIndexProvider: FC<QueueItemByIndexProviderProps> = ({
|
|
9
|
+
index,
|
|
10
|
+
children,
|
|
11
|
+
}) => {
|
|
12
|
+
const aui = useAui({
|
|
13
|
+
queueItem: Derived({
|
|
14
|
+
source: "composer",
|
|
15
|
+
query: { index },
|
|
16
|
+
get: (aui) => aui.composer().queueItem({ index }),
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return <AuiProvider value={aui}>{children}</AuiProvider>;
|
|
21
|
+
};
|
|
@@ -480,6 +480,7 @@ export class RemoteThreadListThreadListRuntimeCore
|
|
|
480
480
|
throw new Error("Thread is not yet initialized");
|
|
481
481
|
|
|
482
482
|
await this._ensureThreadIsNotMain(data.id);
|
|
483
|
+
this._hookManager.stopThreadRuntime(data.id);
|
|
483
484
|
|
|
484
485
|
return this._state.optimisticUpdate({
|
|
485
486
|
execute: async () => {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Tool } from "assistant-stream";
|
|
2
|
+
import type { Unsubscribe } from "../../..";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Schema type matching Tool["parameters"] from assistant-stream.
|
|
6
|
+
* Accepts both StandardSchemaV1 and JSONSchema7.
|
|
7
|
+
*/
|
|
8
|
+
export type InteractableStateSchema = NonNullable<
|
|
9
|
+
Extract<Tool, { parameters: unknown }>["parameters"]
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
export type InteractableDefinition = {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
stateSchema: InteractableStateSchema;
|
|
17
|
+
state: unknown;
|
|
18
|
+
selected?: boolean | undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type InteractableRegistration = {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
stateSchema: InteractableStateSchema;
|
|
26
|
+
initialState: unknown;
|
|
27
|
+
selected?: boolean | undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type InteractablesState = {
|
|
31
|
+
/** Keyed by instance id */
|
|
32
|
+
definitions: Record<string, InteractableDefinition>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type InteractablesMethods = {
|
|
36
|
+
getState(): InteractablesState;
|
|
37
|
+
register(def: InteractableRegistration): Unsubscribe;
|
|
38
|
+
setState(id: string, updater: (prev: unknown) => unknown): void;
|
|
39
|
+
setSelected(id: string, selected: boolean): void;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type InteractablesClientSchema = {
|
|
43
|
+
methods: InteractablesMethods;
|
|
44
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ToolsClientSchema } from "./scopes/tools";
|
|
2
2
|
import type { DataRenderersClientSchema } from "./scopes/dataRenderers";
|
|
3
|
+
import type { InteractablesClientSchema } from "./scopes/interactables";
|
|
3
4
|
|
|
4
5
|
declare module "@assistant-ui/store" {
|
|
5
6
|
interface ScopeRegistry {
|
|
6
7
|
tools: ToolsClientSchema;
|
|
7
8
|
dataRenderers: DataRenderersClientSchema;
|
|
9
|
+
interactables: InteractablesClientSchema;
|
|
8
10
|
}
|
|
9
11
|
}
|
|
@@ -9,7 +9,10 @@ import type { QuoteInfo } from "../../types/quote";
|
|
|
9
9
|
import type { Unsubscribe } from "../../types/unsubscribe";
|
|
10
10
|
import type { RunConfig } from "../../types/message";
|
|
11
11
|
import { BaseSubscribable } from "../../subscribable/subscribable";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
type AttachmentAdapter,
|
|
14
|
+
fileMatchesAccept,
|
|
15
|
+
} from "../../adapters/attachment";
|
|
13
16
|
import type {
|
|
14
17
|
ComposerRuntimeCore,
|
|
15
18
|
ComposerRuntimeEventType,
|
|
@@ -216,6 +219,17 @@ export abstract class BaseComposerRuntimeCore
|
|
|
216
219
|
const adapter = this.getAttachmentAdapter();
|
|
217
220
|
if (!adapter) throw new Error("Attachments are not supported");
|
|
218
221
|
|
|
222
|
+
if (
|
|
223
|
+
!fileMatchesAccept(
|
|
224
|
+
{ name: fileOrAttachment.name, type: fileOrAttachment.type },
|
|
225
|
+
adapter.accept,
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`File type ${fileOrAttachment.type || "unknown"} is not accepted. Accepted types: ${adapter.accept}`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
const upsertAttachment = (a: PendingAttachment) => {
|
|
220
234
|
const idx = this._attachments.findIndex(
|
|
221
235
|
(attachment) => attachment.id === a.id,
|
|
@@ -233,17 +247,39 @@ export abstract class BaseComposerRuntimeCore
|
|
|
233
247
|
this._notifySubscribers();
|
|
234
248
|
};
|
|
235
249
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
let lastAttachment: PendingAttachment | undefined;
|
|
251
|
+
try {
|
|
252
|
+
const promiseOrGenerator = adapter.add({ file: fileOrAttachment });
|
|
253
|
+
if (Symbol.asyncIterator in promiseOrGenerator) {
|
|
254
|
+
for await (const r of promiseOrGenerator) {
|
|
255
|
+
lastAttachment = r;
|
|
256
|
+
upsertAttachment(r);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
lastAttachment = await promiseOrGenerator;
|
|
260
|
+
upsertAttachment(lastAttachment);
|
|
240
261
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
262
|
+
} catch (e) {
|
|
263
|
+
if (lastAttachment) {
|
|
264
|
+
upsertAttachment({
|
|
265
|
+
...lastAttachment,
|
|
266
|
+
status: { type: "incomplete", reason: "error" },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
this._notifyEventSubscribers("attachmentAddError");
|
|
271
|
+
} catch {
|
|
272
|
+
// prevent subscriber errors from masking the adapter error
|
|
273
|
+
}
|
|
274
|
+
throw e;
|
|
243
275
|
}
|
|
244
276
|
|
|
245
|
-
|
|
246
|
-
|
|
277
|
+
const hasError =
|
|
278
|
+
lastAttachment?.status.type === "incomplete" &&
|
|
279
|
+
lastAttachment.status.reason === "error";
|
|
280
|
+
this._notifyEventSubscribers(
|
|
281
|
+
hasError ? "attachmentAddError" : "attachmentAdd",
|
|
282
|
+
);
|
|
247
283
|
}
|
|
248
284
|
|
|
249
285
|
async removeAttachment(attachmentId: string) {
|
|
@@ -5,7 +5,10 @@ import type { Unsubscribe } from "../../types/unsubscribe";
|
|
|
5
5
|
import type { RunConfig } from "../../types/message";
|
|
6
6
|
import type { DictationAdapter } from "../../adapters/speech";
|
|
7
7
|
|
|
8
|
-
export type ComposerRuntimeEventType =
|
|
8
|
+
export type ComposerRuntimeEventType =
|
|
9
|
+
| "send"
|
|
10
|
+
| "attachmentAdd"
|
|
11
|
+
| "attachmentAddError";
|
|
9
12
|
|
|
10
13
|
export type DictationState = {
|
|
11
14
|
readonly status: DictationAdapter.Status;
|