@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
package/dist/index.mjs CHANGED
@@ -1,15 +1,682 @@
1
1
  "use client";
2
2
 
3
- // components/admin/admin-chat-input.tsx
4
- import KSUID from "ksuid";
5
- import { Paperclip, Send } from "lucide-react";
6
- import { useRef as useRef2, useState as useState4 } from "react";
3
+ // hooks/use-chat.ts
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+
6
+ // transport/types.ts
7
+ function createLogger(debug = false) {
8
+ return {
9
+ debug: debug ? console.log.bind(console, "[chat]") : () => {
10
+ },
11
+ info: console.info.bind(console, "[chat]"),
12
+ warn: console.warn.bind(console, "[chat]"),
13
+ error: console.error.bind(console, "[chat]")
14
+ };
15
+ }
16
+ var DEFAULT_RETRY_CONFIG = {
17
+ maxRetries: 10,
18
+ intervals: [1e3, 2e3, 5e3],
19
+ backoffMultiplier: 1.5,
20
+ maxBackoffTime: 3e4
21
+ };
22
+ function calculateRetryInterval(retryCount, config = DEFAULT_RETRY_CONFIG) {
23
+ const {
24
+ intervals = DEFAULT_RETRY_CONFIG.intervals,
25
+ backoffMultiplier = DEFAULT_RETRY_CONFIG.backoffMultiplier,
26
+ maxBackoffTime = DEFAULT_RETRY_CONFIG.maxBackoffTime
27
+ } = config;
28
+ if (retryCount < intervals.length) {
29
+ return intervals[retryCount];
30
+ }
31
+ const baseInterval = intervals[intervals.length - 1] || 5e3;
32
+ const backoffTime = baseInterval * Math.pow(backoffMultiplier, retryCount - intervals.length + 1);
33
+ return Math.min(backoffTime, maxBackoffTime);
34
+ }
7
35
 
8
- // context/websocket-chat-admin-context.tsx
9
- import { createContext, useContext } from "react";
36
+ // transport/sse.ts
37
+ function createSSETransport(options = {}) {
38
+ const {
39
+ retryConfig = DEFAULT_RETRY_CONFIG,
40
+ debug = false,
41
+ logger = createLogger(debug)
42
+ } = options;
43
+ let eventSource;
44
+ let config;
45
+ let state = "disconnected";
46
+ let error;
47
+ let retryCount = 0;
48
+ let reconnectTimeout;
49
+ let intentionalDisconnect = false;
50
+ const metrics = {
51
+ latency: 0,
52
+ messagesSent: 0,
53
+ messagesReceived: 0,
54
+ messagesQueued: 0,
55
+ reconnectCount: 0,
56
+ transportType: "sse"
57
+ };
58
+ const globalHandlers = /* @__PURE__ */ new Set();
59
+ const typeHandlers = /* @__PURE__ */ new Map();
60
+ function emit(event) {
61
+ metrics.messagesReceived++;
62
+ metrics.lastMessageAt = Date.now();
63
+ globalHandlers.forEach((handler) => {
64
+ try {
65
+ handler(event);
66
+ } catch (err) {
67
+ logger.error("Error in message handler:", err);
68
+ }
69
+ });
70
+ const handlers = typeHandlers.get(event.type);
71
+ if (handlers) {
72
+ handlers.forEach((handler) => {
73
+ try {
74
+ handler(event);
75
+ } catch (err) {
76
+ logger.error(`Error in ${event.type} handler:`, err);
77
+ }
78
+ });
79
+ }
80
+ }
81
+ async function sendRest(endpoint, body) {
82
+ if (!config) {
83
+ throw new Error("Transport not connected");
84
+ }
85
+ const url = `${config.baseUrl}/${endpoint}`;
86
+ logger.debug(`POST ${endpoint}`, body);
87
+ const response = await fetch(url, {
88
+ method: "POST",
89
+ headers: { "Content-Type": "application/json" },
90
+ body: JSON.stringify(body)
91
+ });
92
+ if (!response.ok) {
93
+ const errorText = await response.text();
94
+ throw new Error(`API error: ${response.status} - ${errorText}`);
95
+ }
96
+ return response.json();
97
+ }
98
+ function handleMessage(event) {
99
+ if (!event.data || event.data === "") return;
100
+ try {
101
+ const data = JSON.parse(event.data);
102
+ logger.debug("Received:", data.type);
103
+ emit(data);
104
+ } catch (err) {
105
+ logger.error("Failed to parse SSE message:", err);
106
+ }
107
+ }
108
+ function setupEventListeners(es) {
109
+ es.addEventListener("message", handleMessage);
110
+ const eventTypes = [
111
+ "reconnected",
112
+ "typing",
113
+ "stopped_typing",
114
+ "waiting",
115
+ "waiting_for_agent",
116
+ "human_agent_joined",
117
+ "human_agent_left",
118
+ "chat_ended",
119
+ "chat_updated",
120
+ "load_chat_response",
121
+ "new_chat_created",
122
+ "show_csat_survey",
123
+ "csat_response",
124
+ "user_suggested_actions",
125
+ "agent_execution_started",
126
+ "agent_execution_ended",
127
+ "agent_context_update",
128
+ "plan_pending_approval",
129
+ "step_started",
130
+ "step_completed",
131
+ "step_failed",
132
+ "plan_completed",
133
+ "skills_changed",
134
+ "summary_update"
135
+ ];
136
+ eventTypes.forEach((type) => {
137
+ es.addEventListener(type, handleMessage);
138
+ });
139
+ }
140
+ function scheduleReconnect() {
141
+ if (intentionalDisconnect || !config) return;
142
+ const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;
143
+ if (retryCount >= maxRetries) {
144
+ logger.error(`Max retries (${maxRetries}) exceeded`);
145
+ error = {
146
+ code: "CONNECTION_FAILED",
147
+ message: `Max retries (${maxRetries}) exceeded`,
148
+ retryable: false,
149
+ timestamp: Date.now()
150
+ };
151
+ return;
152
+ }
153
+ const interval = calculateRetryInterval(retryCount, retryConfig);
154
+ retryCount++;
155
+ metrics.reconnectCount++;
156
+ logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);
157
+ state = "reconnecting";
158
+ reconnectTimeout = setTimeout(() => {
159
+ if (config) {
160
+ transport.connect(config).catch((err) => {
161
+ logger.error("Reconnect failed:", err);
162
+ });
163
+ }
164
+ }, interval);
165
+ }
166
+ const transport = {
167
+ async connect(cfg) {
168
+ config = cfg;
169
+ intentionalDisconnect = false;
170
+ if (eventSource) {
171
+ eventSource.close();
172
+ eventSource = void 0;
173
+ }
174
+ if (reconnectTimeout) {
175
+ clearTimeout(reconnectTimeout);
176
+ reconnectTimeout = void 0;
177
+ }
178
+ state = retryCount > 0 ? "reconnecting" : "connecting";
179
+ return new Promise((resolve, reject) => {
180
+ const connectionStart = Date.now();
181
+ const url = `${cfg.baseUrl}/stream?orgId=${cfg.orgId}&userId=${cfg.userId}&clientType=${cfg.clientType}${cfg.chatKey ? `&chatId=${cfg.chatKey}` : ""}`;
182
+ logger.debug("Connecting to:", url);
183
+ const es = new EventSource(url);
184
+ es.onopen = () => {
185
+ const connectionTime = Date.now() - connectionStart;
186
+ logger.info(`Connected in ${connectionTime}ms`);
187
+ state = "connected";
188
+ error = void 0;
189
+ retryCount = 0;
190
+ metrics.connectedAt = Date.now();
191
+ metrics.latency = connectionTime;
192
+ setupEventListeners(es);
193
+ resolve();
194
+ };
195
+ es.onerror = () => {
196
+ if (es.readyState === EventSource.CLOSED) {
197
+ const sseError = {
198
+ code: "CONNECTION_FAILED",
199
+ message: "SSE connection failed",
200
+ retryable: true,
201
+ timestamp: Date.now()
202
+ };
203
+ error = sseError;
204
+ metrics.lastError = sseError;
205
+ state = "disconnected";
206
+ if (!intentionalDisconnect) {
207
+ scheduleReconnect();
208
+ }
209
+ if (retryCount === 0) {
210
+ reject(sseError);
211
+ }
212
+ }
213
+ };
214
+ eventSource = es;
215
+ });
216
+ },
217
+ disconnect(intentional = true) {
218
+ logger.info("Disconnecting", { intentional });
219
+ intentionalDisconnect = intentional;
220
+ if (reconnectTimeout) {
221
+ clearTimeout(reconnectTimeout);
222
+ reconnectTimeout = void 0;
223
+ }
224
+ if (eventSource) {
225
+ eventSource.close();
226
+ eventSource = void 0;
227
+ }
228
+ state = "disconnected";
229
+ retryCount = 0;
230
+ },
231
+ async send(event) {
232
+ if (!config) {
233
+ throw new Error("Transport not connected");
234
+ }
235
+ switch (event.type) {
236
+ case "message":
237
+ await sendRest("send", {
238
+ orgId: event.orgId,
239
+ chatKey: event.chatKey,
240
+ userId: event.userId,
241
+ message: event.message
242
+ });
243
+ break;
244
+ case "typing":
245
+ await sendRest("typing", {
246
+ orgId: event.orgId,
247
+ chatKey: event.chatKey,
248
+ userId: event.userId,
249
+ typing: true
250
+ });
251
+ break;
252
+ case "stopped_typing":
253
+ await sendRest("typing", {
254
+ orgId: event.orgId,
255
+ chatKey: event.chatKey,
256
+ userId: event.userId,
257
+ typing: false
258
+ });
259
+ break;
260
+ case "load_chat":
261
+ await sendRest("load", {
262
+ orgId: event.orgId,
263
+ chatKey: event.chatKey,
264
+ userId: event.userId
265
+ });
266
+ break;
267
+ case "new_chat":
268
+ await sendRest("create", {
269
+ orgId: event.orgId,
270
+ userId: event.userId,
271
+ metadata: event.data
272
+ });
273
+ break;
274
+ case "end_chat":
275
+ await sendRest("end", {
276
+ orgId: event.orgId,
277
+ chatKey: event.chatKey,
278
+ userId: event.userId,
279
+ data: event.data
280
+ });
281
+ break;
282
+ default:
283
+ await sendRest("event", {
284
+ type: event.type,
285
+ orgId: event.orgId,
286
+ chatKey: event.chatKey,
287
+ userId: event.userId,
288
+ data: event.data
289
+ });
290
+ }
291
+ metrics.messagesSent++;
292
+ },
293
+ async sendMessage(message) {
294
+ if (!config) {
295
+ throw new Error("Transport not connected");
296
+ }
297
+ await sendRest("send", {
298
+ orgId: config.orgId,
299
+ chatKey: config.chatKey,
300
+ userId: config.userId,
301
+ message
302
+ });
303
+ metrics.messagesSent++;
304
+ },
305
+ onMessage(handler) {
306
+ globalHandlers.add(handler);
307
+ return () => globalHandlers.delete(handler);
308
+ },
309
+ on(eventType, handler) {
310
+ if (!typeHandlers.has(eventType)) {
311
+ typeHandlers.set(eventType, /* @__PURE__ */ new Set());
312
+ }
313
+ typeHandlers.get(eventType).add(handler);
314
+ return () => {
315
+ const handlers = typeHandlers.get(eventType);
316
+ if (handlers) {
317
+ handlers.delete(handler);
318
+ if (handlers.size === 0) {
319
+ typeHandlers.delete(eventType);
320
+ }
321
+ }
322
+ };
323
+ },
324
+ getState() {
325
+ return state;
326
+ },
327
+ getMetrics() {
328
+ return { ...metrics };
329
+ },
330
+ getError() {
331
+ return error;
332
+ },
333
+ clearError() {
334
+ error = void 0;
335
+ }
336
+ };
337
+ return transport;
338
+ }
10
339
 
11
- // hooks/use-websocket-chat-admin.ts
12
- import { useCallback as useCallback2, useState as useState2 } from "react";
340
+ // hooks/use-chat.ts
341
+ function getDefaultTransport(type, debug) {
342
+ return createSSETransport({ debug });
343
+ }
344
+ function useChat(options) {
345
+ const {
346
+ baseUrl,
347
+ orgId,
348
+ userId,
349
+ clientType = "customer",
350
+ transport: transportOption,
351
+ onMessage,
352
+ onError,
353
+ onConnectionChange,
354
+ autoConnect = false,
355
+ retryConfig,
356
+ debug = false
357
+ } = options;
358
+ const [connectionState, setConnectionState] = useState("disconnected");
359
+ const [currentChat, setCurrentChat] = useState(null);
360
+ const [chatKey, setChatKey] = useState(null);
361
+ const [messages, setMessages] = useState([]);
362
+ const [error, setError] = useState(null);
363
+ const [metrics, setMetrics] = useState({
364
+ latency: 0,
365
+ messagesSent: 0,
366
+ messagesReceived: 0,
367
+ messagesQueued: 0,
368
+ reconnectCount: 0
369
+ });
370
+ const transportRef = useRef(null);
371
+ const mountedRef = useRef(false);
372
+ const onMessageRef = useRef(onMessage);
373
+ const onErrorRef = useRef(onError);
374
+ const chatCreationResolverRef = useRef(null);
375
+ const typingTimeoutRef = useRef(null);
376
+ useEffect(() => {
377
+ onMessageRef.current = onMessage;
378
+ onErrorRef.current = onError;
379
+ }, [onMessage, onError]);
380
+ useEffect(() => {
381
+ if (typeof transportOption === "object") {
382
+ transportRef.current = transportOption;
383
+ } else {
384
+ transportRef.current = getDefaultTransport(
385
+ transportOption,
386
+ debug
387
+ );
388
+ }
389
+ }, [transportOption, debug]);
390
+ const handleEvent = useCallback((event) => {
391
+ if (!mountedRef.current) return;
392
+ setMetrics((prev) => ({
393
+ ...prev,
394
+ messagesReceived: prev.messagesReceived + 1,
395
+ lastMessageAt: Date.now()
396
+ }));
397
+ switch (event.type) {
398
+ case "new_chat_created":
399
+ const newChatKey = event.data?.chatKey;
400
+ if (newChatKey) {
401
+ setChatKey(newChatKey);
402
+ if (chatCreationResolverRef.current) {
403
+ chatCreationResolverRef.current.resolve(newChatKey);
404
+ chatCreationResolverRef.current = null;
405
+ }
406
+ }
407
+ break;
408
+ case "load_chat_response":
409
+ const chat = event.data?.chat;
410
+ if (chat) {
411
+ setCurrentChat(chat);
412
+ setChatKey(chat.key);
413
+ setMessages(chat.messages || []);
414
+ }
415
+ break;
416
+ case "message":
417
+ if (event.message) {
418
+ setMessages((prev) => [...prev, event.message]);
419
+ }
420
+ break;
421
+ case "chat_ended":
422
+ setCurrentChat(null);
423
+ setChatKey(null);
424
+ break;
425
+ case "error":
426
+ const errorMsg = event.data?.message;
427
+ if (errorMsg) {
428
+ const transportError = {
429
+ code: "NETWORK_ERROR",
430
+ message: errorMsg,
431
+ retryable: true,
432
+ timestamp: Date.now()
433
+ };
434
+ setError(transportError);
435
+ onErrorRef.current?.(transportError);
436
+ }
437
+ break;
438
+ }
439
+ onMessageRef.current?.(event);
440
+ }, []);
441
+ const connect = useCallback(async () => {
442
+ const transport = transportRef.current;
443
+ if (!transport) {
444
+ throw new Error("Transport not initialized");
445
+ }
446
+ if (!orgId) {
447
+ const err = {
448
+ code: "CONNECTION_FAILED",
449
+ message: "orgId is required",
450
+ retryable: false,
451
+ timestamp: Date.now()
452
+ };
453
+ setError(err);
454
+ throw err;
455
+ }
456
+ setConnectionState("connecting");
457
+ try {
458
+ const unsubscribe = transport.onMessage(handleEvent);
459
+ await transport.connect({
460
+ baseUrl,
461
+ orgId,
462
+ userId,
463
+ clientType,
464
+ chatKey: chatKey || void 0,
465
+ debug
466
+ });
467
+ setConnectionState("connected");
468
+ setError(null);
469
+ setMetrics(transport.getMetrics());
470
+ onConnectionChange?.("connected");
471
+ return;
472
+ } catch (err) {
473
+ const transportError = err;
474
+ setConnectionState("disconnected");
475
+ setError(transportError);
476
+ onErrorRef.current?.(transportError);
477
+ throw err;
478
+ }
479
+ }, [baseUrl, orgId, userId, clientType, chatKey, debug, handleEvent, onConnectionChange]);
480
+ const disconnect = useCallback(() => {
481
+ const transport = transportRef.current;
482
+ if (transport) {
483
+ transport.disconnect(true);
484
+ }
485
+ setConnectionState("disconnected");
486
+ onConnectionChange?.("disconnected");
487
+ }, [onConnectionChange]);
488
+ const startChat = useCallback(
489
+ async (metadata) => {
490
+ const transport = transportRef.current;
491
+ if (!transport) {
492
+ throw new Error("Transport not initialized");
493
+ }
494
+ return new Promise((resolve, reject) => {
495
+ chatCreationResolverRef.current = { resolve, reject };
496
+ transport.send({
497
+ type: "new_chat",
498
+ orgId,
499
+ chatKey: "",
500
+ userId,
501
+ timestamp: Date.now(),
502
+ data: metadata
503
+ }).catch((err) => {
504
+ chatCreationResolverRef.current = null;
505
+ reject(err);
506
+ });
507
+ setTimeout(() => {
508
+ if (chatCreationResolverRef.current) {
509
+ chatCreationResolverRef.current = null;
510
+ reject(new Error("Chat creation timed out"));
511
+ }
512
+ }, 3e4);
513
+ });
514
+ },
515
+ [orgId, userId]
516
+ );
517
+ const loadChat = useCallback(
518
+ async (key) => {
519
+ const transport = transportRef.current;
520
+ if (!transport) {
521
+ throw new Error("Transport not initialized");
522
+ }
523
+ setChatKey(key);
524
+ await transport.send({
525
+ type: "load_chat",
526
+ orgId,
527
+ chatKey: key,
528
+ userId,
529
+ timestamp: Date.now()
530
+ });
531
+ },
532
+ [orgId, userId]
533
+ );
534
+ const sendMessage = useCallback(
535
+ async (content, attachments) => {
536
+ const transport = transportRef.current;
537
+ if (!transport) {
538
+ throw new Error("Transport not initialized");
539
+ }
540
+ if (!chatKey) {
541
+ throw new Error("No active chat");
542
+ }
543
+ const message = {
544
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
545
+ role: "user",
546
+ content,
547
+ time: Date.now(),
548
+ status: "sending",
549
+ senderId: userId,
550
+ createdAt: Date.now(),
551
+ attachments
552
+ };
553
+ setMessages((prev) => [...prev, message]);
554
+ await transport.send({
555
+ type: "message",
556
+ orgId,
557
+ chatKey,
558
+ userId,
559
+ timestamp: Date.now(),
560
+ message
561
+ });
562
+ setMetrics((prev) => ({
563
+ ...prev,
564
+ messagesSent: prev.messagesSent + 1
565
+ }));
566
+ },
567
+ [orgId, chatKey, userId]
568
+ );
569
+ const endChat = useCallback(
570
+ async (reason) => {
571
+ const transport = transportRef.current;
572
+ if (!transport) {
573
+ throw new Error("Transport not initialized");
574
+ }
575
+ if (!chatKey) {
576
+ return;
577
+ }
578
+ await transport.send({
579
+ type: "end_chat",
580
+ orgId,
581
+ chatKey,
582
+ userId,
583
+ timestamp: Date.now(),
584
+ data: reason ? { reason } : void 0
585
+ });
586
+ setCurrentChat(null);
587
+ setChatKey(null);
588
+ },
589
+ [orgId, chatKey, userId]
590
+ );
591
+ const startTyping = useCallback(() => {
592
+ const transport = transportRef.current;
593
+ if (!transport || !chatKey) return;
594
+ if (typingTimeoutRef.current) {
595
+ clearTimeout(typingTimeoutRef.current);
596
+ }
597
+ transport.send({
598
+ type: "typing",
599
+ orgId,
600
+ chatKey,
601
+ userId,
602
+ timestamp: Date.now()
603
+ }).catch(() => {
604
+ });
605
+ typingTimeoutRef.current = setTimeout(() => {
606
+ stopTyping();
607
+ }, 3e3);
608
+ }, [orgId, chatKey, userId]);
609
+ const stopTyping = useCallback(() => {
610
+ const transport = transportRef.current;
611
+ if (!transport || !chatKey) return;
612
+ if (typingTimeoutRef.current) {
613
+ clearTimeout(typingTimeoutRef.current);
614
+ typingTimeoutRef.current = null;
615
+ }
616
+ transport.send({
617
+ type: "stopped_typing",
618
+ orgId,
619
+ chatKey,
620
+ userId,
621
+ timestamp: Date.now()
622
+ }).catch(() => {
623
+ });
624
+ }, [orgId, chatKey, userId]);
625
+ const on = useCallback(
626
+ (eventType, handler) => {
627
+ const transport = transportRef.current;
628
+ if (!transport) {
629
+ return () => {
630
+ };
631
+ }
632
+ return transport.on(eventType, handler);
633
+ },
634
+ []
635
+ );
636
+ const clearError = useCallback(() => {
637
+ setError(null);
638
+ transportRef.current?.clearError();
639
+ }, []);
640
+ useEffect(() => {
641
+ mountedRef.current = true;
642
+ if (autoConnect) {
643
+ connect().catch(() => {
644
+ });
645
+ }
646
+ return () => {
647
+ mountedRef.current = false;
648
+ if (typingTimeoutRef.current) {
649
+ clearTimeout(typingTimeoutRef.current);
650
+ }
651
+ disconnect();
652
+ };
653
+ }, []);
654
+ const isConnected = connectionState === "connected";
655
+ return {
656
+ // Connection
657
+ connect,
658
+ disconnect,
659
+ connectionState,
660
+ isConnected,
661
+ // Chat operations
662
+ startChat,
663
+ loadChat,
664
+ sendMessage,
665
+ endChat,
666
+ // Typing
667
+ startTyping,
668
+ stopTyping,
669
+ // State
670
+ currentChat,
671
+ chatKey,
672
+ messages,
673
+ error,
674
+ metrics,
675
+ // Events
676
+ on,
677
+ clearError
678
+ };
679
+ }
13
680
 
14
681
  // models/chat-models.ts
15
682
  var ChatStatusActive = "active";
@@ -128,6 +795,11 @@ var ChatEventTypePong = "pong";
128
795
  var ChatEventTypeSkillActivate = "skill_activate";
129
796
  var ChatEventTypeSkillDeactivate = "skill_deactivate";
130
797
  var ChatEventTypeSkillsChanged = "skills_changed";
798
+ var ChatEventTypeAttachmentProcessingStarted = "attachment_processing_started";
799
+ var ChatEventTypeAttachmentProcessingProgress = "attachment_processing_progress";
800
+ var ChatEventTypeAttachmentProcessingComplete = "attachment_processing_complete";
801
+ var ChatEventTypeAttachmentProcessingError = "attachment_processing_error";
802
+ var ChatEventTypeRetryAttachment = "retry_attachment";
131
803
  var MessageStatusSending = "sending";
132
804
  var MessageStatusSent = "sent";
133
805
  var MessageStatusDelivered = "delivered";
@@ -149,6 +821,8 @@ var AttachmentTypeSticker = "sticker";
149
821
  var AttachmentTypeData = "data";
150
822
  var AttachmentTypeKGNodes = "kgNodes";
151
823
  var AttachmentTypeDocumentSources = "document_sources";
824
+ var AttachmentTypeSpreadsheet = "spreadsheet";
825
+ var AttachmentTypeDataFile = "data_file";
152
826
  var ChatSessionStatusActive = "active";
153
827
  var ChatSessionStatusIdle = "idle";
154
828
  var ChatSessionStatusExpired = "expired";
@@ -193,1999 +867,7 @@ var UpdateUserStatusSubject = "chat.user.status.update";
193
867
  var GetOnlineUsersSubject = "chat.users.online.get";
194
868
  var TriggerAnalyticsScanSubject = "chat.analytics.trigger-scan";
195
869
  var SetupOrgSubject = "chat.org.setup";
196
-
197
- // hooks/use-websocket-chat-base.ts
198
- import { useCallback, useEffect, useRef, useState } from "react";
199
- var createDefaultLogger = (debug) => ({
200
- debug: debug ? console.log : () => {
201
- },
202
- info: console.info,
203
- warn: console.warn,
204
- error: console.error
205
- });
206
- var DEFAULT_RETRY_CONFIG = {
207
- maxRetries: 10,
208
- intervals: [1e3, 2e3, 5e3],
209
- backoffMultiplier: 1.5,
210
- maxBackoffTime: 3e4
211
- };
212
- var DEFAULT_QUEUE_CONFIG = {
213
- maxSize: 100,
214
- dropStrategy: "oldest"
215
- };
216
- var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
217
- var DEFAULT_HEARTBEAT_TIMEOUT = 5e3;
218
- var DEFAULT_TRANSPORT = "websocket";
219
- function isChatEvent(data) {
220
- return data && typeof data === "object" && (typeof data.type === "string" || data.message);
221
- }
222
- var useWebSocketChatBase = ({
223
- serverBaseUrl,
224
- orgId,
225
- clientType,
226
- product,
227
- onMessage,
228
- retryConfig = DEFAULT_RETRY_CONFIG,
229
- queueConfig = DEFAULT_QUEUE_CONFIG,
230
- debug = false,
231
- logger = createDefaultLogger(debug),
232
- heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL,
233
- heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT,
234
- transport = DEFAULT_TRANSPORT
235
- }) => {
236
- const [connectionState, setConnectionState] = useState("disconnected");
237
- const [error, setError] = useState(void 0);
238
- const [startTime, setStartTime] = useState(void 0);
239
- const [metrics, setMetrics] = useState({
240
- latency: 0,
241
- messagesSent: 0,
242
- messagesReceived: 0,
243
- messagesQueued: 0,
244
- reconnectCount: 0,
245
- transportType: transport
246
- });
247
- const wsRef = useRef(void 0);
248
- const sseRef = useRef(void 0);
249
- const transportRef = useRef(transport);
250
- useEffect(() => {
251
- transportRef.current = transport;
252
- }, [transport]);
253
- const reconnectTimeoutRef = useRef(void 0);
254
- const retryCountRef = useRef(0);
255
- const messageQueueRef = useRef([]);
256
- const mountedRef = useRef(false);
257
- const currentChatKeyRef = useRef(void 0);
258
- const currentUserIdRef = useRef(void 0);
259
- const intentionalDisconnectRef = useRef(false);
260
- const eventHandlersRef = useRef(
261
- /* @__PURE__ */ new Map()
262
- );
263
- const onMessageRef = useRef(onMessage);
264
- const pendingMessagesRef = useRef(/* @__PURE__ */ new Map());
265
- const heartbeatIntervalRef = useRef(void 0);
266
- const heartbeatTimeoutRef = useRef(void 0);
267
- const lastPongRef = useRef(Date.now());
268
- const chatCreationPromiseRef = useRef(null);
269
- const loadChatRetryMapRef = useRef(/* @__PURE__ */ new Map());
270
- useEffect(() => {
271
- onMessageRef.current = onMessage;
272
- }, [onMessage]);
273
- const isConnected = connectionState === "connected";
274
- const on = useCallback((eventType, handler) => {
275
- if (!eventHandlersRef.current.has(eventType)) {
276
- eventHandlersRef.current.set(eventType, /* @__PURE__ */ new Set());
277
- }
278
- eventHandlersRef.current.get(eventType).add(handler);
279
- return () => off(eventType, handler);
280
- }, []);
281
- const off = useCallback((eventType, handler) => {
282
- const handlers = eventHandlersRef.current.get(eventType);
283
- if (handlers) {
284
- handlers.delete(handler);
285
- if (handlers.size === 0) {
286
- eventHandlersRef.current.delete(eventType);
287
- }
288
- }
289
- }, []);
290
- const emit = useCallback(
291
- (eventType, data) => {
292
- const handlers = eventHandlersRef.current.get(eventType);
293
- if (handlers) {
294
- handlers.forEach((handler) => {
295
- try {
296
- handler(data);
297
- } catch (error2) {
298
- logger.error(`Error in event handler for ${eventType}:`, error2);
299
- }
300
- });
301
- }
302
- },
303
- [logger]
304
- );
305
- const updateMetrics = useCallback((updates) => {
306
- setMetrics((prev) => ({ ...prev, ...updates }));
307
- }, []);
308
- const addToQueue = useCallback(
309
- (event) => {
310
- const currentQueueSize = messageQueueRef.current.length;
311
- const maxSize = queueConfig.maxSize || DEFAULT_QUEUE_CONFIG.maxSize;
312
- if (currentQueueSize >= maxSize) {
313
- if (queueConfig.dropStrategy === "newest") {
314
- logger.warn("Message queue full, dropping new message");
315
- return false;
316
- } else {
317
- const dropped = messageQueueRef.current.shift();
318
- logger.warn("Message queue full, dropped oldest message", dropped);
319
- }
320
- }
321
- messageQueueRef.current.push(event);
322
- updateMetrics({ messagesQueued: messageQueueRef.current.length });
323
- return true;
324
- },
325
- [queueConfig, logger, updateMetrics]
326
- );
327
- const calculateRetryInterval = useCallback(
328
- (retryCount) => {
329
- const config = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
330
- const {
331
- intervals = [],
332
- backoffMultiplier = 1.5,
333
- maxBackoffTime = 3e4
334
- } = config;
335
- if (retryCount < intervals.length) {
336
- return intervals[retryCount];
337
- }
338
- const baseInterval = intervals[intervals.length - 1] || 5e3;
339
- const backoffTime = baseInterval * Math.pow(backoffMultiplier, retryCount - intervals.length + 1);
340
- return Math.min(backoffTime, maxBackoffTime);
341
- },
342
- [retryConfig]
343
- );
344
- const startHeartbeat = useCallback(() => {
345
- if (!heartbeatInterval || heartbeatInterval <= 0) return;
346
- const sendPing = () => {
347
- if (wsRef.current?.readyState === WebSocket.OPEN) {
348
- const pingTime = Date.now();
349
- wsRef.current.send(
350
- JSON.stringify({ type: "ping", timestamp: pingTime })
351
- );
352
- heartbeatTimeoutRef.current = setTimeout(() => {
353
- logger.warn("Heartbeat timeout - no pong received");
354
- if (wsRef.current) {
355
- wsRef.current.close(4e3, "Heartbeat timeout");
356
- }
357
- }, heartbeatTimeout);
358
- }
359
- };
360
- if (heartbeatIntervalRef.current) {
361
- clearInterval(heartbeatIntervalRef.current);
362
- }
363
- heartbeatIntervalRef.current = setInterval(sendPing, heartbeatInterval);
364
- logger.debug("Heartbeat started");
365
- }, [heartbeatInterval, heartbeatTimeout, logger]);
366
- const stopHeartbeat = useCallback(() => {
367
- if (heartbeatIntervalRef.current) {
368
- clearInterval(heartbeatIntervalRef.current);
369
- heartbeatIntervalRef.current = void 0;
370
- }
371
- if (heartbeatTimeoutRef.current) {
372
- clearTimeout(heartbeatTimeoutRef.current);
373
- heartbeatTimeoutRef.current = void 0;
374
- }
375
- logger.debug("Heartbeat stopped");
376
- }, [logger]);
377
- const cleanup = useCallback(() => {
378
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
379
- wsRef.current.close(1e3, "Cleanup");
380
- }
381
- wsRef.current = void 0;
382
- if (sseRef.current) {
383
- sseRef.current.close();
384
- sseRef.current = void 0;
385
- }
386
- if (reconnectTimeoutRef.current) {
387
- clearTimeout(reconnectTimeoutRef.current);
388
- reconnectTimeoutRef.current = void 0;
389
- }
390
- stopHeartbeat();
391
- pendingMessagesRef.current.forEach(({ reject, timeout }) => {
392
- clearTimeout(timeout);
393
- reject(new Error("Connection closed"));
394
- });
395
- pendingMessagesRef.current.clear();
396
- loadChatRetryMapRef.current.forEach((retryState) => {
397
- if (retryState.timeoutId) {
398
- clearTimeout(retryState.timeoutId);
399
- }
400
- });
401
- loadChatRetryMapRef.current.clear();
402
- }, [stopHeartbeat]);
403
- const getRestApiUrl = useCallback((endpoint) => {
404
- return `${serverBaseUrl}/${endpoint}`;
405
- }, [serverBaseUrl]);
406
- const sendRestMessage = useCallback(
407
- async (endpoint, body) => {
408
- const url = getRestApiUrl(endpoint);
409
- logger.debug(`SSE REST API call: POST ${endpoint}`, body);
410
- try {
411
- const response = await fetch(url, {
412
- method: "POST",
413
- headers: {
414
- "Content-Type": "application/json"
415
- },
416
- body: JSON.stringify(body)
417
- });
418
- if (!response.ok) {
419
- const errorText = await response.text();
420
- throw new Error(`REST API error: ${response.status} - ${errorText}`);
421
- }
422
- const data = await response.json();
423
- return data;
424
- } catch (error2) {
425
- logger.error(`SSE REST API error for ${endpoint}:`, error2);
426
- throw error2;
427
- }
428
- },
429
- [getRestApiUrl, logger]
430
- );
431
- const connect = useCallback(
432
- async (userId) => {
433
- if (!mountedRef.current) {
434
- mountedRef.current = true;
435
- }
436
- if (!orgId) {
437
- const error2 = {
438
- code: "CONNECTION_FAILED",
439
- message: "Cannot connect: orgId is undefined",
440
- retryable: false,
441
- timestamp: Date.now()
442
- };
443
- logger.error("Cannot connect: orgId is undefined");
444
- setError(error2);
445
- return Promise.reject(error2);
446
- }
447
- if (wsRef.current?.readyState === WebSocket.OPEN) {
448
- logger.debug("Already connected (WebSocket)");
449
- return Promise.resolve();
450
- }
451
- if (sseRef.current?.readyState === EventSource.OPEN) {
452
- logger.debug("Already connected (SSE)");
453
- return Promise.resolve();
454
- }
455
- if (connectionState === "connecting" || connectionState === "reconnecting") {
456
- logger.debug("Connection already in progress");
457
- return Promise.resolve();
458
- }
459
- const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;
460
- if (retryCountRef.current >= maxRetries && !intentionalDisconnectRef.current) {
461
- const error2 = {
462
- code: "CONNECTION_FAILED",
463
- message: `Max retries (${maxRetries}) exceeded`,
464
- retryable: false,
465
- timestamp: Date.now()
466
- };
467
- setError(error2);
468
- updateMetrics({ lastError: error2 });
469
- return Promise.reject(error2);
470
- }
471
- cleanup();
472
- setConnectionState(
473
- retryCountRef.current > 0 ? "reconnecting" : "connecting"
474
- );
475
- intentionalDisconnectRef.current = false;
476
- return new Promise((resolve, reject) => {
477
- try {
478
- const connectionStartTime = Date.now();
479
- logger.info(`\u{1F504} Connecting with transport: ${transportRef.current}`);
480
- if (transportRef.current === "sse") {
481
- const sseUrl = getRestApiUrl(`stream?orgId=${orgId}&userId=${userId}&clientType=${clientType}&chatId=${currentChatKeyRef.current || ""}`);
482
- logger.debug("Connecting to SSE:", sseUrl);
483
- console.log(`\u23F3 Initiating SSE connection to ${sseUrl}...`);
484
- const eventSource = new EventSource(sseUrl);
485
- eventSource.onopen = () => {
486
- if (!mountedRef.current) {
487
- eventSource.close();
488
- reject(new Error("Component unmounted"));
489
- return;
490
- }
491
- const connectionTimeMs = Date.now() - connectionStartTime;
492
- const connectionTimeSec = (connectionTimeMs / 1e3).toFixed(2);
493
- logger.info("\u2705 SSE connected", {
494
- userId,
495
- retryCount: retryCountRef.current,
496
- connectionTime: `${connectionTimeSec}s (${connectionTimeMs}ms)`
497
- });
498
- console.log(`\u{1F50C} SSE connection established in ${connectionTimeSec} seconds`);
499
- setConnectionState("connected");
500
- setError(void 0);
501
- const wasReconnecting = retryCountRef.current > 0;
502
- retryCountRef.current = 0;
503
- updateMetrics({
504
- connectedAt: Date.now(),
505
- latency: connectionTimeMs,
506
- transportType: "sse",
507
- reconnectCount: wasReconnecting ? metrics.reconnectCount + 1 : metrics.reconnectCount
508
- });
509
- currentUserIdRef.current = userId;
510
- if (currentChatKeyRef.current) {
511
- logger.info("Loading chat after SSE reconnection:", currentChatKeyRef.current);
512
- sendRestMessage("load", {
513
- orgId,
514
- chatKey: currentChatKeyRef.current,
515
- userId
516
- }).then((response) => {
517
- if (response.success && response.data?.chat) {
518
- const chatEvent = {
519
- type: "load_chat_response",
520
- orgId,
521
- chatKey: currentChatKeyRef.current,
522
- userId,
523
- timestamp: Date.now(),
524
- data: response.data
525
- };
526
- emit("load_chat_response", chatEvent);
527
- if (onMessageRef.current) {
528
- onMessageRef.current(chatEvent);
529
- }
530
- }
531
- }).catch((err) => {
532
- logger.error("Failed to load chat after SSE reconnection:", err);
533
- });
534
- }
535
- emit("connected", { userId, wasReconnecting, transport: "sse" });
536
- resolve();
537
- };
538
- const handleSSEMessage = (event) => {
539
- if (!mountedRef.current) return;
540
- if (!event.data || event.data === "") {
541
- return;
542
- }
543
- try {
544
- const data = JSON.parse(event.data);
545
- if (!isChatEvent(data)) {
546
- logger.warn("Received invalid SSE message format:", data);
547
- return;
548
- }
549
- const chatEvent = data;
550
- logger.debug("SSE message received:", chatEvent.type);
551
- updateMetrics({
552
- messagesReceived: metrics.messagesReceived + 1,
553
- lastMessageAt: Date.now()
554
- });
555
- switch (chatEvent.type) {
556
- case "new_chat_created":
557
- const newChatKey = chatEvent.data?.chatKey;
558
- if (newChatKey) {
559
- logger.info("New chat created with key:", newChatKey);
560
- currentChatKeyRef.current = newChatKey;
561
- if (chatCreationPromiseRef.current) {
562
- chatCreationPromiseRef.current.resolve(newChatKey);
563
- chatCreationPromiseRef.current = null;
564
- }
565
- }
566
- break;
567
- case "load_chat_response":
568
- const chat = chatEvent.data?.chat;
569
- if (chat && chat.key) {
570
- logger.info("Chat loaded with key:", chat.key);
571
- currentChatKeyRef.current = chat.key;
572
- }
573
- break;
574
- case "chat_ended":
575
- logger.info("Chat ended, clearing key");
576
- currentChatKeyRef.current = void 0;
577
- break;
578
- }
579
- emit(chatEvent.type || "message", chatEvent);
580
- if (onMessageRef.current) {
581
- onMessageRef.current(chatEvent);
582
- }
583
- } catch (error2) {
584
- logger.error("Failed to parse SSE message:", error2);
585
- }
586
- };
587
- eventSource.addEventListener("message", handleSSEMessage);
588
- eventSource.addEventListener("reconnected", handleSSEMessage);
589
- eventSource.addEventListener("typing", handleSSEMessage);
590
- eventSource.addEventListener("stopped_typing", handleSSEMessage);
591
- eventSource.addEventListener("waiting", handleSSEMessage);
592
- eventSource.addEventListener("waiting_for_agent", handleSSEMessage);
593
- eventSource.addEventListener("human_agent_joined", handleSSEMessage);
594
- eventSource.addEventListener("human_agent_left", handleSSEMessage);
595
- eventSource.addEventListener("chat_ended", handleSSEMessage);
596
- eventSource.addEventListener("chat_updated", handleSSEMessage);
597
- eventSource.addEventListener("load_chat_response", handleSSEMessage);
598
- eventSource.addEventListener("new_chat_created", handleSSEMessage);
599
- eventSource.addEventListener("show_csat_survey", handleSSEMessage);
600
- eventSource.addEventListener("csat_response", handleSSEMessage);
601
- eventSource.addEventListener("user_suggested_actions", handleSSEMessage);
602
- eventSource.addEventListener("agent_execution_started", handleSSEMessage);
603
- eventSource.addEventListener("agent_execution_ended", handleSSEMessage);
604
- eventSource.addEventListener("agent_context_update", handleSSEMessage);
605
- eventSource.addEventListener("plan_pending_approval", handleSSEMessage);
606
- eventSource.addEventListener("step_started", handleSSEMessage);
607
- eventSource.addEventListener("step_completed", handleSSEMessage);
608
- eventSource.addEventListener("step_failed", handleSSEMessage);
609
- eventSource.addEventListener("plan_completed", handleSSEMessage);
610
- eventSource.addEventListener("skills_changed", handleSSEMessage);
611
- eventSource.addEventListener("summary_update", handleSSEMessage);
612
- eventSource.onerror = (error2) => {
613
- logger.error("SSE error:", error2);
614
- if (!mountedRef.current) return;
615
- if (eventSource.readyState === EventSource.CLOSED) {
616
- const sseError = {
617
- code: "CONNECTION_FAILED",
618
- message: "SSE connection failed",
619
- retryable: true,
620
- timestamp: Date.now()
621
- };
622
- setError(sseError);
623
- updateMetrics({ lastError: sseError });
624
- setConnectionState("disconnected");
625
- emit("disconnected", { reason: "SSE error" });
626
- if (!intentionalDisconnectRef.current && mountedRef.current) {
627
- const retryInterval = calculateRetryInterval(retryCountRef.current);
628
- retryCountRef.current++;
629
- logger.info(`SSE reconnecting in ${retryInterval}ms (attempt ${retryCountRef.current})`);
630
- if (reconnectTimeoutRef.current) {
631
- clearTimeout(reconnectTimeoutRef.current);
632
- }
633
- reconnectTimeoutRef.current = setTimeout(() => {
634
- connect(userId);
635
- }, retryInterval);
636
- }
637
- }
638
- };
639
- sseRef.current = eventSource;
640
- return;
641
- }
642
- const wsUrl = `${serverBaseUrl}?orgId=${orgId}&userId=${userId}&clientType=${clientType}&product=${product}`;
643
- logger.debug("Connecting to WebSocket:", wsUrl);
644
- console.log(`\u23F3 Initiating WebSocket connection to ${serverBaseUrl}...`);
645
- const ws = new WebSocket(wsUrl);
646
- ws.onopen = () => {
647
- if (!mountedRef.current) {
648
- ws.close(1e3, "Component unmounted");
649
- reject(new Error("Component unmounted"));
650
- return;
651
- }
652
- const connectionTimeMs = Date.now() - connectionStartTime;
653
- const connectionTimeSec = (connectionTimeMs / 1e3).toFixed(2);
654
- logger.info("\u2705 WebSocket connected", {
655
- userId,
656
- retryCount: retryCountRef.current,
657
- connectionTime: `${connectionTimeSec}s (${connectionTimeMs}ms)`
658
- });
659
- console.log(`\u{1F50C} WebSocket connection established in ${connectionTimeSec} seconds`);
660
- setConnectionState("connected");
661
- setError(void 0);
662
- const wasReconnecting = retryCountRef.current > 0;
663
- retryCountRef.current = 0;
664
- updateMetrics({
665
- connectedAt: Date.now(),
666
- latency: connectionTimeMs,
667
- reconnectCount: wasReconnecting ? metrics.reconnectCount + 1 : metrics.reconnectCount
668
- });
669
- while (messageQueueRef.current.length > 0 && ws.readyState === WebSocket.OPEN) {
670
- const event = messageQueueRef.current.shift();
671
- if (event) {
672
- ws.send(JSON.stringify({ ...event, timestamp: Date.now() }));
673
- updateMetrics({
674
- messagesSent: metrics.messagesSent + 1,
675
- messagesQueued: messageQueueRef.current.length
676
- });
677
- }
678
- }
679
- currentUserIdRef.current = userId;
680
- if (currentChatKeyRef.current) {
681
- if (!orgId) {
682
- logger.error("Cannot resubscribe to chat: orgId is undefined");
683
- } else {
684
- logger.info(
685
- "Resubscribing to chat after reconnection:",
686
- currentChatKeyRef.current
687
- );
688
- const resubscribeEvent = {
689
- type: "load_chat",
690
- orgId,
691
- chatKey: currentChatKeyRef.current,
692
- userId,
693
- timestamp: Date.now(),
694
- data: {}
695
- };
696
- ws.send(JSON.stringify(resubscribeEvent));
697
- }
698
- }
699
- startHeartbeat();
700
- emit("connected", { userId, wasReconnecting });
701
- resolve();
702
- };
703
- ws.onmessage = (event) => {
704
- if (!mountedRef.current) return;
705
- try {
706
- const data = JSON.parse(event.data);
707
- if (!isChatEvent(data)) {
708
- logger.warn("Received invalid message format:", data);
709
- return;
710
- }
711
- const chatEvent = data;
712
- logger.debug("Message received:", chatEvent.type);
713
- updateMetrics({
714
- messagesReceived: metrics.messagesReceived + 1,
715
- lastMessageAt: Date.now()
716
- });
717
- if (chatEvent.type === "pong") {
718
- if (heartbeatTimeoutRef.current) {
719
- clearTimeout(heartbeatTimeoutRef.current);
720
- heartbeatTimeoutRef.current = void 0;
721
- }
722
- const latency = Date.now() - (chatEvent.timestamp || Date.now());
723
- lastPongRef.current = Date.now();
724
- updateMetrics({ latency });
725
- return;
726
- }
727
- switch (chatEvent.type) {
728
- case "new_chat_created":
729
- const newChatKey = chatEvent.data?.chatKey;
730
- if (newChatKey) {
731
- logger.info("New chat created with key:", newChatKey);
732
- currentChatKeyRef.current = newChatKey;
733
- if (chatCreationPromiseRef.current) {
734
- chatCreationPromiseRef.current.resolve(newChatKey);
735
- chatCreationPromiseRef.current = null;
736
- }
737
- if (!orgId) {
738
- logger.error("Cannot send load_chat: orgId is undefined");
739
- return;
740
- }
741
- const loadChatEvent = {
742
- type: "load_chat",
743
- orgId,
744
- chatKey: newChatKey,
745
- userId: currentUserIdRef.current || userId,
746
- timestamp: Date.now(),
747
- data: {}
748
- };
749
- ws.send(JSON.stringify(loadChatEvent));
750
- }
751
- break;
752
- case "load_chat_response":
753
- const chat = chatEvent.data?.chat;
754
- if (chat && chat.key) {
755
- logger.info("Chat loaded with key:", chat.key);
756
- currentChatKeyRef.current = chat.key;
757
- const retryState = loadChatRetryMapRef.current.get(chat.key);
758
- if (retryState) {
759
- if (retryState.timeoutId) {
760
- clearTimeout(retryState.timeoutId);
761
- }
762
- loadChatRetryMapRef.current.delete(chat.key);
763
- }
764
- } else if (!chat) {
765
- logger.warn("Chat load failed, clearing key");
766
- currentChatKeyRef.current = void 0;
767
- }
768
- break;
769
- case "chat_ended":
770
- logger.info("Chat ended, clearing key");
771
- currentChatKeyRef.current = void 0;
772
- break;
773
- case "error":
774
- const errorMessage = chatEvent.data?.message || "Unknown error";
775
- logger.error("Received error from server:", errorMessage);
776
- if (errorMessage.includes("nats: key not found") || errorMessage.includes("Failed to load chat")) {
777
- const chatKeyFromError = currentChatKeyRef.current;
778
- if (chatKeyFromError) {
779
- const maxRetries2 = 5;
780
- let retryState = loadChatRetryMapRef.current.get(chatKeyFromError);
781
- if (!retryState) {
782
- retryState = { retryCount: 0, timeoutId: null };
783
- loadChatRetryMapRef.current.set(chatKeyFromError, retryState);
784
- }
785
- if (retryState.retryCount < maxRetries2) {
786
- const delay = 200 * Math.pow(2, retryState.retryCount);
787
- retryState.retryCount++;
788
- logger.info(
789
- `Chat load failed, retrying (${retryState.retryCount}/${maxRetries2}) in ${delay}ms...`,
790
- chatKeyFromError
791
- );
792
- retryState.timeoutId = setTimeout(() => {
793
- if (!wsRef.current || !mountedRef.current) return;
794
- if (!orgId) {
795
- logger.error("Cannot retry load_chat: orgId is undefined", chatKeyFromError);
796
- loadChatRetryMapRef.current.delete(chatKeyFromError);
797
- return;
798
- }
799
- logger.debug("Retrying load_chat:", chatKeyFromError);
800
- const retryLoadEvent = {
801
- type: "load_chat",
802
- orgId,
803
- chatKey: chatKeyFromError,
804
- userId: currentUserIdRef.current || "",
805
- timestamp: Date.now(),
806
- data: {}
807
- };
808
- ws.send(JSON.stringify(retryLoadEvent));
809
- }, delay);
810
- } else {
811
- logger.error("Max retries reached for loading chat:", chatKeyFromError);
812
- loadChatRetryMapRef.current.delete(chatKeyFromError);
813
- }
814
- }
815
- }
816
- const wsError = {
817
- code: "NETWORK_ERROR",
818
- message: errorMessage,
819
- retryable: true,
820
- timestamp: Date.now()
821
- };
822
- setError(wsError);
823
- updateMetrics({ lastError: wsError });
824
- break;
825
- }
826
- emit(chatEvent.type || "message", chatEvent);
827
- if (onMessageRef.current) {
828
- onMessageRef.current(chatEvent);
829
- }
830
- } catch (error2) {
831
- logger.error("Failed to parse WebSocket message:", error2);
832
- const parseError = {
833
- code: "PARSE_ERROR",
834
- message: "Failed to parse message",
835
- retryable: false,
836
- timestamp: Date.now()
837
- };
838
- setError(parseError);
839
- updateMetrics({ lastError: parseError });
840
- }
841
- };
842
- ws.onclose = (event) => {
843
- if (!mountedRef.current) return;
844
- logger.info("WebSocket closed", {
845
- code: event.code,
846
- reason: event.reason
847
- });
848
- setConnectionState("disconnected");
849
- wsRef.current = void 0;
850
- stopHeartbeat();
851
- emit("disconnected", { code: event.code, reason: event.reason });
852
- if (event.code !== 1e3 && !intentionalDisconnectRef.current && mountedRef.current) {
853
- const retryInterval = calculateRetryInterval(
854
- retryCountRef.current
855
- );
856
- retryCountRef.current++;
857
- logger.info(
858
- `Reconnecting in ${retryInterval}ms (attempt ${retryCountRef.current})`
859
- );
860
- if (reconnectTimeoutRef.current) {
861
- clearTimeout(reconnectTimeoutRef.current);
862
- }
863
- reconnectTimeoutRef.current = setTimeout(() => {
864
- connect(userId);
865
- }, retryInterval);
866
- }
867
- };
868
- ws.onerror = (error2) => {
869
- logger.error("WebSocket error:", error2);
870
- if (!mountedRef.current) return;
871
- const wsError = {
872
- code: "CONNECTION_FAILED",
873
- message: "WebSocket connection failed",
874
- retryable: true,
875
- timestamp: Date.now()
876
- };
877
- setError(wsError);
878
- updateMetrics({ lastError: wsError });
879
- reject(wsError);
880
- };
881
- wsRef.current = ws;
882
- } catch (error2) {
883
- logger.error("Failed to create WebSocket:", error2);
884
- const wsError = {
885
- code: "CONNECTION_FAILED",
886
- message: error2 instanceof Error ? error2.message : "Failed to create connection",
887
- retryable: true,
888
- timestamp: Date.now()
889
- };
890
- setError(wsError);
891
- updateMetrics({ lastError: wsError });
892
- reject(wsError);
893
- }
894
- });
895
- },
896
- [
897
- serverBaseUrl,
898
- orgId,
899
- clientType,
900
- product,
901
- connectionState,
902
- logger,
903
- retryConfig,
904
- metrics,
905
- updateMetrics,
906
- cleanup,
907
- calculateRetryInterval,
908
- startHeartbeat,
909
- emit
910
- ]
911
- );
912
- const sendMessage = useCallback(
913
- (event, overrideUserId) => {
914
- return new Promise(async (resolve, reject) => {
915
- if (!mountedRef.current) {
916
- reject(new Error("Component not mounted"));
917
- return;
918
- }
919
- const fullEvent = {
920
- ...event,
921
- timestamp: Date.now()
922
- };
923
- logger.debug("Sending message:", fullEvent.type);
924
- if (transportRef.current === "sse") {
925
- if (!sseRef.current || sseRef.current.readyState !== EventSource.OPEN) {
926
- logger.debug("SSE not connected, attempting to connect");
927
- if (connectionState === "disconnected" && overrideUserId) {
928
- try {
929
- await connect(overrideUserId);
930
- } catch (error2) {
931
- reject(error2);
932
- return;
933
- }
934
- } else {
935
- reject(new Error("SSE not connected"));
936
- return;
937
- }
938
- }
939
- try {
940
- switch (fullEvent.type) {
941
- case "message":
942
- await sendRestMessage("send", {
943
- orgId: fullEvent.orgId || orgId,
944
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
945
- userId: fullEvent.userId || currentUserIdRef.current,
946
- message: fullEvent.message
947
- });
948
- break;
949
- case "typing":
950
- await sendRestMessage("typing", {
951
- orgId: fullEvent.orgId || orgId,
952
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
953
- userId: fullEvent.userId || currentUserIdRef.current,
954
- typing: true
955
- });
956
- break;
957
- case "stopped_typing":
958
- await sendRestMessage("typing", {
959
- orgId: fullEvent.orgId || orgId,
960
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
961
- userId: fullEvent.userId || currentUserIdRef.current,
962
- typing: false
963
- });
964
- break;
965
- case "load_chat":
966
- const loadResponse = await sendRestMessage("load", {
967
- orgId: fullEvent.orgId || orgId,
968
- chatKey: fullEvent.chatKey,
969
- userId: fullEvent.userId || currentUserIdRef.current
970
- });
971
- if (loadResponse.success && loadResponse.data?.chat) {
972
- currentChatKeyRef.current = loadResponse.data.chat.key;
973
- const chatEvent = {
974
- type: "load_chat_response",
975
- orgId: fullEvent.orgId,
976
- chatKey: loadResponse.data.chat.key,
977
- userId: fullEvent.userId,
978
- timestamp: Date.now(),
979
- data: loadResponse.data
980
- };
981
- emit("load_chat_response", chatEvent);
982
- if (onMessageRef.current) {
983
- onMessageRef.current(chatEvent);
984
- }
985
- }
986
- break;
987
- case "new_chat":
988
- const createResponse = await sendRestMessage("create", {
989
- orgId: fullEvent.orgId || orgId,
990
- userId: fullEvent.userId || currentUserIdRef.current,
991
- metadata: fullEvent.data
992
- });
993
- if (createResponse.success && createResponse.data?.chatKey) {
994
- currentChatKeyRef.current = createResponse.data.chatKey;
995
- const newChatEvent = {
996
- type: "new_chat_created",
997
- orgId: fullEvent.orgId,
998
- chatKey: createResponse.data.chatKey,
999
- userId: fullEvent.userId,
1000
- timestamp: Date.now(),
1001
- data: { chatKey: createResponse.data.chatKey }
1002
- };
1003
- emit("new_chat_created", newChatEvent);
1004
- if (onMessageRef.current) {
1005
- onMessageRef.current(newChatEvent);
1006
- }
1007
- if (chatCreationPromiseRef.current) {
1008
- chatCreationPromiseRef.current.resolve(createResponse.data.chatKey);
1009
- chatCreationPromiseRef.current = null;
1010
- }
1011
- }
1012
- break;
1013
- case "end_chat":
1014
- await sendRestMessage("end", {
1015
- orgId: fullEvent.orgId || orgId,
1016
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1017
- userId: fullEvent.userId || currentUserIdRef.current,
1018
- data: fullEvent.data
1019
- });
1020
- break;
1021
- case "human_agent_joined":
1022
- await sendRestMessage("agent-join", {
1023
- orgId: fullEvent.orgId || orgId,
1024
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1025
- user: fullEvent.data?.user
1026
- });
1027
- break;
1028
- case "human_agent_left":
1029
- await sendRestMessage("agent-leave", {
1030
- orgId: fullEvent.orgId || orgId,
1031
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1032
- user: fullEvent.data?.user
1033
- });
1034
- break;
1035
- // Event types that use the generic /event endpoint
1036
- case "load_agent_context":
1037
- case "skill_activate":
1038
- case "skill_deactivate":
1039
- case "sync_metadata":
1040
- case "sync_user_session":
1041
- case "csat_response":
1042
- case "plan_approved":
1043
- case "plan_rejected":
1044
- await sendRestMessage("event", {
1045
- type: fullEvent.type,
1046
- orgId: fullEvent.orgId || orgId,
1047
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1048
- userId: fullEvent.userId || currentUserIdRef.current,
1049
- data: fullEvent.data
1050
- });
1051
- break;
1052
- default:
1053
- logger.warn("Sending unrecognized event type via generic endpoint:", fullEvent.type);
1054
- await sendRestMessage("event", {
1055
- type: fullEvent.type,
1056
- orgId: fullEvent.orgId || orgId,
1057
- chatKey: fullEvent.chatKey || currentChatKeyRef.current,
1058
- userId: fullEvent.userId || currentUserIdRef.current,
1059
- data: fullEvent.data
1060
- });
1061
- break;
1062
- }
1063
- updateMetrics({ messagesSent: metrics.messagesSent + 1 });
1064
- logger.debug("SSE REST message sent successfully");
1065
- resolve();
1066
- } catch (error2) {
1067
- logger.error("Failed to send SSE REST message:", error2);
1068
- const sendError = {
1069
- code: "SEND_FAILED",
1070
- message: error2 instanceof Error ? error2.message : "Failed to send message",
1071
- retryable: true,
1072
- timestamp: Date.now()
1073
- };
1074
- setError(sendError);
1075
- updateMetrics({ lastError: sendError });
1076
- reject(sendError);
1077
- }
1078
- return;
1079
- }
1080
- if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
1081
- if (addToQueue(fullEvent)) {
1082
- logger.debug("Message queued, attempting to connect");
1083
- if (connectionState === "disconnected" && overrideUserId) {
1084
- connect(overrideUserId).then(() => resolve()).catch(reject);
1085
- } else {
1086
- resolve();
1087
- }
1088
- } else {
1089
- reject(new Error("Message queue full"));
1090
- }
1091
- return;
1092
- }
1093
- try {
1094
- wsRef.current.send(JSON.stringify(fullEvent));
1095
- updateMetrics({ messagesSent: metrics.messagesSent + 1 });
1096
- logger.debug("Message sent successfully");
1097
- resolve();
1098
- } catch (error2) {
1099
- logger.error("Failed to send message:", error2);
1100
- if (addToQueue(fullEvent)) {
1101
- resolve();
1102
- } else {
1103
- const sendError = {
1104
- code: "SEND_FAILED",
1105
- message: error2 instanceof Error ? error2.message : "Failed to send message",
1106
- retryable: true,
1107
- timestamp: Date.now()
1108
- };
1109
- setError(sendError);
1110
- updateMetrics({ lastError: sendError });
1111
- reject(sendError);
1112
- }
1113
- }
1114
- });
1115
- },
1116
- [connectionState, connect, addToQueue, logger, metrics, updateMetrics, sendRestMessage, emit, orgId]
1117
- );
1118
- const startNewChat = useCallback(
1119
- (userId, data) => {
1120
- return new Promise((resolve, reject) => {
1121
- if (!userId) {
1122
- reject(new Error("User ID is required"));
1123
- return;
1124
- }
1125
- logger.info("Requesting new chat from server with userId:", userId);
1126
- setStartTime(/* @__PURE__ */ new Date());
1127
- chatCreationPromiseRef.current = { resolve, reject };
1128
- sendMessage(
1129
- {
1130
- type: "new_chat",
1131
- orgId,
1132
- chatKey: "",
1133
- // Server will generate
1134
- userId,
1135
- data: data ?? {}
1136
- },
1137
- userId
1138
- ).catch((error2) => {
1139
- chatCreationPromiseRef.current = null;
1140
- reject(error2);
1141
- });
1142
- setTimeout(() => {
1143
- if (chatCreationPromiseRef.current) {
1144
- chatCreationPromiseRef.current = null;
1145
- reject(new Error("Chat creation timed out"));
1146
- }
1147
- }, 3e4);
1148
- });
1149
- },
1150
- [sendMessage, orgId, logger]
1151
- );
1152
- const disconnect = useCallback(
1153
- (intentional = true) => {
1154
- logger.info("Disconnecting", { intentional, transport: transportRef.current });
1155
- intentionalDisconnectRef.current = intentional;
1156
- cleanup();
1157
- setConnectionState("disconnected");
1158
- messageQueueRef.current = [];
1159
- retryCountRef.current = 0;
1160
- mountedRef.current = false;
1161
- currentChatKeyRef.current = void 0;
1162
- currentUserIdRef.current = void 0;
1163
- },
1164
- [cleanup, logger]
1165
- );
1166
- const clearError = useCallback(() => {
1167
- setError(void 0);
1168
- }, []);
1169
- useEffect(() => {
1170
- mountedRef.current = true;
1171
- return () => {
1172
- mountedRef.current = false;
1173
- disconnect(true);
1174
- };
1175
- }, []);
1176
- return {
1177
- connectionState,
1178
- isConnected,
1179
- sendMessage,
1180
- error,
1181
- connect,
1182
- startNewChat,
1183
- startTime,
1184
- disconnect,
1185
- metrics,
1186
- on,
1187
- off,
1188
- clearError
1189
- };
1190
- };
1191
-
1192
- // hooks/use-websocket-chat-admin.ts
1193
- var useWebSocketChatAdmin = ({
1194
- serverBaseUrl,
1195
- orgId,
1196
- product
1197
- }) => {
1198
- const [chats, setChats] = useState2(/* @__PURE__ */ new Map());
1199
- const [selectedChat, setSelectedChat] = useState2(void 0);
1200
- const handleMessage = useCallback2(
1201
- (chatEvent) => {
1202
- switch (chatEvent.type) {
1203
- case "message":
1204
- if (!selectedChat || !chatEvent.message) return;
1205
- setChats((prev) => {
1206
- const newMap = new Map(prev);
1207
- newMap.set(selectedChat.key, {
1208
- ...selectedChat,
1209
- messages: [...selectedChat.messages, chatEvent.message]
1210
- });
1211
- return newMap;
1212
- });
1213
- setSelectedChat((prev) => {
1214
- if (!prev) return prev;
1215
- return {
1216
- ...prev,
1217
- messages: [...prev.messages, chatEvent.message]
1218
- };
1219
- });
1220
- break;
1221
- case "list_chats":
1222
- const chatList = chatEvent.data?.chats;
1223
- if (!chatList) return;
1224
- setChats(new Map(chatList.map((chat) => [chat.key, chat])));
1225
- break;
1226
- case "chat_updated":
1227
- const updatedChat = chatEvent.data?.chat?.value;
1228
- if (updatedChat) {
1229
- setChats((prev) => new Map(prev).set(updatedChat.key, updatedChat));
1230
- }
1231
- break;
1232
- case "chat_removed":
1233
- const chatKey = chatEvent.data?.chatKey?.value;
1234
- if (chatKey) {
1235
- setChats((prev) => {
1236
- const newMap = new Map(prev);
1237
- newMap.delete(chatKey);
1238
- return newMap;
1239
- });
1240
- }
1241
- break;
1242
- case "load_chat":
1243
- const history = chatEvent.data?.chat?.value;
1244
- if (history) {
1245
- setChats((prev) => {
1246
- const existingChat = prev.get(history.key);
1247
- if (!existingChat) return prev;
1248
- return new Map(prev).set(history.key, {
1249
- ...existingChat,
1250
- messages: history.messages
1251
- });
1252
- });
1253
- }
1254
- break;
1255
- }
1256
- },
1257
- [selectedChat]
1258
- );
1259
- const base = useWebSocketChatBase({
1260
- serverBaseUrl,
1261
- orgId,
1262
- clientType: "humanAgent",
1263
- onMessage: handleMessage,
1264
- product
1265
- });
1266
- const selectChat = useCallback2(
1267
- (chatKey) => {
1268
- const chat = chats.get(chatKey);
1269
- if (!chat) {
1270
- console.error("Unable to select chat", chatKey);
1271
- return;
1272
- }
1273
- setSelectedChat(chat);
1274
- },
1275
- [chats, orgId, base.sendMessage]
1276
- );
1277
- const addUserToChat = useCallback2(
1278
- (orgId2, chatKey, user) => {
1279
- base.sendMessage({
1280
- type: "human_agent_joined",
1281
- orgId: orgId2,
1282
- chatKey,
1283
- userId: user.email,
1284
- data: { user }
1285
- });
1286
- setChats((prev) => {
1287
- const newMap = new Map(prev);
1288
- const chat = newMap.get(chatKey);
1289
- if (!chat) return prev;
1290
- newMap.set(chatKey, {
1291
- ...chat,
1292
- humanAgentEngaged: user.role === ChatRoleHumanAgent,
1293
- users: [...chat?.users ?? [], user]
1294
- });
1295
- return newMap;
1296
- });
1297
- setSelectedChat((prev) => {
1298
- if (!prev) return prev;
1299
- return {
1300
- ...prev,
1301
- humanAgentEngaged: user.role === ChatRoleHumanAgent,
1302
- users: [...prev.users ?? [], user]
1303
- };
1304
- });
1305
- },
1306
- [base.sendMessage]
1307
- );
1308
- const removeUserFromChat = useCallback2(
1309
- (orgId2, chatKey, userId) => {
1310
- const chat = chats.get(chatKey);
1311
- if (!chat) return;
1312
- const user = chat.users?.find((u) => u.id === userId);
1313
- if (!user) return;
1314
- base.sendMessage({
1315
- type: "human_agent_left",
1316
- orgId: orgId2,
1317
- chatKey,
1318
- userId,
1319
- data: { user }
1320
- });
1321
- setChats((prev) => {
1322
- const newMap = new Map(prev);
1323
- const chat2 = newMap.get(chatKey);
1324
- if (!chat2) return prev;
1325
- newMap.set(chatKey, {
1326
- ...chat2,
1327
- humanAgentEngaged: false,
1328
- users: chat2.users?.filter((u) => u.id !== userId)
1329
- });
1330
- return newMap;
1331
- });
1332
- },
1333
- [chats, base.sendMessage]
1334
- );
1335
- const blockUser = useCallback2(
1336
- (orgId2, chatKey, userId) => {
1337
- base.sendMessage({
1338
- type: "block_user",
1339
- orgId: orgId2,
1340
- chatKey,
1341
- userId
1342
- });
1343
- },
1344
- [base.sendMessage]
1345
- );
1346
- return {
1347
- ...base,
1348
- chats,
1349
- selectedChat,
1350
- selectChat,
1351
- addUserToChat,
1352
- removeUserFromChat,
1353
- blockUser
1354
- };
1355
- };
1356
-
1357
- // hooks/use-websocket-chat-customer.ts
1358
- import { useCallback as useCallback3, useState as useState3 } from "react";
1359
- var useWebSocketChatCustomer = ({
1360
- serverBaseUrl,
1361
- orgId,
1362
- chatKey,
1363
- product
1364
- }) => {
1365
- const [currentChat, setCurrentChat] = useState3(void 0);
1366
- const handleMessage = useCallback3((chatEvent) => {
1367
- console.log("Received event:", chatEvent.type);
1368
- switch (chatEvent.type) {
1369
- case "message":
1370
- if (!chatEvent.message) return;
1371
- console.log(
1372
- "got message:",
1373
- chatEvent.message.role,
1374
- ":",
1375
- chatEvent.message.content
1376
- );
1377
- setCurrentChat((prev) => {
1378
- if (!prev) return prev;
1379
- return {
1380
- ...prev,
1381
- messages: [...prev.messages, chatEvent.message]
1382
- };
1383
- });
1384
- break;
1385
- case "chat_updated":
1386
- const chat = chatEvent.data?.chat?.value;
1387
- if (chat) {
1388
- setCurrentChat(chat);
1389
- }
1390
- break;
1391
- case "load_chat":
1392
- const history = chatEvent.data?.chat;
1393
- if (!history) return;
1394
- setCurrentChat(history);
1395
- break;
1396
- default:
1397
- break;
1398
- }
1399
- }, []);
1400
- const base = useWebSocketChatBase({
1401
- serverBaseUrl,
1402
- orgId,
1403
- clientType: "customer",
1404
- onMessage: handleMessage,
1405
- product
1406
- });
1407
- return {
1408
- ...base,
1409
- chatKey,
1410
- title: currentChat?.title,
1411
- messages: currentChat?.messages ?? [],
1412
- users: currentChat?.users ?? [],
1413
- isWaiting: currentChat?.isWaiting ?? false,
1414
- isWaitingForAgent: currentChat?.isWaitingForAgent ?? false,
1415
- aiEngaged: currentChat?.aiEngaged ?? false,
1416
- humanAgentEngaged: currentChat?.humanAgentEngaged ?? false,
1417
- metadata: currentChat?.metadata ?? {},
1418
- status: currentChat?.status
1419
- };
1420
- };
1421
-
1422
- // context/websocket-chat-admin-context.tsx
1423
- import { jsx } from "react/jsx-runtime";
1424
- var WebSocketChatAdminContext = createContext(void 0);
1425
- function WebSocketChatAdminProvider(props) {
1426
- const { children, serverBaseUrl, orgId, userId, product } = props;
1427
- const webSocket = useWebSocketChatAdmin({
1428
- serverBaseUrl,
1429
- orgId,
1430
- product
1431
- });
1432
- return /* @__PURE__ */ jsx(WebSocketChatAdminContext.Provider, { value: webSocket, children });
1433
- }
1434
- var useWebSocketChatAdminContext = () => {
1435
- const context = useContext(WebSocketChatAdminContext);
1436
- if (context === void 0) {
1437
- throw new Error(
1438
- "useWebSocketChatAdminContext must be used within a WebSocketChatAdminProvider"
1439
- );
1440
- }
1441
- return context;
1442
- };
1443
-
1444
- // components/admin/admin-chat-input.tsx
1445
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1446
- var AdminChatInput = ({
1447
- userId,
1448
- orgId,
1449
- onFileUpload,
1450
- chatKey
1451
- }) => {
1452
- const { sendMessage } = useWebSocketChatAdminContext();
1453
- const [input, setInput] = useState4("");
1454
- const [isUploading, setIsUploading] = useState4(false);
1455
- const [isSending, setIsSending] = useState4(false);
1456
- const fileInputRef = useRef2(null);
1457
- const MAX_FILE_SIZE = 5 * 1024 * 1024;
1458
- const handleSend = async () => {
1459
- if (!input.trim() && !isUploading || isSending) return;
1460
- if (!chatKey || !orgId || !userId) {
1461
- console.error("chatKey, orgId, or userId is not defined");
1462
- return;
1463
- }
1464
- try {
1465
- setIsSending(true);
1466
- sendMessage({
1467
- type: "message",
1468
- chatKey,
1469
- orgId,
1470
- userId,
1471
- message: {
1472
- id: KSUID.randomSync().string,
1473
- content: input,
1474
- role: "user",
1475
- senderId: userId,
1476
- time: Date.now(),
1477
- status: "sending",
1478
- createdAt: Date.now()
1479
- }
1480
- });
1481
- setInput("");
1482
- } finally {
1483
- setIsSending(false);
1484
- }
1485
- };
1486
- const handleFileUpload = async (event) => {
1487
- const files = event.target.files;
1488
- if (!files || !onFileUpload) return;
1489
- try {
1490
- setIsUploading(true);
1491
- const uploadedUrls = await Promise.all(
1492
- Array.from(files).map(async (file) => {
1493
- if (file.size > MAX_FILE_SIZE) {
1494
- throw new Error(
1495
- `File ${file.name} is too large. Maximum size is 5MB.`
1496
- );
1497
- }
1498
- const url = await onFileUpload(file);
1499
- return {
1500
- type: file.type,
1501
- url,
1502
- name: file.name
1503
- };
1504
- })
1505
- );
1506
- const attachments = uploadedUrls.map((file) => ({
1507
- type: "image",
1508
- url: file.url,
1509
- title: file.name
1510
- }));
1511
- if (!chatKey || !orgId || !userId) {
1512
- console.error("chatKey, orgId, or userId is not defined");
1513
- return;
1514
- }
1515
- } finally {
1516
- setIsUploading(false);
1517
- if (fileInputRef.current) {
1518
- fileInputRef.current.value = "";
1519
- }
1520
- }
1521
- };
1522
- return /* @__PURE__ */ jsxs("div", { className: "p-4 border-t bg-white rounded-b-lg", children: [
1523
- /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
1524
- /* @__PURE__ */ jsx2(
1525
- "input",
1526
- {
1527
- type: "file",
1528
- ref: fileInputRef,
1529
- onChange: handleFileUpload,
1530
- className: "hidden",
1531
- multiple: true,
1532
- accept: "image/*,.pdf,.doc,.docx,.txt"
1533
- }
1534
- ),
1535
- /* @__PURE__ */ jsx2(
1536
- "button",
1537
- {
1538
- onClick: () => fileInputRef.current?.click(),
1539
- className: "p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100 transition-colors",
1540
- disabled: isUploading,
1541
- "aria-label": "Attach file",
1542
- children: isUploading ? /* @__PURE__ */ jsx2("span", { className: "w-5 h-5 animate-spin rounded-full border-2 border-gray-300 border-t-gray-600" }) : /* @__PURE__ */ jsx2(Paperclip, { className: "w-5 h-5" })
1543
- }
1544
- ),
1545
- /* @__PURE__ */ jsx2("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsx2(
1546
- "textarea",
1547
- {
1548
- value: input,
1549
- onChange: (e) => setInput(e.target.value),
1550
- onKeyDown: (e) => {
1551
- if (e.key === "Enter" && !e.shiftKey) {
1552
- e.preventDefault();
1553
- handleSend();
1554
- }
1555
- },
1556
- className: "w-full p-3 border rounded-2xl focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none",
1557
- placeholder: "Type a message...",
1558
- rows: 1,
1559
- style: {
1560
- minHeight: "44px",
1561
- maxHeight: "200px"
1562
- }
1563
- }
1564
- ) }),
1565
- /* @__PURE__ */ jsx2(
1566
- "button",
1567
- {
1568
- onClick: handleSend,
1569
- disabled: !input.trim() && !isUploading || isSending,
1570
- className: `p-2 rounded-full transition-colors ${!input.trim() && !isUploading || isSending ? "text-gray-400 bg-gray-100" : "text-white bg-blue-500 hover:bg-blue-600"}`,
1571
- "aria-label": "Send message",
1572
- children: isSending ? /* @__PURE__ */ jsx2("span", { className: "w-5 h-5 animate-spin rounded-full border-2 border-gray-300 border-t-white" }) : /* @__PURE__ */ jsx2(Send, { className: "w-5 h-5" })
1573
- }
1574
- )
1575
- ] }),
1576
- isUploading && /* @__PURE__ */ jsx2("div", { className: "mt-2", children: /* @__PURE__ */ jsx2("div", { className: "w-full bg-gray-200 rounded-full h-1", children: /* @__PURE__ */ jsx2("div", { className: "bg-blue-500 h-1 rounded-full animate-pulse" }) }) })
1577
- ] });
1578
- };
1579
-
1580
- // components/admin/chat-human-agent-actions.tsx
1581
- import { LogOut, Phone, Plus, User as UserIcon, XCircle } from "lucide-react";
1582
-
1583
- // context/websocket-chat-customer-context.tsx
1584
- import { createContext as createContext2, useContext as useContext2 } from "react";
1585
- import { jsx as jsx3 } from "react/jsx-runtime";
1586
- var WebSocketChatCustomerContext = createContext2(void 0);
1587
- function WebSocketChatCustomerProvider(props) {
1588
- const { children, serverBaseUrl, orgId, userId, chatKey, product } = props;
1589
- const webSocket = useWebSocketChatCustomer({
1590
- serverBaseUrl,
1591
- orgId,
1592
- chatKey,
1593
- product
1594
- });
1595
- return /* @__PURE__ */ jsx3(WebSocketChatCustomerContext.Provider, { value: webSocket, children });
1596
- }
1597
- var useWebSocketChatCustomerContext = () => {
1598
- const context = useContext2(WebSocketChatCustomerContext);
1599
- if (context === void 0) {
1600
- throw new Error(
1601
- "useWebSocketChatCustomerContext must be used within a WebSocketChatCustomerProvider"
1602
- );
1603
- }
1604
- return context;
1605
- };
1606
-
1607
- // components/admin/chat-human-agent-actions.tsx
1608
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1609
- var ChatHumanAgentActions = ({ user, orgId }) => {
1610
- const { selectedChat, addUserToChat, removeUserFromChat, blockUser } = useWebSocketChatAdminContext();
1611
- const handleJoinChat = () => {
1612
- if (!selectedChat || !user || !orgId) return;
1613
- addUserToChat(orgId, selectedChat.key, {
1614
- id: user.id,
1615
- role: "humanAgent",
1616
- name: [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email,
1617
- email: user.email,
1618
- authProvider: "tbd",
1619
- authToken: "tbd"
1620
- });
1621
- };
1622
- const handleLeaveChat = () => {
1623
- if (!selectedChat || !user || !orgId) return;
1624
- removeUserFromChat(orgId, selectedChat.key, user.id);
1625
- };
1626
- const handleEndChat = () => {
1627
- if (!selectedChat || !user || !orgId) return;
1628
- };
1629
- const handleStartCall = () => {
1630
- if (!selectedChat || !user || !orgId) return;
1631
- };
1632
- const handleCreateCase = () => {
1633
- if (!selectedChat || !user || !orgId) return;
1634
- };
1635
- const handleBlockUser = () => {
1636
- if (!selectedChat || !user || !orgId) return;
1637
- blockUser(orgId, selectedChat.key, user.id);
1638
- };
1639
- if (!selectedChat) return null;
1640
- return /* @__PURE__ */ jsxs2("div", { className: "p-4 border-b bg-white flex items-center justify-between", children: [
1641
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center space-x-4", children: [
1642
- /* @__PURE__ */ jsx4("h2", { className: "font-semibold text-lg", children: selectedChat.title || `Chat ${selectedChat.key}` }),
1643
- selectedChat.humanAgentEngaged && /* @__PURE__ */ jsxs2("span", { className: "inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-sm font-medium text-green-800", children: [
1644
- /* @__PURE__ */ jsx4(UserIcon, { className: "mr-1 h-4 w-4" }),
1645
- "Agent Engaged"
1646
- ] })
1647
- ] }),
1648
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center space-x-2", children: [
1649
- !selectedChat.humanAgentEngaged ? /* @__PURE__ */ jsxs2(
1650
- "button",
1651
- {
1652
- onClick: handleJoinChat,
1653
- className: "bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors",
1654
- children: [
1655
- /* @__PURE__ */ jsx4(UserIcon, { className: "h-4 w-4" }),
1656
- /* @__PURE__ */ jsx4("span", { children: "Join Chat" })
1657
- ]
1658
- }
1659
- ) : /* @__PURE__ */ jsxs2(
1660
- "button",
1661
- {
1662
- onClick: handleLeaveChat,
1663
- className: "bg-orange-500 hover:bg-orange-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors",
1664
- children: [
1665
- /* @__PURE__ */ jsx4(LogOut, { className: "h-4 w-4" }),
1666
- /* @__PURE__ */ jsx4("span", { children: "Leave Chat" })
1667
- ]
1668
- }
1669
- ),
1670
- /* @__PURE__ */ jsxs2(
1671
- "button",
1672
- {
1673
- onClick: handleEndChat,
1674
- className: "bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors",
1675
- children: [
1676
- /* @__PURE__ */ jsx4(XCircle, { className: "h-4 w-4" }),
1677
- /* @__PURE__ */ jsx4("span", { children: "End Chat" })
1678
- ]
1679
- }
1680
- ),
1681
- /* @__PURE__ */ jsxs2(
1682
- "button",
1683
- {
1684
- onClick: handleBlockUser,
1685
- className: "bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors",
1686
- children: [
1687
- /* @__PURE__ */ jsx4(XCircle, { className: "h-4 w-4" }),
1688
- /* @__PURE__ */ jsx4("span", { children: "Block User" })
1689
- ]
1690
- }
1691
- ),
1692
- /* @__PURE__ */ jsxs2(
1693
- "button",
1694
- {
1695
- onClick: handleStartCall,
1696
- disabled: !selectedChat.humanAgentEngaged,
1697
- className: "bg-green-500 hover:bg-green-600 disabled:bg-gray-300 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors",
1698
- children: [
1699
- /* @__PURE__ */ jsx4(Phone, { className: "h-4 w-4" }),
1700
- /* @__PURE__ */ jsx4("span", { children: "Start Call" })
1701
- ]
1702
- }
1703
- ),
1704
- /* @__PURE__ */ jsxs2(
1705
- "button",
1706
- {
1707
- onClick: handleCreateCase,
1708
- className: "bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg flex items-center space-x-2 transition-colors",
1709
- children: [
1710
- /* @__PURE__ */ jsx4(Plus, { className: "h-4 w-4" }),
1711
- /* @__PURE__ */ jsx4("span", { children: "Create Case" })
1712
- ]
1713
- }
1714
- )
1715
- ] })
1716
- ] });
1717
- };
1718
-
1719
- // components/admin/admin-chat-list.tsx
1720
- import { AlertCircle, Bot, Loader2, UserCheck } from "lucide-react";
1721
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1722
- var ScrollArea = ({ className, children }) => /* @__PURE__ */ jsx5("div", { className: `overflow-auto ${className || ""}`, children });
1723
- var AdminChatList = ({ className }) => {
1724
- const { chats, selectedChat, selectChat } = useWebSocketChatAdminContext();
1725
- const sortedChats = Array.from(chats.values()).sort(
1726
- (a, b) => b.lastUpdated - a.lastUpdated
1727
- );
1728
- return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col h-full", children: [
1729
- /* @__PURE__ */ jsx5("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsx5("h2", { className: "font-semibold text-lg", children: "Active Chats" }) }),
1730
- /* @__PURE__ */ jsx5("div", { className: "flex-1 overflow-y-auto", children: sortedChats.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "p-4 text-center text-gray-500", children: "No active chats" }) : /* @__PURE__ */ jsx5("div", { className: "divide-y", children: /* @__PURE__ */ jsx5(ScrollArea, { className: "h-[calc(100vh-15rem)]", children: sortedChats.map((chat, index) => /* @__PURE__ */ jsx5(
1731
- AdminChatListItem,
1732
- {
1733
- chat,
1734
- isSelected: chat.key === selectedChat?.key,
1735
- onSelect: () => selectChat(chat.key)
1736
- },
1737
- index
1738
- )) }) }) })
1739
- ] });
1740
- };
1741
- var AdminChatListItem = ({
1742
- chat,
1743
- isSelected,
1744
- onSelect
1745
- }) => {
1746
- const lastMessage = chat.messages[chat.messages.length - 1];
1747
- return /* @__PURE__ */ jsx5(
1748
- "button",
1749
- {
1750
- onClick: onSelect,
1751
- className: `w-full p-4 text-left hover:bg-gray-50 focus:outline-none focus:bg-gray-50 ${isSelected ? "bg-blue-50" : ""}`,
1752
- children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start space-x-3", children: [
1753
- /* @__PURE__ */ jsxs3("div", { className: "shrink-0", children: [
1754
- chat.aiEngaged && /* @__PURE__ */ jsx5(Bot, { className: "w-6 h-6 text-blue-500" }),
1755
- chat.isWaitingForAgent && /* @__PURE__ */ jsx5(Loader2, { className: "w-6 h-6 text-blue-500 animate-spin text-orange-500" }),
1756
- chat.humanAgentEngaged && /* @__PURE__ */ jsx5(UserCheck, { className: "w-6 h-6 text-blue-500 text-green-500" })
1757
- ] }),
1758
- /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
1759
- /* @__PURE__ */ jsx5("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium text-gray-900 truncate", children: chat.title || `Chat ${chat.key}` }) }),
1760
- lastMessage && /* @__PURE__ */ jsx5("p", { className: "mt-1 text-sm text-gray-500 truncate", children: lastMessage.content }),
1761
- /* @__PURE__ */ jsxs3("div", { className: "mt-1 flex items-center space-x-2", children: [
1762
- chat.isWaiting && /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800", children: [
1763
- /* @__PURE__ */ jsx5(AlertCircle, { className: "mr-1 h-3 w-3" }),
1764
- "Waiting"
1765
- ] }),
1766
- chat.humanAgentEngaged && /* @__PURE__ */ jsxs3("span", { className: "inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800", children: [
1767
- "Agent Engaged:",
1768
- " ",
1769
- chat.users?.find((u) => u.role === ChatRoleHumanAgent)?.name
1770
- ] }),
1771
- chat.isWaitingForAgent && /* @__PURE__ */ jsx5("span", { className: "inline-flex items-center rounded-full bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-800", children: "Waiting For Agent" })
1772
- ] })
1773
- ] })
1774
- ] })
1775
- }
1776
- );
1777
- };
1778
-
1779
- // components/admin/admin-chat-header.tsx
1780
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1781
- var AdminChatHeader = ({
1782
- className
1783
- }) => {
1784
- const { isConnected, chats } = useWebSocketChatAdminContext();
1785
- return /* @__PURE__ */ jsx6("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-center space-x-2", children: [
1786
- /* @__PURE__ */ jsx6(
1787
- "div",
1788
- {
1789
- className: `w-2 h-2 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}`
1790
- }
1791
- ),
1792
- /* @__PURE__ */ jsxs4("span", { className: "text-sm text-gray-600", children: [
1793
- "Active Chats: ",
1794
- /* @__PURE__ */ jsx6("span", { className: "font-bold", children: chats.size })
1795
- ] }),
1796
- /* @__PURE__ */ jsx6("span", { className: "text-sm text-gray-500", children: "|" }),
1797
- /* @__PURE__ */ jsxs4("span", { className: "text-sm text-gray-600", children: [
1798
- "Waiting For Agent:",
1799
- " ",
1800
- /* @__PURE__ */ jsx6("span", { className: "font-bold", children: Array.from(chats.values()).filter(
1801
- (chat) => chat.isWaitingForAgent
1802
- ).length })
1803
- ] })
1804
- ] }) });
1805
- };
1806
-
1807
- // components/customer/chat-status-customer-ui.tsx
1808
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1809
- var ChatStatusCustomerUI = ({ isConnected }) => {
1810
- return /* @__PURE__ */ jsxs5("div", { className: "flex items-center space-x-2", children: [
1811
- /* @__PURE__ */ jsx7(
1812
- "span",
1813
- {
1814
- className: `w-3 h-3 rounded-full ${isConnected ? "bg-green-500" : "bg-red-500"}`,
1815
- role: "status",
1816
- "aria-label": isConnected ? "Connected" : "Disconnected"
1817
- }
1818
- ),
1819
- /* @__PURE__ */ jsx7("span", { className: "text-sm text-gray-500", children: isConnected ? "Connected" : "Reconnecting..." })
1820
- ] });
1821
- };
1822
-
1823
- // components/customer/chat-header.tsx
1824
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1825
- var ChatHeader = () => {
1826
- const { isConnected, title } = useWebSocketChatCustomerContext();
1827
- return /* @__PURE__ */ jsx8("div", { className: "p-4 border-b bg-white rounded-t-lg", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
1828
- /* @__PURE__ */ jsx8("h2", { className: "font-bold text-lg", children: title || "Chat" }),
1829
- /* @__PURE__ */ jsx8(ChatStatusCustomerUI, { isConnected })
1830
- ] }) });
1831
- };
1832
-
1833
- // components/customer/chat-input.tsx
1834
- import KSUID2 from "ksuid";
1835
- import { Paperclip as Paperclip2, Send as Send2 } from "lucide-react";
1836
- import { useRef as useRef3, useState as useState5 } from "react";
1837
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1838
- var ChatInput = ({ userId, orgId, onFileUpload }) => {
1839
- const { sendMessage, chatKey } = useWebSocketChatCustomerContext();
1840
- const [input, setInput] = useState5("");
1841
- const [isUploading, setIsUploading] = useState5(false);
1842
- const [isSending, setIsSending] = useState5(false);
1843
- const fileInputRef = useRef3(null);
1844
- const MAX_FILE_SIZE = 5 * 1024 * 1024;
1845
- const handleSend = async () => {
1846
- if (!input.trim() && !isUploading || isSending) return;
1847
- if (!chatKey || !orgId || !userId) {
1848
- console.error("chatKey, orgId, or userId is not defined");
1849
- return;
1850
- }
1851
- try {
1852
- setIsSending(true);
1853
- sendMessage({
1854
- type: "message",
1855
- chatKey,
1856
- orgId,
1857
- userId,
1858
- message: {
1859
- id: KSUID2.randomSync().string,
1860
- content: input,
1861
- role: "user",
1862
- senderId: userId,
1863
- time: Date.now(),
1864
- status: "sending",
1865
- createdAt: Date.now()
1866
- }
1867
- });
1868
- setInput("");
1869
- } finally {
1870
- setIsSending(false);
1871
- }
1872
- };
1873
- const handleFileUpload = async (event) => {
1874
- const files = event.target.files;
1875
- if (!files || !onFileUpload) return;
1876
- try {
1877
- setIsUploading(true);
1878
- const uploadedUrls = await Promise.all(
1879
- Array.from(files).map(async (file) => {
1880
- if (file.size > MAX_FILE_SIZE) {
1881
- throw new Error(
1882
- `File ${file.name} is too large. Maximum size is 5MB.`
1883
- );
1884
- }
1885
- const url = await onFileUpload(file);
1886
- return {
1887
- type: file.type,
1888
- url,
1889
- name: file.name
1890
- };
1891
- })
1892
- );
1893
- const attachments = uploadedUrls.map((file) => ({
1894
- type: "image",
1895
- url: file.url,
1896
- title: file.name
1897
- }));
1898
- if (!chatKey || !orgId || !userId) {
1899
- console.error("chatKey, orgId, or userId is not defined");
1900
- return;
1901
- }
1902
- } finally {
1903
- setIsUploading(false);
1904
- if (fileInputRef.current) {
1905
- fileInputRef.current.value = "";
1906
- }
1907
- }
1908
- };
1909
- return /* @__PURE__ */ jsxs7("div", { className: "p-4 border-t bg-white rounded-b-lg", children: [
1910
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center space-x-2", children: [
1911
- /* @__PURE__ */ jsx9(
1912
- "input",
1913
- {
1914
- type: "file",
1915
- ref: fileInputRef,
1916
- onChange: handleFileUpload,
1917
- className: "hidden",
1918
- multiple: true,
1919
- accept: "image/*,.pdf,.doc,.docx,.txt"
1920
- }
1921
- ),
1922
- /* @__PURE__ */ jsx9(
1923
- "button",
1924
- {
1925
- onClick: () => fileInputRef.current?.click(),
1926
- className: "p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100 transition-colors",
1927
- disabled: isUploading,
1928
- "aria-label": "Attach file",
1929
- children: isUploading ? /* @__PURE__ */ jsx9("span", { className: "w-5 h-5 animate-spin rounded-full border-2 border-gray-300 border-t-gray-600" }) : /* @__PURE__ */ jsx9(Paperclip2, { className: "w-5 h-5" })
1930
- }
1931
- ),
1932
- /* @__PURE__ */ jsx9("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsx9(
1933
- "textarea",
1934
- {
1935
- value: input,
1936
- onChange: (e) => setInput(e.target.value),
1937
- onKeyDown: (e) => {
1938
- if (e.key === "Enter" && !e.shiftKey) {
1939
- e.preventDefault();
1940
- handleSend();
1941
- }
1942
- },
1943
- className: "w-full p-3 border rounded-2xl focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none",
1944
- placeholder: "Type a message...",
1945
- rows: 1,
1946
- style: {
1947
- minHeight: "44px",
1948
- maxHeight: "200px"
1949
- }
1950
- }
1951
- ) }),
1952
- /* @__PURE__ */ jsx9(
1953
- "button",
1954
- {
1955
- onClick: handleSend,
1956
- disabled: !input.trim() && !isUploading || isSending,
1957
- className: `p-2 rounded-full transition-colors ${!input.trim() && !isUploading || isSending ? "text-gray-400 bg-gray-100" : "text-white bg-blue-500 hover:bg-blue-600"}`,
1958
- "aria-label": "Send message",
1959
- children: isSending ? /* @__PURE__ */ jsx9("span", { className: "w-5 h-5 animate-spin rounded-full border-2 border-gray-300 border-t-white" }) : /* @__PURE__ */ jsx9(Send2, { className: "w-5 h-5" })
1960
- }
1961
- )
1962
- ] }),
1963
- isUploading && /* @__PURE__ */ jsx9("div", { className: "mt-2", children: /* @__PURE__ */ jsx9("div", { className: "w-full bg-gray-200 rounded-full h-1", children: /* @__PURE__ */ jsx9("div", { className: "bg-blue-500 h-1 rounded-full animate-pulse" }) }) })
1964
- ] });
1965
- };
1966
-
1967
- // components/customer/chat-messages.tsx
1968
- import { useEffect as useEffect2, useRef as useRef4 } from "react";
1969
-
1970
- // components/customer/chat-message.tsx
1971
- import { format } from "date-fns";
1972
- import {
1973
- AlertCircle as AlertCircle2,
1974
- Bot as Bot2,
1975
- Check,
1976
- CheckCheck,
1977
- Paperclip as Paperclip3,
1978
- User,
1979
- UserCog
1980
- } from "lucide-react";
1981
-
1982
- // components/markdown-renderer.tsx
1983
- import ReactMarkdown from "react-markdown";
1984
- import remarkGfm from "remark-gfm";
1985
- import { jsx as jsx10 } from "react/jsx-runtime";
1986
- function MarkdownRenderer({ content }) {
1987
- return /* @__PURE__ */ jsx10(
1988
- ReactMarkdown,
1989
- {
1990
- remarkPlugins: [remarkGfm],
1991
- components: {
1992
- a: ({ node, ...props }) => /* @__PURE__ */ jsx10(
1993
- "a",
1994
- {
1995
- target: "_blank",
1996
- rel: "noopener noreferrer",
1997
- className: "text-blue-600 hover:underline",
1998
- ...props
1999
- }
2000
- ),
2001
- h1: ({ node, ...props }) => /* @__PURE__ */ jsx10("h1", { className: "text-2xl font-bold mt-6 mb-4", ...props }),
2002
- h2: ({ node, ...props }) => /* @__PURE__ */ jsx10("h2", { className: "text-xl font-semibold mt-5 mb-3", ...props }),
2003
- h3: ({ node, ...props }) => /* @__PURE__ */ jsx10("h3", { className: "text-lg font-semibold mt-4 mb-2", ...props }),
2004
- code: ({ node, ...props }) => /* @__PURE__ */ jsx10(
2005
- "code",
2006
- {
2007
- className: "block bg-gray-100 p-3 rounded font-mono",
2008
- ...props
2009
- }
2010
- ),
2011
- ul: ({ node, ...props }) => /* @__PURE__ */ jsx10("ul", { className: "list-disc pl-6 my-4", ...props }),
2012
- ol: ({ node, ...props }) => /* @__PURE__ */ jsx10("ol", { className: "list-decimal pl-6 my-4", ...props }),
2013
- p: ({ node, ...props }) => /* @__PURE__ */ jsx10("p", { className: "my-4", ...props }),
2014
- blockquote: ({ node, ...props }) => /* @__PURE__ */ jsx10(
2015
- "blockquote",
2016
- {
2017
- className: "border-l-4 border-gray-200 pl-4 my-4 italic",
2018
- ...props
2019
- }
2020
- )
2021
- },
2022
- children: content
2023
- }
2024
- );
2025
- }
2026
-
2027
- // components/customer/chat-message.tsx
2028
- import { Fragment, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
2029
- var Message = ({
2030
- message,
2031
- isConsecutive,
2032
- onRetry
2033
- }) => {
2034
- const renderAttachment = (attachment, index) => /* @__PURE__ */ jsx11("div", { className: "mt-2", children: attachment.type === "image" ? /* @__PURE__ */ jsx11(
2035
- "img",
2036
- {
2037
- src: attachment.url,
2038
- alt: attachment.title,
2039
- className: "max-w-full rounded",
2040
- loading: "lazy"
2041
- }
2042
- ) : /* @__PURE__ */ jsxs8(
2043
- "a",
2044
- {
2045
- href: attachment.url,
2046
- className: "flex items-center space-x-2 text-sm hover:underline",
2047
- target: "_blank",
2048
- rel: "noopener noreferrer",
2049
- children: [
2050
- /* @__PURE__ */ jsx11(Paperclip3, { className: "w-4 h-4" }),
2051
- /* @__PURE__ */ jsx11("span", { children: attachment.title })
2052
- ]
2053
- }
2054
- ) }, index);
2055
- return /* @__PURE__ */ jsxs8(
2056
- "div",
2057
- {
2058
- className: "flex justify-start gap-2",
2059
- "data-message-id": message.id,
2060
- role: "article",
2061
- "aria-label": `Message from ${message.senderId}`,
2062
- children: [
2063
- /* @__PURE__ */ jsx11("div", { className: "flex pt-10 w-10", children: /* @__PURE__ */ jsxs8("div", { className: "w-8 h-8 rounded-full flex justify-center", children: [
2064
- message.role === "user" && /* @__PURE__ */ jsx11(User, {}),
2065
- message.role === "ai" && /* @__PURE__ */ jsx11(Bot2, {}),
2066
- message.role === "humanAgent" && /* @__PURE__ */ jsx11(UserCog, {})
2067
- ] }) }),
2068
- /* @__PURE__ */ jsxs8(
2069
- "div",
2070
- {
2071
- className: `p-3 rounded-lg max-w-[80%] bg-gray-100 text-primary-900 ${isConsecutive ? "mt-1" : "mt-4"}`,
2072
- children: [
2073
- /* @__PURE__ */ jsx11("div", { className: "break-words", children: /* @__PURE__ */ jsx11(MarkdownRenderer, { content: message.content }) }),
2074
- message.attachments?.map(
2075
- (attachment, i) => renderAttachment(attachment, i)
2076
- ),
2077
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-end space-x-1 mt-1", children: [
2078
- /* @__PURE__ */ jsx11("span", { className: "text-xs opacity-70", children: format(message.time, "HH:mm") }),
2079
- message.role === "user" && /* @__PURE__ */ jsxs8(Fragment, { children: [
2080
- message.status === "sending" && /* @__PURE__ */ jsx11("span", { className: "w-4 h-4 animate-spin rounded-full border-2 border-gray-300 border-t-white" }),
2081
- message.status === "sent" && /* @__PURE__ */ jsx11(Check, { className: "w-4 h-4" }),
2082
- message.status === "delivered" && /* @__PURE__ */ jsx11(CheckCheck, { className: "w-4 h-4" }),
2083
- message.status === "failed" && /* @__PURE__ */ jsx11(
2084
- "button",
2085
- {
2086
- onClick: () => onRetry(message.id),
2087
- className: "text-red-300 hover:text-red-400",
2088
- "aria-label": "Retry sending message",
2089
- children: /* @__PURE__ */ jsx11(AlertCircle2, { className: "w-4 h-4" })
2090
- }
2091
- )
2092
- ] })
2093
- ] })
2094
- ]
2095
- }
2096
- )
2097
- ]
2098
- }
2099
- );
2100
- };
2101
-
2102
- // components/customer/chat-messages.tsx
2103
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2104
- var ChatMessages = ({
2105
- onRetry,
2106
- messages
2107
- }) => {
2108
- const messageEndRef = useRef4(null);
2109
- const scrollToBottom = () => {
2110
- messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
2111
- };
2112
- useEffect2(() => {
2113
- scrollToBottom();
2114
- }, [messages]);
2115
- return /* @__PURE__ */ jsxs9(
2116
- "div",
2117
- {
2118
- className: "flex-1 overflow-y-auto p-4 space-y-4 bg-white min-h-[calc(100vh-290px)]",
2119
- role: "log",
2120
- "aria-label": "Chat messages",
2121
- children: [
2122
- messages.filter(
2123
- (msg) => (!msg.toolCalls?.length || msg.toolCalls.length === 0) && !msg.toolCallId
2124
- ).map((msg, index) => {
2125
- const isConsecutive = index > 0 && messages[index - 1].senderId === msg.senderId && msg.time - messages[index - 1].time < 3e5;
2126
- return /* @__PURE__ */ jsx12(
2127
- Message,
2128
- {
2129
- message: msg,
2130
- isConsecutive,
2131
- onRetry
2132
- },
2133
- msg.id
2134
- );
2135
- }),
2136
- /* @__PURE__ */ jsx12("div", { ref: messageEndRef })
2137
- ]
2138
- }
2139
- );
2140
- };
2141
-
2142
- // components/customer/chat-typing-indicator.tsx
2143
- import { jsxs as jsxs10 } from "react/jsx-runtime";
2144
- var ChatTypingIndicator = ({
2145
- typingUsers
2146
- }) => {
2147
- if (typingUsers.size === 0) return null;
2148
- return /* @__PURE__ */ jsxs10("div", { className: "px-4 py-2 text-sm text-gray-500 italic", role: "status", children: [
2149
- Array.from(typingUsers).join(", "),
2150
- " typing..."
2151
- ] });
2152
- };
2153
-
2154
- // components/customer/generic-chat-widget.tsx
2155
- import { useState as useState6 } from "react";
2156
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2157
- var Alert = ({ title, description }) => /* @__PURE__ */ jsxs11("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", role: "alert", children: [
2158
- /* @__PURE__ */ jsx13("p", { className: "font-medium text-red-800", children: title }),
2159
- /* @__PURE__ */ jsx13("p", { className: "text-sm text-red-600", children: description })
2160
- ] });
2161
- var GenericChatWidget = ({
2162
- onFileUpload
2163
- }) => {
2164
- const [typingUsers] = useState6(/* @__PURE__ */ new Set());
2165
- const { error, messages } = useWebSocketChatCustomerContext();
2166
- const handleRetry = (messageId) => {
2167
- };
2168
- return /* @__PURE__ */ jsxs11(
2169
- "div",
2170
- {
2171
- className: "flex flex-col w-full max-w-lg border rounded-lg shadow-lg",
2172
- role: "region",
2173
- "aria-label": "Chat interface",
2174
- children: [
2175
- /* @__PURE__ */ jsx13(ChatHeader, {}),
2176
- error && /* @__PURE__ */ jsx13(Alert, { title: "Error", description: error.message }),
2177
- /* @__PURE__ */ jsx13(ChatMessages, { onRetry: handleRetry, messages }),
2178
- /* @__PURE__ */ jsx13(ChatTypingIndicator, { typingUsers }),
2179
- /* @__PURE__ */ jsx13(ChatInput, { onFileUpload })
2180
- ]
2181
- }
2182
- );
2183
- };
2184
870
  export {
2185
- AdminChatHeader,
2186
- AdminChatInput,
2187
- AdminChatList,
2188
- AdminChatListItem,
2189
871
  AgentStatusAway,
2190
872
  AgentStatusBusy,
2191
873
  AgentStatusOffline,
@@ -2196,6 +878,7 @@ export {
2196
878
  AttachmentTypeAudio,
2197
879
  AttachmentTypeBullets,
2198
880
  AttachmentTypeData,
881
+ AttachmentTypeDataFile,
2199
882
  AttachmentTypeDocument,
2200
883
  AttachmentTypeDocumentAnalysis,
2201
884
  AttachmentTypeDocumentSources,
@@ -2204,6 +887,7 @@ export {
2204
887
  AttachmentTypeLocation,
2205
888
  AttachmentTypeRecords,
2206
889
  AttachmentTypeReferences,
890
+ AttachmentTypeSpreadsheet,
2207
891
  AttachmentTypeSticker,
2208
892
  AttachmentTypeSubsections,
2209
893
  AttachmentTypeVideo,
@@ -2217,6 +901,10 @@ export {
2217
901
  ChatEventTypeAgentSessionEnd,
2218
902
  ChatEventTypeAgentSessionStart,
2219
903
  ChatEventTypeAgentStatusChange,
904
+ ChatEventTypeAttachmentProcessingComplete,
905
+ ChatEventTypeAttachmentProcessingError,
906
+ ChatEventTypeAttachmentProcessingProgress,
907
+ ChatEventTypeAttachmentProcessingStarted,
2220
908
  ChatEventTypeBlockUser,
2221
909
  ChatEventTypeCSATRequest,
2222
910
  ChatEventTypeCSATResponse,
@@ -2268,6 +956,7 @@ export {
2268
956
  ChatEventTypePong,
2269
957
  ChatEventTypeRead,
2270
958
  ChatEventTypeReconnected,
959
+ ChatEventTypeRetryAttachment,
2271
960
  ChatEventTypeRoomCreated,
2272
961
  ChatEventTypeRoomDeleted,
2273
962
  ChatEventTypeRoomUpdated,
@@ -2302,10 +991,6 @@ export {
2302
991
  ChatEventTypeUserSuggestedActions,
2303
992
  ChatEventTypeWaiting,
2304
993
  ChatEventTypeWaitingForAgent,
2305
- ChatHeader,
2306
- ChatHumanAgentActions,
2307
- ChatInput,
2308
- ChatMessages,
2309
994
  ChatRoleAI,
2310
995
  ChatRoleDataQuery,
2311
996
  ChatRoleEvent,
@@ -2321,19 +1006,16 @@ export {
2321
1006
  ChatStatusActive,
2322
1007
  ChatStatusArchived,
2323
1008
  ChatStatusClosed,
2324
- ChatStatusCustomerUI,
2325
1009
  ChatStatusDisconnected,
2326
1010
  ChatTypeCustomerSupport,
2327
1011
  ChatTypeDirect,
2328
1012
  ChatTypeGroup,
2329
1013
  ChatTypePrivateRoom,
2330
1014
  ChatTypePublicRoom,
2331
- ChatTypingIndicator,
2332
1015
  CompleteChatByAgentSubject,
2333
1016
  CreateAgentQueueSubject,
2334
1017
  DeleteAgentQueueSubject,
2335
1018
  EndAgentSessionSubject,
2336
- GenericChatWidget,
2337
1019
  GetActiveChatCountSubject,
2338
1020
  GetActiveChatsSubject,
2339
1021
  GetAgentQueuesSubject,
@@ -2369,14 +1051,6 @@ export {
2369
1051
  UserStatusBusy,
2370
1052
  UserStatusOffline,
2371
1053
  UserStatusOnline,
2372
- WebSocketChatAdminContext,
2373
- WebSocketChatAdminProvider,
2374
- WebSocketChatCustomerContext,
2375
- WebSocketChatCustomerProvider,
2376
- useWebSocketChatAdmin,
2377
- useWebSocketChatAdminContext,
2378
- useWebSocketChatBase,
2379
- useWebSocketChatCustomer,
2380
- useWebSocketChatCustomerContext
1054
+ useChat
2381
1055
  };
2382
1056
  //# sourceMappingURL=index.mjs.map