@blumessage/react-chat 1.8.1 → 1.8.3

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.
@@ -1,1150 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.BlumessageChat = void 0;
40
- const jsx_runtime_1 = require("react/jsx-runtime");
41
- const react_1 = __importStar(require("react"));
42
- const react_markdown_1 = __importDefault(require("react-markdown"));
43
- const remark_gfm_1 = __importDefault(require("remark-gfm"));
44
- const LucideIcons = __importStar(require("lucide-react"));
45
- require("./styles.css");
46
- // Custom CSS animations that don't depend on Tailwind
47
- const customStyles = `
48
- @keyframes bounce {
49
- 0%, 100% {
50
- transform: translateY(0);
51
- }
52
- 50% {
53
- transform: translateY(-25%);
54
- }
55
- }
56
-
57
- @keyframes spin {
58
- 0% {
59
- transform: rotate(0deg);
60
- }
61
- 100% {
62
- transform: rotate(360deg);
63
- }
64
- }
65
-
66
- @keyframes helpBubbleIn {
67
- 0% {
68
- transform: translateY(20px) scale(0.8);
69
- opacity: 0;
70
- }
71
- 100% {
72
- transform: translateY(0) scale(1);
73
- opacity: 1;
74
- }
75
- }
76
-
77
- @keyframes helpBubbleOut {
78
- 0% {
79
- transform: translateY(0) scale(1);
80
- opacity: 1;
81
- }
82
- 100% {
83
- transform: translateY(20px) scale(0.8);
84
- opacity: 0;
85
- }
86
- }
87
-
88
- @keyframes helpBubbleInTop {
89
- 0% {
90
- transform: translateY(-20px) scale(0.8);
91
- opacity: 0;
92
- }
93
- 100% {
94
- transform: translateY(0) scale(1);
95
- opacity: 1;
96
- }
97
- }
98
-
99
- @keyframes helpBubbleOutTop {
100
- 0% {
101
- transform: translateY(0) scale(1);
102
- opacity: 1;
103
- }
104
- 100% {
105
- transform: translateY(-20px) scale(0.8);
106
- opacity: 0;
107
- }
108
- }
109
-
110
- .blumessage-animate-bounce {
111
- animation: bounce 1s infinite;
112
- }
113
-
114
- .blumessage-animate-spin {
115
- animation: spin 1s linear infinite;
116
- }
117
-
118
- .blumessage-help-bubble-in {
119
- animation: helpBubbleIn 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
120
- }
121
-
122
- .blumessage-help-bubble-out {
123
- animation: helpBubbleOut 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
124
- }
125
-
126
- .blumessage-help-bubble-in-top {
127
- animation: helpBubbleInTop 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
128
- }
129
-
130
- .blumessage-help-bubble-out-top {
131
- animation: helpBubbleOutTop 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
132
- }
133
-
134
- /* Mobile-specific styles */
135
- @media (max-width: 768px) {
136
- .blumessage-mobile-fullscreen {
137
- position: fixed !important;
138
- top: 0 !important;
139
- left: 0 !important;
140
- right: 0 !important;
141
- bottom: 0 !important;
142
- width: 100% !important;
143
- height: 100% !important;
144
- max-width: 100% !important;
145
- max-height: 100% !important;
146
- border-radius: 0 !important;
147
- z-index: 9999 !important;
148
- }
149
-
150
- .blumessage-mobile-chat {
151
- border-radius: 16px !important;
152
- }
153
-
154
- .blumessage-mobile-button {
155
- position: fixed !important;
156
- bottom: 20px !important;
157
- right: 20px !important;
158
- left: auto !important;
159
- top: auto !important;
160
- }
161
- }
162
-
163
- @media (max-width: 480px) {
164
- .blumessage-mobile-fullscreen {
165
- border-radius: 0 !important;
166
- }
167
-
168
- .blumessage-mobile-chat {
169
- border-radius: 12px !important;
170
- }
171
-
172
- .blumessage-mobile-button {
173
- bottom: 16px !important;
174
- right: 16px !important;
175
- }
176
- }
177
- `;
178
- // Inject custom styles
179
- if (typeof document !== 'undefined' && !document.getElementById('blumessage-styles')) {
180
- const style = document.createElement('style');
181
- style.id = 'blumessage-styles';
182
- style.textContent = customStyles;
183
- document.head.appendChild(style);
184
- }
185
- // Session storage key for conversation token
186
- const CONVERSATION_TOKEN_KEY = 'blumessage_conversation_token';
187
- // Utility functions for storage
188
- const saveConversationToken = (token, persistent = false) => {
189
- try {
190
- const storage = persistent ? localStorage : sessionStorage;
191
- storage.setItem(CONVERSATION_TOKEN_KEY, token);
192
- }
193
- catch (error) {
194
- // Silent fail - storage might not be available
195
- }
196
- };
197
- const getStoredConversationToken = (persistent = false) => {
198
- try {
199
- const storage = persistent ? localStorage : sessionStorage;
200
- return storage.getItem(CONVERSATION_TOKEN_KEY);
201
- }
202
- catch (error) {
203
- // Silent fail - storage might not be available
204
- return null;
205
- }
206
- };
207
- const clearStoredConversationToken = (persistent = false) => {
208
- try {
209
- const storage = persistent ? localStorage : sessionStorage;
210
- storage.removeItem(CONVERSATION_TOKEN_KEY);
211
- }
212
- catch (error) {
213
- // Silent fail - storage might not be available
214
- }
215
- };
216
- // Custom hook for mobile detection
217
- const useIsMobile = () => {
218
- const [isMobile, setIsMobile] = (0, react_1.useState)(false);
219
- (0, react_1.useEffect)(() => {
220
- const checkIsMobile = () => {
221
- const userAgent = navigator.userAgent || navigator.vendor || window.opera;
222
- const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i;
223
- const isMobileDevice = mobileRegex.test(userAgent.toLowerCase());
224
- const isSmallScreen = window.innerWidth <= 768;
225
- setIsMobile(isMobileDevice || isSmallScreen);
226
- };
227
- checkIsMobile();
228
- window.addEventListener('resize', checkIsMobile);
229
- return () => window.removeEventListener('resize', checkIsMobile);
230
- }, []);
231
- return isMobile;
232
- };
233
- exports.BlumessageChat = (0, react_1.forwardRef)(({ apiKey, placeholder = "Type your message...", theme = 'light', width, height, size = 'medium', name = "Blumessage AI", subtitle = "Online • Instant responses", initialMessages = [], onUserMessage, onAssistantMessage, token: initialToken, onTokenChange, onChatWidgetOpen, onChatWidgetClosed, onError, persistent = false, showTimestamps = false, typingText = "Agent is typing...", emptyStateText = "Start a conversation!", markdown = true, disableAutoScroll = false,
234
- // Floating button props
235
- floating = true, buttonText = "Chat with us", buttonPosition = 'bottom-right', buttonStyle, defaultOpen = false, maximizeToggleButton = true, fullScreen = false, icon = 'message-circle',
236
- // Styling props
237
- primaryColor = "linear-gradient(to right, #3b82f6,rgb(8, 98, 242))",
238
- // Help bubble props
239
- showHelpBubble = true, helpBubbleMessage = "Have a question? We're here to help!", helpBubbleIcon = 'message-circle-question-mark', helpBubbleIconName, helpBubbleShowAfter = 1, // 1 second
240
- helpBubbleHideAfter = 5, // 5 seconds
241
- helpBubbleBackgroundColor = "#000000", context, }, ref) => {
242
- const [isInitialized, setIsInitialized] = (0, react_1.useState)(false);
243
- const [error, setError] = (0, react_1.useState)(null);
244
- const [messages, setMessages] = (0, react_1.useState)(initialMessages);
245
- const [inputValue, setInputValue] = (0, react_1.useState)('');
246
- const [token, setToken] = (0, react_1.useState)(initialToken || getStoredConversationToken(persistent));
247
- const [isOpen, setIsOpen] = (0, react_1.useState)(defaultOpen);
248
- const [isAnimating, setIsAnimating] = (0, react_1.useState)(false);
249
- const [isLoading, setIsLoading] = (0, react_1.useState)(false);
250
- const [isMaximized, setIsMaximized] = (0, react_1.useState)(false);
251
- const messagesEndRef = (0, react_1.useRef)(null);
252
- const isInitialLoad = (0, react_1.useRef)(true);
253
- const [isHelpBubbleVisible, setIsHelpBubbleVisible] = (0, react_1.useState)(false);
254
- const [isHelpBubbleAnimating, setIsHelpBubbleAnimating] = (0, react_1.useState)(false);
255
- // Mobile detection
256
- const isMobile = useIsMobile();
257
- // Help bubble effect
258
- (0, react_1.useEffect)(() => {
259
- if (!floating || !showHelpBubble)
260
- return;
261
- const showTimer = setTimeout(() => {
262
- setIsHelpBubbleAnimating(true);
263
- setIsHelpBubbleVisible(true);
264
- // Remove animating state after animation completes
265
- setTimeout(() => {
266
- setIsHelpBubbleAnimating(false);
267
- }, 300);
268
- // Hide the bubble after the specified time if hideAfter > 0
269
- if (helpBubbleHideAfter > 0) {
270
- const hideTimer = setTimeout(() => {
271
- setIsHelpBubbleAnimating(true);
272
- setIsHelpBubbleVisible(false);
273
- // Remove animating state after animation completes
274
- setTimeout(() => {
275
- setIsHelpBubbleAnimating(false);
276
- }, 300);
277
- }, helpBubbleHideAfter * 1000);
278
- return () => clearTimeout(hideTimer);
279
- }
280
- }, helpBubbleShowAfter * 1000);
281
- return () => clearTimeout(showTimer);
282
- }, [floating, showHelpBubble, helpBubbleShowAfter, helpBubbleHideAfter]);
283
- // Helper function to format timestamp
284
- const formatTimestamp = (timestamp) => {
285
- const date = new Date(timestamp);
286
- const now = new Date();
287
- // Check if the message is from today
288
- const isToday = date.toDateString() === now.toDateString();
289
- if (isToday) {
290
- // If today: show time only in user's locale format (14:00 or 2:00 PM)
291
- return date.toLocaleTimeString(undefined, {
292
- hour: '2-digit',
293
- minute: '2-digit',
294
- hour12: undefined // Let browser decide based on user's locale
295
- });
296
- }
297
- else {
298
- // If more than a day: show "17 July, 13:00" format in user's locale
299
- const dateStr = date.toLocaleDateString(undefined, {
300
- day: 'numeric',
301
- month: 'long'
302
- });
303
- const timeStr = date.toLocaleTimeString(undefined, {
304
- hour: '2-digit',
305
- minute: '2-digit',
306
- hour12: undefined // Let browser decide based on user's locale
307
- });
308
- return `${dateStr}, ${timeStr}`;
309
- }
310
- };
311
- // Helper function to get dimensions based on size
312
- const getDimensions = () => {
313
- // If custom width/height are provided, use them
314
- if (width && height) {
315
- return { width, height };
316
- }
317
- // Mobile-first responsive dimensions
318
- if (isMobile) {
319
- // On mobile, use fullscreen or near-fullscreen dimensions
320
- if (floating) {
321
- return {
322
- width: 'calc(100vw - 32px)',
323
- height: 'calc(100vh - 100px)'
324
- };
325
- }
326
- else {
327
- return {
328
- width: '100%',
329
- height: '500px'
330
- };
331
- }
332
- }
333
- // Desktop dimensions based on size
334
- let dimensions;
335
- switch (size) {
336
- case 'small':
337
- dimensions = { width: '320px', height: '400px' };
338
- break;
339
- case 'large':
340
- dimensions = { width: '680px', height: '800px' };
341
- break;
342
- case 'medium':
343
- default:
344
- dimensions = { width: '600px', height: '600px' };
345
- }
346
- return dimensions;
347
- };
348
- const { width: actualWidth, height: actualHeight } = getDimensions();
349
- // Function to clear conversation and start fresh
350
- const clearConversation = () => {
351
- updateConversationToken(null);
352
- setMessages([]);
353
- };
354
- // Function to update conversation token and notify parent
355
- const updateConversationToken = (newToken) => {
356
- setToken(newToken);
357
- if (newToken) {
358
- saveConversationToken(newToken, persistent);
359
- }
360
- else {
361
- clearStoredConversationToken(persistent);
362
- }
363
- if (onTokenChange) {
364
- onTokenChange(newToken);
365
- }
366
- };
367
- // Function to fetch conversation history
368
- const fetchConversationHistory = async (convToken) => {
369
- try {
370
- const response = await fetch(`https://api.blumessage.com/api/v1/conversations/session/${convToken}`, {
371
- method: 'GET',
372
- headers: {
373
- 'Authorization': `Bearer ${apiKey}`,
374
- },
375
- });
376
- if (response.ok) {
377
- const sessionData = await response.json();
378
- return sessionData.messages || [];
379
- }
380
- else {
381
- // Clear invalid conversation ID from storage
382
- updateConversationToken(null);
383
- if (onError) {
384
- onError(`Failed to fetch conversation history: ${response.status} ${response.statusText}`, "conversation_history");
385
- }
386
- return [];
387
- }
388
- }
389
- catch (error) {
390
- // Clear invalid conversation ID from storage
391
- updateConversationToken(null);
392
- if (onError) {
393
- onError(`Error fetching conversation history: ${error}`, "conversation_history");
394
- }
395
- return [];
396
- }
397
- };
398
- (0, react_1.useEffect)(() => {
399
- if (!apiKey) {
400
- const errorMessage = "API key is required";
401
- setError(errorMessage);
402
- if (onError) {
403
- onError(errorMessage, "missing_api_key");
404
- }
405
- return;
406
- }
407
- // Validate API key with Blumessage API
408
- const validateApiKey = async () => {
409
- try {
410
- setIsInitialized(false);
411
- setError(null);
412
- const response = await fetch('https://api.blumessage.com/api/v1/api-keys/validate', {
413
- method: 'POST',
414
- headers: {
415
- 'Content-Type': 'application/json',
416
- },
417
- body: JSON.stringify({ apiKey }),
418
- });
419
- if (response.ok) {
420
- setIsInitialized(true);
421
- setError(null);
422
- }
423
- else {
424
- const errorMessage = "Unable to connect - invalid API key";
425
- setError(errorMessage);
426
- setIsInitialized(false);
427
- if (onError) {
428
- onError(errorMessage, "api_key_validation");
429
- }
430
- }
431
- }
432
- catch (err) {
433
- const errorMessage = "Unable to connect - network error";
434
- setError(errorMessage);
435
- setIsInitialized(false);
436
- if (onError) {
437
- onError(errorMessage, "network_error");
438
- }
439
- }
440
- };
441
- validateApiKey();
442
- }, [apiKey]);
443
- // Handle initial message loading (only runs once on mount)
444
- (0, react_1.useEffect)(() => {
445
- const loadInitialMessages = async () => {
446
- if (isInitialLoad.current) {
447
- isInitialLoad.current = false;
448
- // Handle message initialization based on conversation state
449
- if (initialMessages.length > 0 && token) {
450
- // Warn if both are provided - initialMessages takes precedence
451
- setMessages(initialMessages);
452
- }
453
- else if (initialMessages.length > 0) {
454
- // Use provided initial messages
455
- setMessages(initialMessages);
456
- }
457
- else if (token) {
458
- // Fetch conversation history if we have a stored conversation ID
459
- const historyMessages = await fetchConversationHistory(token);
460
- setMessages(historyMessages); // Will be empty array if fetch failed
461
- }
462
- else {
463
- // No conversation ID and no initial messages - start with empty array
464
- setMessages([]);
465
- }
466
- }
467
- };
468
- // Only load initial messages after API key is validated
469
- if (isInitialized) {
470
- loadInitialMessages();
471
- }
472
- }, [isInitialized]);
473
- const handleSendMessage = async () => {
474
- if (!inputValue.trim() || isLoading)
475
- return;
476
- const userMessage = {
477
- id: Date.now().toString(),
478
- content: inputValue.trim(),
479
- role: 'user',
480
- timestamp: Date.now()
481
- };
482
- // Add user message to UI immediately
483
- setMessages(prev => [...prev, userMessage]);
484
- const currentInput = inputValue.trim();
485
- setInputValue('');
486
- setIsLoading(true);
487
- // Call the callback if provided
488
- if (onUserMessage) {
489
- onUserMessage(userMessage);
490
- }
491
- try {
492
- // Call Blumessage API
493
- const requestBody = {
494
- message: currentInput,
495
- };
496
- // Include token if we have one
497
- if (token) {
498
- requestBody.token = token;
499
- }
500
- // Include user context if provided
501
- if (context && Object.keys(context).length > 0) {
502
- requestBody.context = context;
503
- }
504
- const response = await fetch('https://api.blumessage.com/api/v1/conversations', {
505
- method: 'POST',
506
- headers: {
507
- 'Content-Type': 'application/json',
508
- 'Authorization': `Bearer ${apiKey}`,
509
- },
510
- body: JSON.stringify(requestBody),
511
- });
512
- if (response.ok) {
513
- const apiResponse = await response.json();
514
- // Update token from response (for both first message and continuing conversations)
515
- updateConversationToken(apiResponse.token);
516
- // Find the assistant's response (the last message that's not from user)
517
- const assistantMessages = apiResponse.messages.filter(msg => msg.role === 'assistant');
518
- const latestAssistantMessage = assistantMessages[assistantMessages.length - 1];
519
- if (latestAssistantMessage) {
520
- // Add assistant's response to messages
521
- const assistantResponse = {
522
- id: (Date.now() + 1).toString(),
523
- content: latestAssistantMessage.content,
524
- role: 'assistant',
525
- timestamp: latestAssistantMessage.timestamp,
526
- };
527
- setMessages(prev => [...prev, assistantResponse]);
528
- if (onAssistantMessage) {
529
- onAssistantMessage(assistantResponse);
530
- }
531
- }
532
- }
533
- else {
534
- // If token is invalid, clear it
535
- if (response.status === 401 || response.status === 403) {
536
- updateConversationToken(null);
537
- }
538
- // Add error message
539
- const errorMessage = {
540
- id: (Date.now() + 1).toString(),
541
- content: "Sorry, I'm having trouble connecting right now. Please try again.",
542
- role: 'assistant',
543
- timestamp: Date.now(),
544
- };
545
- setMessages(prev => [...prev, errorMessage]);
546
- if (onError) {
547
- onError(`Failed to send message: ${response.status} ${response.statusText}`, "message_send");
548
- }
549
- }
550
- }
551
- catch (error) {
552
- // Add error message
553
- const errorMessage = {
554
- id: (Date.now() + 1).toString(),
555
- content: "Sorry, I'm having trouble connecting right now. Please try again.",
556
- role: 'assistant',
557
- timestamp: Date.now(),
558
- };
559
- setMessages(prev => [...prev, errorMessage]);
560
- if (onError) {
561
- onError(`Error sending message: ${error}`, "message_send");
562
- }
563
- }
564
- finally {
565
- setIsLoading(false);
566
- }
567
- };
568
- const handleKeyPress = (e) => {
569
- if (e.key === 'Enter' && !e.shiftKey && !isLoading) {
570
- e.preventDefault();
571
- handleSendMessage();
572
- }
573
- };
574
- // Auto-scroll to bottom when messages change
575
- (0, react_1.useEffect)(() => {
576
- if (!disableAutoScroll) {
577
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
578
- }
579
- }, [messages]);
580
- // Auto-scroll to bottom when chat widget opens
581
- (0, react_1.useEffect)(() => {
582
- if (isOpen && floating && !disableAutoScroll) {
583
- // Add a small delay to ensure the DOM is updated after the animation starts
584
- setTimeout(() => {
585
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
586
- }, 50);
587
- }
588
- }, [isOpen, floating, disableAutoScroll]);
589
- // Handle opening the chat with animation
590
- const handleOpenChat = () => {
591
- if (!isOpen && !isAnimating) {
592
- setIsAnimating(true);
593
- // Small delay to ensure smooth animation start
594
- setTimeout(() => {
595
- setIsOpen(true);
596
- // Call the callback after the chat is open
597
- if (onChatWidgetOpen) {
598
- onChatWidgetOpen();
599
- }
600
- }, 10);
601
- // Animation duration
602
- setTimeout(() => {
603
- setIsAnimating(false);
604
- }, 300);
605
- }
606
- };
607
- // Handle closing the chat with animation
608
- const handleCloseChat = () => {
609
- if (isOpen && !isAnimating) {
610
- setIsAnimating(true);
611
- setIsOpen(false);
612
- // Reset maximized state when closing
613
- setIsMaximized(false);
614
- // Call the callback when closing
615
- if (onChatWidgetClosed) {
616
- onChatWidgetClosed();
617
- }
618
- // Wait for animation to complete
619
- setTimeout(() => {
620
- setIsAnimating(false);
621
- }, 300);
622
- }
623
- };
624
- // Handle maximize/minimize toggle
625
- const handleToggleMaximize = () => {
626
- setIsMaximized(!isMaximized);
627
- };
628
- // Helper function to get any Lucide React icon by name
629
- const getLucideIcon = (iconName) => {
630
- if (!iconName)
631
- return LucideIcons.MessageCircle;
632
- // Convert icon name to PascalCase (e.g., "message-circle" -> "MessageCircle")
633
- const pascalCaseName = iconName
634
- .split(/[-_\s]+/)
635
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
636
- .join('');
637
- // Return the icon component if it exists, otherwise fallback to MessageCircle
638
- const IconComponent = LucideIcons[pascalCaseName];
639
- if (!IconComponent) {
640
- }
641
- return (IconComponent || LucideIcons.MessageCircle);
642
- };
643
- // Get the help bubble icon component based on the helpBubbleIcon or helpBubbleIconName prop
644
- const getHelpBubbleIconComponent = () => {
645
- // If helpBubbleIconName is provided, use it directly
646
- if (helpBubbleIconName) {
647
- return getLucideIcon(helpBubbleIconName);
648
- }
649
- // Otherwise use the helpBubbleIcon prop
650
- return getLucideIcon(helpBubbleIcon);
651
- };
652
- // Helper function to determine if a color is dark (for text contrast)
653
- const isColorDark = (color) => {
654
- // Handle hex colors
655
- if (color.startsWith('#')) {
656
- const hex = color.replace('#', '');
657
- const r = parseInt(hex.substr(0, 2), 16);
658
- const g = parseInt(hex.substr(2, 2), 16);
659
- const b = parseInt(hex.substr(4, 2), 16);
660
- // Calculate luminance
661
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
662
- return luminance < 0.5;
663
- }
664
- // Handle rgb/rgba colors
665
- if (color.startsWith('rgb')) {
666
- const match = color.match(/\d+/g);
667
- if (match && match.length >= 3) {
668
- const r = parseInt(match[0]);
669
- const g = parseInt(match[1]);
670
- const b = parseInt(match[2]);
671
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
672
- return luminance < 0.5;
673
- }
674
- }
675
- // Default to assuming light color for unknown formats
676
- return false;
677
- };
678
- // Helper function to get appropriate text color based on background
679
- const getTextColor = (backgroundColor, theme) => {
680
- // If custom background color is provided, determine text color based on brightness
681
- if (backgroundColor && backgroundColor !== '#ffffff' && backgroundColor !== '#1f2937') {
682
- return isColorDark(backgroundColor) ? 'blumessage-text-white' : 'blumessage-text-gray-800';
683
- }
684
- // Fall back to theme-based colors
685
- return theme === 'dark' ? 'blumessage-text-gray-100' : 'blumessage-text-gray-800';
686
- };
687
- // Get the icon component based on the icon prop
688
- const getIconComponent = () => {
689
- return getLucideIcon(icon);
690
- };
691
- // Expose methods to parent component via ref
692
- (0, react_1.useImperativeHandle)(ref, () => ({
693
- sendMessage: async (message) => {
694
- if (!message.trim() || isLoading)
695
- return;
696
- const userMessage = {
697
- id: Date.now().toString(),
698
- content: message.trim(),
699
- role: 'user',
700
- timestamp: Date.now()
701
- };
702
- // Add user message to UI immediately
703
- setMessages(prev => [...prev, userMessage]);
704
- const currentInput = message.trim();
705
- setIsLoading(true);
706
- // Call the callback if provided
707
- if (onUserMessage) {
708
- onUserMessage(userMessage);
709
- }
710
- try {
711
- // Call Blumessage API
712
- const requestBody = {
713
- message: currentInput,
714
- };
715
- // Include token if we have one
716
- if (token) {
717
- requestBody.token = token;
718
- }
719
- // Include user context if provided
720
- if (context && Object.keys(context).length > 0) {
721
- requestBody.context = context;
722
- }
723
- const response = await fetch('https://api.blumessage.com/api/v1/conversations', {
724
- method: 'POST',
725
- headers: {
726
- 'Content-Type': 'application/json',
727
- 'Authorization': `Bearer ${apiKey}`,
728
- },
729
- body: JSON.stringify(requestBody),
730
- });
731
- if (response.ok) {
732
- const apiResponse = await response.json();
733
- // Update token from response (for both first message and continuing conversations)
734
- updateConversationToken(apiResponse.token);
735
- // Find the assistant's response (the last message that's not from user)
736
- const assistantMessages = apiResponse.messages.filter(msg => msg.role === 'assistant');
737
- const latestAssistantMessage = assistantMessages[assistantMessages.length - 1];
738
- if (latestAssistantMessage) {
739
- // Add assistant's response to messages
740
- const assistantResponse = {
741
- id: (Date.now() + 1).toString(),
742
- content: latestAssistantMessage.content,
743
- role: 'assistant',
744
- timestamp: latestAssistantMessage.timestamp,
745
- };
746
- setMessages(prev => [...prev, assistantResponse]);
747
- if (onAssistantMessage) {
748
- onAssistantMessage(assistantResponse);
749
- }
750
- }
751
- }
752
- else {
753
- // If token is invalid, clear it
754
- if (response.status === 401 || response.status === 403) {
755
- updateConversationToken(null);
756
- }
757
- // Add error message
758
- const errorMessage = {
759
- id: (Date.now() + 1).toString(),
760
- content: "Sorry, I'm having trouble connecting right now. Please try again.",
761
- role: 'assistant',
762
- timestamp: Date.now(),
763
- };
764
- setMessages(prev => [...prev, errorMessage]);
765
- if (onError) {
766
- onError(`Failed to send message: ${response.status} ${response.statusText}`, "message_send");
767
- }
768
- }
769
- }
770
- catch (error) {
771
- // Add error message
772
- const errorMessage = {
773
- id: (Date.now() + 1).toString(),
774
- content: "Sorry, I'm having trouble connecting right now. Please try again.",
775
- role: 'assistant',
776
- timestamp: Date.now(),
777
- };
778
- setMessages(prev => [...prev, errorMessage]);
779
- if (onError) {
780
- onError(`Error sending message: ${error}`, "message_send");
781
- }
782
- }
783
- finally {
784
- setIsLoading(false);
785
- }
786
- },
787
- openChat: () => {
788
- if (!isOpen && !isAnimating) {
789
- setIsAnimating(true);
790
- // Small delay to ensure smooth animation start
791
- setTimeout(() => {
792
- setIsOpen(true);
793
- // Call the callback after the chat is open
794
- if (onChatWidgetOpen) {
795
- onChatWidgetOpen();
796
- }
797
- }, 10);
798
- // Animation duration
799
- setTimeout(() => {
800
- setIsAnimating(false);
801
- }, 300);
802
- }
803
- },
804
- closeChat: () => {
805
- if (isOpen && !isAnimating) {
806
- setIsAnimating(true);
807
- setIsOpen(false);
808
- // Reset maximized state when closing
809
- setIsMaximized(false);
810
- // Call the callback when closing
811
- if (onChatWidgetClosed) {
812
- onChatWidgetClosed();
813
- }
814
- // Wait for animation to complete
815
- setTimeout(() => {
816
- setIsAnimating(false);
817
- }, 300);
818
- }
819
- },
820
- clearConversation: () => {
821
- setMessages([]);
822
- updateConversationToken(null);
823
- },
824
- getMessages: () => messages,
825
- getToken: () => token,
826
- }));
827
- // Helper function to get position styles for floating button
828
- const getButtonPositionStyles = () => {
829
- const baseStyles = {
830
- position: 'fixed',
831
- zIndex: 1000,
832
- opacity: isOpen ? 0 : 1,
833
- pointerEvents: isOpen ? 'none' : 'auto',
834
- transition: 'opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
835
- };
836
- // Mobile-specific positioning
837
- if (isMobile) {
838
- return {
839
- ...baseStyles,
840
- bottom: '20px',
841
- right: '20px',
842
- left: 'auto',
843
- top: 'auto'
844
- };
845
- }
846
- // Desktop positioning
847
- switch (buttonPosition) {
848
- case 'bottom-right':
849
- return { ...baseStyles, bottom: '24px', right: '24px' };
850
- case 'bottom-left':
851
- return { ...baseStyles, bottom: '24px', left: '24px' };
852
- case 'top-right':
853
- return { ...baseStyles, top: '24px', right: '24px' };
854
- case 'top-left':
855
- return { ...baseStyles, top: '24px', left: '24px' };
856
- default:
857
- return { ...baseStyles, bottom: '24px', right: '24px' };
858
- }
859
- };
860
- // Helper function to get position styles for help bubble
861
- const getHelpBubblePositionStyles = () => {
862
- const baseStyles = {
863
- position: 'fixed',
864
- zIndex: 999,
865
- };
866
- // Mobile-specific positioning
867
- if (isMobile) {
868
- return {
869
- ...baseStyles,
870
- bottom: '80px',
871
- right: '20px',
872
- left: 'auto',
873
- top: 'auto'
874
- };
875
- }
876
- // Desktop positioning - position above the button
877
- switch (buttonPosition) {
878
- case 'bottom-right':
879
- return { ...baseStyles, bottom: '80px', right: '24px' };
880
- case 'bottom-left':
881
- return { ...baseStyles, bottom: '80px', left: '24px' };
882
- case 'top-right':
883
- return { ...baseStyles, top: '80px', right: '24px' };
884
- case 'top-left':
885
- return { ...baseStyles, top: '80px', left: '24px' };
886
- default:
887
- return { ...baseStyles, bottom: '80px', right: '24px' };
888
- }
889
- };
890
- // Helper function to get position styles for floating chat window
891
- const getChatPositionStyles = () => {
892
- const getTransform = () => {
893
- if (!isOpen) {
894
- // Slide down for bottom positions, slide up for top positions
895
- return buttonPosition?.includes('bottom') ? 'translateY(100%)' : 'translateY(-100%)';
896
- }
897
- return 'translateY(0)';
898
- };
899
- const baseStyles = {
900
- position: 'fixed',
901
- zIndex: 999,
902
- transform: getTransform(),
903
- transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
904
- opacity: isOpen ? 1 : 0,
905
- pointerEvents: isOpen ? 'auto' : 'none',
906
- };
907
- // Mobile-specific positioning (always fullscreen on mobile)
908
- if (isMobile) {
909
- return {
910
- ...baseStyles,
911
- top: '0',
912
- left: '0',
913
- right: '0',
914
- bottom: '0',
915
- transform: isOpen ? 'translateY(0)' : 'translateY(100%)',
916
- transformOrigin: 'bottom center',
917
- };
918
- }
919
- // If fullScreen or maximized, override positioning to be centered and full screen
920
- if (fullScreen || isMaximized) {
921
- // Set transform origin and direction based on button position for proper animation
922
- let transformOrigin = 'center';
923
- let closedTransform = 'translateY(100%)';
924
- switch (buttonPosition) {
925
- case 'bottom-right':
926
- transformOrigin = 'bottom right';
927
- closedTransform = 'translateY(100%)';
928
- break;
929
- case 'bottom-left':
930
- transformOrigin = 'bottom left';
931
- closedTransform = 'translateY(100%)';
932
- break;
933
- case 'top-right':
934
- transformOrigin = 'top right';
935
- closedTransform = 'translateY(-100%)';
936
- break;
937
- case 'top-left':
938
- transformOrigin = 'top left';
939
- closedTransform = 'translateY(-100%)';
940
- break;
941
- default:
942
- transformOrigin = 'bottom right';
943
- closedTransform = 'translateY(100%)';
944
- }
945
- return {
946
- ...baseStyles,
947
- top: '20px',
948
- left: '20px',
949
- right: '20px',
950
- bottom: '20px',
951
- transform: isOpen ? 'translateY(0)' : closedTransform,
952
- transformOrigin,
953
- };
954
- }
955
- // Desktop positioning
956
- switch (buttonPosition) {
957
- case 'bottom-right':
958
- return { ...baseStyles, bottom: '24px', right: '24px', transformOrigin: 'bottom right' };
959
- case 'bottom-left':
960
- return { ...baseStyles, bottom: '24px', left: '24px', transformOrigin: 'bottom left' };
961
- case 'top-right':
962
- return { ...baseStyles, top: '24px', right: '24px', transformOrigin: 'top right' };
963
- case 'top-left':
964
- return { ...baseStyles, top: '24px', left: '24px', transformOrigin: 'top left' };
965
- default:
966
- return { ...baseStyles, bottom: '24px', right: '24px', transformOrigin: 'bottom right' };
967
- }
968
- };
969
- const renderMessage = (message) => {
970
- return ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-flex blumessage-gap-3 blumessage-mb-4 ${message.role === 'user' ? 'blumessage-flex-row-reverse' : ''}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-flex-col blumessage-gap-1 blumessage-max-w-[80%]", children: [(0, jsx_runtime_1.jsx)("div", { className: `blumessage-rounded-lg blumessage-px-4 blumessage-py-2 ${message.role === 'user'
971
- ? 'blumessage-bg-gradient-to-r blumessage-from-blue-500 blumessage-to-purple-600 blumessage-text-white'
972
- : theme === 'light'
973
- ? 'blumessage-bg-gray-100'
974
- : 'blumessage-bg-gray-700'}`, children: message.role === 'assistant' ? ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-prose ${theme === 'dark' ? 'blumessage-text-white' : 'blumessage-text-gray-900'}`, children: markdown ? ((0, jsx_runtime_1.jsx)(react_markdown_1.default, { remarkPlugins: [remark_gfm_1.default], children: message.content }, `assistant-${message.id}`)) : ((0, jsx_runtime_1.jsx)("p", { className: "blumessage-text-sm blumessage-whitespace-pre-wrap", children: message.content })) })) : ((0, jsx_runtime_1.jsx)("div", { className: "blumessage-text-sm blumessage-whitespace-pre-wrap blumessage-text-white", children: markdown ? ((0, jsx_runtime_1.jsx)(react_markdown_1.default, { remarkPlugins: [remark_gfm_1.default], children: message.content }, `user-${message.id}`)) : ((0, jsx_runtime_1.jsx)("p", { className: "blumessage-text-sm blumessage-whitespace-pre-wrap", children: message.content })) })) }), showTimestamps && ((0, jsx_runtime_1.jsx)("span", { className: "blumessage-text-xs blumessage-text-gray-500", children: formatTimestamp(message.timestamp) }))] }) }, message.id));
975
- };
976
- // Render chat window component
977
- const renderChatWindow = () => {
978
- const isFullscreenMode = fullScreen || isMaximized || isMobile;
979
- const chatContent = ((0, jsx_runtime_1.jsxs)("div", { className: `blumessage-shadow-2xl blumessage-flex blumessage-flex-col blumessage-overflow-hidden blumessage-border ${isMobile ? 'blumessage-mobile-fullscreen' : ''} ${theme === 'dark'
980
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
981
- : 'blumessage-bg-white blumessage-border-black/10'}`, style: {
982
- width: isFullscreenMode ? '100%' : actualWidth,
983
- height: isFullscreenMode ? '100%' : actualHeight,
984
- fontFamily: 'system-ui, -apple-system, sans-serif',
985
- borderRadius: isMobile ? '0' : (isFullscreenMode ? '12px' : '24px')
986
- }, children: [(0, jsx_runtime_1.jsxs)("div", { className: `blumessage-border-b ${theme === 'dark'
987
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
988
- : 'blumessage-bg-white blumessage-border-gray-100'}`, style: {
989
- display: 'blumessage-flex',
990
- alignItems: 'center',
991
- padding: isMobile ? '12px 16px' : '16px 24px'
992
- }, children: [(0, jsx_runtime_1.jsx)("div", { className: "blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center", style: {
993
- backgroundImage: primaryColor,
994
- width: isMobile ? '40px' : '48px',
995
- height: isMobile ? '40px' : '48px',
996
- marginRight: isMobile ? '16px' : '24px'
997
- }, children: react_1.default.createElement(getIconComponent(), {
998
- className: isMobile ? "blumessage-w-5 blumessage-h-5 blumessage-text-white" : "blumessage-w-6 blumessage-h-6 blumessage-text-white"
999
- }) }), (0, jsx_runtime_1.jsxs)("div", { style: { flex: 1 }, children: [(0, jsx_runtime_1.jsx)("div", { className: `${isMobile ? 'blumessage-text-base' : 'blumessage-text-lg'} blumessage-font-semibold blumessage-m-0 blumessage-leading-6 ${theme === 'dark' ? 'blumessage-text-gray-100' : 'blumessage-text-gray-900'}`, children: name }), (0, jsx_runtime_1.jsx)("div", { className: `blumessage-text-sm blumessage-m-0 blumessage-leading-5 ${theme === 'dark' ? 'blumessage-text-gray-400' : 'blumessage-text-gray-500'}`, children: subtitle })] }), floating && ((0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-items-center blumessage-gap-1", children: [maximizeToggleButton && !isMobile && ((0, jsx_runtime_1.jsx)("button", { onClick: handleToggleMaximize, className: `blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-transition-colors ${theme === 'dark'
1000
- ? 'hover:blumessage-bg-gray-800 blumessage-text-gray-400 hover:blumessage-text-gray-200'
1001
- : 'hover:blumessage-bg-gray-100 blumessage-text-gray-500 hover:blumessage-text-gray-700'}`, children: isMaximized ? (0, jsx_runtime_1.jsx)(LucideIcons.Minimize2, { className: "blumessage-w-4 blumessage-h-4" }) : (0, jsx_runtime_1.jsx)(LucideIcons.Maximize, { className: "blumessage-w-4 blumessage-h-4" }) })), (0, jsx_runtime_1.jsx)("button", { onClick: handleCloseChat, className: `blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-transition-colors ${theme === 'dark'
1002
- ? 'hover:blumessage-bg-gray-800 blumessage-text-gray-400 hover:blumessage-text-gray-200'
1003
- : 'hover:blumessage-bg-gray-100 blumessage-text-gray-500 hover:blumessage-text-gray-700'}`, children: (0, jsx_runtime_1.jsx)(LucideIcons.X, { className: "blumessage-w-4 blumessage-h-4" }) })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: `blumessage-flex-1 blumessage-overflow-y-auto ${theme === 'dark' ? 'blumessage-bg-gray-800' : 'blumessage-bg-gray-50'}`, style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: [messages.map((message) => ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-flex ${message.role === 'user' ? 'blumessage-justify-end' : 'blumessage-justify-start'} blumessage-mb-4`, children: (0, jsx_runtime_1.jsx)("div", { className: `${isMobile ? 'blumessage-max-w-[85%]' : 'blumessage-max-w-[80%]'} blumessage-inline-block ${message.role === 'user' ? '' : 'blumessage-text-left'}`, children: (0, jsx_runtime_1.jsxs)("div", { className: `blumessage-inline-block blumessage-px-4 blumessage-py-3 blumessage-rounded-2xl blumessage-text-sm blumessage-leading-6 blumessage-text-left ${message.role === 'user'
1004
- ? 'blumessage-text-white'
1005
- : theme === 'dark'
1006
- ? 'blumessage-bg-gray-700 blumessage-text-gray-100 blumessage-border blumessage-border-gray-600 blumessage-shadow-sm'
1007
- : 'blumessage-bg-white blumessage-text-gray-800 blumessage-border blumessage-border-gray-200 blumessage-shadow-sm'}`, style: message.role === 'user' ? { backgroundImage: primaryColor } : {}, children: [message.role === 'assistant' ? (markdown ? ((0, jsx_runtime_1.jsx)(react_markdown_1.default, { remarkPlugins: [remark_gfm_1.default], children: message.content }, `assistant-${message.id}`)) : ((0, jsx_runtime_1.jsx)("div", { className: "blumessage-whitespace-pre-wrap", children: message.content }))) : ((0, jsx_runtime_1.jsx)("div", { className: "blumessage-inline-block", children: markdown ? ((0, jsx_runtime_1.jsx)(react_markdown_1.default, { remarkPlugins: [remark_gfm_1.default], children: message.content }, `user-${message.id}`)) : ((0, jsx_runtime_1.jsx)("div", { className: "blumessage-whitespace-pre-wrap", children: message.content })) })), showTimestamps && ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-text-[10px] blumessage-mt-1 blumessage-opacity-75 ${message.role === 'user'
1008
- ? 'blumessage-text-white/75'
1009
- : theme === 'dark'
1010
- ? 'blumessage-text-gray-400'
1011
- : 'blumessage-text-gray-500'}`, style: {
1012
- textAlign: message.role === 'user' ? 'right' : 'left'
1013
- }, children: formatTimestamp(message.timestamp) }))] }) }) }, message.id))), isLoading && ((0, jsx_runtime_1.jsx)("div", { className: "blumessage-flex blumessage-justify-start blumessage-mb-4", children: (0, jsx_runtime_1.jsx)("div", { className: `blumessage-px-4 blumessage-py-3 blumessage-rounded-2xl ${isMobile ? 'blumessage-max-w-[85%]' : 'blumessage-max-w-[80%]'} blumessage-text-sm blumessage-leading-6 blumessage-shadow-sm ${theme === 'dark'
1014
- ? 'blumessage-bg-gray-700 blumessage-text-gray-100 blumessage-border blumessage-border-gray-600'
1015
- : 'blumessage-bg-white blumessage-text-gray-800 blumessage-border blumessage-border-gray-200'}`, children: (0, jsx_runtime_1.jsx)("div", { className: `${theme === 'dark' ? 'blumessage-text-gray-300' : 'blumessage-text-gray-600'}`, style: { fontStyle: 'italic' }, children: typingText }) }) })), messages.length === 0 && !isLoading && ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-text-center blumessage-text-sm blumessage-py-8 ${theme === 'dark' ? 'blumessage-text-gray-400' : 'blumessage-text-gray-500'}`, children: emptyStateText })), (0, jsx_runtime_1.jsx)("div", { ref: messagesEndRef })] }), (0, jsx_runtime_1.jsx)("div", { className: `blumessage-border-t ${theme === 'dark'
1016
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
1017
- : 'blumessage-bg-white blumessage-border-gray-100'}`, style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: (0, jsx_runtime_1.jsxs)("div", { className: `blumessage-flex blumessage-items-center blumessage-rounded-2xl blumessage-px-4 blumessage-py-3 blumessage-border ${theme === 'dark'
1018
- ? 'blumessage-bg-gray-800 blumessage-border-gray-600'
1019
- : 'blumessage-bg-gray-50 blumessage-border-gray-200'}`, children: [(0, jsx_runtime_1.jsx)("textarea", { className: `blumessage-flex-1 blumessage-border-none blumessage-bg-transparent blumessage-outline-none blumessage-text-sm blumessage-font-inherit blumessage-resize-none ${theme === 'dark' ? 'blumessage-text-gray-100' : 'blumessage-text-gray-700'}`, placeholder: placeholder, value: inputValue, onChange: (e) => {
1020
- const newValue = e.target.value;
1021
- if (newValue.length <= 1000) {
1022
- setInputValue(newValue);
1023
- }
1024
- }, onPaste: (e) => {
1025
- e.preventDefault();
1026
- const pastedText = e.clipboardData.getData('text');
1027
- const currentValue = inputValue;
1028
- const newValue = currentValue + pastedText;
1029
- if (newValue.length <= 1000) {
1030
- setInputValue(newValue);
1031
- }
1032
- else {
1033
- // Truncate to exactly 1000 characters
1034
- const truncatedValue = newValue.substring(0, 1000);
1035
- setInputValue(truncatedValue);
1036
- }
1037
- }, onKeyDown: handleKeyPress, rows: 1, maxLength: 1000, style: {
1038
- minHeight: '20px',
1039
- maxHeight: isMobile ? '100px' : '120px',
1040
- overflowY: inputValue.split('\n').length > 4 ? 'auto' : 'hidden',
1041
- lineHeight: '1.5',
1042
- paddingTop: '2px',
1043
- paddingBottom: '2px'
1044
- }, onInput: (e) => {
1045
- const target = e.target;
1046
- target.style.height = 'auto';
1047
- target.style.height = Math.min(target.scrollHeight, isMobile ? 100 : 120) + 'px';
1048
- } }), (0, jsx_runtime_1.jsx)("button", { className: "blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-border-none blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-ml-2 blumessage-text-white blumessage-transition-all disabled:blumessage-opacity-50 disabled:blumessage-cursor-not-allowed", style: { backgroundImage: primaryColor }, onClick: handleSendMessage, disabled: !inputValue.trim() || isLoading, children: isLoading ? ((0, jsx_runtime_1.jsx)(LucideIcons.Loader2, { className: "blumessage-w-4 blumessage-h-4 blumessage-animate-spin" })) : ((0, jsx_runtime_1.jsx)(LucideIcons.Send, { className: "blumessage-w-4 blumessage-h-4" })) })] }) })] }));
1049
- if (floating) {
1050
- return ((0, jsx_runtime_1.jsx)("div", { style: getChatPositionStyles(), children: chatContent }));
1051
- }
1052
- return chatContent;
1053
- };
1054
- // Don't render anything if no API key is provided
1055
- if (!apiKey) {
1056
- return null;
1057
- }
1058
- if (error) {
1059
- const isFullscreenMode = fullScreen || isMaximized || isMobile;
1060
- const errorContent = ((0, jsx_runtime_1.jsxs)("div", { className: `blumessage-shadow-2xl blumessage-flex blumessage-flex-col blumessage-overflow-hidden blumessage-border ${isMobile ? 'blumessage-mobile-fullscreen' : ''} ${theme === 'dark'
1061
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
1062
- : 'blumessage-bg-white blumessage-border-black/10'}`, style: {
1063
- width: isFullscreenMode ? '100%' : actualWidth,
1064
- height: isFullscreenMode ? '100%' : actualHeight,
1065
- fontFamily: 'system-ui, -apple-system, sans-serif',
1066
- borderRadius: isMobile ? '0' : (isFullscreenMode ? '12px' : '24px')
1067
- }, children: [(0, jsx_runtime_1.jsxs)("div", { className: `blumessage-flex blumessage-items-center blumessage-border-b ${theme === 'dark'
1068
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
1069
- : 'blumessage-bg-white blumessage-border-gray-100'}`, style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: [(0, jsx_runtime_1.jsx)("div", { className: "blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center", style: {
1070
- backgroundColor: '#ef4444',
1071
- width: isMobile ? '40px' : '48px',
1072
- height: isMobile ? '40px' : '48px',
1073
- marginRight: isMobile ? '16px' : '24px'
1074
- }, children: (0, jsx_runtime_1.jsx)(LucideIcons.AlertTriangle, { className: isMobile ? "blumessage-w-5 blumessage-h-5 blumessage-text-white" : "blumessage-w-6 blumessage-h-6 blumessage-text-white" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex-1", children: [(0, jsx_runtime_1.jsx)("div", { className: `${isMobile ? 'blumessage-text-base' : 'blumessage-text-lg'} blumessage-font-semibold blumessage-m-0 blumessage-leading-6 ${theme === 'dark' ? 'blumessage-text-gray-100' : 'blumessage-text-gray-900'}`, children: "Connection Error" }), (0, jsx_runtime_1.jsx)("div", { className: `blumessage-text-sm blumessage-m-0 blumessage-leading-5 ${theme === 'dark' ? 'blumessage-text-gray-400' : 'blumessage-text-gray-500'}`, children: "Unable to connect" })] }), floating && ((0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-items-center blumessage-gap-1", children: [maximizeToggleButton && !isMobile && ((0, jsx_runtime_1.jsx)("button", { onClick: handleToggleMaximize, className: `blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-transition-colors ${theme === 'dark'
1075
- ? 'hover:blumessage-bg-gray-800 blumessage-text-gray-400 hover:blumessage-text-gray-200'
1076
- : 'hover:blumessage-bg-gray-100 blumessage-text-gray-500 hover:blumessage-text-gray-700'}`, children: isMaximized ? (0, jsx_runtime_1.jsx)(LucideIcons.Minimize2, { className: "blumessage-w-4 blumessage-h-4" }) : (0, jsx_runtime_1.jsx)(LucideIcons.Maximize, { className: "blumessage-w-4 blumessage-h-4" }) })), (0, jsx_runtime_1.jsx)("button", { onClick: handleCloseChat, className: `blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-transition-colors ${theme === 'dark'
1077
- ? 'hover:blumessage-bg-gray-800 blumessage-text-gray-400 hover:blumessage-text-gray-200'
1078
- : 'hover:blumessage-bg-gray-100 blumessage-text-gray-500 hover:blumessage-text-gray-700'}`, children: (0, jsx_runtime_1.jsx)(LucideIcons.X, { className: "blumessage-w-4 blumessage-h-4" }) })] }))] }), (0, jsx_runtime_1.jsx)("div", { className: `blumessage-flex-1 blumessage-overflow-y-auto ${theme === 'dark' ? 'blumessage-bg-gray-800' : 'blumessage-bg-gray-50'}`, style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: (0, jsx_runtime_1.jsx)("div", { className: "blumessage-flex blumessage-justify-start", children: (0, jsx_runtime_1.jsx)("div", { className: `blumessage-px-4 blumessage-py-3 blumessage-rounded-2xl ${isMobile ? 'blumessage-max-w-[85%]' : 'blumessage-max-w-[80%]'} blumessage-text-sm blumessage-leading-6 blumessage-shadow-sm ${theme === 'dark'
1079
- ? 'blumessage-bg-gray-700 blumessage-text-gray-100 blumessage-border blumessage-border-gray-600'
1080
- : 'blumessage-bg-white blumessage-text-gray-800 blumessage-border blumessage-border-gray-200'}`, children: error }) }) })] }));
1081
- if (floating) {
1082
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { onClick: handleOpenChat, className: `blumessage-text-white blumessage-rounded-full blumessage-shadow-lg blumessage-transition-all blumessage-duration-200 blumessage-flex blumessage-items-center blumessage-justify-center blumessage-gap-2 ${isMobile ? 'blumessage-mobile-button' : ''} ${buttonText ? 'blumessage-px-4 blumessage-py-3 blumessage-h-12' : 'blumessage-w-14 blumessage-h-14'}`, style: { ...getButtonPositionStyles(), ...buttonStyle, backgroundImage: primaryColor }, children: [react_1.default.createElement(getIconComponent(), { className: "blumessage-w-6 blumessage-h-6" }), buttonText && !isMobile && (0, jsx_runtime_1.jsx)("span", { className: "blumessage-text-sm blumessage-font-medium blumessage-whitespace-nowrap", children: buttonText })] }), showHelpBubble && (isHelpBubbleVisible || isHelpBubbleAnimating) && ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-fixed blumessage-z-[999] blumessage-border blumessage-rounded-lg blumessage-shadow-lg blumessage-px-3 blumessage-py-2 blumessage-text-sm blumessage-max-w-[200px] ${isHelpBubbleVisible
1083
- ? (buttonPosition?.includes('top') ? 'blumessage-help-bubble-in-top' : 'blumessage-help-bubble-in')
1084
- : (buttonPosition?.includes('top') ? 'blumessage-help-bubble-out-top' : 'blumessage-help-bubble-out')} ${theme === 'dark'
1085
- ? 'blumessage-bg-gray-800 blumessage-border-gray-600'
1086
- : 'blumessage-bg-white blumessage-border-gray-200'}`, style: {
1087
- ...getHelpBubblePositionStyles(),
1088
- backgroundColor: helpBubbleBackgroundColor || (theme === 'dark' ? '#1f2937' : '#ffffff'),
1089
- transformOrigin: buttonPosition?.includes('bottom') ? 'bottom center' : 'top center',
1090
- }, children: (0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-items-center blumessage-gap-2", children: [react_1.default.createElement(getHelpBubbleIconComponent(), {
1091
- className: "blumessage-w-4 blumessage-h-4 blumessage-flex-shrink-0",
1092
- style: { color: primaryColor.includes('linear-gradient') ? '#3b82f6' : primaryColor }
1093
- }), (0, jsx_runtime_1.jsx)("span", { className: `blumessage-leading-tight ${getTextColor(helpBubbleBackgroundColor || (theme === 'dark' ? '#1f2937' : '#ffffff'), theme)}`, children: helpBubbleMessage })] }) })), (0, jsx_runtime_1.jsx)("div", { style: getChatPositionStyles(), children: errorContent })] }));
1094
- }
1095
- return errorContent;
1096
- }
1097
- if (!isInitialized) {
1098
- const isFullscreenMode = fullScreen || isMaximized || isMobile;
1099
- const loadingContent = ((0, jsx_runtime_1.jsxs)("div", { className: `blumessage-shadow-2xl blumessage-flex blumessage-flex-col blumessage-overflow-hidden blumessage-border ${isMobile ? 'blumessage-mobile-fullscreen' : ''} ${theme === 'dark'
1100
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
1101
- : 'blumessage-bg-white blumessage-border-black/10'}`, style: {
1102
- width: isFullscreenMode ? '100%' : actualWidth,
1103
- height: isFullscreenMode ? '100%' : actualHeight,
1104
- fontFamily: 'system-ui, -apple-system, sans-serif',
1105
- borderRadius: isMobile ? '0' : (isFullscreenMode ? '12px' : '24px')
1106
- }, children: [(0, jsx_runtime_1.jsxs)("div", { className: `blumessage-flex blumessage-items-center blumessage-border-b ${theme === 'dark'
1107
- ? 'blumessage-bg-gray-900 blumessage-border-gray-700'
1108
- : 'blumessage-bg-white blumessage-border-gray-100'}`, style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: [(0, jsx_runtime_1.jsx)("div", { className: "blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center", style: {
1109
- backgroundImage: primaryColor,
1110
- width: isMobile ? '40px' : '48px',
1111
- height: isMobile ? '40px' : '48px',
1112
- marginRight: isMobile ? '16px' : '24px'
1113
- }, children: (0, jsx_runtime_1.jsx)(LucideIcons.Loader2, { className: isMobile ? "blumessage-w-5 blumessage-h-5 blumessage-text-white blumessage-animate-spin" : "blumessage-w-6 blumessage-h-6 blumessage-text-white blumessage-animate-spin" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex-1", children: [(0, jsx_runtime_1.jsx)("div", { className: `${isMobile ? 'blumessage-text-base' : 'blumessage-text-lg'} blumessage-font-semibold blumessage-m-0 blumessage-leading-6 ${theme === 'dark' ? 'blumessage-text-gray-100' : 'blumessage-text-gray-900'}`, children: name }), (0, jsx_runtime_1.jsx)("div", { className: `blumessage-text-sm blumessage-m-0 blumessage-leading-5 ${theme === 'dark' ? 'blumessage-text-gray-400' : 'blumessage-text-gray-500'}`, children: subtitle })] }), floating && ((0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-items-center blumessage-gap-1", children: [maximizeToggleButton && !isMobile && ((0, jsx_runtime_1.jsx)("button", { onClick: handleToggleMaximize, className: `blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-transition-colors ${theme === 'dark'
1114
- ? 'hover:blumessage-bg-gray-800 blumessage-text-gray-400 hover:blumessage-text-gray-200'
1115
- : 'hover:blumessage-bg-gray-100 blumessage-text-gray-500 hover:blumessage-text-gray-700'}`, children: isMaximized ? (0, jsx_runtime_1.jsx)(LucideIcons.Minimize2, { className: "blumessage-w-4 blumessage-h-4" }) : (0, jsx_runtime_1.jsx)(LucideIcons.Maximize, { className: "blumessage-w-4 blumessage-h-4" }) })), (0, jsx_runtime_1.jsx)("button", { onClick: handleCloseChat, className: `blumessage-w-8 blumessage-h-8 blumessage-rounded-full blumessage-flex blumessage-items-center blumessage-justify-center blumessage-cursor-pointer blumessage-transition-colors ${theme === 'dark'
1116
- ? 'hover:blumessage-bg-gray-800 blumessage-text-gray-400 hover:blumessage-text-gray-200'
1117
- : 'hover:blumessage-bg-gray-100 blumessage-text-gray-500 hover:blumessage-text-gray-700'}`, children: (0, jsx_runtime_1.jsx)(LucideIcons.X, { className: "blumessage-w-4 blumessage-h-4" }) })] }))] }), (0, jsx_runtime_1.jsx)("div", { className: `blumessage-flex-1 blumessage-overflow-y-auto ${theme === 'dark' ? 'blumessage-bg-gray-800' : 'blumessage-bg-gray-50'}`, style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: (0, jsx_runtime_1.jsx)("div", { className: `blumessage-text-center blumessage-text-sm blumessage-py-8 ${theme === 'dark' ? 'blumessage-text-gray-400' : 'blumessage-text-gray-500'}`, children: "Ready to chat..." }) })] }));
1118
- if (floating) {
1119
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { onClick: handleOpenChat, className: `blumessage-text-white blumessage-rounded-full blumessage-shadow-lg blumessage-transition-all blumessage-duration-200 blumessage-flex blumessage-items-center blumessage-justify-center blumessage-gap-2 ${isMobile ? 'blumessage-mobile-button' : ''} ${buttonText ? 'blumessage-px-4 blumessage-py-3 blumessage-h-12' : 'blumessage-w-14 blumessage-h-14'}`, style: { ...getButtonPositionStyles(), ...buttonStyle, backgroundImage: primaryColor }, children: [react_1.default.createElement(getIconComponent(), { className: "blumessage-w-6 blumessage-h-6" }), buttonText && !isMobile && (0, jsx_runtime_1.jsx)("span", { className: "blumessage-text-sm blumessage-font-medium blumessage-whitespace-nowrap", children: buttonText })] }), showHelpBubble && (isHelpBubbleVisible || isHelpBubbleAnimating) && ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-fixed blumessage-z-[999] blumessage-border blumessage-rounded-lg blumessage-shadow-lg blumessage-px-3 blumessage-py-2 blumessage-text-sm blumessage-max-w-[200px] ${isHelpBubbleVisible
1120
- ? (buttonPosition?.includes('top') ? 'blumessage-help-bubble-in-top' : 'blumessage-help-bubble-in')
1121
- : (buttonPosition?.includes('top') ? 'blumessage-help-bubble-out-top' : 'blumessage-help-bubble-out')} ${theme === 'dark'
1122
- ? 'blumessage-bg-gray-800 blumessage-border-gray-600'
1123
- : 'blumessage-bg-white blumessage-border-gray-200'}`, style: {
1124
- ...getHelpBubblePositionStyles(),
1125
- backgroundColor: helpBubbleBackgroundColor || (theme === 'dark' ? '#1f2937' : '#ffffff'),
1126
- transformOrigin: buttonPosition?.includes('bottom') ? 'bottom center' : 'top center',
1127
- }, children: (0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-items-center blumessage-gap-2", children: [react_1.default.createElement(getHelpBubbleIconComponent(), {
1128
- className: "blumessage-w-4 blumessage-h-4 blumessage-flex-shrink-0",
1129
- style: { color: primaryColor.includes('linear-gradient') ? '#3b82f6' : primaryColor }
1130
- }), (0, jsx_runtime_1.jsx)("span", { className: `blumessage-leading-tight ${getTextColor(helpBubbleBackgroundColor || (theme === 'dark' ? '#1f2937' : '#ffffff'), theme)}`, children: helpBubbleMessage })] }) })), (0, jsx_runtime_1.jsx)("div", { style: getChatPositionStyles(), children: loadingContent })] }));
1131
- }
1132
- return loadingContent;
1133
- }
1134
- // Main render for initialized state
1135
- if (floating) {
1136
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { onClick: handleOpenChat, className: `blumessage-text-white blumessage-rounded-full blumessage-shadow-lg blumessage-transition-all blumessage-duration-200 blumessage-flex blumessage-items-center blumessage-justify-center blumessage-gap-2 ${isMobile ? 'blumessage-mobile-button' : ''} ${buttonText ? 'blumessage-px-4 blumessage-py-3 blumessage-h-12' : 'blumessage-w-14 blumessage-h-14'}`, style: { ...getButtonPositionStyles(), ...buttonStyle, backgroundImage: primaryColor }, children: [react_1.default.createElement(getIconComponent(), { className: "blumessage-w-6 blumessage-h-6" }), buttonText && !isMobile && (0, jsx_runtime_1.jsx)("span", { className: "blumessage-text-sm blumessage-font-medium blumessage-whitespace-nowrap", children: buttonText })] }), showHelpBubble && (isHelpBubbleVisible || isHelpBubbleAnimating) && ((0, jsx_runtime_1.jsx)("div", { className: `blumessage-fixed blumessage-z-[999] blumessage-border blumessage-rounded-lg blumessage-shadow-lg blumessage-px-3 blumessage-py-2 blumessage-text-sm blumessage-max-w-[200px] ${isHelpBubbleVisible
1137
- ? (buttonPosition?.includes('top') ? 'blumessage-help-bubble-in-top' : 'blumessage-help-bubble-in')
1138
- : (buttonPosition?.includes('top') ? 'blumessage-help-bubble-out-top' : 'blumessage-help-bubble-out')} ${theme === 'dark'
1139
- ? 'blumessage-bg-gray-800 blumessage-border-gray-600'
1140
- : 'blumessage-bg-white blumessage-border-gray-200'}`, style: {
1141
- ...getHelpBubblePositionStyles(),
1142
- backgroundColor: helpBubbleBackgroundColor || (theme === 'dark' ? '#1f2937' : '#ffffff'),
1143
- transformOrigin: buttonPosition?.includes('bottom') ? 'bottom center' : 'top center',
1144
- }, children: (0, jsx_runtime_1.jsxs)("div", { className: "blumessage-flex blumessage-items-center blumessage-gap-2", children: [react_1.default.createElement(getHelpBubbleIconComponent(), {
1145
- className: "blumessage-w-4 blumessage-h-4 blumessage-flex-shrink-0",
1146
- style: { color: primaryColor.includes('linear-gradient') ? '#3b82f6' : primaryColor }
1147
- }), (0, jsx_runtime_1.jsx)("span", { className: `blumessage-leading-tight ${getTextColor(helpBubbleBackgroundColor || (theme === 'dark' ? '#1f2937' : '#ffffff'), theme)}`, children: helpBubbleMessage })] }) })), renderChatWindow()] }));
1148
- }
1149
- return renderChatWindow();
1150
- });