@steerable/agent-ui 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/useChatStream.d.ts +31 -0
- package/dist/hooks/useChatStream.d.ts.map +1 -1
- package/dist/hooks/useChatStream.js +68 -1
- package/dist/hooks/useChatStream.js.map +1 -1
- package/dist/state/useChatList.d.ts +2 -0
- package/dist/state/useChatList.d.ts.map +1 -1
- package/dist/state/useChatList.js +4 -1
- package/dist/state/useChatList.js.map +1 -1
- package/package.json +3 -3
- package/src/hooks/useChatStream.test.ts +231 -0
- package/src/hooks/useChatStream.ts +105 -1
- package/src/state/useChatList.ts +7 -4
|
@@ -26,6 +26,14 @@ export interface ChatStreamTransport {
|
|
|
26
26
|
* call on unmount or when the user clicks "stop".
|
|
27
27
|
*/
|
|
28
28
|
stream: (input: ChatStreamSendInput, onEvent: (event: SSEEvent) => void) => Promise<void | (() => void)>;
|
|
29
|
+
/**
|
|
30
|
+
* Optional mid-turn steering: inject a user message into the currently
|
|
31
|
+
* running turn (CoreLoop `steer`). Resolve `true` when the running turn
|
|
32
|
+
* accepted the message; `false` when no steerable turn is active (the
|
|
33
|
+
* caller should keep the draft — a normal send can follow once the turn
|
|
34
|
+
* ends). When omitted, the UI keeps send-during-streaming disabled.
|
|
35
|
+
*/
|
|
36
|
+
steer?: (content: string) => Promise<boolean>;
|
|
29
37
|
}
|
|
30
38
|
export interface UseChatStreamOptions {
|
|
31
39
|
/** Required transport handle; see `ChatStreamTransport`. */
|
|
@@ -48,7 +56,30 @@ export interface UseChatStreamOptions {
|
|
|
48
56
|
export interface UseChatStreamReturn {
|
|
49
57
|
messages: ChatMessage[];
|
|
50
58
|
isStreaming: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Send a user message. Queue-safe (W6-2): if a turn is currently streaming
|
|
61
|
+
* the message is NOT dropped — it is appended to the follow-up queue and
|
|
62
|
+
* sent automatically as the next turn when the current one ends.
|
|
63
|
+
*/
|
|
51
64
|
sendUserMessage: (input: ChatStreamSendInput) => Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Steer the running turn with an extra user message. No-op resolving
|
|
67
|
+
* `false` when not streaming or the transport has no `steer`. On success
|
|
68
|
+
* the message is appended to the visible transcript immediately (the loop
|
|
69
|
+
* consumes it at the next round boundary).
|
|
70
|
+
*/
|
|
71
|
+
steerUserMessage: (content: string) => Promise<boolean>;
|
|
72
|
+
/**
|
|
73
|
+
* Explicitly queue a message as a follow-up (W6-2): sent automatically as
|
|
74
|
+
* the next turn once the current turn finishes. When not streaming this is
|
|
75
|
+
* equivalent to `sendUserMessage`. Distinct from `steerUserMessage`, which
|
|
76
|
+
* redirects the *current* turn mid-flight.
|
|
77
|
+
*/
|
|
78
|
+
followUpUserMessage: (input: ChatStreamSendInput) => void;
|
|
79
|
+
/** Follow-up messages queued while streaming, in order (not yet sent). */
|
|
80
|
+
pendingFollowUps: ChatStreamSendInput[];
|
|
81
|
+
/** Remove a queued follow-up by index (e.g. the user withdraws it). */
|
|
82
|
+
removeFollowUp: (index: number) => void;
|
|
52
83
|
cancel: () => void;
|
|
53
84
|
/** Replace the message buffer (e.g. when switching chat). */
|
|
54
85
|
setMessages: (messages: ChatMessage[]) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAER,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,EAAE,CACN,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"useChatStream.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAER,UAAU,EACX,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,mBAAmB;IAClC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;OAQG;IACH,MAAM,EAAE,CACN,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,KAC/B,OAAO,CAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,SAAS,EAAE,mBAAmB,CAAC;IAC/B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,WAAW,EAAE,CAAC;IAChC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,WAAW,GAAG,IAAI,CAAC;IACjE;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;;;OAKG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC1D,0EAA0E;IAC1E,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,uEAAuE;IACvE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,6DAA6D;IAC7D,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC/C,mEAAmE;IACnE,aAAa,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAC/C;AAsID,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CA+MrB"}
|
|
@@ -133,6 +133,14 @@ export function useChatStream(options) {
|
|
|
133
133
|
const isStreamingRef = useRef(false);
|
|
134
134
|
const cancelRef = useRef(null);
|
|
135
135
|
const [, forceRerender] = useReducer((x) => x + 1, 0);
|
|
136
|
+
// W6-2 follow-up queue: messages submitted while a turn is streaming. Kept
|
|
137
|
+
// in a ref (drained imperatively at turn end) with a version counter to
|
|
138
|
+
// re-render consumers that show the pending queue.
|
|
139
|
+
const followUpsRef = useRef([]);
|
|
140
|
+
const [followUpVersion, bumpFollowUpVersion] = useReducer((x) => x + 1, 0);
|
|
141
|
+
// Self-handle so a finishing turn can start the next queued turn without a
|
|
142
|
+
// circular useCallback dependency.
|
|
143
|
+
const sendRef = useRef();
|
|
136
144
|
const setStreaming = useCallback((next) => {
|
|
137
145
|
isStreamingRef.current = next;
|
|
138
146
|
forceRerender();
|
|
@@ -195,6 +203,10 @@ export function useChatStream(options) {
|
|
|
195
203
|
}, [options]);
|
|
196
204
|
const sendUserMessage = useCallback(async (input) => {
|
|
197
205
|
if (isStreamingRef.current) {
|
|
206
|
+
// W6-2: queue instead of dropping. The finishing turn's `finally`
|
|
207
|
+
// drains the queue and starts the next turn automatically.
|
|
208
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
209
|
+
bumpFollowUpVersion();
|
|
198
210
|
return;
|
|
199
211
|
}
|
|
200
212
|
dispatch({ type: 'append', message: newUserMessage(input.content) });
|
|
@@ -216,15 +228,53 @@ export function useChatStream(options) {
|
|
|
216
228
|
finally {
|
|
217
229
|
cancelRef.current = null;
|
|
218
230
|
setStreaming(false);
|
|
231
|
+
// W6-2: drain the follow-up queue — send the next queued message as
|
|
232
|
+
// a fresh turn. isStreamingRef is already false here, so the nested
|
|
233
|
+
// call starts a new turn rather than re-queueing.
|
|
234
|
+
const next = followUpsRef.current.shift();
|
|
235
|
+
if (next !== undefined) {
|
|
236
|
+
bumpFollowUpVersion();
|
|
237
|
+
void sendRef.current?.(next);
|
|
238
|
+
}
|
|
219
239
|
}
|
|
220
240
|
}, [handleEvent, options.transport, setStreaming]);
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
sendRef.current = sendUserMessage;
|
|
243
|
+
}, [sendUserMessage]);
|
|
244
|
+
const followUpUserMessage = useCallback((input) => {
|
|
245
|
+
if (!isStreamingRef.current) {
|
|
246
|
+
void sendRef.current?.(input);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
250
|
+
bumpFollowUpVersion();
|
|
251
|
+
}, []);
|
|
252
|
+
const removeFollowUp = useCallback((index) => {
|
|
253
|
+
followUpsRef.current = followUpsRef.current.filter((_, i) => i !== index);
|
|
254
|
+
bumpFollowUpVersion();
|
|
255
|
+
}, []);
|
|
221
256
|
const cancel = useCallback(() => {
|
|
257
|
+
// Cancelling the current turn also drops queued follow-ups — an explicit
|
|
258
|
+
// stop means "don't keep going", so we don't auto-send what was queued.
|
|
259
|
+
followUpsRef.current = [];
|
|
260
|
+
bumpFollowUpVersion();
|
|
222
261
|
if (cancelRef.current) {
|
|
223
262
|
cancelRef.current();
|
|
224
263
|
cancelRef.current = null;
|
|
225
264
|
}
|
|
226
265
|
setStreaming(false);
|
|
227
266
|
}, [setStreaming]);
|
|
267
|
+
const steerUserMessage = useCallback(async (content) => {
|
|
268
|
+
const steer = options.transport.steer;
|
|
269
|
+
if (!isStreamingRef.current || !steer || !content.trim()) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
const ok = await steer(content);
|
|
273
|
+
if (ok) {
|
|
274
|
+
dispatch({ type: 'append', message: newUserMessage(content) });
|
|
275
|
+
}
|
|
276
|
+
return ok;
|
|
277
|
+
}, [options.transport]);
|
|
228
278
|
const setMessages = useCallback((messages) => {
|
|
229
279
|
dispatch({ type: 'reset', messages });
|
|
230
280
|
}, []);
|
|
@@ -243,9 +293,26 @@ export function useChatStream(options) {
|
|
|
243
293
|
messages: state.messages,
|
|
244
294
|
isStreaming: isStreamingRef.current,
|
|
245
295
|
sendUserMessage,
|
|
296
|
+
steerUserMessage,
|
|
297
|
+
followUpUserMessage,
|
|
298
|
+
// followUpVersion re-keys this memo so queue changes surface to the UI.
|
|
299
|
+
pendingFollowUps: followUpsRef.current,
|
|
300
|
+
removeFollowUp,
|
|
301
|
+
cancel,
|
|
302
|
+
setMessages,
|
|
303
|
+
appendMessage,
|
|
304
|
+
}),
|
|
305
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
306
|
+
[
|
|
307
|
+
state.messages,
|
|
308
|
+
sendUserMessage,
|
|
309
|
+
steerUserMessage,
|
|
310
|
+
followUpUserMessage,
|
|
311
|
+
removeFollowUp,
|
|
246
312
|
cancel,
|
|
247
313
|
setMessages,
|
|
248
314
|
appendMessage,
|
|
249
|
-
|
|
315
|
+
followUpVersion,
|
|
316
|
+
]);
|
|
250
317
|
}
|
|
251
318
|
//# sourceMappingURL=useChatStream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatStream.js","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useChatStream.js","sourceRoot":"","sources":["../../src/hooks/useChatStream.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,6 +29,7 @@ export interface ChatListTransport<TChat, TAgent> {
|
|
|
29
29
|
listAgents: () => Promise<TAgent[]>;
|
|
30
30
|
createChat?: (input: {
|
|
31
31
|
agentId?: string;
|
|
32
|
+
projectId?: string;
|
|
32
33
|
}) => Promise<TChat | string>;
|
|
33
34
|
deleteChat?: (chatId: string) => Promise<boolean>;
|
|
34
35
|
/** Tell the hook how to read a chat's id and title. */
|
|
@@ -62,6 +63,7 @@ export interface UseChatListReturn<TChat, TAgent> {
|
|
|
62
63
|
loadMoreChats: () => Promise<void>;
|
|
63
64
|
createChat: (input?: {
|
|
64
65
|
agentId?: string;
|
|
66
|
+
projectId?: string;
|
|
65
67
|
}) => Promise<string | null>;
|
|
66
68
|
deleteChat: (chatId: string) => Promise<boolean>;
|
|
67
69
|
/** In-place title patcher — does not hit the server. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatList.d.ts","sourceRoot":"","sources":["../../src/state/useChatList.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAC9C,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAChE,KAAK,EAAE,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"useChatList.d.ts","sourceRoot":"","sources":["../../src/state/useChatList.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAML,KAAK,QAAQ,EACb,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAC9C,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAChE,KAAK,EAAE,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC,CAAC;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;IAC1F,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,uDAAuD;IACvD,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IACnC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;IACrD,uEAAuE;IACvE,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB,CAAC,KAAK,EAAE,MAAM;IAC/C,SAAS,EAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAC9C,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;OAGG;IACH,kBAAkB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IAC5D,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzF,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,wDAAwD;IACxD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EACvC,OAAO,EAAE,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,GACzC,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CA8IlC"}
|
|
@@ -94,7 +94,10 @@ export function useChatList(options) {
|
|
|
94
94
|
const t = transportRef.current;
|
|
95
95
|
if (!t.createChat)
|
|
96
96
|
return null;
|
|
97
|
-
const created = await t.createChat({
|
|
97
|
+
const created = await t.createChat({
|
|
98
|
+
agentId: input?.agentId ?? selectedAgentId ?? undefined,
|
|
99
|
+
projectId: input?.projectId,
|
|
100
|
+
});
|
|
98
101
|
// Either a chat object or a bare id.
|
|
99
102
|
const id = typeof created === 'string' ? created : t.getChatId(created);
|
|
100
103
|
await refreshChats();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatList.js","sourceRoot":"","sources":["../../src/state/useChatList.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"useChatList.js","sourceRoot":"","sources":["../../src/state/useChatList.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Steerable framework Tier 4 — headless React hooks (useAgentSession, useChatStream, useToolCallStatus) and Tailwind-themed components (ChatPanel, MessageList, OrchestrationPlanCard, ToolCallRenderer, SSEStreamView) for embedding Steerable agents in any React app.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
-
"homepage": "https://
|
|
6
|
+
"homepage": "https://steerableframework.com/storybook/",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/pathlyapp/steerable-framework.git",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"README.md"
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@steerable/agent-protocol": "0.
|
|
60
|
+
"@steerable/agent-protocol": "0.3.0"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"react": ">=18.0.0",
|
|
@@ -12,9 +12,36 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
12
12
|
import type { SSEEvent } from '@steerable/agent-protocol';
|
|
13
13
|
import {
|
|
14
14
|
useChatStream,
|
|
15
|
+
type ChatStreamSendInput,
|
|
15
16
|
type ChatStreamTransport,
|
|
16
17
|
} from './useChatStream';
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* A transport whose streams stay open until the test explicitly finishes them,
|
|
21
|
+
* so we can submit follow-ups mid-stream and watch the queue drain.
|
|
22
|
+
*/
|
|
23
|
+
function makeControllableTransport() {
|
|
24
|
+
const streams: Array<{
|
|
25
|
+
input: ChatStreamSendInput;
|
|
26
|
+
onEvent: (e: SSEEvent) => void;
|
|
27
|
+
finish: () => void;
|
|
28
|
+
}> = [];
|
|
29
|
+
const transport: ChatStreamTransport = {
|
|
30
|
+
stream: (input, onEvent) =>
|
|
31
|
+
new Promise<void>((resolve) => {
|
|
32
|
+
streams.push({
|
|
33
|
+
input,
|
|
34
|
+
onEvent,
|
|
35
|
+
finish: () => {
|
|
36
|
+
onEvent({ type: 'done' });
|
|
37
|
+
resolve();
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
return { transport, streams };
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
function makeTransport(
|
|
19
46
|
script: SSEEvent[][],
|
|
20
47
|
): { transport: ChatStreamTransport; cancel: ReturnType<typeof vi.fn> } {
|
|
@@ -231,3 +258,207 @@ describe('useChatStream', () => {
|
|
|
231
258
|
expect(true).toBe(true);
|
|
232
259
|
});
|
|
233
260
|
});
|
|
261
|
+
|
|
262
|
+
describe('steerUserMessage', () => {
|
|
263
|
+
it('appends the user message when the transport accepts the steer', async () => {
|
|
264
|
+
const steer = vi.fn().mockResolvedValue(true);
|
|
265
|
+
const transport: ChatStreamTransport = {
|
|
266
|
+
stream: vi.fn(async (_input, onEvent) => {
|
|
267
|
+
onEvent({ type: 'content', content: 'working…' });
|
|
268
|
+
// keep the stream open until the test steers
|
|
269
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 30));
|
|
270
|
+
onEvent({ type: 'content', content: 'done' });
|
|
271
|
+
onEvent({ type: 'done' });
|
|
272
|
+
}),
|
|
273
|
+
steer,
|
|
274
|
+
};
|
|
275
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
276
|
+
|
|
277
|
+
let done: Promise<void>;
|
|
278
|
+
act(() => {
|
|
279
|
+
done = result.current.sendUserMessage({ content: 'start' });
|
|
280
|
+
});
|
|
281
|
+
await act(async () => {
|
|
282
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
let ok = false;
|
|
286
|
+
await act(async () => {
|
|
287
|
+
ok = await result.current.steerUserMessage('补充一句');
|
|
288
|
+
});
|
|
289
|
+
expect(ok).toBe(true);
|
|
290
|
+
expect(steer).toHaveBeenCalledWith('补充一句');
|
|
291
|
+
expect(
|
|
292
|
+
result.current.messages.some((m) => m.role === 'user' && m.content === '补充一句'),
|
|
293
|
+
).toBe(true);
|
|
294
|
+
|
|
295
|
+
await act(async () => {
|
|
296
|
+
await done;
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('returns false and appends nothing when not streaming', async () => {
|
|
301
|
+
const steer = vi.fn().mockResolvedValue(true);
|
|
302
|
+
const transport: ChatStreamTransport = {
|
|
303
|
+
stream: vi.fn(async () => {}),
|
|
304
|
+
steer,
|
|
305
|
+
};
|
|
306
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
307
|
+
|
|
308
|
+
let ok = true;
|
|
309
|
+
await act(async () => {
|
|
310
|
+
ok = await result.current.steerUserMessage('hello');
|
|
311
|
+
});
|
|
312
|
+
expect(ok).toBe(false);
|
|
313
|
+
expect(steer).not.toHaveBeenCalled();
|
|
314
|
+
expect(result.current.messages).toHaveLength(0);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('returns false when the transport has no steer support', async () => {
|
|
318
|
+
const transport: ChatStreamTransport = {
|
|
319
|
+
stream: vi.fn(async () => {
|
|
320
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 20));
|
|
321
|
+
}),
|
|
322
|
+
};
|
|
323
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
324
|
+
|
|
325
|
+
let done: Promise<void>;
|
|
326
|
+
act(() => {
|
|
327
|
+
done = result.current.sendUserMessage({ content: 'start' });
|
|
328
|
+
});
|
|
329
|
+
let ok = true;
|
|
330
|
+
await act(async () => {
|
|
331
|
+
ok = await result.current.steerUserMessage('mid');
|
|
332
|
+
});
|
|
333
|
+
expect(ok).toBe(false);
|
|
334
|
+
await act(async () => {
|
|
335
|
+
await done;
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('follow-up queue (W6-2)', () => {
|
|
341
|
+
it('sendUserMessage while streaming queues instead of dropping, then auto-sends next turn', async () => {
|
|
342
|
+
const { transport, streams } = makeControllableTransport();
|
|
343
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
344
|
+
|
|
345
|
+
// Turn 1 starts and stays open.
|
|
346
|
+
act(() => {
|
|
347
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
348
|
+
});
|
|
349
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
350
|
+
expect(result.current.isStreaming).toBe(true);
|
|
351
|
+
|
|
352
|
+
// Submit a second message mid-stream — must be queued, not dropped.
|
|
353
|
+
await act(async () => {
|
|
354
|
+
await result.current.sendUserMessage({ content: 'second' });
|
|
355
|
+
});
|
|
356
|
+
expect(streams).toHaveLength(1); // no new turn yet
|
|
357
|
+
expect(result.current.pendingFollowUps).toEqual([{ content: 'second' }]);
|
|
358
|
+
|
|
359
|
+
// Finish turn 1 → the queued message auto-sends as turn 2.
|
|
360
|
+
await act(async () => {
|
|
361
|
+
streams[0]!.finish();
|
|
362
|
+
});
|
|
363
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
364
|
+
expect(streams[1]!.input.content).toBe('second');
|
|
365
|
+
expect(result.current.pendingFollowUps).toEqual([]);
|
|
366
|
+
|
|
367
|
+
await act(async () => {
|
|
368
|
+
streams[1]!.finish();
|
|
369
|
+
});
|
|
370
|
+
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
371
|
+
// Both user messages landed in the transcript.
|
|
372
|
+
const userTexts = result.current.messages
|
|
373
|
+
.filter((m) => m.role === 'user')
|
|
374
|
+
.map((m) => m.content);
|
|
375
|
+
expect(userTexts).toEqual(['first', 'second']);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('followUpUserMessage queues while streaming and sends immediately when idle', async () => {
|
|
379
|
+
const { transport, streams } = makeControllableTransport();
|
|
380
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
381
|
+
|
|
382
|
+
// Idle: followUpUserMessage behaves like a normal send.
|
|
383
|
+
await act(async () => {
|
|
384
|
+
result.current.followUpUserMessage({ content: 'idle' });
|
|
385
|
+
});
|
|
386
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
387
|
+
expect(streams[0]!.input.content).toBe('idle');
|
|
388
|
+
|
|
389
|
+
// Streaming: queues.
|
|
390
|
+
await act(async () => {
|
|
391
|
+
result.current.followUpUserMessage({ content: 'queued' });
|
|
392
|
+
});
|
|
393
|
+
expect(streams).toHaveLength(1);
|
|
394
|
+
expect(result.current.pendingFollowUps).toEqual([{ content: 'queued' }]);
|
|
395
|
+
|
|
396
|
+
await act(async () => {
|
|
397
|
+
streams[0]!.finish();
|
|
398
|
+
});
|
|
399
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
400
|
+
expect(streams[1]!.input.content).toBe('queued');
|
|
401
|
+
await act(async () => {
|
|
402
|
+
streams[1]!.finish();
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('removeFollowUp withdraws a queued message before it sends', async () => {
|
|
407
|
+
const { transport, streams } = makeControllableTransport();
|
|
408
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
409
|
+
|
|
410
|
+
act(() => {
|
|
411
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
412
|
+
});
|
|
413
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
414
|
+
|
|
415
|
+
await act(async () => {
|
|
416
|
+
result.current.followUpUserMessage({ content: 'keep' });
|
|
417
|
+
result.current.followUpUserMessage({ content: 'drop' });
|
|
418
|
+
});
|
|
419
|
+
expect(result.current.pendingFollowUps.map((m) => m.content)).toEqual(['keep', 'drop']);
|
|
420
|
+
|
|
421
|
+
await act(async () => {
|
|
422
|
+
result.current.removeFollowUp(1); // remove 'drop'
|
|
423
|
+
});
|
|
424
|
+
expect(result.current.pendingFollowUps.map((m) => m.content)).toEqual(['keep']);
|
|
425
|
+
|
|
426
|
+
await act(async () => {
|
|
427
|
+
streams[0]!.finish();
|
|
428
|
+
});
|
|
429
|
+
await waitFor(() => expect(streams).toHaveLength(2));
|
|
430
|
+
expect(streams[1]!.input.content).toBe('keep');
|
|
431
|
+
await act(async () => {
|
|
432
|
+
streams[1]!.finish();
|
|
433
|
+
});
|
|
434
|
+
// 'drop' never sent.
|
|
435
|
+
expect(streams).toHaveLength(2);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it('cancel() clears the queue so nothing auto-sends afterwards', async () => {
|
|
439
|
+
const { transport, streams } = makeControllableTransport();
|
|
440
|
+
const { result } = renderHook(() => useChatStream({ transport }));
|
|
441
|
+
|
|
442
|
+
act(() => {
|
|
443
|
+
void result.current.sendUserMessage({ content: 'first' });
|
|
444
|
+
});
|
|
445
|
+
await waitFor(() => expect(streams).toHaveLength(1));
|
|
446
|
+
|
|
447
|
+
await act(async () => {
|
|
448
|
+
result.current.followUpUserMessage({ content: 'queued' });
|
|
449
|
+
});
|
|
450
|
+
expect(result.current.pendingFollowUps).toHaveLength(1);
|
|
451
|
+
|
|
452
|
+
await act(async () => {
|
|
453
|
+
result.current.cancel();
|
|
454
|
+
});
|
|
455
|
+
expect(result.current.pendingFollowUps).toEqual([]);
|
|
456
|
+
|
|
457
|
+
// Even if the stream later finishes, no follow-up turn starts.
|
|
458
|
+
await act(async () => {
|
|
459
|
+
streams[0]!.finish();
|
|
460
|
+
});
|
|
461
|
+
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
462
|
+
expect(streams).toHaveLength(1);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
@@ -38,6 +38,14 @@ export interface ChatStreamTransport {
|
|
|
38
38
|
input: ChatStreamSendInput,
|
|
39
39
|
onEvent: (event: SSEEvent) => void,
|
|
40
40
|
) => Promise<void | (() => void)>;
|
|
41
|
+
/**
|
|
42
|
+
* Optional mid-turn steering: inject a user message into the currently
|
|
43
|
+
* running turn (CoreLoop `steer`). Resolve `true` when the running turn
|
|
44
|
+
* accepted the message; `false` when no steerable turn is active (the
|
|
45
|
+
* caller should keep the draft — a normal send can follow once the turn
|
|
46
|
+
* ends). When omitted, the UI keeps send-during-streaming disabled.
|
|
47
|
+
*/
|
|
48
|
+
steer?: (content: string) => Promise<boolean>;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
export interface UseChatStreamOptions {
|
|
@@ -62,7 +70,30 @@ export interface UseChatStreamOptions {
|
|
|
62
70
|
export interface UseChatStreamReturn {
|
|
63
71
|
messages: ChatMessage[];
|
|
64
72
|
isStreaming: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Send a user message. Queue-safe (W6-2): if a turn is currently streaming
|
|
75
|
+
* the message is NOT dropped — it is appended to the follow-up queue and
|
|
76
|
+
* sent automatically as the next turn when the current one ends.
|
|
77
|
+
*/
|
|
65
78
|
sendUserMessage: (input: ChatStreamSendInput) => Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* Steer the running turn with an extra user message. No-op resolving
|
|
81
|
+
* `false` when not streaming or the transport has no `steer`. On success
|
|
82
|
+
* the message is appended to the visible transcript immediately (the loop
|
|
83
|
+
* consumes it at the next round boundary).
|
|
84
|
+
*/
|
|
85
|
+
steerUserMessage: (content: string) => Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Explicitly queue a message as a follow-up (W6-2): sent automatically as
|
|
88
|
+
* the next turn once the current turn finishes. When not streaming this is
|
|
89
|
+
* equivalent to `sendUserMessage`. Distinct from `steerUserMessage`, which
|
|
90
|
+
* redirects the *current* turn mid-flight.
|
|
91
|
+
*/
|
|
92
|
+
followUpUserMessage: (input: ChatStreamSendInput) => void;
|
|
93
|
+
/** Follow-up messages queued while streaming, in order (not yet sent). */
|
|
94
|
+
pendingFollowUps: ChatStreamSendInput[];
|
|
95
|
+
/** Remove a queued follow-up by index (e.g. the user withdraws it). */
|
|
96
|
+
removeFollowUp: (index: number) => void;
|
|
66
97
|
cancel: () => void;
|
|
67
98
|
/** Replace the message buffer (e.g. when switching chat). */
|
|
68
99
|
setMessages: (messages: ChatMessage[]) => void;
|
|
@@ -211,6 +242,14 @@ export function useChatStream(
|
|
|
211
242
|
const isStreamingRef = useRef(false);
|
|
212
243
|
const cancelRef = useRef<(() => void) | null>(null);
|
|
213
244
|
const [, forceRerender] = useReducer((x: number) => x + 1, 0);
|
|
245
|
+
// W6-2 follow-up queue: messages submitted while a turn is streaming. Kept
|
|
246
|
+
// in a ref (drained imperatively at turn end) with a version counter to
|
|
247
|
+
// re-render consumers that show the pending queue.
|
|
248
|
+
const followUpsRef = useRef<ChatStreamSendInput[]>([]);
|
|
249
|
+
const [followUpVersion, bumpFollowUpVersion] = useReducer((x: number) => x + 1, 0);
|
|
250
|
+
// Self-handle so a finishing turn can start the next queued turn without a
|
|
251
|
+
// circular useCallback dependency.
|
|
252
|
+
const sendRef = useRef<(input: ChatStreamSendInput) => Promise<void>>();
|
|
214
253
|
|
|
215
254
|
const setStreaming = useCallback((next: boolean) => {
|
|
216
255
|
isStreamingRef.current = next;
|
|
@@ -277,6 +316,10 @@ export function useChatStream(
|
|
|
277
316
|
const sendUserMessage = useCallback(
|
|
278
317
|
async (input: ChatStreamSendInput) => {
|
|
279
318
|
if (isStreamingRef.current) {
|
|
319
|
+
// W6-2: queue instead of dropping. The finishing turn's `finally`
|
|
320
|
+
// drains the queue and starts the next turn automatically.
|
|
321
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
322
|
+
bumpFollowUpVersion();
|
|
280
323
|
return;
|
|
281
324
|
}
|
|
282
325
|
dispatch({ type: 'append', message: newUserMessage(input.content) });
|
|
@@ -297,12 +340,42 @@ export function useChatStream(
|
|
|
297
340
|
} finally {
|
|
298
341
|
cancelRef.current = null;
|
|
299
342
|
setStreaming(false);
|
|
343
|
+
// W6-2: drain the follow-up queue — send the next queued message as
|
|
344
|
+
// a fresh turn. isStreamingRef is already false here, so the nested
|
|
345
|
+
// call starts a new turn rather than re-queueing.
|
|
346
|
+
const next = followUpsRef.current.shift();
|
|
347
|
+
if (next !== undefined) {
|
|
348
|
+
bumpFollowUpVersion();
|
|
349
|
+
void sendRef.current?.(next);
|
|
350
|
+
}
|
|
300
351
|
}
|
|
301
352
|
},
|
|
302
353
|
[handleEvent, options.transport, setStreaming],
|
|
303
354
|
);
|
|
304
355
|
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
sendRef.current = sendUserMessage;
|
|
358
|
+
}, [sendUserMessage]);
|
|
359
|
+
|
|
360
|
+
const followUpUserMessage = useCallback((input: ChatStreamSendInput) => {
|
|
361
|
+
if (!isStreamingRef.current) {
|
|
362
|
+
void sendRef.current?.(input);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
followUpsRef.current = [...followUpsRef.current, input];
|
|
366
|
+
bumpFollowUpVersion();
|
|
367
|
+
}, []);
|
|
368
|
+
|
|
369
|
+
const removeFollowUp = useCallback((index: number) => {
|
|
370
|
+
followUpsRef.current = followUpsRef.current.filter((_, i) => i !== index);
|
|
371
|
+
bumpFollowUpVersion();
|
|
372
|
+
}, []);
|
|
373
|
+
|
|
305
374
|
const cancel = useCallback(() => {
|
|
375
|
+
// Cancelling the current turn also drops queued follow-ups — an explicit
|
|
376
|
+
// stop means "don't keep going", so we don't auto-send what was queued.
|
|
377
|
+
followUpsRef.current = [];
|
|
378
|
+
bumpFollowUpVersion();
|
|
306
379
|
if (cancelRef.current) {
|
|
307
380
|
cancelRef.current();
|
|
308
381
|
cancelRef.current = null;
|
|
@@ -310,6 +383,21 @@ export function useChatStream(
|
|
|
310
383
|
setStreaming(false);
|
|
311
384
|
}, [setStreaming]);
|
|
312
385
|
|
|
386
|
+
const steerUserMessage = useCallback(
|
|
387
|
+
async (content: string): Promise<boolean> => {
|
|
388
|
+
const steer = options.transport.steer;
|
|
389
|
+
if (!isStreamingRef.current || !steer || !content.trim()) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
const ok = await steer(content);
|
|
393
|
+
if (ok) {
|
|
394
|
+
dispatch({ type: 'append', message: newUserMessage(content) });
|
|
395
|
+
}
|
|
396
|
+
return ok;
|
|
397
|
+
},
|
|
398
|
+
[options.transport],
|
|
399
|
+
);
|
|
400
|
+
|
|
313
401
|
const setMessages = useCallback((messages: ChatMessage[]) => {
|
|
314
402
|
dispatch({ type: 'reset', messages });
|
|
315
403
|
}, []);
|
|
@@ -332,10 +420,26 @@ export function useChatStream(
|
|
|
332
420
|
messages: state.messages,
|
|
333
421
|
isStreaming: isStreamingRef.current,
|
|
334
422
|
sendUserMessage,
|
|
423
|
+
steerUserMessage,
|
|
424
|
+
followUpUserMessage,
|
|
425
|
+
// followUpVersion re-keys this memo so queue changes surface to the UI.
|
|
426
|
+
pendingFollowUps: followUpsRef.current,
|
|
427
|
+
removeFollowUp,
|
|
335
428
|
cancel,
|
|
336
429
|
setMessages,
|
|
337
430
|
appendMessage,
|
|
338
431
|
}),
|
|
339
|
-
|
|
432
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
433
|
+
[
|
|
434
|
+
state.messages,
|
|
435
|
+
sendUserMessage,
|
|
436
|
+
steerUserMessage,
|
|
437
|
+
followUpUserMessage,
|
|
438
|
+
removeFollowUp,
|
|
439
|
+
cancel,
|
|
440
|
+
setMessages,
|
|
441
|
+
appendMessage,
|
|
442
|
+
followUpVersion,
|
|
443
|
+
],
|
|
340
444
|
);
|
|
341
445
|
}
|
package/src/state/useChatList.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface ChatListTransport<TChat, TAgent> {
|
|
|
34
34
|
hasMore: boolean;
|
|
35
35
|
}>;
|
|
36
36
|
listAgents: () => Promise<TAgent[]>;
|
|
37
|
-
createChat?: (input: { agentId?: string }) => Promise<TChat | string>;
|
|
37
|
+
createChat?: (input: { agentId?: string; projectId?: string }) => Promise<TChat | string>;
|
|
38
38
|
deleteChat?: (chatId: string) => Promise<boolean>;
|
|
39
39
|
/** Tell the hook how to read a chat's id and title. */
|
|
40
40
|
getChatId: (chat: TChat) => string;
|
|
@@ -67,7 +67,7 @@ export interface UseChatListReturn<TChat, TAgent> {
|
|
|
67
67
|
refreshChats: () => Promise<void>;
|
|
68
68
|
refreshAgents: () => Promise<void>;
|
|
69
69
|
loadMoreChats: () => Promise<void>;
|
|
70
|
-
createChat: (input?: { agentId?: string }) => Promise<string | null>;
|
|
70
|
+
createChat: (input?: { agentId?: string; projectId?: string }) => Promise<string | null>;
|
|
71
71
|
deleteChat: (chatId: string) => Promise<boolean>;
|
|
72
72
|
/** In-place title patcher — does not hit the server. */
|
|
73
73
|
patchChatTitle: (chatId: string, title: string) => void;
|
|
@@ -140,10 +140,13 @@ export function useChatList<TChat, TAgent>(
|
|
|
140
140
|
}, [hasMoreChats, isLoadingMoreChats, pageSize]);
|
|
141
141
|
|
|
142
142
|
const createChat = useCallback(
|
|
143
|
-
async (input?: { agentId?: string }) => {
|
|
143
|
+
async (input?: { agentId?: string; projectId?: string }) => {
|
|
144
144
|
const t = transportRef.current;
|
|
145
145
|
if (!t.createChat) return null;
|
|
146
|
-
const created = await t.createChat({
|
|
146
|
+
const created = await t.createChat({
|
|
147
|
+
agentId: input?.agentId ?? selectedAgentId ?? undefined,
|
|
148
|
+
projectId: input?.projectId,
|
|
149
|
+
});
|
|
147
150
|
// Either a chat object or a bare id.
|
|
148
151
|
const id = typeof created === 'string' ? created : t.getChatId(created);
|
|
149
152
|
await refreshChats();
|