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