@deltakit/react 0.1.4 → 0.2.1
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/README.md +116 -4
- package/dist/index.cjs +490 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +146 -6
- package/dist/index.d.ts +146 -6
- package/dist/index.js +495 -97
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
fromAgnoAgents: () => import_core2.fromAgnoAgents,
|
|
23
24
|
fromOpenAiAgents: () => import_core2.fromOpenAiAgents,
|
|
24
25
|
parseSSEStream: () => import_core2.parseSSEStream,
|
|
25
26
|
useAutoScroll: () => useAutoScroll,
|
|
@@ -108,8 +109,9 @@ function useAutoScroll(dependencies, options) {
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
// src/use-stream-chat.ts
|
|
111
|
-
var import_core = require("@deltakit/core");
|
|
112
112
|
var import_react2 = require("react");
|
|
113
|
+
|
|
114
|
+
// src/chat-controller.ts
|
|
113
115
|
var counter = 0;
|
|
114
116
|
function generateId() {
|
|
115
117
|
return `msg_${Date.now()}_${++counter}`;
|
|
@@ -117,30 +119,403 @@ function generateId() {
|
|
|
117
119
|
function createMessage(role, parts) {
|
|
118
120
|
return { id: generateId(), role, parts };
|
|
119
121
|
}
|
|
122
|
+
function createChatTransportContext(options) {
|
|
123
|
+
const helpers = {
|
|
124
|
+
appendPart: options.appendPart,
|
|
125
|
+
appendText: options.appendText,
|
|
126
|
+
setMessages: options.setMessages
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
emit: (event) => {
|
|
130
|
+
options.eventHandler(event, helpers);
|
|
131
|
+
},
|
|
132
|
+
ensureAssistantMessage: () => {
|
|
133
|
+
options.setMessages((prev) => {
|
|
134
|
+
const last = prev[prev.length - 1];
|
|
135
|
+
if (last?.role === "assistant") {
|
|
136
|
+
return prev;
|
|
137
|
+
}
|
|
138
|
+
return [...prev, createMessage("assistant", [])];
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
fail: (error) => {
|
|
142
|
+
options.setError(error);
|
|
143
|
+
options.onError?.(error);
|
|
144
|
+
options.setIsLoading(false);
|
|
145
|
+
options.setRunId(null);
|
|
146
|
+
},
|
|
147
|
+
finish: () => {
|
|
148
|
+
options.setIsLoading(false);
|
|
149
|
+
options.setRunId(null);
|
|
150
|
+
const finalMessages = options.getMessages();
|
|
151
|
+
const lastMessage = finalMessages[finalMessages.length - 1];
|
|
152
|
+
if (lastMessage?.role === "assistant") {
|
|
153
|
+
options.onMessage?.(lastMessage);
|
|
154
|
+
}
|
|
155
|
+
options.onFinish?.(finalMessages);
|
|
156
|
+
},
|
|
157
|
+
getMessages: options.getMessages,
|
|
158
|
+
setRunId: (runId) => {
|
|
159
|
+
options.setRunId(runId);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/transports.ts
|
|
165
|
+
var import_core = require("@deltakit/core");
|
|
166
|
+
function toError(error) {
|
|
167
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
168
|
+
}
|
|
169
|
+
function isAbortError(error) {
|
|
170
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
171
|
+
}
|
|
172
|
+
function resolveRunId(response) {
|
|
173
|
+
if (!response || typeof response !== "object") {
|
|
174
|
+
throw new Error("Background SSE start response did not contain a run id");
|
|
175
|
+
}
|
|
176
|
+
const maybeRunId = "runId" in response ? response.runId : "job_id" in response ? response.job_id : null;
|
|
177
|
+
if (typeof maybeRunId !== "string" || maybeRunId.length === 0) {
|
|
178
|
+
throw new Error("Background SSE start response did not contain a run id");
|
|
179
|
+
}
|
|
180
|
+
return maybeRunId;
|
|
181
|
+
}
|
|
182
|
+
function resolveUrl(url, runId) {
|
|
183
|
+
return typeof url === "function" ? url(runId) : url.replace(":runId", runId);
|
|
184
|
+
}
|
|
185
|
+
async function streamFetchSSE(response, context, signal) {
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`SSE request failed: ${response.status} ${response.statusText}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (!response.body) {
|
|
192
|
+
throw new Error("Response body is null \u2014 SSE streaming not supported");
|
|
193
|
+
}
|
|
194
|
+
for await (const event of (0, import_core.parseSSEStream)(response.body, signal)) {
|
|
195
|
+
context.emit(event);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function createDirectSSETransport(config) {
|
|
199
|
+
return {
|
|
200
|
+
start: ({ context, message }) => {
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
const run = {
|
|
203
|
+
close: () => {
|
|
204
|
+
controller.abort();
|
|
205
|
+
},
|
|
206
|
+
stop: () => {
|
|
207
|
+
controller.abort();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
211
|
+
void (async () => {
|
|
212
|
+
try {
|
|
213
|
+
const response = await fetchImpl(config.api, {
|
|
214
|
+
body: JSON.stringify({ message, ...config.body }),
|
|
215
|
+
headers: {
|
|
216
|
+
"Content-Type": "application/json",
|
|
217
|
+
...config.headers
|
|
218
|
+
},
|
|
219
|
+
method: config.method ?? "POST",
|
|
220
|
+
signal: controller.signal
|
|
221
|
+
});
|
|
222
|
+
await streamFetchSSE(response, context, controller.signal);
|
|
223
|
+
context.finish();
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (!isAbortError(error)) {
|
|
226
|
+
context.fail(toError(error));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
})();
|
|
230
|
+
return run;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function createBackgroundSSETransport(config) {
|
|
235
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
236
|
+
const connect = (runId, context) => {
|
|
237
|
+
const controller = new AbortController();
|
|
238
|
+
void (async () => {
|
|
239
|
+
try {
|
|
240
|
+
context.ensureAssistantMessage();
|
|
241
|
+
const response = await fetchImpl(resolveUrl(config.eventsApi, runId), {
|
|
242
|
+
headers: config.eventHeaders,
|
|
243
|
+
method: "GET",
|
|
244
|
+
signal: controller.signal
|
|
245
|
+
});
|
|
246
|
+
await streamFetchSSE(response, context, controller.signal);
|
|
247
|
+
context.finish();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (!isAbortError(error)) {
|
|
250
|
+
context.fail(toError(error));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
})();
|
|
254
|
+
return {
|
|
255
|
+
close: () => {
|
|
256
|
+
controller.abort();
|
|
257
|
+
},
|
|
258
|
+
stop: () => {
|
|
259
|
+
controller.abort();
|
|
260
|
+
if (config.cancelApi) {
|
|
261
|
+
void fetchImpl(resolveUrl(config.cancelApi, runId), {
|
|
262
|
+
method: "POST"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
runId
|
|
267
|
+
};
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
resume: ({ context, runId }) => {
|
|
271
|
+
context.setRunId(runId);
|
|
272
|
+
return connect(runId, context);
|
|
273
|
+
},
|
|
274
|
+
start: ({ context, message }) => {
|
|
275
|
+
const startController = new AbortController();
|
|
276
|
+
let activeRun;
|
|
277
|
+
void (async () => {
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetchImpl(config.startApi, {
|
|
280
|
+
body: JSON.stringify({ message, ...config.startBody }),
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
...config.startHeaders
|
|
284
|
+
},
|
|
285
|
+
method: config.startMethod ?? "POST",
|
|
286
|
+
signal: startController.signal
|
|
287
|
+
});
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`Background SSE start failed: ${response.status} ${response.statusText}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const data = await response.json();
|
|
294
|
+
const runId = (config.resolveRunId ?? resolveRunId)(data);
|
|
295
|
+
context.setRunId(runId);
|
|
296
|
+
activeRun = connect(runId, context);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (!isAbortError(error)) {
|
|
299
|
+
context.fail(toError(error));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
return {
|
|
304
|
+
close: () => {
|
|
305
|
+
startController.abort();
|
|
306
|
+
void activeRun?.close?.();
|
|
307
|
+
},
|
|
308
|
+
stop: () => {
|
|
309
|
+
startController.abort();
|
|
310
|
+
activeRun?.stop?.();
|
|
311
|
+
},
|
|
312
|
+
runId: null
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function defaultParseWebSocketMessage(data) {
|
|
318
|
+
if (typeof data !== "string") {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const parsed = JSON.parse(data);
|
|
322
|
+
return parsed;
|
|
323
|
+
}
|
|
324
|
+
function createWebSocketTransport(config) {
|
|
325
|
+
const parseMessage = config.parseMessage ?? defaultParseWebSocketMessage;
|
|
326
|
+
const serializeMessage = config.serializeMessage ?? JSON.stringify;
|
|
327
|
+
let resolvedRunId = null;
|
|
328
|
+
const applyIncomingEvents = (parsed, context) => {
|
|
329
|
+
const events = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
|
|
330
|
+
for (const item of events) {
|
|
331
|
+
const nextRunId = config.resolveRunId?.(item) ?? null;
|
|
332
|
+
if (nextRunId) {
|
|
333
|
+
resolvedRunId = nextRunId;
|
|
334
|
+
context.setRunId(nextRunId);
|
|
335
|
+
}
|
|
336
|
+
context.emit(item);
|
|
337
|
+
if (item.type === "done") {
|
|
338
|
+
context.finish();
|
|
339
|
+
} else if (item.type === "error") {
|
|
340
|
+
context.fail(
|
|
341
|
+
new Error(
|
|
342
|
+
"message" in item && typeof item.message === "string" ? item.message : "Stream error"
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
resume: ({ context, runId }) => {
|
|
350
|
+
context.setRunId(runId);
|
|
351
|
+
context.ensureAssistantMessage();
|
|
352
|
+
const socket = new WebSocket(
|
|
353
|
+
typeof config.url === "function" ? config.url(runId) : config.url,
|
|
354
|
+
config.protocols
|
|
355
|
+
);
|
|
356
|
+
let manuallyClosed = false;
|
|
357
|
+
let sentResumePayload = false;
|
|
358
|
+
const sendResumePayload = () => {
|
|
359
|
+
if (sentResumePayload || socket.readyState !== WebSocket.OPEN) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
sentResumePayload = true;
|
|
363
|
+
const payload = config.buildResumePayload?.(runId) ?? { [config.runIdKey ?? "runId"]: runId };
|
|
364
|
+
socket.send(serializeMessage(payload));
|
|
365
|
+
};
|
|
366
|
+
socket.onopen = sendResumePayload;
|
|
367
|
+
socket.onmessage = (event) => {
|
|
368
|
+
try {
|
|
369
|
+
applyIncomingEvents(parseMessage(event.data), context);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
context.fail(toError(error));
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
socket.onerror = () => {
|
|
375
|
+
context.fail(new Error("WebSocket connection failed"));
|
|
376
|
+
};
|
|
377
|
+
socket.onclose = () => {
|
|
378
|
+
if (!manuallyClosed) {
|
|
379
|
+
context.finish();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
queueMicrotask(sendResumePayload);
|
|
383
|
+
return {
|
|
384
|
+
close: () => {
|
|
385
|
+
manuallyClosed = true;
|
|
386
|
+
socket.close();
|
|
387
|
+
},
|
|
388
|
+
stop: () => {
|
|
389
|
+
manuallyClosed = true;
|
|
390
|
+
const stopRunId = resolvedRunId ?? runId;
|
|
391
|
+
if (stopRunId && config.cancelUrl) {
|
|
392
|
+
void fetch(resolveUrl(config.cancelUrl, stopRunId), {
|
|
393
|
+
method: "POST"
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
socket.close();
|
|
397
|
+
},
|
|
398
|
+
runId
|
|
399
|
+
};
|
|
400
|
+
},
|
|
401
|
+
start: ({ context, message }) => {
|
|
402
|
+
const runId = config.runId ?? config.getResumeKey?.() ?? null;
|
|
403
|
+
const socket = new WebSocket(
|
|
404
|
+
typeof config.url === "function" ? config.url(runId) : config.url,
|
|
405
|
+
config.protocols
|
|
406
|
+
);
|
|
407
|
+
let manuallyClosed = false;
|
|
408
|
+
let sentStartPayload = false;
|
|
409
|
+
const sendStartPayload = () => {
|
|
410
|
+
if (sentStartPayload || socket.readyState !== WebSocket.OPEN) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
sentStartPayload = true;
|
|
414
|
+
context.ensureAssistantMessage();
|
|
415
|
+
const payload = {
|
|
416
|
+
message,
|
|
417
|
+
...config.body
|
|
418
|
+
};
|
|
419
|
+
if (runId) {
|
|
420
|
+
payload[config.runIdKey ?? "runId"] = runId;
|
|
421
|
+
}
|
|
422
|
+
socket.send(serializeMessage(payload));
|
|
423
|
+
};
|
|
424
|
+
socket.onopen = sendStartPayload;
|
|
425
|
+
socket.onmessage = (event) => {
|
|
426
|
+
try {
|
|
427
|
+
applyIncomingEvents(parseMessage(event.data), context);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
context.fail(toError(error));
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
socket.onerror = () => {
|
|
433
|
+
context.fail(new Error("WebSocket connection failed"));
|
|
434
|
+
};
|
|
435
|
+
socket.onclose = () => {
|
|
436
|
+
if (!manuallyClosed) {
|
|
437
|
+
context.finish();
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
queueMicrotask(sendStartPayload);
|
|
441
|
+
return {
|
|
442
|
+
close: () => {
|
|
443
|
+
manuallyClosed = true;
|
|
444
|
+
socket.close();
|
|
445
|
+
},
|
|
446
|
+
stop: () => {
|
|
447
|
+
manuallyClosed = true;
|
|
448
|
+
const stopRunId = resolvedRunId ?? runId;
|
|
449
|
+
if (stopRunId && config.cancelUrl) {
|
|
450
|
+
void fetch(resolveUrl(config.cancelUrl, stopRunId), {
|
|
451
|
+
method: "POST"
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
socket.close();
|
|
455
|
+
},
|
|
456
|
+
runId
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function resolveTransport(options) {
|
|
462
|
+
if (typeof options.transport === "object" && options.transport) {
|
|
463
|
+
return options.transport;
|
|
464
|
+
}
|
|
465
|
+
const transportKind = options.transport ?? "sse";
|
|
466
|
+
if (transportKind === "background-sse") {
|
|
467
|
+
const config = options.transportOptions?.backgroundSSE;
|
|
468
|
+
if (!config) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
'`transportOptions.backgroundSSE` is required when transport is "background-sse"'
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
return createBackgroundSSETransport(config);
|
|
474
|
+
}
|
|
475
|
+
if (transportKind === "websocket") {
|
|
476
|
+
const config = options.transportOptions?.websocket;
|
|
477
|
+
if (!config) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
'`transportOptions.websocket` is required when transport is "websocket"'
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
return createWebSocketTransport(config);
|
|
483
|
+
}
|
|
484
|
+
const sseConfig = options.transportOptions?.sse ?? {
|
|
485
|
+
api: options.api,
|
|
486
|
+
body: options.body,
|
|
487
|
+
headers: options.headers
|
|
488
|
+
};
|
|
489
|
+
if (!sseConfig.api) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
"`api` or `transportOptions.sse.api` is required when using the default SSE transport"
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
return createDirectSSETransport({
|
|
495
|
+
...sseConfig,
|
|
496
|
+
api: sseConfig.api
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/use-stream-chat.ts
|
|
120
501
|
function defaultOnEvent(event, helpers) {
|
|
121
502
|
if (event.type === "text_delta") {
|
|
122
503
|
helpers.appendText(event.delta);
|
|
123
504
|
}
|
|
124
505
|
}
|
|
125
506
|
function useStreamChat(options) {
|
|
126
|
-
const {
|
|
127
|
-
|
|
128
|
-
headers,
|
|
129
|
-
body,
|
|
130
|
-
initialMessages,
|
|
131
|
-
onEvent,
|
|
132
|
-
onMessage,
|
|
133
|
-
onError,
|
|
134
|
-
onFinish
|
|
135
|
-
} = options;
|
|
136
|
-
const [messages, setMessages] = (0, import_react2.useState)(
|
|
137
|
-
initialMessages ?? []
|
|
138
|
-
);
|
|
507
|
+
const { initialMessages, onEvent, onMessage, onError, onFinish } = options;
|
|
508
|
+
const [messages, setMessages] = (0, import_react2.useState)(initialMessages ?? []);
|
|
139
509
|
const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
|
|
140
510
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
141
|
-
const
|
|
511
|
+
const [runId, setRunId] = (0, import_react2.useState)(null);
|
|
512
|
+
const runRef = (0, import_react2.useRef)(null);
|
|
513
|
+
const resumedRunIdRef = (0, import_react2.useRef)(null);
|
|
514
|
+
const manuallyStoppedRef = (0, import_react2.useRef)(false);
|
|
142
515
|
const messagesRef = (0, import_react2.useRef)(messages);
|
|
143
516
|
messagesRef.current = messages;
|
|
517
|
+
const transportOptionsRef = (0, import_react2.useRef)(options.transportOptions);
|
|
518
|
+
transportOptionsRef.current = options.transportOptions;
|
|
144
519
|
const appendText = (0, import_react2.useCallback)((delta) => {
|
|
145
520
|
setMessages((prev) => {
|
|
146
521
|
const last = prev[prev.length - 1];
|
|
@@ -156,29 +531,69 @@ function useStreamChat(options) {
|
|
|
156
531
|
} else {
|
|
157
532
|
parts.push({ type: "text", text: delta });
|
|
158
533
|
}
|
|
159
|
-
|
|
160
|
-
return [...prev.slice(0, -1), updated];
|
|
534
|
+
return [...prev.slice(0, -1), { ...last, parts }];
|
|
161
535
|
});
|
|
162
536
|
}, []);
|
|
163
537
|
const appendPart = (0, import_react2.useCallback)((part) => {
|
|
164
538
|
setMessages((prev) => {
|
|
165
539
|
const last = prev[prev.length - 1];
|
|
166
540
|
if (!last || last.role !== "assistant") return prev;
|
|
167
|
-
|
|
168
|
-
...
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
541
|
+
return [
|
|
542
|
+
...prev.slice(0, -1),
|
|
543
|
+
{
|
|
544
|
+
...last,
|
|
545
|
+
parts: [...last.parts, part]
|
|
546
|
+
}
|
|
547
|
+
];
|
|
172
548
|
});
|
|
173
549
|
}, []);
|
|
550
|
+
const transportRef = (0, import_react2.useRef)(null);
|
|
551
|
+
if (!transportRef.current) {
|
|
552
|
+
transportRef.current = resolveTransport(options);
|
|
553
|
+
}
|
|
554
|
+
const transport = transportRef.current;
|
|
555
|
+
const eventHandler = onEvent ?? defaultOnEvent;
|
|
556
|
+
const eventHandlerRef = (0, import_react2.useRef)(eventHandler);
|
|
557
|
+
eventHandlerRef.current = eventHandler;
|
|
558
|
+
const onErrorRef = (0, import_react2.useRef)(onError);
|
|
559
|
+
onErrorRef.current = onError;
|
|
560
|
+
const onFinishRef = (0, import_react2.useRef)(onFinish);
|
|
561
|
+
onFinishRef.current = onFinish;
|
|
562
|
+
const onMessageRef = (0, import_react2.useRef)(onMessage);
|
|
563
|
+
onMessageRef.current = onMessage;
|
|
564
|
+
const transportContext = (0, import_react2.useMemo)(
|
|
565
|
+
() => createChatTransportContext({
|
|
566
|
+
appendPart,
|
|
567
|
+
appendText,
|
|
568
|
+
eventHandler: (event, helpers) => eventHandlerRef.current(event, helpers),
|
|
569
|
+
getMessages: () => messagesRef.current,
|
|
570
|
+
onError: (...args) => onErrorRef.current?.(...args),
|
|
571
|
+
onFinish: (...args) => onFinishRef.current?.(...args),
|
|
572
|
+
onMessage: (...args) => onMessageRef.current?.(...args),
|
|
573
|
+
setError,
|
|
574
|
+
setIsLoading,
|
|
575
|
+
setMessages,
|
|
576
|
+
setRunId: (next) => {
|
|
577
|
+
setRunId(next);
|
|
578
|
+
transportOptionsRef.current?.backgroundSSE?.onRunIdChange?.(next);
|
|
579
|
+
transportOptionsRef.current?.websocket?.onRunIdChange?.(next);
|
|
580
|
+
}
|
|
581
|
+
}),
|
|
582
|
+
[appendPart, appendText]
|
|
583
|
+
);
|
|
174
584
|
const stop = (0, import_react2.useCallback)(() => {
|
|
175
|
-
|
|
176
|
-
|
|
585
|
+
const activeRun = runRef.current;
|
|
586
|
+
if (!activeRun?.stop) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
manuallyStoppedRef.current = true;
|
|
590
|
+
void activeRun.stop();
|
|
591
|
+
runRef.current = null;
|
|
177
592
|
setIsLoading(false);
|
|
178
593
|
}, []);
|
|
179
594
|
const sendMessage = (0, import_react2.useCallback)(
|
|
180
595
|
(text) => {
|
|
181
|
-
if (
|
|
596
|
+
if (runRef.current || isLoading) {
|
|
182
597
|
return;
|
|
183
598
|
}
|
|
184
599
|
const userMessage = createMessage("user", [
|
|
@@ -193,89 +608,69 @@ function useStreamChat(options) {
|
|
|
193
608
|
onMessage?.(userMessage);
|
|
194
609
|
setError(null);
|
|
195
610
|
setIsLoading(true);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
};
|
|
204
|
-
(async () => {
|
|
205
|
-
try {
|
|
206
|
-
const response = await fetch(api, {
|
|
207
|
-
method: "POST",
|
|
208
|
-
headers: {
|
|
209
|
-
"Content-Type": "application/json",
|
|
210
|
-
...headers
|
|
211
|
-
},
|
|
212
|
-
body: JSON.stringify({ message: text, ...body }),
|
|
213
|
-
signal: controller.signal
|
|
214
|
-
});
|
|
215
|
-
if (!response.ok) {
|
|
216
|
-
throw new Error(
|
|
217
|
-
`SSE request failed: ${response.status} ${response.statusText}`
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
if (!response.body) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
"Response body is null \u2014 SSE streaming not supported"
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
for await (const event of (0, import_core.parseSSEStream)(
|
|
226
|
-
response.body,
|
|
227
|
-
controller.signal
|
|
228
|
-
)) {
|
|
229
|
-
eventHandler(event, helpers);
|
|
230
|
-
}
|
|
231
|
-
const finalMessages = messagesRef.current;
|
|
232
|
-
const lastMessage = finalMessages[finalMessages.length - 1];
|
|
233
|
-
if (lastMessage?.role === "assistant") {
|
|
234
|
-
onMessage?.(lastMessage);
|
|
235
|
-
}
|
|
236
|
-
onFinish?.(finalMessages);
|
|
237
|
-
} catch (err) {
|
|
238
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
242
|
-
setError(error2);
|
|
243
|
-
onError?.(error2);
|
|
244
|
-
} finally {
|
|
245
|
-
abortRef.current = null;
|
|
246
|
-
setIsLoading(false);
|
|
247
|
-
}
|
|
248
|
-
})();
|
|
611
|
+
resumedRunIdRef.current = null;
|
|
612
|
+
manuallyStoppedRef.current = false;
|
|
613
|
+
const run = transport.start({ context: transportContext, message: text });
|
|
614
|
+
runRef.current = run ?? null;
|
|
615
|
+
if (run?.runId) {
|
|
616
|
+
setRunId(run.runId);
|
|
617
|
+
}
|
|
249
618
|
},
|
|
250
|
-
[
|
|
251
|
-
api,
|
|
252
|
-
headers,
|
|
253
|
-
body,
|
|
254
|
-
onEvent,
|
|
255
|
-
onMessage,
|
|
256
|
-
onError,
|
|
257
|
-
onFinish,
|
|
258
|
-
appendText,
|
|
259
|
-
appendPart
|
|
260
|
-
]
|
|
619
|
+
[isLoading, onMessage, transport, transportContext]
|
|
261
620
|
);
|
|
621
|
+
const candidateRunId = options.transportOptions?.backgroundSSE?.runId ?? options.transportOptions?.backgroundSSE?.getResumeKey?.() ?? options.transportOptions?.websocket?.runId ?? options.transportOptions?.websocket?.getResumeKey?.() ?? null;
|
|
622
|
+
(0, import_react2.useEffect)(() => {
|
|
623
|
+
if (runRef.current) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (!candidateRunId) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (manuallyStoppedRef.current) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (resumedRunIdRef.current === candidateRunId) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (!transport.resume) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
resumedRunIdRef.current = candidateRunId;
|
|
639
|
+
setError(null);
|
|
640
|
+
setIsLoading(true);
|
|
641
|
+
const run = transport.resume({
|
|
642
|
+
context: transportContext,
|
|
643
|
+
runId: candidateRunId
|
|
644
|
+
});
|
|
645
|
+
runRef.current = run ?? null;
|
|
646
|
+
setRunId(candidateRunId);
|
|
647
|
+
}, [candidateRunId, transport, transportContext]);
|
|
262
648
|
(0, import_react2.useEffect)(() => {
|
|
263
649
|
return () => {
|
|
264
|
-
|
|
265
|
-
|
|
650
|
+
void runRef.current?.close?.();
|
|
651
|
+
runRef.current = null;
|
|
266
652
|
};
|
|
267
653
|
}, []);
|
|
654
|
+
const prevIsLoadingRef = (0, import_react2.useRef)(isLoading);
|
|
655
|
+
(0, import_react2.useEffect)(() => {
|
|
656
|
+
if (prevIsLoadingRef.current && !isLoading) {
|
|
657
|
+
runRef.current = null;
|
|
658
|
+
}
|
|
659
|
+
prevIsLoadingRef.current = isLoading;
|
|
660
|
+
}, [isLoading]);
|
|
268
661
|
return {
|
|
269
|
-
messages,
|
|
270
|
-
isLoading,
|
|
271
662
|
error,
|
|
663
|
+
isLoading,
|
|
664
|
+
messages,
|
|
665
|
+
runId,
|
|
272
666
|
sendMessage,
|
|
273
|
-
|
|
274
|
-
|
|
667
|
+
setMessages,
|
|
668
|
+
stop
|
|
275
669
|
};
|
|
276
670
|
}
|
|
277
671
|
// Annotate the CommonJS export names for ESM import in node:
|
|
278
672
|
0 && (module.exports = {
|
|
673
|
+
fromAgnoAgents,
|
|
279
674
|
fromOpenAiAgents,
|
|
280
675
|
parseSSEStream,
|
|
281
676
|
useAutoScroll,
|