@elqnt/chat 2.0.8 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +433 -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 +80 -0
  9. package/dist/hooks/index.d.ts +80 -0
  10. package/dist/hooks/index.js +741 -0
  11. package/dist/hooks/index.js.map +1 -0
  12. package/dist/hooks/index.mjs +715 -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 +731 -2039
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.mjs +722 -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 +972 -0
  29. package/dist/transport/index.js.map +1 -0
  30. package/dist/transport/index.mjs +940 -0
  31. package/dist/transport/index.mjs.map +1 -0
  32. package/dist/types-7UNI1iYv.d.ts +250 -0
  33. package/dist/types-CQHtUQ6p.d.mts +250 -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,714 @@
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
+ const json = await response.json();
97
+ if (json && typeof json === "object" && "data" in json) {
98
+ return json.data;
99
+ }
100
+ return json;
101
+ }
102
+ function handleMessage(event) {
103
+ if (!event.data || event.data === "") return;
104
+ try {
105
+ const data = JSON.parse(event.data);
106
+ logger.debug("Received:", data.type);
107
+ emit(data);
108
+ } catch (err) {
109
+ logger.error("Failed to parse SSE message:", err);
110
+ }
111
+ }
112
+ function setupEventListeners(es) {
113
+ es.addEventListener("message", handleMessage);
114
+ const eventTypes = [
115
+ "reconnected",
116
+ "typing",
117
+ "stopped_typing",
118
+ "waiting",
119
+ "waiting_for_agent",
120
+ "human_agent_joined",
121
+ "human_agent_left",
122
+ "chat_ended",
123
+ "chat_updated",
124
+ "load_chat_response",
125
+ "new_chat_created",
126
+ "show_csat_survey",
127
+ "csat_response",
128
+ "user_suggested_actions",
129
+ "agent_execution_started",
130
+ "agent_execution_ended",
131
+ "agent_context_update",
132
+ "plan_pending_approval",
133
+ "step_started",
134
+ "step_completed",
135
+ "step_failed",
136
+ "plan_completed",
137
+ "skills_changed",
138
+ "summary_update"
139
+ ];
140
+ eventTypes.forEach((type) => {
141
+ es.addEventListener(type, handleMessage);
142
+ });
143
+ }
144
+ function scheduleReconnect() {
145
+ if (intentionalDisconnect || !config) return;
146
+ const maxRetries = retryConfig.maxRetries ?? DEFAULT_RETRY_CONFIG.maxRetries;
147
+ if (retryCount >= maxRetries) {
148
+ logger.error(`Max retries (${maxRetries}) exceeded`);
149
+ error = {
150
+ code: "CONNECTION_FAILED",
151
+ message: `Max retries (${maxRetries}) exceeded`,
152
+ retryable: false,
153
+ timestamp: Date.now()
154
+ };
155
+ return;
156
+ }
157
+ const interval = calculateRetryInterval(retryCount, retryConfig);
158
+ retryCount++;
159
+ metrics.reconnectCount++;
160
+ logger.info(`Reconnecting in ${interval}ms (attempt ${retryCount})`);
161
+ state = "reconnecting";
162
+ reconnectTimeout = setTimeout(() => {
163
+ if (config) {
164
+ transport.connect(config).catch((err) => {
165
+ logger.error("Reconnect failed:", err);
166
+ });
167
+ }
168
+ }, interval);
169
+ }
170
+ const transport = {
171
+ async connect(cfg) {
172
+ config = cfg;
173
+ intentionalDisconnect = false;
174
+ if (eventSource) {
175
+ eventSource.close();
176
+ eventSource = void 0;
177
+ }
178
+ if (reconnectTimeout) {
179
+ clearTimeout(reconnectTimeout);
180
+ reconnectTimeout = void 0;
181
+ }
182
+ state = retryCount > 0 ? "reconnecting" : "connecting";
183
+ return new Promise((resolve, reject) => {
184
+ const connectionStart = Date.now();
185
+ const url = `${cfg.baseUrl}/stream?orgId=${cfg.orgId}&userId=${cfg.userId}&clientType=${cfg.clientType}${cfg.chatKey ? `&chatId=${cfg.chatKey}` : ""}`;
186
+ logger.debug("Connecting to:", url);
187
+ const es = new EventSource(url);
188
+ es.onopen = () => {
189
+ const connectionTime = Date.now() - connectionStart;
190
+ logger.info(`Connected in ${connectionTime}ms`);
191
+ state = "connected";
192
+ error = void 0;
193
+ retryCount = 0;
194
+ metrics.connectedAt = Date.now();
195
+ metrics.latency = connectionTime;
196
+ setupEventListeners(es);
197
+ resolve();
198
+ };
199
+ es.onerror = () => {
200
+ if (es.readyState === EventSource.CLOSED) {
201
+ const sseError = {
202
+ code: "CONNECTION_FAILED",
203
+ message: "SSE connection failed",
204
+ retryable: true,
205
+ timestamp: Date.now()
206
+ };
207
+ error = sseError;
208
+ metrics.lastError = sseError;
209
+ state = "disconnected";
210
+ if (!intentionalDisconnect) {
211
+ scheduleReconnect();
212
+ }
213
+ if (retryCount === 0) {
214
+ reject(sseError);
215
+ }
216
+ }
217
+ };
218
+ eventSource = es;
219
+ });
220
+ },
221
+ disconnect(intentional = true) {
222
+ logger.info("Disconnecting", { intentional });
223
+ intentionalDisconnect = intentional;
224
+ if (reconnectTimeout) {
225
+ clearTimeout(reconnectTimeout);
226
+ reconnectTimeout = void 0;
227
+ }
228
+ if (eventSource) {
229
+ eventSource.close();
230
+ eventSource = void 0;
231
+ }
232
+ state = "disconnected";
233
+ retryCount = 0;
234
+ },
235
+ async send(event) {
236
+ if (!config) {
237
+ throw new Error("Transport not connected");
238
+ }
239
+ switch (event.type) {
240
+ case "message":
241
+ await sendRest("send", {
242
+ orgId: event.orgId,
243
+ chatKey: event.chatKey,
244
+ userId: event.userId,
245
+ message: event.message
246
+ });
247
+ break;
248
+ case "typing":
249
+ await sendRest("typing", {
250
+ orgId: event.orgId,
251
+ chatKey: event.chatKey,
252
+ userId: event.userId,
253
+ typing: true
254
+ });
255
+ break;
256
+ case "stopped_typing":
257
+ await sendRest("typing", {
258
+ orgId: event.orgId,
259
+ chatKey: event.chatKey,
260
+ userId: event.userId,
261
+ typing: false
262
+ });
263
+ break;
264
+ case "load_chat":
265
+ await sendRest("load", {
266
+ orgId: event.orgId,
267
+ chatKey: event.chatKey,
268
+ userId: event.userId
269
+ });
270
+ break;
271
+ case "new_chat":
272
+ await sendRest("create", {
273
+ orgId: event.orgId,
274
+ userId: event.userId,
275
+ metadata: event.data
276
+ });
277
+ break;
278
+ case "end_chat":
279
+ await sendRest("end", {
280
+ orgId: event.orgId,
281
+ chatKey: event.chatKey,
282
+ userId: event.userId,
283
+ data: event.data
284
+ });
285
+ break;
286
+ default:
287
+ await sendRest("event", {
288
+ type: event.type,
289
+ orgId: event.orgId,
290
+ chatKey: event.chatKey,
291
+ userId: event.userId,
292
+ data: event.data
293
+ });
294
+ }
295
+ metrics.messagesSent++;
296
+ },
297
+ async sendMessage(message) {
298
+ if (!config) {
299
+ throw new Error("Transport not connected");
300
+ }
301
+ await sendRest("send", {
302
+ orgId: config.orgId,
303
+ chatKey: config.chatKey,
304
+ userId: config.userId,
305
+ message
306
+ });
307
+ metrics.messagesSent++;
308
+ },
309
+ async createChat(options2) {
310
+ if (!config) {
311
+ throw new Error("Transport not connected");
312
+ }
313
+ const response = await sendRest("create", {
314
+ orgId: options2.orgId,
315
+ userId: options2.userId,
316
+ metadata: options2.metadata
317
+ });
318
+ if (!response?.chatKey) {
319
+ throw new Error("Failed to create chat: no chatKey returned");
320
+ }
321
+ return { chatKey: response.chatKey };
322
+ },
323
+ async loadChatData(options2) {
324
+ if (!config) {
325
+ throw new Error("Transport not connected");
326
+ }
327
+ const response = await sendRest("load", {
328
+ orgId: options2.orgId,
329
+ chatKey: options2.chatKey,
330
+ userId: options2.userId
331
+ });
332
+ if (!response?.chat) {
333
+ throw new Error("Failed to load chat: no chat data returned");
334
+ }
335
+ return {
336
+ chat: response.chat,
337
+ agentId: response.agentId
338
+ };
339
+ },
340
+ onMessage(handler) {
341
+ globalHandlers.add(handler);
342
+ return () => globalHandlers.delete(handler);
343
+ },
344
+ on(eventType, handler) {
345
+ if (!typeHandlers.has(eventType)) {
346
+ typeHandlers.set(eventType, /* @__PURE__ */ new Set());
347
+ }
348
+ typeHandlers.get(eventType).add(handler);
349
+ return () => {
350
+ const handlers = typeHandlers.get(eventType);
351
+ if (handlers) {
352
+ handlers.delete(handler);
353
+ if (handlers.size === 0) {
354
+ typeHandlers.delete(eventType);
355
+ }
356
+ }
357
+ };
358
+ },
359
+ getState() {
360
+ return state;
361
+ },
362
+ getMetrics() {
363
+ return { ...metrics };
364
+ },
365
+ getError() {
366
+ return error;
367
+ },
368
+ clearError() {
369
+ error = void 0;
370
+ }
371
+ };
372
+ return transport;
373
+ }
10
374
 
11
- // hooks/use-websocket-chat-admin.ts
12
- import { useCallback as useCallback2, useState as useState2 } from "react";
375
+ // hooks/use-chat.ts
376
+ function getDefaultTransport(type, debug) {
377
+ return createSSETransport({ debug });
378
+ }
379
+ function useChat(options) {
380
+ const {
381
+ baseUrl,
382
+ orgId,
383
+ userId,
384
+ clientType = "customer",
385
+ transport: transportOption,
386
+ onMessage,
387
+ onError,
388
+ onConnectionChange,
389
+ autoConnect = false,
390
+ retryConfig,
391
+ debug = false
392
+ } = options;
393
+ const [connectionState, setConnectionState] = useState("disconnected");
394
+ const [currentChat, setCurrentChat] = useState(null);
395
+ const [chatKey, setChatKey] = useState(null);
396
+ const [messages, setMessages] = useState([]);
397
+ const [error, setError] = useState(null);
398
+ const [metrics, setMetrics] = useState({
399
+ latency: 0,
400
+ messagesSent: 0,
401
+ messagesReceived: 0,
402
+ messagesQueued: 0,
403
+ reconnectCount: 0
404
+ });
405
+ const transportRef = useRef(null);
406
+ const mountedRef = useRef(false);
407
+ const onMessageRef = useRef(onMessage);
408
+ const onErrorRef = useRef(onError);
409
+ const typingTimeoutRef = useRef(null);
410
+ useEffect(() => {
411
+ onMessageRef.current = onMessage;
412
+ onErrorRef.current = onError;
413
+ }, [onMessage, onError]);
414
+ useEffect(() => {
415
+ if (typeof transportOption === "object") {
416
+ transportRef.current = transportOption;
417
+ } else {
418
+ transportRef.current = getDefaultTransport(
419
+ transportOption,
420
+ debug
421
+ );
422
+ }
423
+ }, [transportOption, debug]);
424
+ const handleEvent = useCallback((event) => {
425
+ if (!mountedRef.current) return;
426
+ setMetrics((prev) => ({
427
+ ...prev,
428
+ messagesReceived: prev.messagesReceived + 1,
429
+ lastMessageAt: Date.now()
430
+ }));
431
+ switch (event.type) {
432
+ case "new_chat_created":
433
+ const newChatKey = event.data?.chatKey;
434
+ if (newChatKey) {
435
+ setChatKey(newChatKey);
436
+ }
437
+ break;
438
+ case "load_chat_response":
439
+ const chat = event.data?.chat;
440
+ if (chat) {
441
+ setCurrentChat(chat);
442
+ setChatKey(chat.key);
443
+ setMessages(chat.messages || []);
444
+ }
445
+ break;
446
+ case "message":
447
+ if (event.message) {
448
+ setMessages((prev) => [...prev, event.message]);
449
+ }
450
+ break;
451
+ case "chat_ended":
452
+ setCurrentChat(null);
453
+ setChatKey(null);
454
+ break;
455
+ case "error":
456
+ const errorMsg = event.data?.message;
457
+ if (errorMsg) {
458
+ const transportError = {
459
+ code: "NETWORK_ERROR",
460
+ message: errorMsg,
461
+ retryable: true,
462
+ timestamp: Date.now()
463
+ };
464
+ setError(transportError);
465
+ onErrorRef.current?.(transportError);
466
+ }
467
+ break;
468
+ }
469
+ onMessageRef.current?.(event);
470
+ }, []);
471
+ const connect = useCallback(async () => {
472
+ const transport = transportRef.current;
473
+ if (!transport) {
474
+ throw new Error("Transport not initialized");
475
+ }
476
+ if (!orgId) {
477
+ const err = {
478
+ code: "CONNECTION_FAILED",
479
+ message: "orgId is required",
480
+ retryable: false,
481
+ timestamp: Date.now()
482
+ };
483
+ setError(err);
484
+ throw err;
485
+ }
486
+ setConnectionState("connecting");
487
+ try {
488
+ const unsubscribe = transport.onMessage(handleEvent);
489
+ await transport.connect({
490
+ baseUrl,
491
+ orgId,
492
+ userId,
493
+ clientType,
494
+ chatKey: chatKey || void 0,
495
+ debug
496
+ });
497
+ setConnectionState("connected");
498
+ setError(null);
499
+ setMetrics(transport.getMetrics());
500
+ onConnectionChange?.("connected");
501
+ return;
502
+ } catch (err) {
503
+ const transportError = err;
504
+ setConnectionState("disconnected");
505
+ setError(transportError);
506
+ onErrorRef.current?.(transportError);
507
+ throw err;
508
+ }
509
+ }, [baseUrl, orgId, userId, clientType, chatKey, debug, handleEvent, onConnectionChange]);
510
+ const disconnect = useCallback(() => {
511
+ const transport = transportRef.current;
512
+ if (transport) {
513
+ transport.disconnect(true);
514
+ }
515
+ setConnectionState("disconnected");
516
+ onConnectionChange?.("disconnected");
517
+ }, [onConnectionChange]);
518
+ const startChat = useCallback(
519
+ async (metadata) => {
520
+ const transport = transportRef.current;
521
+ if (!transport) {
522
+ throw new Error("Transport not initialized");
523
+ }
524
+ const response = await transport.createChat({
525
+ orgId,
526
+ userId,
527
+ metadata
528
+ });
529
+ setChatKey(response.chatKey);
530
+ return response.chatKey;
531
+ },
532
+ [orgId, userId]
533
+ );
534
+ const loadChat = useCallback(
535
+ async (key) => {
536
+ const transport = transportRef.current;
537
+ if (!transport) {
538
+ throw new Error("Transport not initialized");
539
+ }
540
+ const response = await transport.loadChatData({
541
+ orgId,
542
+ chatKey: key,
543
+ userId
544
+ });
545
+ setCurrentChat(response.chat);
546
+ setChatKey(response.chat.key);
547
+ setMessages(response.chat.messages || []);
548
+ return response.chat;
549
+ },
550
+ [orgId, userId]
551
+ );
552
+ const sendMessage = useCallback(
553
+ async (content, attachments) => {
554
+ const transport = transportRef.current;
555
+ if (!transport) {
556
+ throw new Error("Transport not initialized");
557
+ }
558
+ if (!chatKey) {
559
+ throw new Error("No active chat");
560
+ }
561
+ const message = {
562
+ id: `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`,
563
+ role: "user",
564
+ content,
565
+ time: Date.now(),
566
+ status: "sending",
567
+ senderId: userId,
568
+ createdAt: Date.now(),
569
+ attachments
570
+ };
571
+ setMessages((prev) => [...prev, message]);
572
+ await transport.send({
573
+ type: "message",
574
+ orgId,
575
+ chatKey,
576
+ userId,
577
+ timestamp: Date.now(),
578
+ message
579
+ });
580
+ setMetrics((prev) => ({
581
+ ...prev,
582
+ messagesSent: prev.messagesSent + 1
583
+ }));
584
+ },
585
+ [orgId, chatKey, userId]
586
+ );
587
+ const endChat = useCallback(
588
+ async (reason) => {
589
+ const transport = transportRef.current;
590
+ if (!transport) {
591
+ throw new Error("Transport not initialized");
592
+ }
593
+ if (!chatKey) {
594
+ return;
595
+ }
596
+ await transport.send({
597
+ type: "end_chat",
598
+ orgId,
599
+ chatKey,
600
+ userId,
601
+ timestamp: Date.now(),
602
+ data: reason ? { reason } : void 0
603
+ });
604
+ setCurrentChat(null);
605
+ setChatKey(null);
606
+ },
607
+ [orgId, chatKey, userId]
608
+ );
609
+ const sendEvent = useCallback(
610
+ async (event) => {
611
+ const transport = transportRef.current;
612
+ if (!transport) {
613
+ throw new Error("Transport not initialized");
614
+ }
615
+ await transport.send({
616
+ ...event,
617
+ timestamp: Date.now()
618
+ });
619
+ },
620
+ []
621
+ );
622
+ const startTyping = useCallback(() => {
623
+ const transport = transportRef.current;
624
+ if (!transport || !chatKey) return;
625
+ if (typingTimeoutRef.current) {
626
+ clearTimeout(typingTimeoutRef.current);
627
+ }
628
+ transport.send({
629
+ type: "typing",
630
+ orgId,
631
+ chatKey,
632
+ userId,
633
+ timestamp: Date.now()
634
+ }).catch(() => {
635
+ });
636
+ typingTimeoutRef.current = setTimeout(() => {
637
+ stopTyping();
638
+ }, 3e3);
639
+ }, [orgId, chatKey, userId]);
640
+ const stopTyping = useCallback(() => {
641
+ const transport = transportRef.current;
642
+ if (!transport || !chatKey) return;
643
+ if (typingTimeoutRef.current) {
644
+ clearTimeout(typingTimeoutRef.current);
645
+ typingTimeoutRef.current = null;
646
+ }
647
+ transport.send({
648
+ type: "stopped_typing",
649
+ orgId,
650
+ chatKey,
651
+ userId,
652
+ timestamp: Date.now()
653
+ }).catch(() => {
654
+ });
655
+ }, [orgId, chatKey, userId]);
656
+ const on = useCallback(
657
+ (eventType, handler) => {
658
+ const transport = transportRef.current;
659
+ if (!transport) {
660
+ return () => {
661
+ };
662
+ }
663
+ return transport.on(eventType, handler);
664
+ },
665
+ []
666
+ );
667
+ const clearError = useCallback(() => {
668
+ setError(null);
669
+ transportRef.current?.clearError();
670
+ }, []);
671
+ useEffect(() => {
672
+ mountedRef.current = true;
673
+ if (autoConnect) {
674
+ connect().catch(() => {
675
+ });
676
+ }
677
+ return () => {
678
+ mountedRef.current = false;
679
+ if (typingTimeoutRef.current) {
680
+ clearTimeout(typingTimeoutRef.current);
681
+ }
682
+ disconnect();
683
+ };
684
+ }, []);
685
+ const isConnected = connectionState === "connected";
686
+ return {
687
+ // Connection
688
+ connect,
689
+ disconnect,
690
+ connectionState,
691
+ isConnected,
692
+ // Chat operations
693
+ startChat,
694
+ loadChat,
695
+ sendMessage,
696
+ sendEvent,
697
+ endChat,
698
+ // Typing
699
+ startTyping,
700
+ stopTyping,
701
+ // State
702
+ currentChat,
703
+ chatKey,
704
+ messages,
705
+ error,
706
+ metrics,
707
+ // Events
708
+ on,
709
+ clearError
710
+ };
711
+ }
13
712
 
14
713
  // models/chat-models.ts
15
714
  var ChatStatusActive = "active";
@@ -128,6 +827,11 @@ var ChatEventTypePong = "pong";
128
827
  var ChatEventTypeSkillActivate = "skill_activate";
129
828
  var ChatEventTypeSkillDeactivate = "skill_deactivate";
130
829
  var ChatEventTypeSkillsChanged = "skills_changed";
830
+ var ChatEventTypeAttachmentProcessingStarted = "attachment_processing_started";
831
+ var ChatEventTypeAttachmentProcessingProgress = "attachment_processing_progress";
832
+ var ChatEventTypeAttachmentProcessingComplete = "attachment_processing_complete";
833
+ var ChatEventTypeAttachmentProcessingError = "attachment_processing_error";
834
+ var ChatEventTypeRetryAttachment = "retry_attachment";
131
835
  var MessageStatusSending = "sending";
132
836
  var MessageStatusSent = "sent";
133
837
  var MessageStatusDelivered = "delivered";
@@ -149,6 +853,8 @@ var AttachmentTypeSticker = "sticker";
149
853
  var AttachmentTypeData = "data";
150
854
  var AttachmentTypeKGNodes = "kgNodes";
151
855
  var AttachmentTypeDocumentSources = "document_sources";
856
+ var AttachmentTypeSpreadsheet = "spreadsheet";
857
+ var AttachmentTypeDataFile = "data_file";
152
858
  var ChatSessionStatusActive = "active";
153
859
  var ChatSessionStatusIdle = "idle";
154
860
  var ChatSessionStatusExpired = "expired";
@@ -193,1999 +899,7 @@ var UpdateUserStatusSubject = "chat.user.status.update";
193
899
  var GetOnlineUsersSubject = "chat.users.online.get";
194
900
  var TriggerAnalyticsScanSubject = "chat.analytics.trigger-scan";
195
901
  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
902
  export {
2185
- AdminChatHeader,
2186
- AdminChatInput,
2187
- AdminChatList,
2188
- AdminChatListItem,
2189
903
  AgentStatusAway,
2190
904
  AgentStatusBusy,
2191
905
  AgentStatusOffline,
@@ -2196,6 +910,7 @@ export {
2196
910
  AttachmentTypeAudio,
2197
911
  AttachmentTypeBullets,
2198
912
  AttachmentTypeData,
913
+ AttachmentTypeDataFile,
2199
914
  AttachmentTypeDocument,
2200
915
  AttachmentTypeDocumentAnalysis,
2201
916
  AttachmentTypeDocumentSources,
@@ -2204,6 +919,7 @@ export {
2204
919
  AttachmentTypeLocation,
2205
920
  AttachmentTypeRecords,
2206
921
  AttachmentTypeReferences,
922
+ AttachmentTypeSpreadsheet,
2207
923
  AttachmentTypeSticker,
2208
924
  AttachmentTypeSubsections,
2209
925
  AttachmentTypeVideo,
@@ -2217,6 +933,10 @@ export {
2217
933
  ChatEventTypeAgentSessionEnd,
2218
934
  ChatEventTypeAgentSessionStart,
2219
935
  ChatEventTypeAgentStatusChange,
936
+ ChatEventTypeAttachmentProcessingComplete,
937
+ ChatEventTypeAttachmentProcessingError,
938
+ ChatEventTypeAttachmentProcessingProgress,
939
+ ChatEventTypeAttachmentProcessingStarted,
2220
940
  ChatEventTypeBlockUser,
2221
941
  ChatEventTypeCSATRequest,
2222
942
  ChatEventTypeCSATResponse,
@@ -2268,6 +988,7 @@ export {
2268
988
  ChatEventTypePong,
2269
989
  ChatEventTypeRead,
2270
990
  ChatEventTypeReconnected,
991
+ ChatEventTypeRetryAttachment,
2271
992
  ChatEventTypeRoomCreated,
2272
993
  ChatEventTypeRoomDeleted,
2273
994
  ChatEventTypeRoomUpdated,
@@ -2302,10 +1023,6 @@ export {
2302
1023
  ChatEventTypeUserSuggestedActions,
2303
1024
  ChatEventTypeWaiting,
2304
1025
  ChatEventTypeWaitingForAgent,
2305
- ChatHeader,
2306
- ChatHumanAgentActions,
2307
- ChatInput,
2308
- ChatMessages,
2309
1026
  ChatRoleAI,
2310
1027
  ChatRoleDataQuery,
2311
1028
  ChatRoleEvent,
@@ -2321,19 +1038,16 @@ export {
2321
1038
  ChatStatusActive,
2322
1039
  ChatStatusArchived,
2323
1040
  ChatStatusClosed,
2324
- ChatStatusCustomerUI,
2325
1041
  ChatStatusDisconnected,
2326
1042
  ChatTypeCustomerSupport,
2327
1043
  ChatTypeDirect,
2328
1044
  ChatTypeGroup,
2329
1045
  ChatTypePrivateRoom,
2330
1046
  ChatTypePublicRoom,
2331
- ChatTypingIndicator,
2332
1047
  CompleteChatByAgentSubject,
2333
1048
  CreateAgentQueueSubject,
2334
1049
  DeleteAgentQueueSubject,
2335
1050
  EndAgentSessionSubject,
2336
- GenericChatWidget,
2337
1051
  GetActiveChatCountSubject,
2338
1052
  GetActiveChatsSubject,
2339
1053
  GetAgentQueuesSubject,
@@ -2369,14 +1083,6 @@ export {
2369
1083
  UserStatusBusy,
2370
1084
  UserStatusOffline,
2371
1085
  UserStatusOnline,
2372
- WebSocketChatAdminContext,
2373
- WebSocketChatAdminProvider,
2374
- WebSocketChatCustomerContext,
2375
- WebSocketChatCustomerProvider,
2376
- useWebSocketChatAdmin,
2377
- useWebSocketChatAdminContext,
2378
- useWebSocketChatBase,
2379
- useWebSocketChatCustomer,
2380
- useWebSocketChatCustomerContext
1086
+ useChat
2381
1087
  };
2382
1088
  //# sourceMappingURL=index.mjs.map