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