@elqnt/chat 2.0.8 → 3.0.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.
Files changed (52) hide show
  1. package/README.md +386 -0
  2. package/dist/api/index.d.mts +250 -8
  3. package/dist/api/index.d.ts +250 -8
  4. package/dist/api/index.js +115 -0
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/api/index.mjs +109 -0
  7. package/dist/api/index.mjs.map +1 -1
  8. package/dist/hooks/index.d.mts +78 -0
  9. package/dist/hooks/index.d.ts +78 -0
  10. package/dist/hooks/index.js +709 -0
  11. package/dist/hooks/index.js.map +1 -0
  12. package/dist/hooks/index.mjs +683 -0
  13. package/dist/hooks/index.mjs.map +1 -0
  14. package/dist/index.d.mts +4 -109
  15. package/dist/index.d.ts +4 -109
  16. package/dist/index.js +699 -2039
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +690 -2016
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/models/index.d.mts +76 -6
  21. package/dist/models/index.d.ts +76 -6
  22. package/dist/models/index.js +21 -0
  23. package/dist/models/index.js.map +1 -1
  24. package/dist/models/index.mjs +14 -0
  25. package/dist/models/index.mjs.map +1 -1
  26. package/dist/transport/index.d.mts +243 -0
  27. package/dist/transport/index.d.ts +243 -0
  28. package/dist/transport/index.js +875 -0
  29. package/dist/transport/index.js.map +1 -0
  30. package/dist/transport/index.mjs +843 -0
  31. package/dist/transport/index.mjs.map +1 -0
  32. package/dist/types-BB5nRdZs.d.mts +222 -0
  33. package/dist/types-CNvuxtcv.d.ts +222 -0
  34. package/package.json +20 -38
  35. package/dist/hooks/use-websocket-chat-admin.d.mts +0 -17
  36. package/dist/hooks/use-websocket-chat-admin.d.ts +0 -17
  37. package/dist/hooks/use-websocket-chat-admin.js +0 -1196
  38. package/dist/hooks/use-websocket-chat-admin.js.map +0 -1
  39. package/dist/hooks/use-websocket-chat-admin.mjs +0 -1172
  40. package/dist/hooks/use-websocket-chat-admin.mjs.map +0 -1
  41. package/dist/hooks/use-websocket-chat-base.d.mts +0 -81
  42. package/dist/hooks/use-websocket-chat-base.d.ts +0 -81
  43. package/dist/hooks/use-websocket-chat-base.js +0 -1025
  44. package/dist/hooks/use-websocket-chat-base.js.map +0 -1
  45. package/dist/hooks/use-websocket-chat-base.mjs +0 -1001
  46. package/dist/hooks/use-websocket-chat-base.mjs.map +0 -1
  47. package/dist/hooks/use-websocket-chat-customer.d.mts +0 -24
  48. package/dist/hooks/use-websocket-chat-customer.d.ts +0 -24
  49. package/dist/hooks/use-websocket-chat-customer.js +0 -1092
  50. package/dist/hooks/use-websocket-chat-customer.js.map +0 -1
  51. package/dist/hooks/use-websocket-chat-customer.mjs +0 -1068
  52. package/dist/hooks/use-websocket-chat-customer.mjs.map +0 -1
@@ -1,1172 +0,0 @@
1
- "use client";
2
- "use client";
3
-
4
- // hooks/use-websocket-chat-admin.ts
5
- import { useCallback as useCallback2, useState as useState2 } from "react";
6
-
7
- // models/chat-models.ts
8
- var ChatRoleHumanAgent = "humanAgent";
9
-
10
- // hooks/use-websocket-chat-base.ts
11
- import { useCallback, useEffect, useRef, useState } from "react";
12
- var createDefaultLogger = (debug) => ({
13
- debug: debug ? console.log : () => {
14
- },
15
- info: console.info,
16
- warn: console.warn,
17
- error: console.error
18
- });
19
- var DEFAULT_RETRY_CONFIG = {
20
- maxRetries: 10,
21
- intervals: [1e3, 2e3, 5e3],
22
- backoffMultiplier: 1.5,
23
- maxBackoffTime: 3e4
24
- };
25
- var DEFAULT_QUEUE_CONFIG = {
26
- maxSize: 100,
27
- dropStrategy: "oldest"
28
- };
29
- var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
30
- var DEFAULT_HEARTBEAT_TIMEOUT = 5e3;
31
- var DEFAULT_TRANSPORT = "websocket";
32
- function isChatEvent(data) {
33
- return data && typeof data === "object" && (typeof data.type === "string" || data.message);
34
- }
35
- var useWebSocketChatBase = ({
36
- serverBaseUrl,
37
- orgId,
38
- clientType,
39
- product,
40
- onMessage,
41
- retryConfig = DEFAULT_RETRY_CONFIG,
42
- queueConfig = DEFAULT_QUEUE_CONFIG,
43
- debug = false,
44
- logger = createDefaultLogger(debug),
45
- heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,
46
- heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT,
47
- transport = DEFAULT_TRANSPORT
48
- }) => {
49
- const [connectionState, setConnectionState] = useState("disconnected");
50
- const [error, setError] = useState(void 0);
51
- const [startTime, setStartTime] = useState(void 0);
52
- const [metrics, setMetrics] = useState({
53
- latency: 0,
54
- messagesSent: 0,
55
- messagesReceived: 0,
56
- messagesQueued: 0,
57
- reconnectCount: 0,
58
- transportType: transport
59
- });
60
- const wsRef = useRef(void 0);
61
- const sseRef = useRef(void 0);
62
- const transportRef = useRef(transport);
63
- useEffect(() => {
64
- transportRef.current = transport;
65
- }, [transport]);
66
- const reconnectTimeoutRef = useRef(void 0);
67
- const retryCountRef = useRef(0);
68
- const messageQueueRef = useRef([]);
69
- const mountedRef = useRef(false);
70
- const currentChatKeyRef = useRef(void 0);
71
- const currentUserIdRef = useRef(void 0);
72
- const intentionalDisconnectRef = useRef(false);
73
- const eventHandlersRef = useRef(
74
- /* @__PURE__ */ new Map()
75
- );
76
- const onMessageRef = useRef(onMessage);
77
- const pendingMessagesRef = useRef(/* @__PURE__ */ new Map());
78
- const heartbeatIntervalRef = useRef(void 0);
79
- const heartbeatTimeoutRef = useRef(void 0);
80
- const lastPongRef = useRef(Date.now());
81
- const chatCreationPromiseRef = useRef(null);
82
- const loadChatRetryMapRef = useRef(/* @__PURE__ */ new Map());
83
- useEffect(() => {
84
- onMessageRef.current = onMessage;
85
- }, [onMessage]);
86
- const isConnected = connectionState === "connected";
87
- const on = useCallback((eventType, handler) => {
88
- if (!eventHandlersRef.current.has(eventType)) {
89
- eventHandlersRef.current.set(eventType, /* @__PURE__ */ new Set());
90
- }
91
- eventHandlersRef.current.get(eventType).add(handler);
92
- return () => off(eventType, handler);
93
- }, []);
94
- const off = useCallback((eventType, handler) => {
95
- const handlers = eventHandlersRef.current.get(eventType);
96
- if (handlers) {
97
- handlers.delete(handler);
98
- if (handlers.size === 0) {
99
- eventHandlersRef.current.delete(eventType);
100
- }
101
- }
102
- }, []);
103
- const emit = useCallback(
104
- (eventType, data) => {
105
- const handlers = eventHandlersRef.current.get(eventType);
106
- if (handlers) {
107
- handlers.forEach((handler) => {
108
- try {
109
- handler(data);
110
- } catch (error2) {
111
- logger.error(`Error in event handler for ${eventType}:`, error2);
112
- }
113
- });
114
- }
115
- },
116
- [logger]
117
- );
118
- const updateMetrics = useCallback((updates) => {
119
- setMetrics((prev) => ({ ...prev, ...updates }));
120
- }, []);
121
- const addToQueue = useCallback(
122
- (event) => {
123
- const currentQueueSize = messageQueueRef.current.length;
124
- const maxSize = queueConfig.maxSize || DEFAULT_QUEUE_CONFIG.maxSize;
125
- if (currentQueueSize >= maxSize) {
126
- if (queueConfig.dropStrategy === "newest") {
127
- logger.warn("Message queue full, dropping new message");
128
- return false;
129
- } else {
130
- const dropped = messageQueueRef.current.shift();
131
- logger.warn("Message queue full, dropped oldest message", dropped);
132
- }
133
- }
134
- messageQueueRef.current.push(event);
135
- updateMetrics({ messagesQueued: messageQueueRef.current.length });
136
- return true;
137
- },
138
- [queueConfig, logger, updateMetrics]
139
- );
140
- const calculateRetryInterval = useCallback(
141
- (retryCount) => {
142
- const config = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
143
- const {
144
- intervals = [],
145
- backoffMultiplier = 1.5,
146
- maxBackoffTime = 3e4
147
- } = config;
148
- if (retryCount < intervals.length) {
149
- return intervals[retryCount];
150
- }
151
- const baseInterval = intervals[intervals.length - 1] || 5e3;
152
- const backoffTime = baseInterval * Math.pow(backoffMultiplier, retryCount - intervals.length + 1);
153
- return Math.min(backoffTime, maxBackoffTime);
154
- },
155
- [retryConfig]
156
- );
157
- const startHeartbeat = useCallback(() => {
158
- if (!heartbeatInterval || heartbeatInterval <= 0) return;
159
- const sendPing = () => {
160
- if (wsRef.current?.readyState === WebSocket.OPEN) {
161
- const pingTime = Date.now();
162
- wsRef.current.send(
163
- JSON.stringify({ type: "ping", timestamp: pingTime })
164
- );
165
- heartbeatTimeoutRef.current = setTimeout(() => {
166
- logger.warn("Heartbeat timeout - no pong received");
167
- if (wsRef.current) {
168
- wsRef.current.close(4e3, "Heartbeat timeout");
169
- }
170
- }, heartbeatTimeout);
171
- }
172
- };
173
- if (heartbeatIntervalRef.current) {
174
- clearInterval(heartbeatIntervalRef.current);
175
- }
176
- heartbeatIntervalRef.current = setInterval(sendPing, heartbeatInterval);
177
- logger.debug("Heartbeat started");
178
- }, [heartbeatInterval, heartbeatTimeout, logger]);
179
- const stopHeartbeat = useCallback(() => {
180
- if (heartbeatIntervalRef.current) {
181
- clearInterval(heartbeatIntervalRef.current);
182
- heartbeatIntervalRef.current = void 0;
183
- }
184
- if (heartbeatTimeoutRef.current) {
185
- clearTimeout(heartbeatTimeoutRef.current);
186
- heartbeatTimeoutRef.current = void 0;
187
- }
188
- logger.debug("Heartbeat stopped");
189
- }, [logger]);
190
- const cleanup = useCallback(() => {
191
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
192
- wsRef.current.close(1e3, "Cleanup");
193
- }
194
- wsRef.current = void 0;
195
- if (sseRef.current) {
196
- sseRef.current.close();
197
- sseRef.current = void 0;
198
- }
199
- if (reconnectTimeoutRef.current) {
200
- clearTimeout(reconnectTimeoutRef.current);
201
- reconnectTimeoutRef.current = void 0;
202
- }
203
- stopHeartbeat();
204
- pendingMessagesRef.current.forEach(({ reject, timeout }) => {
205
- clearTimeout(timeout);
206
- reject(new Error("Connection closed"));
207
- });
208
- pendingMessagesRef.current.clear();
209
- loadChatRetryMapRef.current.forEach((retryState) => {
210
- if (retryState.timeoutId) {
211
- clearTimeout(retryState.timeoutId);
212
- }
213
- });
214
- loadChatRetryMapRef.current.clear();
215
- }, [stopHeartbeat]);
216
- const getRestApiUrl = useCallback((endpoint) => {
217
- return `${serverBaseUrl}/${endpoint}`;
218
- }, [serverBaseUrl]);
219
- const sendRestMessage = useCallback(
220
- async (endpoint, body) => {
221
- const url = getRestApiUrl(endpoint);
222
- logger.debug(`SSE REST API call: POST ${endpoint}`, body);
223
- try {
224
- const response = await fetch(url, {
225
- method: "POST",
226
- headers: {
227
- "Content-Type": "application/json"
228
- },
229
- body: JSON.stringify(body)
230
- });
231
- if (!response.ok) {
232
- const errorText = await response.text();
233
- throw new Error(`REST API error: ${response.status} - ${errorText}`);
234
- }
235
- const data = await response.json();
236
- return data;
237
- } catch (error2) {
238
- logger.error(`SSE REST API error for ${endpoint}:`, error2);
239
- throw error2;
240
- }
241
- },
242
- [getRestApiUrl, logger]
243
- );
244
- const connect = useCallback(
245
- async (userId) => {
246
- if (!mountedRef.current) {
247
- mountedRef.current = true;
248
- }
249
- if (!orgId) {
250
- const error2 = {
251
- code: "CONNECTION_FAILED",
252
- message: "Cannot connect: orgId is undefined",
253
- retryable: false,
254
- timestamp: Date.now()
255
- };
256
- logger.error("Cannot connect: orgId is undefined");
257
- setError(error2);
258
- return Promise.reject(error2);
259
- }
260
- if (wsRef.current?.readyState === WebSocket.OPEN) {
261
- logger.debug("Already connected (WebSocket)");
262
- return Promise.resolve();
263
- }
264
- if (sseRef.current?.readyState === EventSource.OPEN) {
265
- logger.debug("Already connected (SSE)");
266
- return Promise.resolve();
267
- }
268
- if (connectionState === "connecting" || connectionState === "reconnecting") {
269
- logger.debug("Connection already in progress");
270
- return Promise.resolve();
271
- }
272
- const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;
273
- if (retryCountRef.current >= maxRetries && !intentionalDisconnectRef.current) {
274
- const error2 = {
275
- code: "CONNECTION_FAILED",
276
- message: `Max retries (${maxRetries}) exceeded`,
277
- retryable: false,
278
- timestamp: Date.now()
279
- };
280
- setError(error2);
281
- updateMetrics({ lastError: error2 });
282
- return Promise.reject(error2);
283
- }
284
- cleanup();
285
- setConnectionState(
286
- retryCountRef.current > 0 ? "reconnecting" : "connecting"
287
- );
288
- intentionalDisconnectRef.current = false;
289
- return new Promise((resolve, reject) => {
290
- try {
291
- const connectionStartTime = Date.now();
292
- logger.info(`\u{1F504} Connecting with transport: ${transportRef.current}`);
293
- if (transportRef.current === "sse") {
294
- const sseUrl = getRestApiUrl(`stream?orgId=${orgId}&userId=${userId}&clientType=${clientType}&chatId=${currentChatKeyRef.current || ""}`);
295
- logger.debug("Connecting to SSE:", sseUrl);
296
- console.log(`\u23F3 Initiating SSE connection to ${sseUrl}...`);
297
- const eventSource = new EventSource(sseUrl);
298
- eventSource.onopen = () => {
299
- if (!mountedRef.current) {
300
- eventSource.close();
301
- reject(new Error("Component unmounted"));
302
- return;
303
- }
304
- const connectionTimeMs = Date.now() - connectionStartTime;
305
- const connectionTimeSec = (connectionTimeMs / 1e3).toFixed(2);
306
- logger.info("\u2705 SSE connected", {
307
- userId,
308
- retryCount: retryCountRef.current,
309
- connectionTime: `${connectionTimeSec}s (${connectionTimeMs}ms)`
310
- });
311
- console.log(`\u{1F50C} SSE connection established in ${connectionTimeSec} seconds`);
312
- setConnectionState("connected");
313
- setError(void 0);
314
- const wasReconnecting = retryCountRef.current > 0;
315
- retryCountRef.current = 0;
316
- updateMetrics({
317
- connectedAt: Date.now(),
318
- latency: connectionTimeMs,
319
- transportType: "sse",
320
- reconnectCount: wasReconnecting ? metrics.reconnectCount + 1 : metrics.reconnectCount
321
- });
322
- currentUserIdRef.current = userId;
323
- if (currentChatKeyRef.current) {
324
- logger.info("Loading chat after SSE reconnection:", currentChatKeyRef.current);
325
- sendRestMessage("load", {
326
- orgId,
327
- chatKey: currentChatKeyRef.current,
328
- userId
329
- }).then((response) => {
330
- if (response.success && response.data?.chat) {
331
- const chatEvent = {
332
- type: "load_chat_response",
333
- orgId,
334
- chatKey: currentChatKeyRef.current,
335
- userId,
336
- timestamp: Date.now(),
337
- data: response.data
338
- };
339
- emit("load_chat_response", chatEvent);
340
- if (onMessageRef.current) {
341
- onMessageRef.current(chatEvent);
342
- }
343
- }
344
- }).catch((err) => {
345
- logger.error("Failed to load chat after SSE reconnection:", err);
346
- });
347
- }
348
- emit("connected", { userId, wasReconnecting, transport: "sse" });
349
- resolve();
350
- };
351
- const handleSSEMessage = (event) => {
352
- if (!mountedRef.current) return;
353
- if (!event.data || event.data === "") {
354
- return;
355
- }
356
- try {
357
- const data = JSON.parse(event.data);
358
- if (!isChatEvent(data)) {
359
- logger.warn("Received invalid SSE message format:", data);
360
- return;
361
- }
362
- const chatEvent = data;
363
- logger.debug("SSE message received:", chatEvent.type);
364
- updateMetrics({
365
- messagesReceived: metrics.messagesReceived + 1,
366
- lastMessageAt: Date.now()
367
- });
368
- switch (chatEvent.type) {
369
- case "new_chat_created":
370
- const newChatKey = chatEvent.data?.chatKey;
371
- if (newChatKey) {
372
- logger.info("New chat created with key:", newChatKey);
373
- currentChatKeyRef.current = newChatKey;
374
- if (chatCreationPromiseRef.current) {
375
- chatCreationPromiseRef.current.resolve(newChatKey);
376
- chatCreationPromiseRef.current = null;
377
- }
378
- }
379
- break;
380
- case "load_chat_response":
381
- const chat = chatEvent.data?.chat;
382
- if (chat && chat.key) {
383
- logger.info("Chat loaded with key:", chat.key);
384
- currentChatKeyRef.current = chat.key;
385
- }
386
- break;
387
- case "chat_ended":
388
- logger.info("Chat ended, clearing key");
389
- currentChatKeyRef.current = void 0;
390
- break;
391
- }
392
- emit(chatEvent.type || "message", chatEvent);
393
- if (onMessageRef.current) {
394
- onMessageRef.current(chatEvent);
395
- }
396
- } catch (error2) {
397
- logger.error("Failed to parse SSE message:", error2);
398
- }
399
- };
400
- eventSource.addEventListener("message", handleSSEMessage);
401
- eventSource.addEventListener("reconnected", handleSSEMessage);
402
- eventSource.addEventListener("typing", handleSSEMessage);
403
- eventSource.addEventListener("stopped_typing", handleSSEMessage);
404
- eventSource.addEventListener("waiting", handleSSEMessage);
405
- eventSource.addEventListener("waiting_for_agent", handleSSEMessage);
406
- eventSource.addEventListener("human_agent_joined", handleSSEMessage);
407
- eventSource.addEventListener("human_agent_left", handleSSEMessage);
408
- eventSource.addEventListener("chat_ended", handleSSEMessage);
409
- eventSource.addEventListener("chat_updated", handleSSEMessage);
410
- eventSource.addEventListener("load_chat_response", handleSSEMessage);
411
- eventSource.addEventListener("new_chat_created", handleSSEMessage);
412
- eventSource.addEventListener("show_csat_survey", handleSSEMessage);
413
- eventSource.addEventListener("csat_response", handleSSEMessage);
414
- eventSource.addEventListener("user_suggested_actions", handleSSEMessage);
415
- eventSource.addEventListener("agent_execution_started", handleSSEMessage);
416
- eventSource.addEventListener("agent_execution_ended", handleSSEMessage);
417
- eventSource.addEventListener("agent_context_update", handleSSEMessage);
418
- eventSource.addEventListener("plan_pending_approval", handleSSEMessage);
419
- eventSource.addEventListener("step_started", handleSSEMessage);
420
- eventSource.addEventListener("step_completed", handleSSEMessage);
421
- eventSource.addEventListener("step_failed", handleSSEMessage);
422
- eventSource.addEventListener("plan_completed", handleSSEMessage);
423
- eventSource.addEventListener("skills_changed", handleSSEMessage);
424
- eventSource.addEventListener("summary_update", handleSSEMessage);
425
- eventSource.onerror = (error2) => {
426
- logger.error("SSE error:", error2);
427
- if (!mountedRef.current) return;
428
- if (eventSource.readyState === EventSource.CLOSED) {
429
- const sseError = {
430
- code: "CONNECTION_FAILED",
431
- message: "SSE connection failed",
432
- retryable: true,
433
- timestamp: Date.now()
434
- };
435
- setError(sseError);
436
- updateMetrics({ lastError: sseError });
437
- setConnectionState("disconnected");
438
- emit("disconnected", { reason: "SSE error" });
439
- if (!intentionalDisconnectRef.current && mountedRef.current) {
440
- const retryInterval = calculateRetryInterval(retryCountRef.current);
441
- retryCountRef.current++;
442
- logger.info(`SSE reconnecting in ${retryInterval}ms (attempt ${retryCountRef.current})`);
443
- if (reconnectTimeoutRef.current) {
444
- clearTimeout(reconnectTimeoutRef.current);
445
- }
446
- reconnectTimeoutRef.current = setTimeout(() => {
447
- connect(userId);
448
- }, retryInterval);
449
- }
450
- }
451
- };
452
- sseRef.current = eventSource;
453
- return;
454
- }
455
- const wsUrl = `${serverBaseUrl}?orgId=${orgId}&userId=${userId}&clientType=${clientType}&product=${product}`;
456
- logger.debug("Connecting to WebSocket:", wsUrl);
457
- console.log(`\u23F3 Initiating WebSocket connection to ${serverBaseUrl}...`);
458
- const ws = new WebSocket(wsUrl);
459
- ws.onopen = () => {
460
- if (!mountedRef.current) {
461
- ws.close(1e3, "Component unmounted");
462
- reject(new Error("Component unmounted"));
463
- return;
464
- }
465
- const connectionTimeMs = Date.now() - connectionStartTime;
466
- const connectionTimeSec = (connectionTimeMs / 1e3).toFixed(2);
467
- logger.info("\u2705 WebSocket connected", {
468
- userId,
469
- retryCount: retryCountRef.current,
470
- connectionTime: `${connectionTimeSec}s (${connectionTimeMs}ms)`
471
- });
472
- console.log(`\u{1F50C} WebSocket connection established in ${connectionTimeSec} seconds`);
473
- setConnectionState("connected");
474
- setError(void 0);
475
- const wasReconnecting = retryCountRef.current > 0;
476
- retryCountRef.current = 0;
477
- updateMetrics({
478
- connectedAt: Date.now(),
479
- latency: connectionTimeMs,
480
- reconnectCount: wasReconnecting ? metrics.reconnectCount + 1 : metrics.reconnectCount
481
- });
482
- while (messageQueueRef.current.length > 0 && ws.readyState === WebSocket.OPEN) {
483
- const event = messageQueueRef.current.shift();
484
- if (event) {
485
- ws.send(JSON.stringify({ ...event, timestamp: Date.now() }));
486
- updateMetrics({
487
- messagesSent: metrics.messagesSent + 1,
488
- messagesQueued: messageQueueRef.current.length
489
- });
490
- }
491
- }
492
- currentUserIdRef.current = userId;
493
- if (currentChatKeyRef.current) {
494
- if (!orgId) {
495
- logger.error("Cannot resubscribe to chat: orgId is undefined");
496
- } else {
497
- logger.info(
498
- "Resubscribing to chat after reconnection:",
499
- currentChatKeyRef.current
500
- );
501
- const resubscribeEvent = {
502
- type: "load_chat",
503
- orgId,
504
- chatKey: currentChatKeyRef.current,
505
- userId,
506
- timestamp: Date.now(),
507
- data: {}
508
- };
509
- ws.send(JSON.stringify(resubscribeEvent));
510
- }
511
- }
512
- startHeartbeat();
513
- emit("connected", { userId, wasReconnecting });
514
- resolve();
515
- };
516
- ws.onmessage = (event) => {
517
- if (!mountedRef.current) return;
518
- try {
519
- const data = JSON.parse(event.data);
520
- if (!isChatEvent(data)) {
521
- logger.warn("Received invalid message format:", data);
522
- return;
523
- }
524
- const chatEvent = data;
525
- logger.debug("Message received:", chatEvent.type);
526
- updateMetrics({
527
- messagesReceived: metrics.messagesReceived + 1,
528
- lastMessageAt: Date.now()
529
- });
530
- if (chatEvent.type === "pong") {
531
- if (heartbeatTimeoutRef.current) {
532
- clearTimeout(heartbeatTimeoutRef.current);
533
- heartbeatTimeoutRef.current = void 0;
534
- }
535
- const latency = Date.now() - (chatEvent.timestamp || Date.now());
536
- lastPongRef.current = Date.now();
537
- updateMetrics({ latency });
538
- return;
539
- }
540
- switch (chatEvent.type) {
541
- case "new_chat_created":
542
- const newChatKey = chatEvent.data?.chatKey;
543
- if (newChatKey) {
544
- logger.info("New chat created with key:", newChatKey);
545
- currentChatKeyRef.current = newChatKey;
546
- if (chatCreationPromiseRef.current) {
547
- chatCreationPromiseRef.current.resolve(newChatKey);
548
- chatCreationPromiseRef.current = null;
549
- }
550
- if (!orgId) {
551
- logger.error("Cannot send load_chat: orgId is undefined");
552
- return;
553
- }
554
- const loadChatEvent = {
555
- type: "load_chat",
556
- orgId,
557
- chatKey: newChatKey,
558
- userId: currentUserIdRef.current || userId,
559
- timestamp: Date.now(),
560
- data: {}
561
- };
562
- ws.send(JSON.stringify(loadChatEvent));
563
- }
564
- break;
565
- case "load_chat_response":
566
- const chat = chatEvent.data?.chat;
567
- if (chat && chat.key) {
568
- logger.info("Chat loaded with key:", chat.key);
569
- currentChatKeyRef.current = chat.key;
570
- const retryState = loadChatRetryMapRef.current.get(chat.key);
571
- if (retryState) {
572
- if (retryState.timeoutId) {
573
- clearTimeout(retryState.timeoutId);
574
- }
575
- loadChatRetryMapRef.current.delete(chat.key);
576
- }
577
- } else if (!chat) {
578
- logger.warn("Chat load failed, clearing key");
579
- currentChatKeyRef.current = void 0;
580
- }
581
- break;
582
- case "chat_ended":
583
- logger.info("Chat ended, clearing key");
584
- currentChatKeyRef.current = void 0;
585
- break;
586
- case "error":
587
- const errorMessage = chatEvent.data?.message || "Unknown error";
588
- logger.error("Received error from server:", errorMessage);
589
- if (errorMessage.includes("nats: key not found") || errorMessage.includes("Failed to load chat")) {
590
- const chatKeyFromError = currentChatKeyRef.current;
591
- if (chatKeyFromError) {
592
- const maxRetries2 = 5;
593
- let retryState = loadChatRetryMapRef.current.get(chatKeyFromError);
594
- if (!retryState) {
595
- retryState = { retryCount: 0, timeoutId: null };
596
- loadChatRetryMapRef.current.set(chatKeyFromError, retryState);
597
- }
598
- if (retryState.retryCount < maxRetries2) {
599
- const delay = 200 * Math.pow(2, retryState.retryCount);
600
- retryState.retryCount++;
601
- logger.info(
602
- `Chat load failed, retrying (${retryState.retryCount}/${maxRetries2}) in ${delay}ms...`,
603
- chatKeyFromError
604
- );
605
- retryState.timeoutId = setTimeout(() => {
606
- if (!wsRef.current || !mountedRef.current) return;
607
- if (!orgId) {
608
- logger.error("Cannot retry load_chat: orgId is undefined", chatKeyFromError);
609
- loadChatRetryMapRef.current.delete(chatKeyFromError);
610
- return;
611
- }
612
- logger.debug("Retrying load_chat:", chatKeyFromError);
613
- const retryLoadEvent = {
614
- type: "load_chat",
615
- orgId,
616
- chatKey: chatKeyFromError,
617
- userId: currentUserIdRef.current || "",
618
- timestamp: Date.now(),
619
- data: {}
620
- };
621
- ws.send(JSON.stringify(retryLoadEvent));
622
- }, delay);
623
- } else {
624
- logger.error("Max retries reached for loading chat:", chatKeyFromError);
625
- loadChatRetryMapRef.current.delete(chatKeyFromError);
626
- }
627
- }
628
- }
629
- const wsError = {
630
- code: "NETWORK_ERROR",
631
- message: errorMessage,
632
- retryable: true,
633
- timestamp: Date.now()
634
- };
635
- setError(wsError);
636
- updateMetrics({ lastError: wsError });
637
- break;
638
- }
639
- emit(chatEvent.type || "message", chatEvent);
640
- if (onMessageRef.current) {
641
- onMessageRef.current(chatEvent);
642
- }
643
- } catch (error2) {
644
- logger.error("Failed to parse WebSocket message:", error2);
645
- const parseError = {
646
- code: "PARSE_ERROR",
647
- message: "Failed to parse message",
648
- retryable: false,
649
- timestamp: Date.now()
650
- };
651
- setError(parseError);
652
- updateMetrics({ lastError: parseError });
653
- }
654
- };
655
- ws.onclose = (event) => {
656
- if (!mountedRef.current) return;
657
- logger.info("WebSocket closed", {
658
- code: event.code,
659
- reason: event.reason
660
- });
661
- setConnectionState("disconnected");
662
- wsRef.current = void 0;
663
- stopHeartbeat();
664
- emit("disconnected", { code: event.code, reason: event.reason });
665
- if (event.code !== 1e3 && !intentionalDisconnectRef.current && mountedRef.current) {
666
- const retryInterval = calculateRetryInterval(
667
- retryCountRef.current
668
- );
669
- retryCountRef.current++;
670
- logger.info(
671
- `Reconnecting in ${retryInterval}ms (attempt ${retryCountRef.current})`
672
- );
673
- if (reconnectTimeoutRef.current) {
674
- clearTimeout(reconnectTimeoutRef.current);
675
- }
676
- reconnectTimeoutRef.current = setTimeout(() => {
677
- connect(userId);
678
- }, retryInterval);
679
- }
680
- };
681
- ws.onerror = (error2) => {
682
- logger.error("WebSocket error:", error2);
683
- if (!mountedRef.current) return;
684
- const wsError = {
685
- code: "CONNECTION_FAILED",
686
- message: "WebSocket connection failed",
687
- retryable: true,
688
- timestamp: Date.now()
689
- };
690
- setError(wsError);
691
- updateMetrics({ lastError: wsError });
692
- reject(wsError);
693
- };
694
- wsRef.current = ws;
695
- } catch (error2) {
696
- logger.error("Failed to create WebSocket:", error2);
697
- const wsError = {
698
- code: "CONNECTION_FAILED",
699
- message: error2 instanceof Error ? error2.message : "Failed to create connection",
700
- retryable: true,
701
- timestamp: Date.now()
702
- };
703
- setError(wsError);
704
- updateMetrics({ lastError: wsError });
705
- reject(wsError);
706
- }
707
- });
708
- },
709
- [
710
- serverBaseUrl,
711
- orgId,
712
- clientType,
713
- product,
714
- connectionState,
715
- logger,
716
- retryConfig,
717
- metrics,
718
- updateMetrics,
719
- cleanup,
720
- calculateRetryInterval,
721
- startHeartbeat,
722
- emit
723
- ]
724
- );
725
- const sendMessage = useCallback(
726
- (event, overrideUserId) => {
727
- return new Promise(async (resolve, reject) => {
728
- if (!mountedRef.current) {
729
- reject(new Error("Component not mounted"));
730
- return;
731
- }
732
- const fullEvent = {
733
- ...event,
734
- timestamp: Date.now()
735
- };
736
- logger.debug("Sending message:", fullEvent.type);
737
- if (transportRef.current === "sse") {
738
- if (!sseRef.current || sseRef.current.readyState !== EventSource.OPEN) {
739
- logger.debug("SSE not connected, attempting to connect");
740
- if (connectionState === "disconnected" && overrideUserId) {
741
- try {
742
- await connect(overrideUserId);
743
- } catch (error2) {
744
- reject(error2);
745
- return;
746
- }
747
- } else {
748
- reject(new Error("SSE not connected"));
749
- return;
750
- }
751
- }
752
- try {
753
- switch (fullEvent.type) {
754
- case "message":
755
- await sendRestMessage("send", {
756
- orgId: fullEvent.orgId || orgId,
757
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
758
- userId: fullEvent.userId || currentUserIdRef.current,
759
- message: fullEvent.message
760
- });
761
- break;
762
- case "typing":
763
- await sendRestMessage("typing", {
764
- orgId: fullEvent.orgId || orgId,
765
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
766
- userId: fullEvent.userId || currentUserIdRef.current,
767
- typing: true
768
- });
769
- break;
770
- case "stopped_typing":
771
- await sendRestMessage("typing", {
772
- orgId: fullEvent.orgId || orgId,
773
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
774
- userId: fullEvent.userId || currentUserIdRef.current,
775
- typing: false
776
- });
777
- break;
778
- case "load_chat":
779
- const loadResponse = await sendRestMessage("load", {
780
- orgId: fullEvent.orgId || orgId,
781
- chatKey: fullEvent.chatKey,
782
- userId: fullEvent.userId || currentUserIdRef.current
783
- });
784
- if (loadResponse.success && loadResponse.data?.chat) {
785
- currentChatKeyRef.current = loadResponse.data.chat.key;
786
- const chatEvent = {
787
- type: "load_chat_response",
788
- orgId: fullEvent.orgId,
789
- chatKey: loadResponse.data.chat.key,
790
- userId: fullEvent.userId,
791
- timestamp: Date.now(),
792
- data: loadResponse.data
793
- };
794
- emit("load_chat_response", chatEvent);
795
- if (onMessageRef.current) {
796
- onMessageRef.current(chatEvent);
797
- }
798
- }
799
- break;
800
- case "new_chat":
801
- const createResponse = await sendRestMessage("create", {
802
- orgId: fullEvent.orgId || orgId,
803
- userId: fullEvent.userId || currentUserIdRef.current,
804
- metadata: fullEvent.data
805
- });
806
- if (createResponse.success && createResponse.data?.chatKey) {
807
- currentChatKeyRef.current = createResponse.data.chatKey;
808
- const newChatEvent = {
809
- type: "new_chat_created",
810
- orgId: fullEvent.orgId,
811
- chatKey: createResponse.data.chatKey,
812
- userId: fullEvent.userId,
813
- timestamp: Date.now(),
814
- data: { chatKey: createResponse.data.chatKey }
815
- };
816
- emit("new_chat_created", newChatEvent);
817
- if (onMessageRef.current) {
818
- onMessageRef.current(newChatEvent);
819
- }
820
- if (chatCreationPromiseRef.current) {
821
- chatCreationPromiseRef.current.resolve(createResponse.data.chatKey);
822
- chatCreationPromiseRef.current = null;
823
- }
824
- }
825
- break;
826
- case "end_chat":
827
- await sendRestMessage("end", {
828
- orgId: fullEvent.orgId || orgId,
829
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
830
- userId: fullEvent.userId || currentUserIdRef.current,
831
- data: fullEvent.data
832
- });
833
- break;
834
- case "human_agent_joined":
835
- await sendRestMessage("agent-join", {
836
- orgId: fullEvent.orgId || orgId,
837
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
838
- user: fullEvent.data?.user
839
- });
840
- break;
841
- case "human_agent_left":
842
- await sendRestMessage("agent-leave", {
843
- orgId: fullEvent.orgId || orgId,
844
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
845
- user: fullEvent.data?.user
846
- });
847
- break;
848
- // Event types that use the generic /event endpoint
849
- case "load_agent_context":
850
- case "skill_activate":
851
- case "skill_deactivate":
852
- case "sync_metadata":
853
- case "sync_user_session":
854
- case "csat_response":
855
- case "plan_approved":
856
- case "plan_rejected":
857
- await sendRestMessage("event", {
858
- type: fullEvent.type,
859
- orgId: fullEvent.orgId || orgId,
860
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
861
- userId: fullEvent.userId || currentUserIdRef.current,
862
- data: fullEvent.data
863
- });
864
- break;
865
- default:
866
- logger.warn("Sending unrecognized event type via generic endpoint:", fullEvent.type);
867
- await sendRestMessage("event", {
868
- type: fullEvent.type,
869
- orgId: fullEvent.orgId || orgId,
870
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
871
- userId: fullEvent.userId || currentUserIdRef.current,
872
- data: fullEvent.data
873
- });
874
- break;
875
- }
876
- updateMetrics({ messagesSent: metrics.messagesSent + 1 });
877
- logger.debug("SSE REST message sent successfully");
878
- resolve();
879
- } catch (error2) {
880
- logger.error("Failed to send SSE REST message:", error2);
881
- const sendError = {
882
- code: "SEND_FAILED",
883
- message: error2 instanceof Error ? error2.message : "Failed to send message",
884
- retryable: true,
885
- timestamp: Date.now()
886
- };
887
- setError(sendError);
888
- updateMetrics({ lastError: sendError });
889
- reject(sendError);
890
- }
891
- return;
892
- }
893
- if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
894
- if (addToQueue(fullEvent)) {
895
- logger.debug("Message queued, attempting to connect");
896
- if (connectionState === "disconnected" && overrideUserId) {
897
- connect(overrideUserId).then(() => resolve()).catch(reject);
898
- } else {
899
- resolve();
900
- }
901
- } else {
902
- reject(new Error("Message queue full"));
903
- }
904
- return;
905
- }
906
- try {
907
- wsRef.current.send(JSON.stringify(fullEvent));
908
- updateMetrics({ messagesSent: metrics.messagesSent + 1 });
909
- logger.debug("Message sent successfully");
910
- resolve();
911
- } catch (error2) {
912
- logger.error("Failed to send message:", error2);
913
- if (addToQueue(fullEvent)) {
914
- resolve();
915
- } else {
916
- const sendError = {
917
- code: "SEND_FAILED",
918
- message: error2 instanceof Error ? error2.message : "Failed to send message",
919
- retryable: true,
920
- timestamp: Date.now()
921
- };
922
- setError(sendError);
923
- updateMetrics({ lastError: sendError });
924
- reject(sendError);
925
- }
926
- }
927
- });
928
- },
929
- [connectionState, connect, addToQueue, logger, metrics, updateMetrics, sendRestMessage, emit, orgId]
930
- );
931
- const startNewChat = useCallback(
932
- (userId, data) => {
933
- return new Promise((resolve, reject) => {
934
- if (!userId) {
935
- reject(new Error("User ID is required"));
936
- return;
937
- }
938
- logger.info("Requesting new chat from server with userId:", userId);
939
- setStartTime(/* @__PURE__ */ new Date());
940
- chatCreationPromiseRef.current = { resolve, reject };
941
- sendMessage(
942
- {
943
- type: "new_chat",
944
- orgId,
945
- chatKey: "",
946
- // Server will generate
947
- userId,
948
- data: data ?? {}
949
- },
950
- userId
951
- ).catch((error2) => {
952
- chatCreationPromiseRef.current = null;
953
- reject(error2);
954
- });
955
- setTimeout(() => {
956
- if (chatCreationPromiseRef.current) {
957
- chatCreationPromiseRef.current = null;
958
- reject(new Error("Chat creation timed out"));
959
- }
960
- }, 3e4);
961
- });
962
- },
963
- [sendMessage, orgId, logger]
964
- );
965
- const disconnect = useCallback(
966
- (intentional = true) => {
967
- logger.info("Disconnecting", { intentional, transport: transportRef.current });
968
- intentionalDisconnectRef.current = intentional;
969
- cleanup();
970
- setConnectionState("disconnected");
971
- messageQueueRef.current = [];
972
- retryCountRef.current = 0;
973
- mountedRef.current = false;
974
- currentChatKeyRef.current = void 0;
975
- currentUserIdRef.current = void 0;
976
- },
977
- [cleanup, logger]
978
- );
979
- const clearError = useCallback(() => {
980
- setError(void 0);
981
- }, []);
982
- useEffect(() => {
983
- mountedRef.current = true;
984
- return () => {
985
- mountedRef.current = false;
986
- disconnect(true);
987
- };
988
- }, []);
989
- return {
990
- connectionState,
991
- isConnected,
992
- sendMessage,
993
- error,
994
- connect,
995
- startNewChat,
996
- startTime,
997
- disconnect,
998
- metrics,
999
- on,
1000
- off,
1001
- clearError
1002
- };
1003
- };
1004
-
1005
- // hooks/use-websocket-chat-admin.ts
1006
- var useWebSocketChatAdmin = ({
1007
- serverBaseUrl,
1008
- orgId,
1009
- product
1010
- }) => {
1011
- const [chats, setChats] = useState2(/* @__PURE__ */ new Map());
1012
- const [selectedChat, setSelectedChat] = useState2(void 0);
1013
- const handleMessage = useCallback2(
1014
- (chatEvent) => {
1015
- switch (chatEvent.type) {
1016
- case "message":
1017
- if (!selectedChat || !chatEvent.message) return;
1018
- setChats((prev) => {
1019
- const newMap = new Map(prev);
1020
- newMap.set(selectedChat.key, {
1021
- ...selectedChat,
1022
- messages: [...selectedChat.messages, chatEvent.message]
1023
- });
1024
- return newMap;
1025
- });
1026
- setSelectedChat((prev) => {
1027
- if (!prev) return prev;
1028
- return {
1029
- ...prev,
1030
- messages: [...prev.messages, chatEvent.message]
1031
- };
1032
- });
1033
- break;
1034
- case "list_chats":
1035
- const chatList = chatEvent.data?.chats;
1036
- if (!chatList) return;
1037
- setChats(new Map(chatList.map((chat) => [chat.key, chat])));
1038
- break;
1039
- case "chat_updated":
1040
- const updatedChat = chatEvent.data?.chat?.value;
1041
- if (updatedChat) {
1042
- setChats((prev) => new Map(prev).set(updatedChat.key, updatedChat));
1043
- }
1044
- break;
1045
- case "chat_removed":
1046
- const chatKey = chatEvent.data?.chatKey?.value;
1047
- if (chatKey) {
1048
- setChats((prev) => {
1049
- const newMap = new Map(prev);
1050
- newMap.delete(chatKey);
1051
- return newMap;
1052
- });
1053
- }
1054
- break;
1055
- case "load_chat":
1056
- const history = chatEvent.data?.chat?.value;
1057
- if (history) {
1058
- setChats((prev) => {
1059
- const existingChat = prev.get(history.key);
1060
- if (!existingChat) return prev;
1061
- return new Map(prev).set(history.key, {
1062
- ...existingChat,
1063
- messages: history.messages
1064
- });
1065
- });
1066
- }
1067
- break;
1068
- }
1069
- },
1070
- [selectedChat]
1071
- );
1072
- const base = useWebSocketChatBase({
1073
- serverBaseUrl,
1074
- orgId,
1075
- clientType: "humanAgent",
1076
- onMessage: handleMessage,
1077
- product
1078
- });
1079
- const selectChat = useCallback2(
1080
- (chatKey) => {
1081
- const chat = chats.get(chatKey);
1082
- if (!chat) {
1083
- console.error("Unable to select chat", chatKey);
1084
- return;
1085
- }
1086
- setSelectedChat(chat);
1087
- },
1088
- [chats, orgId, base.sendMessage]
1089
- );
1090
- const addUserToChat = useCallback2(
1091
- (orgId2, chatKey, user) => {
1092
- base.sendMessage({
1093
- type: "human_agent_joined",
1094
- orgId: orgId2,
1095
- chatKey,
1096
- userId: user.email,
1097
- data: { user }
1098
- });
1099
- setChats((prev) => {
1100
- const newMap = new Map(prev);
1101
- const chat = newMap.get(chatKey);
1102
- if (!chat) return prev;
1103
- newMap.set(chatKey, {
1104
- ...chat,
1105
- humanAgentEngaged: user.role === ChatRoleHumanAgent,
1106
- users: [...chat?.users ?? [], user]
1107
- });
1108
- return newMap;
1109
- });
1110
- setSelectedChat((prev) => {
1111
- if (!prev) return prev;
1112
- return {
1113
- ...prev,
1114
- humanAgentEngaged: user.role === ChatRoleHumanAgent,
1115
- users: [...prev.users ?? [], user]
1116
- };
1117
- });
1118
- },
1119
- [base.sendMessage]
1120
- );
1121
- const removeUserFromChat = useCallback2(
1122
- (orgId2, chatKey, userId) => {
1123
- const chat = chats.get(chatKey);
1124
- if (!chat) return;
1125
- const user = chat.users?.find((u) => u.id === userId);
1126
- if (!user) return;
1127
- base.sendMessage({
1128
- type: "human_agent_left",
1129
- orgId: orgId2,
1130
- chatKey,
1131
- userId,
1132
- data: { user }
1133
- });
1134
- setChats((prev) => {
1135
- const newMap = new Map(prev);
1136
- const chat2 = newMap.get(chatKey);
1137
- if (!chat2) return prev;
1138
- newMap.set(chatKey, {
1139
- ...chat2,
1140
- humanAgentEngaged: false,
1141
- users: chat2.users?.filter((u) => u.id !== userId)
1142
- });
1143
- return newMap;
1144
- });
1145
- },
1146
- [chats, base.sendMessage]
1147
- );
1148
- const blockUser = useCallback2(
1149
- (orgId2, chatKey, userId) => {
1150
- base.sendMessage({
1151
- type: "block_user",
1152
- orgId: orgId2,
1153
- chatKey,
1154
- userId
1155
- });
1156
- },
1157
- [base.sendMessage]
1158
- );
1159
- return {
1160
- ...base,
1161
- chats,
1162
- selectedChat,
1163
- selectChat,
1164
- addUserToChat,
1165
- removeUserFromChat,
1166
- blockUser
1167
- };
1168
- };
1169
- export {
1170
- useWebSocketChatAdmin
1171
- };
1172
- //# sourceMappingURL=use-websocket-chat-admin.mjs.map