@apps-megafy/agentic-chat 0.1.22
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/AdmissionChat.d.ts +3 -0
- package/dist/AdmissionChat.d.ts.map +1 -0
- package/dist/AdmissionChat.js +179 -0
- package/dist/AdmissionChat.js.map +1 -0
- package/dist/hooks/useAdmissionState.d.ts +9 -0
- package/dist/hooks/useAdmissionState.d.ts.map +1 -0
- package/dist/hooks/useAdmissionState.js +66 -0
- package/dist/hooks/useAdmissionState.js.map +1 -0
- package/dist/hooks/useAgentStream.d.ts +20 -0
- package/dist/hooks/useAgentStream.d.ts.map +1 -0
- package/dist/hooks/useAgentStream.js +680 -0
- package/dist/hooks/useAgentStream.js.map +1 -0
- package/dist/hooks/useDocumentUpload.d.ts +26 -0
- package/dist/hooks/useDocumentUpload.d.ts.map +1 -0
- package/dist/hooks/useDocumentUpload.js +72 -0
- package/dist/hooks/useDocumentUpload.js.map +1 -0
- package/dist/hooks/useVoiceInput.d.ts +14 -0
- package/dist/hooks/useVoiceInput.d.ts.map +1 -0
- package/dist/hooks/useVoiceInput.js +93 -0
- package/dist/hooks/useVoiceInput.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/services/agentClient.d.ts +38 -0
- package/dist/services/agentClient.d.ts.map +1 -0
- package/dist/services/agentClient.js +74 -0
- package/dist/services/agentClient.js.map +1 -0
- package/dist/services/websocketClient.d.ts +10 -0
- package/dist/services/websocketClient.d.ts.map +1 -0
- package/dist/services/websocketClient.js +29 -0
- package/dist/services/websocketClient.js.map +1 -0
- package/dist/types/index.d.ts +126 -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/package.json +40 -0
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { createClientBulkAnswerMessage, createClientDocumentMessage, createClientDocumentReferenceMessage, createClientMessage, createClientPingMessage, createClientQuickActionMessage, createClientResetSessionMessage, resolveAgentWebSocketUrl } from "../services/agentClient";
|
|
3
|
+
import { createAgentSocket } from "../services/websocketClient";
|
|
4
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 10_000;
|
|
5
|
+
const HEARTBEAT_INTERVAL_MS = 4 * 60 * 1000;
|
|
6
|
+
const RECONNECT_AFTER_OPEN_DELAY_MS = 350;
|
|
7
|
+
export function useAgentStream({ sessionId, websocketUrl, agentBaseUrl, documentUploadUrl, initialMessages = [], connectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS, onProgress, onError }) {
|
|
8
|
+
const [messages, setMessages] = useState(initialMessages);
|
|
9
|
+
const [quickActions, setQuickActions] = useState([]);
|
|
10
|
+
const [widgets, setWidgets] = useState([]);
|
|
11
|
+
const [questionCard, setQuestionCard] = useState(null);
|
|
12
|
+
const [choiceCard, setChoiceCard] = useState(null);
|
|
13
|
+
const [status, setStatus] = useState("idle");
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [connectionAttempt, setConnectionAttempt] = useState(0);
|
|
16
|
+
const socketRef = useRef(null);
|
|
17
|
+
const streamMessageIdRef = useRef(null);
|
|
18
|
+
const documentProcessingMessageIdRef = useRef(null);
|
|
19
|
+
const connectionIdRef = useRef(0);
|
|
20
|
+
const statusRef = useRef("idle");
|
|
21
|
+
const resolvedUrl = useMemo(() => resolveAgentWebSocketUrl({ sessionId, websocketUrl, agentBaseUrl }), [agentBaseUrl, sessionId, websocketUrl]);
|
|
22
|
+
const pushError = useCallback((nextError) => {
|
|
23
|
+
setError(nextError);
|
|
24
|
+
onError?.(nextError);
|
|
25
|
+
}, [onError]);
|
|
26
|
+
const clearError = useCallback(() => {
|
|
27
|
+
setError(null);
|
|
28
|
+
}, []);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
statusRef.current = status;
|
|
31
|
+
}, [status]);
|
|
32
|
+
const handleServerMessage = useCallback((message) => {
|
|
33
|
+
switch (message.type) {
|
|
34
|
+
case "session_ready":
|
|
35
|
+
clearError();
|
|
36
|
+
if (message.progress) {
|
|
37
|
+
onProgress?.(message.progress);
|
|
38
|
+
}
|
|
39
|
+
updateQuickActions(message.quick_actions, setQuickActions);
|
|
40
|
+
updateFrontendWidgets(message.widgets, setWidgets, setQuestionCard, setChoiceCard);
|
|
41
|
+
break;
|
|
42
|
+
case "history":
|
|
43
|
+
clearError();
|
|
44
|
+
documentProcessingMessageIdRef.current = null;
|
|
45
|
+
setMessages(message.messages.map((item, index) => ({
|
|
46
|
+
id: `history-${index}`,
|
|
47
|
+
role: item.role === "assistant" ? "agent" : "user",
|
|
48
|
+
content: item.content,
|
|
49
|
+
attachments: item.attachments,
|
|
50
|
+
confirmation: item.confirmation,
|
|
51
|
+
status: "sent"
|
|
52
|
+
})));
|
|
53
|
+
updateQuickActions(message.quick_actions, setQuickActions);
|
|
54
|
+
updateFrontendWidgets(message.widgets, setWidgets, setQuestionCard, setChoiceCard);
|
|
55
|
+
break;
|
|
56
|
+
case "message":
|
|
57
|
+
clearError();
|
|
58
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
59
|
+
setMessages((current) => [
|
|
60
|
+
...current,
|
|
61
|
+
{
|
|
62
|
+
id: createId("agent"),
|
|
63
|
+
role: message.role === "assistant" ? "agent" : "user",
|
|
64
|
+
content: message.content,
|
|
65
|
+
attachments: message.attachments,
|
|
66
|
+
confirmation: message.confirmation,
|
|
67
|
+
status: "sent"
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
updateQuickActions(message.quick_actions, setQuickActions);
|
|
71
|
+
updateFrontendWidgets(message.widgets, setWidgets, setQuestionCard, setChoiceCard);
|
|
72
|
+
break;
|
|
73
|
+
case "stream_start": {
|
|
74
|
+
clearError();
|
|
75
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
76
|
+
const id = createId("stream");
|
|
77
|
+
streamMessageIdRef.current = id;
|
|
78
|
+
setQuickActions([]);
|
|
79
|
+
setWidgets([]);
|
|
80
|
+
setMessages((current) => [
|
|
81
|
+
...current,
|
|
82
|
+
{
|
|
83
|
+
id,
|
|
84
|
+
role: "agent",
|
|
85
|
+
content: "",
|
|
86
|
+
isStreaming: true,
|
|
87
|
+
status: "sent"
|
|
88
|
+
}
|
|
89
|
+
]);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "stream_token": {
|
|
93
|
+
clearError();
|
|
94
|
+
const streamId = ensureStreamingMessage(streamMessageIdRef, setMessages);
|
|
95
|
+
setMessages((current) => current.map((item) => item.id === streamId ? { ...item, content: item.content + message.token } : item));
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "stream_end": {
|
|
99
|
+
clearError();
|
|
100
|
+
const streamId = streamMessageIdRef.current;
|
|
101
|
+
if (!streamId) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setMessages((current) => current.map((item) => item.id === streamId
|
|
105
|
+
? {
|
|
106
|
+
...item,
|
|
107
|
+
content: message.content,
|
|
108
|
+
attachments: message.attachments,
|
|
109
|
+
confirmation: message.confirmation,
|
|
110
|
+
isStreaming: false
|
|
111
|
+
}
|
|
112
|
+
: item));
|
|
113
|
+
streamMessageIdRef.current = null;
|
|
114
|
+
updateQuickActions(message.quick_actions, setQuickActions);
|
|
115
|
+
updateFrontendWidgets(message.widgets, setWidgets, setQuestionCard, setChoiceCard);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "quick_actions":
|
|
119
|
+
clearError();
|
|
120
|
+
setQuickActions(normalizeQuickActions(message.actions));
|
|
121
|
+
break;
|
|
122
|
+
case "widgets":
|
|
123
|
+
clearError();
|
|
124
|
+
updateFrontendWidgets(message.widgets, setWidgets, setQuestionCard, setChoiceCard);
|
|
125
|
+
break;
|
|
126
|
+
case "progress":
|
|
127
|
+
clearError();
|
|
128
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
129
|
+
onProgress?.(message.data);
|
|
130
|
+
updateQuickActions(message.quick_actions, setQuickActions);
|
|
131
|
+
updateFrontendWidgets(message.widgets, setWidgets, setQuestionCard, setChoiceCard);
|
|
132
|
+
break;
|
|
133
|
+
case "pong":
|
|
134
|
+
clearError();
|
|
135
|
+
break;
|
|
136
|
+
case "error":
|
|
137
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
138
|
+
pushError(new Error(`${message.code}: ${message.message}`));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}, [clearError, onProgress, pushError]);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const connectionId = connectionIdRef.current + 1;
|
|
144
|
+
connectionIdRef.current = connectionId;
|
|
145
|
+
let terminalStatus = null;
|
|
146
|
+
let timeoutId = null;
|
|
147
|
+
let reconnectTimeoutId = null;
|
|
148
|
+
let isActive = true;
|
|
149
|
+
let opened = false;
|
|
150
|
+
const isCurrentConnection = () => isActive && connectionIdRef.current === connectionId;
|
|
151
|
+
const clearConnectionTimeout = () => {
|
|
152
|
+
if (timeoutId) {
|
|
153
|
+
clearTimeout(timeoutId);
|
|
154
|
+
timeoutId = null;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const clearReconnectTimeout = () => {
|
|
158
|
+
if (reconnectTimeoutId) {
|
|
159
|
+
clearTimeout(reconnectTimeoutId);
|
|
160
|
+
reconnectTimeoutId = null;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const scheduleReconnect = () => {
|
|
164
|
+
if (!isCurrentConnection() || terminalStatus) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
clearConnectionTimeout();
|
|
168
|
+
clearReconnectTimeout();
|
|
169
|
+
setStatus("connecting");
|
|
170
|
+
reconnectTimeoutId = setTimeout(() => {
|
|
171
|
+
if (isCurrentConnection()) {
|
|
172
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
173
|
+
}
|
|
174
|
+
}, RECONNECT_AFTER_OPEN_DELAY_MS);
|
|
175
|
+
};
|
|
176
|
+
const finishConnectionAttempt = (nextStatus, nextError) => {
|
|
177
|
+
if (!isCurrentConnection() || terminalStatus) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
terminalStatus = nextStatus;
|
|
181
|
+
clearConnectionTimeout();
|
|
182
|
+
setStatus(nextStatus);
|
|
183
|
+
pushError(nextError);
|
|
184
|
+
};
|
|
185
|
+
setStatus("connecting");
|
|
186
|
+
clearError();
|
|
187
|
+
const socket = createAgentSocket(resolvedUrl, {
|
|
188
|
+
onOpen: () => {
|
|
189
|
+
if (!isCurrentConnection() || terminalStatus) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
opened = true;
|
|
193
|
+
clearConnectionTimeout();
|
|
194
|
+
setStatus("open");
|
|
195
|
+
clearError();
|
|
196
|
+
},
|
|
197
|
+
onMessage: (message) => {
|
|
198
|
+
if (isCurrentConnection()) {
|
|
199
|
+
handleServerMessage(message);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
onError: () => {
|
|
203
|
+
if (opened) {
|
|
204
|
+
pushError(new Error("Se perdió la conexión con el agente. Reconectando..."));
|
|
205
|
+
socket.close();
|
|
206
|
+
scheduleReconnect();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
finishConnectionAttempt("error", new Error("No se pudo conectar con el agente."));
|
|
210
|
+
socket.close();
|
|
211
|
+
},
|
|
212
|
+
onClose: () => {
|
|
213
|
+
if (!isCurrentConnection() || terminalStatus) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (opened) {
|
|
217
|
+
scheduleReconnect();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
clearConnectionTimeout();
|
|
221
|
+
setStatus("closed");
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
socketRef.current = socket;
|
|
225
|
+
timeoutId = setTimeout(() => {
|
|
226
|
+
finishConnectionAttempt("timeout", new Error("El agente no respondió a tiempo. Reintentá la conexión."));
|
|
227
|
+
socket.close();
|
|
228
|
+
}, Math.max(connectionTimeoutMs, 1000));
|
|
229
|
+
return () => {
|
|
230
|
+
isActive = false;
|
|
231
|
+
clearConnectionTimeout();
|
|
232
|
+
clearReconnectTimeout();
|
|
233
|
+
socket.close();
|
|
234
|
+
if (socketRef.current === socket) {
|
|
235
|
+
socketRef.current = null;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}, [
|
|
239
|
+
clearError,
|
|
240
|
+
connectionAttempt,
|
|
241
|
+
connectionTimeoutMs,
|
|
242
|
+
handleServerMessage,
|
|
243
|
+
pushError,
|
|
244
|
+
resolvedUrl
|
|
245
|
+
]);
|
|
246
|
+
const getOpenSocket = useCallback(() => {
|
|
247
|
+
const socket = socketRef.current;
|
|
248
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
249
|
+
return socket;
|
|
250
|
+
}
|
|
251
|
+
if (socket?.readyState === WebSocket.CONNECTING) {
|
|
252
|
+
setStatus("connecting");
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
setStatus("closed");
|
|
256
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
257
|
+
}
|
|
258
|
+
pushError(new Error("El agente no está conectado. Reintentá la conexión."));
|
|
259
|
+
return null;
|
|
260
|
+
}, [pushError]);
|
|
261
|
+
const sendSocketPayload = useCallback((payload) => {
|
|
262
|
+
const socket = getOpenSocket();
|
|
263
|
+
if (!socket) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
socket.send(payload);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
setStatus("closed");
|
|
272
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
273
|
+
pushError(new Error("Se perdió la conexión con el agente. Reintentá la conexión."));
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}, [getOpenSocket, pushError]);
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
if (status !== "open") {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const intervalId = window.setInterval(() => {
|
|
282
|
+
const socket = socketRef.current;
|
|
283
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
284
|
+
setStatus("closed");
|
|
285
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
socket.send(createClientPingMessage());
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
setStatus("closed");
|
|
293
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
294
|
+
}
|
|
295
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
296
|
+
return () => window.clearInterval(intervalId);
|
|
297
|
+
}, [status]);
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
const refreshConnectionIfStale = () => {
|
|
300
|
+
const currentStatus = statusRef.current;
|
|
301
|
+
if (currentStatus === "connecting" || currentStatus === "idle") {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const socket = socketRef.current;
|
|
305
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
306
|
+
try {
|
|
307
|
+
socket.send(createClientPingMessage());
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
setStatus("closed");
|
|
311
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
clearError();
|
|
316
|
+
setStatus("connecting");
|
|
317
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
318
|
+
};
|
|
319
|
+
const handleVisibilityChange = () => {
|
|
320
|
+
if (document.visibilityState === "visible") {
|
|
321
|
+
refreshConnectionIfStale();
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
window.addEventListener("focus", refreshConnectionIfStale);
|
|
325
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
326
|
+
return () => {
|
|
327
|
+
window.removeEventListener("focus", refreshConnectionIfStale);
|
|
328
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
329
|
+
};
|
|
330
|
+
}, [clearError]);
|
|
331
|
+
const sendMessage = useCallback((content) => {
|
|
332
|
+
const cleanContent = content.trim();
|
|
333
|
+
if (!cleanContent) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
clearError();
|
|
337
|
+
if (!sendSocketPayload(createClientMessage(cleanContent))) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
setMessages((current) => [
|
|
341
|
+
...current,
|
|
342
|
+
{
|
|
343
|
+
id: createId("user"),
|
|
344
|
+
role: "user",
|
|
345
|
+
content: cleanContent,
|
|
346
|
+
status: "sent"
|
|
347
|
+
}
|
|
348
|
+
]);
|
|
349
|
+
setQuickActions([]);
|
|
350
|
+
setWidgets([]);
|
|
351
|
+
}, [clearError, sendSocketPayload]);
|
|
352
|
+
const sendDocumentMessage = useCallback(async (attachment, file) => {
|
|
353
|
+
clearError();
|
|
354
|
+
if (!getOpenSocket()) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const processingId = createId("document-processing");
|
|
358
|
+
documentProcessingMessageIdRef.current = processingId;
|
|
359
|
+
setMessages((current) => [
|
|
360
|
+
...current,
|
|
361
|
+
{
|
|
362
|
+
id: createId("user-document"),
|
|
363
|
+
role: "user",
|
|
364
|
+
content: `Subí el documento **${attachment.name}**.`,
|
|
365
|
+
attachments: [attachment],
|
|
366
|
+
status: "sent"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: processingId,
|
|
370
|
+
role: "agent",
|
|
371
|
+
content: "Procesando documento...",
|
|
372
|
+
isStreaming: true,
|
|
373
|
+
status: "sent"
|
|
374
|
+
}
|
|
375
|
+
]);
|
|
376
|
+
setQuickActions([]);
|
|
377
|
+
setWidgets([]);
|
|
378
|
+
try {
|
|
379
|
+
if (documentUploadUrl) {
|
|
380
|
+
const documentReference = await uploadDocumentWithPresignedUrl({
|
|
381
|
+
uploadUrl: documentUploadUrl,
|
|
382
|
+
sessionId,
|
|
383
|
+
attachment,
|
|
384
|
+
file
|
|
385
|
+
});
|
|
386
|
+
if (!sendSocketPayload(createClientDocumentReferenceMessage(documentReference))) {
|
|
387
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const contentBase64 = await readFileAsBase64(file);
|
|
392
|
+
if (!sendSocketPayload(createClientDocumentMessage({
|
|
393
|
+
id: attachment.id,
|
|
394
|
+
name: attachment.name,
|
|
395
|
+
kind: attachment.kind,
|
|
396
|
+
mimeType: attachment.mimeType,
|
|
397
|
+
sizeBytes: attachment.sizeBytes,
|
|
398
|
+
contentBase64
|
|
399
|
+
}))) {
|
|
400
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages);
|
|
405
|
+
pushError(new Error("No se pudo enviar el documento al agente."));
|
|
406
|
+
}
|
|
407
|
+
}, [clearError, documentUploadUrl, getOpenSocket, pushError, sendSocketPayload, sessionId]);
|
|
408
|
+
const sendQuickAction = useCallback((action) => {
|
|
409
|
+
const label = action.label.trim();
|
|
410
|
+
const value = action.value?.trim();
|
|
411
|
+
const visibleContent = label || value;
|
|
412
|
+
if (!visibleContent) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
clearError();
|
|
416
|
+
if (!sendSocketPayload(createClientQuickActionMessage({
|
|
417
|
+
id: action.id,
|
|
418
|
+
label: label || visibleContent,
|
|
419
|
+
value
|
|
420
|
+
}))) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
setMessages((current) => [
|
|
424
|
+
...current,
|
|
425
|
+
{
|
|
426
|
+
id: createId("user-action"),
|
|
427
|
+
role: "user",
|
|
428
|
+
content: visibleContent,
|
|
429
|
+
status: "sent"
|
|
430
|
+
}
|
|
431
|
+
]);
|
|
432
|
+
setQuickActions([]);
|
|
433
|
+
setWidgets([]);
|
|
434
|
+
}, [clearError, sendSocketPayload]);
|
|
435
|
+
const sendWidgetValue = useCallback((widget, value) => {
|
|
436
|
+
if (widget.type !== "date_picker" && widget.type !== "location_picker") {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const cleanValue = value.trim();
|
|
440
|
+
if (!cleanValue) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
clearError();
|
|
444
|
+
if (!sendSocketPayload(createClientMessage(cleanValue))) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
setMessages((current) => [
|
|
448
|
+
...current,
|
|
449
|
+
{
|
|
450
|
+
id: createId("user-widget"),
|
|
451
|
+
role: "user",
|
|
452
|
+
content: cleanValue,
|
|
453
|
+
status: "sent"
|
|
454
|
+
}
|
|
455
|
+
]);
|
|
456
|
+
setQuickActions([]);
|
|
457
|
+
setWidgets([]);
|
|
458
|
+
}, [clearError, sendSocketPayload]);
|
|
459
|
+
const sendBulkAnswer = useCallback((widget, answers) => {
|
|
460
|
+
clearError();
|
|
461
|
+
if (!sendSocketPayload(createClientBulkAnswerMessage(widget.id, answers))) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const answerValues = Object.values(answers).filter(Boolean).join(' | ');
|
|
465
|
+
setMessages((current) => [
|
|
466
|
+
...current,
|
|
467
|
+
{
|
|
468
|
+
id: createId("user-bulk"),
|
|
469
|
+
role: "user",
|
|
470
|
+
content: answerValues || widget.label,
|
|
471
|
+
status: "sent"
|
|
472
|
+
}
|
|
473
|
+
]);
|
|
474
|
+
setQuickActions([]);
|
|
475
|
+
setWidgets([]);
|
|
476
|
+
setQuestionCard(null);
|
|
477
|
+
setChoiceCard(null);
|
|
478
|
+
}, [clearError, sendSocketPayload]);
|
|
479
|
+
const regenerateLastAgentMessage = useCallback(() => {
|
|
480
|
+
sendMessage("Regenera tu respuesta anterior, por favor.");
|
|
481
|
+
}, [sendMessage]);
|
|
482
|
+
const retryConnection = useCallback(() => {
|
|
483
|
+
streamMessageIdRef.current = null;
|
|
484
|
+
documentProcessingMessageIdRef.current = null;
|
|
485
|
+
clearError();
|
|
486
|
+
setWidgets([]);
|
|
487
|
+
setQuestionCard(null);
|
|
488
|
+
setChoiceCard(null);
|
|
489
|
+
setStatus("idle");
|
|
490
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
491
|
+
}, [clearError]);
|
|
492
|
+
const resetSession = useCallback(() => {
|
|
493
|
+
clearError();
|
|
494
|
+
streamMessageIdRef.current = null;
|
|
495
|
+
documentProcessingMessageIdRef.current = null;
|
|
496
|
+
const socket = socketRef.current;
|
|
497
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
498
|
+
setMessages([]);
|
|
499
|
+
setQuickActions([]);
|
|
500
|
+
setWidgets([]);
|
|
501
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
setMessages([]);
|
|
505
|
+
setQuickActions([]);
|
|
506
|
+
setWidgets([]);
|
|
507
|
+
setQuestionCard(null);
|
|
508
|
+
setChoiceCard(null);
|
|
509
|
+
if (!sendSocketPayload(createClientResetSessionMessage())) {
|
|
510
|
+
setConnectionAttempt((attempt) => attempt + 1);
|
|
511
|
+
}
|
|
512
|
+
}, [clearError, sendSocketPayload]);
|
|
513
|
+
return {
|
|
514
|
+
messages,
|
|
515
|
+
quickActions,
|
|
516
|
+
widgets,
|
|
517
|
+
questionCard,
|
|
518
|
+
choiceCard,
|
|
519
|
+
status,
|
|
520
|
+
error,
|
|
521
|
+
sendMessage,
|
|
522
|
+
sendDocumentMessage,
|
|
523
|
+
sendQuickAction,
|
|
524
|
+
sendWidgetValue,
|
|
525
|
+
sendBulkAnswer,
|
|
526
|
+
regenerateLastAgentMessage,
|
|
527
|
+
retryConnection,
|
|
528
|
+
resetSession
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function updateQuickActions(actions, setQuickActions) {
|
|
532
|
+
if (actions === undefined) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
setQuickActions(normalizeQuickActions(actions));
|
|
536
|
+
}
|
|
537
|
+
function normalizeQuickActions(actions) {
|
|
538
|
+
if (!Array.isArray(actions)) {
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
541
|
+
return actions
|
|
542
|
+
.filter((action) => typeof action.label === "string" && action.label.trim())
|
|
543
|
+
.map((action, index) => ({
|
|
544
|
+
...action,
|
|
545
|
+
id: action.id || `action-${index}-${action.label}`
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
function updateFrontendWidgets(rawWidgets, setWidgets, setQuestionCard, setChoiceCard) {
|
|
549
|
+
if (rawWidgets === undefined) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (!Array.isArray(rawWidgets)) {
|
|
553
|
+
setWidgets([]);
|
|
554
|
+
setQuestionCard(null);
|
|
555
|
+
setChoiceCard(null);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const standard = [];
|
|
559
|
+
let questionCard = null;
|
|
560
|
+
let choiceCard = null;
|
|
561
|
+
for (const widget of rawWidgets) {
|
|
562
|
+
if (!widget || typeof widget !== "object")
|
|
563
|
+
continue;
|
|
564
|
+
if (widget.type === "date_picker") {
|
|
565
|
+
if (typeof widget.id === "string" &&
|
|
566
|
+
typeof widget.fieldId === "string" &&
|
|
567
|
+
typeof widget.label === "string") {
|
|
568
|
+
standard.push(widget);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
else if (widget.type === "location_picker") {
|
|
572
|
+
const lw = widget;
|
|
573
|
+
if (typeof lw.id === "string" &&
|
|
574
|
+
typeof lw.fieldId === "string" &&
|
|
575
|
+
typeof lw.label === "string" &&
|
|
576
|
+
typeof lw.center?.lat === "number" &&
|
|
577
|
+
typeof lw.center?.lng === "number") {
|
|
578
|
+
standard.push(lw);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else if (widget.type === "question_card") {
|
|
582
|
+
const qw = widget;
|
|
583
|
+
if (typeof qw.id === "string" && typeof qw.label === "string" && Array.isArray(qw.fields)) {
|
|
584
|
+
questionCard = qw;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else if (widget.type === "choice_card") {
|
|
588
|
+
const cw = widget;
|
|
589
|
+
if (typeof cw.id === "string" && typeof cw.label === "string" && Array.isArray(cw.fields)) {
|
|
590
|
+
choiceCard = cw;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
setWidgets(standard);
|
|
595
|
+
setQuestionCard(questionCard);
|
|
596
|
+
setChoiceCard(choiceCard);
|
|
597
|
+
}
|
|
598
|
+
function ensureStreamingMessage(streamMessageIdRef, setMessages) {
|
|
599
|
+
if (streamMessageIdRef.current) {
|
|
600
|
+
return streamMessageIdRef.current;
|
|
601
|
+
}
|
|
602
|
+
const id = createId("stream");
|
|
603
|
+
streamMessageIdRef.current = id;
|
|
604
|
+
setMessages((current) => [
|
|
605
|
+
...current,
|
|
606
|
+
{ id, role: "agent", content: "", isStreaming: true, status: "sent" }
|
|
607
|
+
]);
|
|
608
|
+
return id;
|
|
609
|
+
}
|
|
610
|
+
function dismissDocumentProcessingMessage(documentProcessingMessageIdRef, setMessages) {
|
|
611
|
+
const processingId = documentProcessingMessageIdRef.current;
|
|
612
|
+
if (!processingId) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
documentProcessingMessageIdRef.current = null;
|
|
616
|
+
setMessages((current) => current.filter((item) => item.id !== processingId));
|
|
617
|
+
}
|
|
618
|
+
function createId(prefix) {
|
|
619
|
+
if ("crypto" in globalThis && typeof globalThis.crypto.randomUUID === "function") {
|
|
620
|
+
return `${prefix}-${globalThis.crypto.randomUUID()}`;
|
|
621
|
+
}
|
|
622
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
623
|
+
}
|
|
624
|
+
function readFileAsBase64(file) {
|
|
625
|
+
return new Promise((resolve, reject) => {
|
|
626
|
+
const reader = new FileReader();
|
|
627
|
+
reader.onload = () => {
|
|
628
|
+
const result = reader.result;
|
|
629
|
+
if (typeof result !== "string") {
|
|
630
|
+
reject(new Error("No se pudo leer el documento."));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
resolve(result.split(",")[1] ?? "");
|
|
634
|
+
};
|
|
635
|
+
reader.onerror = () => reject(new Error("No se pudo leer el documento."));
|
|
636
|
+
reader.readAsDataURL(file);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
async function uploadDocumentWithPresignedUrl({ uploadUrl, sessionId, attachment, file }) {
|
|
640
|
+
const presignResponse = await fetch(uploadUrl, {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: { "Content-Type": "application/json" },
|
|
643
|
+
body: JSON.stringify({
|
|
644
|
+
session_id: sessionId,
|
|
645
|
+
id: attachment.id,
|
|
646
|
+
name: attachment.name,
|
|
647
|
+
kind: attachment.kind,
|
|
648
|
+
mime_type: attachment.mimeType || file.type || "application/octet-stream",
|
|
649
|
+
size_bytes: attachment.sizeBytes || file.size
|
|
650
|
+
})
|
|
651
|
+
});
|
|
652
|
+
if (!presignResponse.ok) {
|
|
653
|
+
throw new Error("No se pudo preparar la carga del documento.");
|
|
654
|
+
}
|
|
655
|
+
const presign = (await presignResponse.json());
|
|
656
|
+
if (!presign.uploadUrl || !presign.document?.bucket || !presign.document.key) {
|
|
657
|
+
throw new Error("La respuesta de carga del documento no es válida.");
|
|
658
|
+
}
|
|
659
|
+
const uploadResponse = await fetch(presign.uploadUrl, {
|
|
660
|
+
method: presign.method || "PUT",
|
|
661
|
+
headers: presign.headers || {
|
|
662
|
+
"Content-Type": attachment.mimeType || file.type || "application/octet-stream"
|
|
663
|
+
},
|
|
664
|
+
body: file
|
|
665
|
+
});
|
|
666
|
+
if (!uploadResponse.ok) {
|
|
667
|
+
throw new Error("No se pudo cargar el documento.");
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
id: presign.document.id || attachment.id,
|
|
671
|
+
name: presign.document.name || attachment.name,
|
|
672
|
+
kind: presign.document.kind || attachment.kind,
|
|
673
|
+
mimeType: presign.document.mime_type || presign.document.mimeType || attachment.mimeType,
|
|
674
|
+
sizeBytes: presign.document.size_bytes || presign.document.sizeBytes || attachment.sizeBytes,
|
|
675
|
+
storage: "s3",
|
|
676
|
+
bucket: presign.document.bucket,
|
|
677
|
+
key: presign.document.key
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
//# sourceMappingURL=useAgentStream.js.map
|