@eshal-bot/chat-widget 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,918 @@
1
+ import { useState, useRef, useEffect, forwardRef, createElement } from 'react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+
4
+ function _defineProperty(e, r, t) {
5
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
6
+ value: t,
7
+ enumerable: !0,
8
+ configurable: !0,
9
+ writable: !0
10
+ }) : e[r] = t, e;
11
+ }
12
+ function ownKeys(e, r) {
13
+ var t = Object.keys(e);
14
+ if (Object.getOwnPropertySymbols) {
15
+ var o = Object.getOwnPropertySymbols(e);
16
+ r && (o = o.filter(function (r) {
17
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
18
+ })), t.push.apply(t, o);
19
+ }
20
+ return t;
21
+ }
22
+ function _objectSpread2(e) {
23
+ for (var r = 1; r < arguments.length; r++) {
24
+ var t = null != arguments[r] ? arguments[r] : {};
25
+ r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
26
+ _defineProperty(e, r, t[r]);
27
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
28
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
29
+ });
30
+ }
31
+ return e;
32
+ }
33
+ function _toPrimitive(t, r) {
34
+ if ("object" != typeof t || !t) return t;
35
+ var e = t[Symbol.toPrimitive];
36
+ if (void 0 !== e) {
37
+ var i = e.call(t, r || "default");
38
+ if ("object" != typeof i) return i;
39
+ throw new TypeError("@@toPrimitive must return a primitive value.");
40
+ }
41
+ return ("string" === r ? String : Number)(t);
42
+ }
43
+ function _toPropertyKey(t) {
44
+ var i = _toPrimitive(t, "string");
45
+ return "symbol" == typeof i ? i : i + "";
46
+ }
47
+
48
+ /**
49
+ * Stream a support reply via Server-Sent Events
50
+ * Expects backend to emit JSON SSE events like:
51
+ * {"type":"processing_start", ...} | {"type":"text_chunk","content":"..."} | {"type":"complete", ...}
52
+ * Falls back to raw text if not JSON.
53
+ */
54
+ const streamSupportReply = async _ref => {
55
+ let {
56
+ apiBaseUrl,
57
+ query,
58
+ organizationId,
59
+ apiKey,
60
+ headers = {},
61
+ onProcessingStart,
62
+ onProcessingEnd,
63
+ onTextChunk,
64
+ onDone,
65
+ onError
66
+ } = _ref;
67
+ if (!apiBaseUrl) {
68
+ throw new Error("apiBaseUrl is required");
69
+ }
70
+ const controller = new AbortController();
71
+ const url = "".concat(apiBaseUrl.replace(/\/$/, ""), "/api/v1/widget/chat");
72
+ try {
73
+ const response = await fetch(url, {
74
+ method: "POST",
75
+ headers: _objectSpread2(_objectSpread2({
76
+ "Content-Type": "application/json",
77
+ "x-eshal-org": organizationId
78
+ }, apiKey && {
79
+ Authorization: "Bearer ".concat(apiKey)
80
+ }), headers),
81
+ body: JSON.stringify({
82
+ message: query
83
+ }),
84
+ signal: controller.signal,
85
+ credentials: "include"
86
+ });
87
+ if (!response.ok) {
88
+ throw new Error("HTTP error! status: ".concat(response.status));
89
+ }
90
+
91
+ // Expect text/event-stream but some servers omit header while still streaming
92
+ const reader = response.body.getReader();
93
+ const decoder = new TextDecoder();
94
+ let buffer = "";
95
+ const pump = async () => {
96
+ while (true) {
97
+ const {
98
+ value,
99
+ done
100
+ } = await reader.read();
101
+ if (done) break;
102
+ buffer += decoder.decode(value, {
103
+ stream: true
104
+ });
105
+
106
+ // Parse SSE lines
107
+ let lineEnd;
108
+ while ((lineEnd = buffer.indexOf("\n")) !== -1) {
109
+ const line = buffer.slice(0, lineEnd).trim();
110
+ buffer = buffer.slice(lineEnd + 1);
111
+ if (!line) continue;
112
+ if (line.startsWith("data:")) {
113
+ const raw = line.slice(5).trim();
114
+ if (raw === "[DONE]") {
115
+ if (onDone) onDone();
116
+ controller.abort();
117
+ return;
118
+ }
119
+ try {
120
+ const evt = JSON.parse(raw);
121
+ if (evt && evt.type === "processing_start") {
122
+ if (onProcessingStart) onProcessingStart(evt);
123
+ } else if (evt && evt.type === "processing_end") {
124
+ if (onProcessingEnd) onProcessingEnd(evt);
125
+ } else if (evt && evt.type === "text_chunk" && typeof evt.content === "string") {
126
+ if (onTextChunk) onTextChunk(evt.content, evt);
127
+ } else if (evt && evt.type === "complete") {
128
+ if (onDone) onDone(evt);
129
+ } else {
130
+ if (onTextChunk) onTextChunk(raw);
131
+ }
132
+ } catch (_) {
133
+ // Only treat as text chunk if it's not a known control message
134
+ if (raw !== "processing_end" && raw !== "processing_start") {
135
+ if (onTextChunk) onTextChunk(raw);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ // Flush remaining buffer (non-SSE servers may just stream text)
142
+ if (buffer && onTextChunk) onTextChunk(buffer);
143
+ if (onDone) onDone();
144
+ };
145
+ pump().catch(err => {
146
+ if (onError) onError(err);
147
+ });
148
+ return controller;
149
+ } catch (error) {
150
+ if (onError) onError(error);
151
+ throw error;
152
+ }
153
+ };
154
+
155
+ const useChatState = _ref => {
156
+ let {
157
+ welcomeMessage,
158
+ companyLogo,
159
+ quickQuestions,
160
+ apiBaseUrl,
161
+ organizationId,
162
+ autoOpen,
163
+ openDelay,
164
+ darkMode
165
+ } = _ref;
166
+ const [isOpen, setIsOpen] = useState(autoOpen);
167
+ const [isMinimized, setIsMinimized] = useState(false);
168
+ const [isDark, setIsDark] = useState(darkMode);
169
+ const [messages, setMessages] = useState([{
170
+ id: 1,
171
+ text: welcomeMessage,
172
+ sender: "bot",
173
+ timestamp: new Date(),
174
+ avatar: null
175
+ }]);
176
+ const [inputValue, setInputValue] = useState("");
177
+ const [isTyping, setIsTyping] = useState(false);
178
+ const widgetRef = useRef(null);
179
+
180
+ // Auto open with delay
181
+ useEffect(() => {
182
+ if (autoOpen && openDelay > 0) {
183
+ const timer = setTimeout(() => setIsOpen(true), openDelay);
184
+ return () => clearTimeout(timer);
185
+ }
186
+ }, [autoOpen, openDelay]);
187
+
188
+ // Sync dark mode prop
189
+ useEffect(() => {
190
+ setIsDark(darkMode);
191
+ }, [darkMode]);
192
+ const handleSend = async () => {
193
+ if (!inputValue.trim()) return;
194
+ const newMessage = {
195
+ id: messages.length + 1,
196
+ text: inputValue,
197
+ sender: "user",
198
+ timestamp: new Date()
199
+ };
200
+ setMessages([...messages, newMessage]);
201
+ const messageText = inputValue;
202
+ setInputValue("");
203
+ setIsTyping(true);
204
+ try {
205
+ // Create placeholder bot message for streaming
206
+ const botMessageId = messages.length + 2;
207
+ setMessages(prev => [...prev, {
208
+ id: botMessageId,
209
+ text: "",
210
+ sender: "bot",
211
+ timestamp: new Date(),
212
+ avatar: null
213
+ }]);
214
+ await streamSupportReply({
215
+ apiBaseUrl,
216
+ organizationId,
217
+ query: messageText,
218
+ onProcessingStart: () => {
219
+ setIsTyping(true);
220
+ },
221
+ onProcessingEnd: () => {
222
+ setIsTyping(true);
223
+ },
224
+ onTextChunk: chunk => {
225
+ if (isTyping) {
226
+ setIsTyping(false);
227
+ }
228
+ ;
229
+ setMessages(prev => prev.map(m => m.id === botMessageId ? _objectSpread2(_objectSpread2({}, m), {}, {
230
+ text: (m.text || "") + chunk
231
+ }) : m));
232
+ },
233
+ onDone: () => {
234
+ setIsTyping(false);
235
+ },
236
+ onError: error => {
237
+ console.error("SSE error:", error);
238
+ setIsTyping(false);
239
+ setMessages(prev => prev.map(m => m.id === botMessageId ? _objectSpread2(_objectSpread2({}, m), {}, {
240
+ text: "Sorry, I'm having trouble connecting right now. Please try again."
241
+ }) : m));
242
+ }
243
+ });
244
+ } catch (error) {
245
+ setIsTyping(false);
246
+ console.error("Error:", error);
247
+ }
248
+ };
249
+ const toggleChat = () => {
250
+ setIsOpen(!isOpen);
251
+ setIsMinimized(false);
252
+ };
253
+ const toggleTheme = () => {
254
+ setIsDark(!isDark);
255
+ };
256
+ const toggleMinimize = () => {
257
+ setIsMinimized(!isMinimized);
258
+ };
259
+ const closeChat = () => {
260
+ setIsOpen(false);
261
+ };
262
+ const handleQuickQuestion = question => {
263
+ setInputValue(question);
264
+ handleSend();
265
+ };
266
+ return {
267
+ // State
268
+ isOpen,
269
+ isMinimized,
270
+ isDark,
271
+ messages,
272
+ inputValue,
273
+ isTyping,
274
+ widgetRef,
275
+ quickQuestions,
276
+ // Actions
277
+ setInputValue,
278
+ handleSend,
279
+ handleQuickQuestion,
280
+ toggleChat,
281
+ toggleTheme,
282
+ toggleMinimize,
283
+ closeChat
284
+ };
285
+ };
286
+
287
+ /**
288
+ * @license lucide-react v0.303.0 - ISC
289
+ *
290
+ * This source code is licensed under the ISC license.
291
+ * See the LICENSE file in the root directory of this source tree.
292
+ */
293
+
294
+ var defaultAttributes = {
295
+ xmlns: "http://www.w3.org/2000/svg",
296
+ width: 24,
297
+ height: 24,
298
+ viewBox: "0 0 24 24",
299
+ fill: "none",
300
+ stroke: "currentColor",
301
+ strokeWidth: 2,
302
+ strokeLinecap: "round",
303
+ strokeLinejoin: "round"
304
+ };
305
+
306
+ /**
307
+ * @license lucide-react v0.303.0 - ISC
308
+ *
309
+ * This source code is licensed under the ISC license.
310
+ * See the LICENSE file in the root directory of this source tree.
311
+ */
312
+
313
+
314
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().trim();
315
+ const createLucideIcon = (iconName, iconNode) => {
316
+ const Component = forwardRef(
317
+ ({ color = "currentColor", size = 24, strokeWidth = 2, absoluteStrokeWidth, className = "", children, ...rest }, ref) => createElement(
318
+ "svg",
319
+ {
320
+ ref,
321
+ ...defaultAttributes,
322
+ width: size,
323
+ height: size,
324
+ stroke: color,
325
+ strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
326
+ className: ["lucide", `lucide-${toKebabCase(iconName)}`, className].join(" "),
327
+ ...rest
328
+ },
329
+ [
330
+ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
331
+ ...Array.isArray(children) ? children : [children]
332
+ ]
333
+ )
334
+ );
335
+ Component.displayName = `${iconName}`;
336
+ return Component;
337
+ };
338
+
339
+ /**
340
+ * @license lucide-react v0.303.0 - ISC
341
+ *
342
+ * This source code is licensed under the ISC license.
343
+ * See the LICENSE file in the root directory of this source tree.
344
+ */
345
+
346
+
347
+ const Bot = createLucideIcon("Bot", [
348
+ ["path", { d: "M12 8V4H8", key: "hb8ula" }],
349
+ ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }],
350
+ ["path", { d: "M2 14h2", key: "vft8re" }],
351
+ ["path", { d: "M20 14h2", key: "4cs60a" }],
352
+ ["path", { d: "M15 13v2", key: "1xurst" }],
353
+ ["path", { d: "M9 13v2", key: "rq6x2g" }]
354
+ ]);
355
+
356
+ /**
357
+ * @license lucide-react v0.303.0 - ISC
358
+ *
359
+ * This source code is licensed under the ISC license.
360
+ * See the LICENSE file in the root directory of this source tree.
361
+ */
362
+
363
+
364
+ const MessageCircle = createLucideIcon("MessageCircle", [
365
+ ["path", { d: "M7.9 20A9 9 0 1 0 4 16.1L2 22Z", key: "vv11sd" }]
366
+ ]);
367
+
368
+ /**
369
+ * @license lucide-react v0.303.0 - ISC
370
+ *
371
+ * This source code is licensed under the ISC license.
372
+ * See the LICENSE file in the root directory of this source tree.
373
+ */
374
+
375
+
376
+ const Minimize2 = createLucideIcon("Minimize2", [
377
+ ["polyline", { points: "4 14 10 14 10 20", key: "11kfnr" }],
378
+ ["polyline", { points: "20 10 14 10 14 4", key: "rlmsce" }],
379
+ ["line", { x1: "14", x2: "21", y1: "10", y2: "3", key: "o5lafz" }],
380
+ ["line", { x1: "3", x2: "10", y1: "21", y2: "14", key: "1atl0r" }]
381
+ ]);
382
+
383
+ /**
384
+ * @license lucide-react v0.303.0 - ISC
385
+ *
386
+ * This source code is licensed under the ISC license.
387
+ * See the LICENSE file in the root directory of this source tree.
388
+ */
389
+
390
+
391
+ const Moon = createLucideIcon("Moon", [
392
+ ["path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z", key: "a7tn18" }]
393
+ ]);
394
+
395
+ /**
396
+ * @license lucide-react v0.303.0 - ISC
397
+ *
398
+ * This source code is licensed under the ISC license.
399
+ * See the LICENSE file in the root directory of this source tree.
400
+ */
401
+
402
+
403
+ const Send = createLucideIcon("Send", [
404
+ ["path", { d: "m22 2-7 20-4-9-9-4Z", key: "1q3vgg" }],
405
+ ["path", { d: "M22 2 11 13", key: "nzbqef" }]
406
+ ]);
407
+
408
+ /**
409
+ * @license lucide-react v0.303.0 - ISC
410
+ *
411
+ * This source code is licensed under the ISC license.
412
+ * See the LICENSE file in the root directory of this source tree.
413
+ */
414
+
415
+
416
+ const Sun = createLucideIcon("Sun", [
417
+ ["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }],
418
+ ["path", { d: "M12 2v2", key: "tus03m" }],
419
+ ["path", { d: "M12 20v2", key: "1lh1kg" }],
420
+ ["path", { d: "m4.93 4.93 1.41 1.41", key: "149t6j" }],
421
+ ["path", { d: "m17.66 17.66 1.41 1.41", key: "ptbguv" }],
422
+ ["path", { d: "M2 12h2", key: "1t8f8n" }],
423
+ ["path", { d: "M20 12h2", key: "1q8mjw" }],
424
+ ["path", { d: "m6.34 17.66-1.41 1.41", key: "1m8zz5" }],
425
+ ["path", { d: "m19.07 4.93-1.41 1.41", key: "1shlcs" }]
426
+ ]);
427
+
428
+ /**
429
+ * @license lucide-react v0.303.0 - ISC
430
+ *
431
+ * This source code is licensed under the ISC license.
432
+ * See the LICENSE file in the root directory of this source tree.
433
+ */
434
+
435
+
436
+ const User = createLucideIcon("User", [
437
+ ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", key: "975kel" }],
438
+ ["circle", { cx: "12", cy: "7", r: "4", key: "17ys0d" }]
439
+ ]);
440
+
441
+ /**
442
+ * @license lucide-react v0.303.0 - ISC
443
+ *
444
+ * This source code is licensed under the ISC license.
445
+ * See the LICENSE file in the root directory of this source tree.
446
+ */
447
+
448
+
449
+ const X = createLucideIcon("X", [
450
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
451
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
452
+ ]);
453
+
454
+ const ChatHeader = _ref => {
455
+ let {
456
+ companyName,
457
+ companyLogo,
458
+ primaryColor,
459
+ isDark,
460
+ onToggleTheme,
461
+ onMinimize,
462
+ onClose
463
+ } = _ref;
464
+ return /*#__PURE__*/jsxs("div", {
465
+ className: "text-white px-5 py-4 flex items-center justify-between relative overflow-hidden",
466
+ style: {
467
+ background: "linear-gradient(135deg, ".concat(primaryColor, " 0%, ").concat(primaryColor, "dd 100%)")
468
+ },
469
+ children: [/*#__PURE__*/jsx("div", {
470
+ className: "absolute inset-0 bg-gradient-to-br from-white/10 to-transparent pointer-events-none"
471
+ }), /*#__PURE__*/jsxs("div", {
472
+ className: "flex items-center gap-3 relative z-10",
473
+ children: [/*#__PURE__*/jsxs("div", {
474
+ className: "relative",
475
+ children: [/*#__PURE__*/jsx("div", {
476
+ className: "w-11 h-11 rounded-xl flex items-center justify-center ".concat(companyLogo ? "bg-transparent" : "bg-white/20 backdrop-blur-sm"),
477
+ children: companyLogo ? /*#__PURE__*/jsx("img", {
478
+ src: companyLogo,
479
+ alt: "Logo",
480
+ className: "w-6 h-6 object-cover rounded"
481
+ }) : /*#__PURE__*/jsx(MessageCircle, {
482
+ className: "w-6 h-6"
483
+ })
484
+ }), /*#__PURE__*/jsx("div", {
485
+ className: "absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-green-400 rounded-full border-2 border-white"
486
+ })]
487
+ }), /*#__PURE__*/jsxs("div", {
488
+ children: [/*#__PURE__*/jsx("h3", {
489
+ className: "font-semibold text-[15px] leading-tight",
490
+ children: companyName
491
+ }), /*#__PURE__*/jsxs("p", {
492
+ className: "text-xs text-white/90 flex items-center gap-1.5 mt-0.5",
493
+ children: [/*#__PURE__*/jsx("span", {
494
+ className: "w-1.5 h-1.5 bg-green-400 rounded-full animate-pulse"
495
+ }), "Typically replies instantly"]
496
+ })]
497
+ })]
498
+ }), /*#__PURE__*/jsxs("div", {
499
+ className: "flex items-center gap-1 relative z-10",
500
+ children: [/*#__PURE__*/jsx("button", {
501
+ onClick: onToggleTheme,
502
+ className: "hover:bg-white/20 p-2 rounded-lg transition-all duration-200",
503
+ "aria-label": "Toggle theme",
504
+ children: isDark ? /*#__PURE__*/jsx(Sun, {
505
+ className: "w-4 h-4"
506
+ }) : /*#__PURE__*/jsx(Moon, {
507
+ className: "w-4 h-4"
508
+ })
509
+ }), /*#__PURE__*/jsx("button", {
510
+ onClick: onMinimize,
511
+ className: "hover:bg-white/20 p-2 rounded-lg transition-all duration-200",
512
+ "aria-label": "Minimize",
513
+ children: /*#__PURE__*/jsx(Minimize2, {
514
+ className: "w-4 h-4"
515
+ })
516
+ }), /*#__PURE__*/jsx("button", {
517
+ onClick: onClose,
518
+ className: "hover:bg-white/20 p-2 rounded-lg transition-all duration-200",
519
+ "aria-label": "Close",
520
+ children: /*#__PURE__*/jsx(X, {
521
+ className: "w-4 h-4"
522
+ })
523
+ })]
524
+ })]
525
+ });
526
+ };
527
+
528
+ const MessageBubble = _ref => {
529
+ let {
530
+ message,
531
+ isDark,
532
+ primaryColor,
533
+ isTyping,
534
+ companyLogo
535
+ } = _ref;
536
+ const isUser = message.sender === "user";
537
+ const isBot = message.sender === "bot";
538
+ const isTypingMessage = isBot && isTyping && (!message.text || message.text.length === 0);
539
+ return /*#__PURE__*/jsxs("div", {
540
+ className: "flex gap-3 ".concat(isUser ? "flex-row-reverse" : "flex-row", " animate-in"),
541
+ children: [isBot && /*#__PURE__*/jsx("div", {
542
+ className: "flex-shrink-0 mt-1",
543
+ children: message.avatar ? /*#__PURE__*/jsx("img", {
544
+ src: message.avatar,
545
+ alt: "Bot",
546
+ className: "w-8 h-8 rounded-full object-cover"
547
+ }) : /*#__PURE__*/jsx("div", {
548
+ className: "w-8 h-8 rounded-full flex items-center justify-center ".concat(isDark ? "bg-gray-700" : "bg-gray-200"),
549
+ children: /*#__PURE__*/jsx(Bot, {
550
+ className: "w-4 h-4",
551
+ style: {
552
+ color: primaryColor
553
+ }
554
+ })
555
+ })
556
+ }), /*#__PURE__*/jsxs("div", {
557
+ className: "flex flex-col ".concat(isUser ? "items-end" : "items-start", " max-w-[75%]"),
558
+ children: [/*#__PURE__*/jsx("div", {
559
+ className: "rounded-2xl px-4 py-2.5 ".concat(isUser ? "text-white shadow-lg" : "".concat(isDark ? "bg-gray-800 text-gray-100 border-gray-700" : "bg-white text-gray-800 border-gray-200", " shadow-sm border"), " ").concat(isUser ? "rounded-tr-sm" : "rounded-tl-sm"),
560
+ style: isUser ? {
561
+ background: "linear-gradient(135deg, ".concat(primaryColor, " 0%, ").concat(primaryColor, "dd 100%)")
562
+ } : {},
563
+ children: isTypingMessage ? /*#__PURE__*/jsxs("div", {
564
+ className: "flex gap-1.5",
565
+ children: [/*#__PURE__*/jsx("div", {
566
+ className: "w-2 h-2 rounded-full bg-gray-400 animate-bounce",
567
+ style: {
568
+ animationDelay: "0ms"
569
+ }
570
+ }), /*#__PURE__*/jsx("div", {
571
+ className: "w-2 h-2 rounded-full bg-gray-400 animate-bounce",
572
+ style: {
573
+ animationDelay: "150ms"
574
+ }
575
+ }), /*#__PURE__*/jsx("div", {
576
+ className: "w-2 h-2 rounded-full bg-gray-400 animate-bounce",
577
+ style: {
578
+ animationDelay: "300ms"
579
+ }
580
+ })]
581
+ }) : /*#__PURE__*/jsx("p", {
582
+ className: "text-[14px] leading-relaxed whitespace-pre-wrap break-words ".concat(isUser ? "text-white" : isDark ? "text-gray-100" : "text-gray-800"),
583
+ children: message.text
584
+ })
585
+ }), /*#__PURE__*/jsx("p", {
586
+ className: "text-[11px] mt-1.5 px-1 ".concat(isDark ? "text-gray-500" : "text-gray-400"),
587
+ children: message.timestamp.toLocaleTimeString([], {
588
+ hour: "2-digit",
589
+ minute: "2-digit"
590
+ })
591
+ })]
592
+ }), isUser && /*#__PURE__*/jsx("div", {
593
+ className: "flex-shrink-0 mt-1",
594
+ children: /*#__PURE__*/jsx("div", {
595
+ className: "w-8 h-8 rounded-full flex items-center justify-center ".concat(isDark ? "bg-gray-700" : "bg-gray-200"),
596
+ children: /*#__PURE__*/jsx(User, {
597
+ className: "w-4 h-4 text-gray-500"
598
+ })
599
+ })
600
+ })]
601
+ });
602
+ };
603
+
604
+ const MessageList = _ref => {
605
+ let {
606
+ messages,
607
+ isDark,
608
+ primaryColor,
609
+ isTyping,
610
+ companyLogo
611
+ } = _ref;
612
+ const messagesEndRef = useRef(null);
613
+ const scrollToBottom = () => {
614
+ var _messagesEndRef$curre;
615
+ (_messagesEndRef$curre = messagesEndRef.current) === null || _messagesEndRef$curre === void 0 || _messagesEndRef$curre.scrollIntoView({
616
+ behavior: "smooth"
617
+ });
618
+ };
619
+ useEffect(() => {
620
+ scrollToBottom();
621
+ }, [messages, isTyping]);
622
+ return /*#__PURE__*/jsxs("div", {
623
+ className: "chat-messages flex-1 overflow-y-auto px-5 py-4 space-y-4 ".concat(isDark ? "bg-gray-900" : "bg-gradient-to-b from-gray-50/50 to-white", " break-words"),
624
+ children: [messages.map(message => /*#__PURE__*/jsx(MessageBubble, {
625
+ message: message,
626
+ isDark: isDark,
627
+ primaryColor: primaryColor,
628
+ isTyping: isTyping,
629
+ companyLogo: companyLogo
630
+ }, message.id)), /*#__PURE__*/jsx("div", {
631
+ ref: messagesEndRef
632
+ })]
633
+ });
634
+ };
635
+
636
+ const ChatInput = _ref => {
637
+ let {
638
+ inputValue,
639
+ setInputValue,
640
+ onSend,
641
+ isDark,
642
+ primaryColor,
643
+ isOpen,
644
+ isMinimized
645
+ } = _ref;
646
+ const inputRef = useRef(null);
647
+ useEffect(() => {
648
+ if (isOpen && !isMinimized) {
649
+ var _inputRef$current;
650
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
651
+ }
652
+ }, [isOpen, isMinimized]);
653
+ const handleKeyPress = e => {
654
+ if (e.key === "Enter" && !e.shiftKey) {
655
+ e.preventDefault();
656
+ onSend();
657
+ }
658
+ };
659
+ return /*#__PURE__*/jsxs("div", {
660
+ className: "px-5 py-4 border-t ".concat(isDark ? "border-gray-700 bg-gray-900" : "border-gray-200 bg-white"),
661
+ children: [/*#__PURE__*/jsxs("div", {
662
+ className: "flex gap-2.5 items-end",
663
+ children: [/*#__PURE__*/jsx("div", {
664
+ className: "flex-1 flex items-center min-h-[48px] ".concat(isDark ? "bg-gray-800" : "bg-gray-50", " rounded-2xl border transition-all duration-200"),
665
+ style: {
666
+ borderColor: inputValue ? primaryColor : isDark ? "#374151" : "#e5e7eb",
667
+ borderWidth: inputValue ? "2px" : "1px"
668
+ },
669
+ children: /*#__PURE__*/jsx("textarea", {
670
+ id: "chat-widget-input",
671
+ ref: inputRef,
672
+ value: inputValue,
673
+ onChange: e => setInputValue(e.target.value),
674
+ onKeyDown: handleKeyPress,
675
+ placeholder: "Type your message...",
676
+ rows: "1",
677
+ className: "w-full px-4 py-0 bg-transparent rounded-xl leading-normal ".concat(isDark ? "text-gray-100" : "text-gray-900", " placeholder-gray-400 text-[14px] resize-none outline-none"),
678
+ style: {
679
+ maxHeight: "120px"
680
+ }
681
+ })
682
+ }), /*#__PURE__*/jsx("button", {
683
+ onClick: onSend,
684
+ disabled: !inputValue.trim(),
685
+ className: "h-12 w-12 p-0 rounded-xl transition-all duration-200 flex items-center justify-center shadow-lg disabled:opacity-40 disabled:cursor-not-allowed disabled:shadow-none ".concat(inputValue.trim() ? "scale-100" : "scale-95"),
686
+ style: {
687
+ backgroundColor: inputValue.trim() ? primaryColor : isDark ? "#374151" : "#e5e7eb",
688
+ color: inputValue.trim() ? "white" : "#9ca3af"
689
+ },
690
+ "aria-label": "Send message",
691
+ children: /*#__PURE__*/jsx(Send, {
692
+ className: "w-5 h-5"
693
+ })
694
+ })]
695
+ }), /*#__PURE__*/jsxs("p", {
696
+ className: "text-[11px] mt-2 px-1 ".concat(isDark ? "text-gray-600" : "text-gray-400", " flex items-center gap-1"),
697
+ children: [/*#__PURE__*/jsx("span", {
698
+ className: "w-1 h-1 rounded-full bg-green-500"
699
+ }), "We're online now"]
700
+ })]
701
+ });
702
+ };
703
+
704
+ const QuickQuestions = _ref => {
705
+ let {
706
+ questions = [],
707
+ isDark,
708
+ primaryColor,
709
+ onQuestionClick,
710
+ isTyping = false
711
+ } = _ref;
712
+ // Don't render if no questions or if typing/streaming
713
+ if (!questions.length || isTyping) {
714
+ return null;
715
+ }
716
+ return /*#__PURE__*/jsxs("div", {
717
+ className: "px-4 py-3 border-t border-gray-200 dark:border-gray-700",
718
+ children: [/*#__PURE__*/jsx("div", {
719
+ className: "text-xs text-gray-500 dark:text-gray-400 mb-2 font-medium",
720
+ children: "Quick questions:"
721
+ }), /*#__PURE__*/jsx("div", {
722
+ className: "flex gap-2 overflow-x-auto scrollbar-hide pb-1",
723
+ children: questions.map((question, index) => /*#__PURE__*/jsx("button", {
724
+ onClick: () => onQuestionClick(question),
725
+ className: "text-xs px-4 py-2 rounded-lg border-2 font-medium transition-all duration-200 hover:scale-105 active:scale-95 whitespace-nowrap flex-shrink-0 shadow-sm",
726
+ style: {
727
+ backgroundColor: isDark ? "rgba(59, 130, 246, 0.15)" : "rgba(59, 130, 246, 0.08)",
728
+ borderColor: isDark ? "rgba(59, 130, 246, 0.4)" : "rgba(59, 130, 246, 0.3)",
729
+ color: isDark ? "rgba(147, 197, 253, 0.95)" : "rgba(30, 64, 175, 0.9)"
730
+ },
731
+ onMouseEnter: e => {
732
+ e.target.style.backgroundColor = isDark ? "rgba(59, 130, 246, 0.25)" : "rgba(59, 130, 246, 0.15)";
733
+ e.target.style.borderColor = isDark ? "rgba(59, 130, 246, 0.6)" : "rgba(59, 130, 246, 0.5)";
734
+ },
735
+ onMouseLeave: e => {
736
+ e.target.style.backgroundColor = isDark ? "rgba(59, 130, 246, 0.15)" : "rgba(59, 130, 246, 0.08)";
737
+ e.target.style.borderColor = isDark ? "rgba(59, 130, 246, 0.4)" : "rgba(59, 130, 246, 0.3)";
738
+ },
739
+ children: question
740
+ }, index))
741
+ })]
742
+ });
743
+ };
744
+
745
+ const ChatWindow = _ref => {
746
+ let {
747
+ isOpen,
748
+ isMinimized,
749
+ isDark,
750
+ primaryColor,
751
+ companyName,
752
+ companyLogo,
753
+ messages,
754
+ isTyping,
755
+ inputValue,
756
+ setInputValue,
757
+ onSend,
758
+ onToggleTheme,
759
+ onMinimize,
760
+ onClose,
761
+ quickQuestions,
762
+ onQuickQuestion
763
+ } = _ref;
764
+ if (!isOpen) return null;
765
+ return /*#__PURE__*/jsxs("div", {
766
+ className: "mb-4 ".concat(isDark ? "bg-gray-900 border-gray-700" : "bg-white border-gray-200", " rounded-2xl shadow-2xl border transition-all duration-300 ").concat(isMinimized ? "w-80 h-16" : "w-[380px] h-[650px]", " flex flex-col overflow-hidden"),
767
+ children: [/*#__PURE__*/jsx(ChatHeader, {
768
+ companyName: companyName,
769
+ companyLogo: companyLogo,
770
+ primaryColor: primaryColor,
771
+ isDark: isDark,
772
+ onToggleTheme: onToggleTheme,
773
+ onMinimize: onMinimize,
774
+ onClose: onClose
775
+ }), !isMinimized && /*#__PURE__*/jsxs(Fragment, {
776
+ children: [/*#__PURE__*/jsx(MessageList, {
777
+ messages: messages,
778
+ isDark: isDark,
779
+ primaryColor: primaryColor,
780
+ isTyping: isTyping,
781
+ companyLogo: companyLogo
782
+ }), /*#__PURE__*/jsx(QuickQuestions, {
783
+ questions: quickQuestions,
784
+ isDark: isDark,
785
+ primaryColor: primaryColor,
786
+ onQuestionClick: onQuickQuestion,
787
+ isTyping: isTyping
788
+ }), /*#__PURE__*/jsx(ChatInput, {
789
+ inputValue: inputValue,
790
+ setInputValue: setInputValue,
791
+ onSend: onSend,
792
+ isDark: isDark,
793
+ primaryColor: primaryColor,
794
+ isOpen: isOpen,
795
+ isMinimized: isMinimized
796
+ })]
797
+ })]
798
+ });
799
+ };
800
+
801
+ const ToggleButton = _ref => {
802
+ let {
803
+ isOpen,
804
+ isDark,
805
+ primaryColor,
806
+ onToggle
807
+ } = _ref;
808
+ return /*#__PURE__*/jsxs("button", {
809
+ onClick: onToggle,
810
+ className: "group relative w-16 h-16 rounded-full shadow-2xl transition-all duration-300 flex items-center justify-center text-white overflow-hidden ".concat(isOpen ? "rotate-0 scale-100" : "hover:scale-110"),
811
+ style: {
812
+ background: isOpen ? isDark ? "#1f2937" : "#374151" : "linear-gradient(135deg, ".concat(primaryColor, " 0%, ").concat(primaryColor, "dd 100%)"),
813
+ boxShadow: isOpen ? "0 10px 30px rgba(0,0,0,0.3)" : "0 10px 30px ".concat(primaryColor, "40")
814
+ },
815
+ "aria-label": isOpen ? "Close chat" : "Open chat",
816
+ children: [/*#__PURE__*/jsx("div", {
817
+ className: "absolute inset-0 rounded-full bg-white/20 scale-0 group-hover:scale-100 transition-transform duration-500"
818
+ }), /*#__PURE__*/jsx("div", {
819
+ className: "relative z-10 transition-transform duration-300",
820
+ children: isOpen ? /*#__PURE__*/jsx(X, {
821
+ className: "w-7 h-7"
822
+ }) : /*#__PURE__*/jsxs(Fragment, {
823
+ children: [/*#__PURE__*/jsx(MessageCircle, {
824
+ className: "w-7 h-7"
825
+ }), /*#__PURE__*/jsx("div", {
826
+ className: "absolute -top-1 -right-1 w-5 h-5 bg-red-500 rounded-full flex items-center justify-center text-[10px] font-bold border-2 border-white animate-pulse",
827
+ children: "1"
828
+ })]
829
+ })
830
+ })]
831
+ });
832
+ };
833
+
834
+ const ChatWidget = _ref => {
835
+ let {
836
+ darkMode = false,
837
+ primaryColor = "#2563eb",
838
+ position = "bottom-right",
839
+ companyName = "Support Team",
840
+ companyLogo = null,
841
+ welcomeMessage = "Hi! How can we help?",
842
+ quickQuestions = [],
843
+ apiBaseUrl,
844
+ organizationId,
845
+ autoOpen = false,
846
+ openDelay = 0,
847
+ locale = "en",
848
+ className = ""
849
+ } = _ref;
850
+ const {
851
+ isOpen,
852
+ isMinimized,
853
+ isDark,
854
+ messages,
855
+ inputValue,
856
+ isTyping,
857
+ widgetRef,
858
+ setInputValue,
859
+ handleSend,
860
+ handleQuickQuestion,
861
+ toggleChat,
862
+ toggleTheme,
863
+ toggleMinimize,
864
+ closeChat
865
+ } = useChatState({
866
+ welcomeMessage,
867
+ companyLogo,
868
+ quickQuestions,
869
+ apiBaseUrl,
870
+ organizationId,
871
+ autoOpen,
872
+ openDelay,
873
+ darkMode
874
+ });
875
+
876
+ // Set CSS variables for theming
877
+ useEffect(() => {
878
+ if (widgetRef.current) {
879
+ widgetRef.current.style.setProperty("--cw-primary", primaryColor);
880
+ }
881
+ }, [primaryColor, widgetRef]);
882
+ const positionClasses = {
883
+ "bottom-right": "bottom-5 right-5",
884
+ "bottom-left": "bottom-5 left-5",
885
+ "top-right": "top-5 right-5",
886
+ "top-left": "top-5 left-5"
887
+ };
888
+ return /*#__PURE__*/jsxs("div", {
889
+ ref: widgetRef,
890
+ className: "chat-widget-root fixed flex flex-col items-end z-[999999] ".concat(isDark ? "dark" : "", " ").concat(className, " ").concat(positionClasses[position] || positionClasses["bottom-right"]),
891
+ children: [/*#__PURE__*/jsx(ChatWindow, {
892
+ isOpen: isOpen,
893
+ isMinimized: isMinimized,
894
+ isDark: isDark,
895
+ primaryColor: primaryColor,
896
+ companyName: companyName,
897
+ companyLogo: companyLogo,
898
+ messages: messages,
899
+ isTyping: isTyping,
900
+ inputValue: inputValue,
901
+ setInputValue: setInputValue,
902
+ onSend: handleSend,
903
+ onToggleTheme: toggleTheme,
904
+ onMinimize: toggleMinimize,
905
+ onClose: closeChat,
906
+ quickQuestions: quickQuestions,
907
+ onQuickQuestion: handleQuickQuestion
908
+ }), /*#__PURE__*/jsx(ToggleButton, {
909
+ isOpen: isOpen,
910
+ isDark: isDark,
911
+ primaryColor: primaryColor,
912
+ onToggle: toggleChat
913
+ })]
914
+ });
915
+ };
916
+
917
+ export { ChatWidget as default };
918
+ //# sourceMappingURL=chat-widget.esm.js.map