@blumessage/react-chat 1.0.5 → 1.0.7

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.
package/README.md CHANGED
@@ -83,6 +83,7 @@ function App() {
83
83
  | `initialMessages` | `Message[]` | `[]` | Pre-populate with messages |
84
84
  | `conversationId` | `string` | - | Continue specific conversation |
85
85
  | `persistent` | `boolean` | `false` | Use localStorage vs sessionStorage |
86
+ | `showTimestamps` | `boolean` | `false` | Display timestamps: today="14:00", older="17 July, 13:00" |
86
87
  | **Event Callbacks** |
87
88
  | `onUserMessage` | `(message: Message) => void` | - | Called when user sends message |
88
89
  | `onAssistantMessage` | `(message: Message) => void` | - | Called when assistant responds |
@@ -155,6 +156,30 @@ const initialMessages = [
155
156
  />
156
157
  ```
157
158
 
159
+ ### Chat with Timestamps
160
+
161
+ ```tsx
162
+ <BlumessageChat
163
+ apiKey="your-api-key"
164
+ showTimestamps={true} // Shows "14:00" or "17 July, 13:00"
165
+ floating={false}
166
+ initialMessages={[
167
+ {
168
+ id: '1',
169
+ role: 'assistant' as const,
170
+ content: 'Message from today',
171
+ timestamp: Date.now() - (60 * 60 * 1000) // Shows: "14:00" (or "2:00 PM")
172
+ },
173
+ {
174
+ id: '2',
175
+ role: 'user' as const,
176
+ content: 'Message from yesterday',
177
+ timestamp: Date.now() - (24 * 60 * 60 * 1000) // Shows: "17 July, 13:00"
178
+ }
179
+ ]}
180
+ />
181
+ ```
182
+
158
183
  ## Icon Options
159
184
 
160
185
  The `icon` prop accepts **any lucide-react icon name** with flexible naming patterns. The component intelligently matches your input to the appropriate lucide-react icon:
@@ -57,6 +57,15 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
57
57
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
58
58
  import React, { useState, useEffect, useRef } from "react";
59
59
  import { MessageCircle, AlertTriangle, Loader2, Send, X, Maximize, Minimize2, Bot, MessageSquare, Phone, Mail, Headphones, Users, User, Heart, Star, Zap } from "lucide-react";
60
+ // Custom CSS animations that don't depend on Tailwind
61
+ var customStyles = "\n @keyframes bounce {\n 0%, 100% {\n transform: translateY(0);\n }\n 50% {\n transform: translateY(-25%);\n }\n }\n \n @keyframes spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n }\n \n .blumessage-animate-bounce {\n animation: bounce 1s infinite;\n }\n \n .blumessage-animate-spin {\n animation: spin 1s linear infinite;\n }\n";
62
+ // Inject custom styles
63
+ if (typeof document !== 'undefined' && !document.getElementById('blumessage-styles')) {
64
+ var style = document.createElement('style');
65
+ style.id = 'blumessage-styles';
66
+ style.textContent = customStyles;
67
+ document.head.appendChild(style);
68
+ }
60
69
  // Session storage key for conversation ID
61
70
  var CONVERSATION_ID_KEY = 'blumessage_conversation_id';
62
71
  // Utility functions for storage
@@ -92,25 +101,54 @@ var clearStoredConversationId = function (persistent) {
92
101
  }
93
102
  };
94
103
  export var BlumessageChat = function (_a) {
95
- var apiKey = _a.apiKey, _b = _a.placeholder, placeholder = _b === void 0 ? "Type your message..." : _b, _c = _a.theme, theme = _c === void 0 ? 'light' : _c, width = _a.width, height = _a.height, _d = _a.size, size = _d === void 0 ? 'medium' : _d, _e = _a.name, name = _e === void 0 ? "Blumessage AI" : _e, _f = _a.subtitle, subtitle = _f === void 0 ? "Online • Instant responses" : _f, _g = _a.initialMessages, initialMessages = _g === void 0 ? [] : _g, onUserMessage = _a.onUserMessage, onAssistantMessage = _a.onAssistantMessage, initialConversationId = _a.conversationId, onConversationIdChange = _a.onConversationIdChange, onChatWidgetOpen = _a.onChatWidgetOpen, onChatWidgetClosed = _a.onChatWidgetClosed, onError = _a.onError, _h = _a.persistent, persistent = _h === void 0 ? false : _h,
104
+ var apiKey = _a.apiKey, _b = _a.placeholder, placeholder = _b === void 0 ? "Type your message..." : _b, _c = _a.theme, theme = _c === void 0 ? 'light' : _c, width = _a.width, height = _a.height, _d = _a.size, size = _d === void 0 ? 'medium' : _d, _e = _a.name, name = _e === void 0 ? "Blumessage AI" : _e, _f = _a.subtitle, subtitle = _f === void 0 ? "Online • Instant responses" : _f, _g = _a.initialMessages, initialMessages = _g === void 0 ? [] : _g, onUserMessage = _a.onUserMessage, onAssistantMessage = _a.onAssistantMessage, initialConversationId = _a.conversationId, onConversationIdChange = _a.onConversationIdChange, onChatWidgetOpen = _a.onChatWidgetOpen, onChatWidgetClosed = _a.onChatWidgetClosed, onError = _a.onError, _h = _a.persistent, persistent = _h === void 0 ? false : _h, _j = _a.showTimestamps, showTimestamps = _j === void 0 ? false : _j,
96
105
  // Floating button props
97
- _j = _a.floating,
106
+ _k = _a.floating,
98
107
  // Floating button props
99
- floating = _j === void 0 ? true : _j, _k = _a.buttonText, buttonText = _k === void 0 ? "Chat with us" : _k, _l = _a.buttonPosition, buttonPosition = _l === void 0 ? 'bottom-right' : _l, buttonStyle = _a.buttonStyle, _m = _a.defaultOpen, defaultOpen = _m === void 0 ? false : _m, _o = _a.maximizeToggleButton, maximizeToggleButton = _o === void 0 ? true : _o, _p = _a.fullScreen, fullScreen = _p === void 0 ? false : _p, _q = _a.icon, icon = _q === void 0 ? 'message-circle' : _q,
108
+ floating = _k === void 0 ? true : _k, _l = _a.buttonText, buttonText = _l === void 0 ? "Chat with us" : _l, _m = _a.buttonPosition, buttonPosition = _m === void 0 ? 'bottom-right' : _m, buttonStyle = _a.buttonStyle, _o = _a.defaultOpen, defaultOpen = _o === void 0 ? false : _o, _p = _a.maximizeToggleButton, maximizeToggleButton = _p === void 0 ? true : _p, _q = _a.fullScreen, fullScreen = _q === void 0 ? false : _q, _r = _a.icon, icon = _r === void 0 ? 'message-circle' : _r,
100
109
  // Styling props
101
- _r = _a.primaryColor,
110
+ _s = _a.primaryColor,
102
111
  // Styling props
103
- primaryColor = _r === void 0 ? "linear-gradient(to right, #3b82f6,rgb(8, 98, 242))" : _r;
104
- var _s = useState(false), isInitialized = _s[0], setIsInitialized = _s[1];
105
- var _t = useState(null), error = _t[0], setError = _t[1];
106
- var _u = useState(initialMessages), messages = _u[0], setMessages = _u[1];
107
- var _v = useState(''), inputValue = _v[0], setInputValue = _v[1];
108
- var _w = useState(initialConversationId || getStoredConversationId(persistent)), conversationId = _w[0], setConversationId = _w[1];
109
- var _x = useState(defaultOpen), isOpen = _x[0], setIsOpen = _x[1];
110
- var _y = useState(false), isAnimating = _y[0], setIsAnimating = _y[1];
111
- var _z = useState(false), isLoading = _z[0], setIsLoading = _z[1];
112
- var _0 = useState(false), isMaximized = _0[0], setIsMaximized = _0[1];
112
+ primaryColor = _s === void 0 ? "linear-gradient(to right, #3b82f6,rgb(8, 98, 242))" : _s;
113
+ var _t = useState(false), isInitialized = _t[0], setIsInitialized = _t[1];
114
+ var _u = useState(null), error = _u[0], setError = _u[1];
115
+ var _v = useState(initialMessages), messages = _v[0], setMessages = _v[1];
116
+ var _w = useState(''), inputValue = _w[0], setInputValue = _w[1];
117
+ var _x = useState(initialConversationId || getStoredConversationId(persistent)), conversationId = _x[0], setConversationId = _x[1];
118
+ var _y = useState(defaultOpen), isOpen = _y[0], setIsOpen = _y[1];
119
+ var _z = useState(false), isAnimating = _z[0], setIsAnimating = _z[1];
120
+ var _0 = useState(false), isLoading = _0[0], setIsLoading = _0[1];
121
+ var _1 = useState(false), isMaximized = _1[0], setIsMaximized = _1[1];
113
122
  var messagesEndRef = useRef(null);
123
+ var isInitialLoad = useRef(true);
124
+ // Helper function to format timestamp
125
+ var formatTimestamp = function (timestamp) {
126
+ var date = new Date(timestamp);
127
+ var now = new Date();
128
+ // Check if the message is from today
129
+ var isToday = date.toDateString() === now.toDateString();
130
+ if (isToday) {
131
+ // If today: show time only in user's locale format (14:00 or 2:00 PM)
132
+ return date.toLocaleTimeString(undefined, {
133
+ hour: '2-digit',
134
+ minute: '2-digit',
135
+ hour12: undefined // Let browser decide based on user's locale
136
+ });
137
+ }
138
+ else {
139
+ // If more than a day: show "17 July, 13:00" format in user's locale
140
+ var dateStr = date.toLocaleDateString(undefined, {
141
+ day: 'numeric',
142
+ month: 'long'
143
+ });
144
+ var timeStr = date.toLocaleTimeString(undefined, {
145
+ hour: '2-digit',
146
+ minute: '2-digit',
147
+ hour12: undefined // Let browser decide based on user's locale
148
+ });
149
+ return "".concat(dateStr, ", ").concat(timeStr);
150
+ }
151
+ };
114
152
  // Helper function to get dimensions based on size
115
153
  var getDimensions = function () {
116
154
  // If custom width/height are provided, use them
@@ -118,17 +156,21 @@ export var BlumessageChat = function (_a) {
118
156
  return { width: width, height: height };
119
157
  }
120
158
  // Otherwise, use size-based dimensions
159
+ var dimensions;
121
160
  switch (size) {
122
161
  case 'small':
123
- return { width: '320px', height: '400px' };
162
+ dimensions = { width: '320px', height: '400px' };
163
+ break;
124
164
  case 'large':
125
- return { width: '480px', height: '600px' };
165
+ dimensions = { width: '680px', height: '800px' };
166
+ break;
126
167
  case 'medium':
127
168
  default:
128
- return { width: '380px', height: '500px' };
169
+ dimensions = { width: '480px', height: '600px' };
129
170
  }
171
+ return dimensions;
130
172
  };
131
- var _1 = getDimensions(), actualWidth = _1.width, actualHeight = _1.height;
173
+ var _2 = getDimensions(), actualWidth = _2.width, actualHeight = _2.height;
132
174
  // Function to update conversation ID and notify parent
133
175
  var updateConversationId = function (newConversationId) {
134
176
  setConversationId(newConversationId);
@@ -198,11 +240,11 @@ export var BlumessageChat = function (_a) {
198
240
  }
199
241
  // Validate API key with Blumessage API
200
242
  var validateApiKey = function () { return __awaiter(void 0, void 0, void 0, function () {
201
- var response, historyMessages, errorMessage, err_1, errorMessage;
243
+ var response, errorMessage, err_1, errorMessage;
202
244
  return __generator(this, function (_a) {
203
245
  switch (_a.label) {
204
246
  case 0:
205
- _a.trys.push([0, 9, , 10]);
247
+ _a.trys.push([0, 2, , 3]);
206
248
  setIsInitialized(false);
207
249
  setError(null);
208
250
  console.log("Initializing Blumessage chat...");
@@ -215,43 +257,22 @@ export var BlumessageChat = function (_a) {
215
257
  })];
216
258
  case 1:
217
259
  response = _a.sent();
218
- if (!response.ok) return [3 /*break*/, 7];
219
- console.log("Blumessage chat initialized successfully");
220
- setIsInitialized(true);
221
- setError(null);
222
- if (!(initialMessages.length > 0 && conversationId)) return [3 /*break*/, 2];
223
- // Warn if both are provided - initialMessages takes precedence
224
- console.warn('Both initialMessages and conversationId provided. Using initialMessages and ignoring stored conversation.');
225
- setMessages(initialMessages);
226
- return [3 /*break*/, 6];
227
- case 2:
228
- if (!(initialMessages.length > 0)) return [3 /*break*/, 3];
229
- // Use provided initial messages
230
- setMessages(initialMessages);
231
- return [3 /*break*/, 6];
232
- case 3:
233
- if (!conversationId) return [3 /*break*/, 5];
234
- return [4 /*yield*/, fetchConversationHistory(conversationId)];
235
- case 4:
236
- historyMessages = _a.sent();
237
- setMessages(historyMessages); // Will be empty array if fetch failed
238
- return [3 /*break*/, 6];
239
- case 5:
240
- // No conversation ID and no initial messages - start with empty array
241
- setMessages([]);
242
- _a.label = 6;
243
- case 6: return [3 /*break*/, 8];
244
- case 7:
245
- console.error("Blumessage chat initialization failed:", response.status, response.statusText);
246
- errorMessage = "Unable to connect - invalid API key";
247
- setError(errorMessage);
248
- setIsInitialized(false);
249
- if (onError) {
250
- onError(errorMessage, "api_key_validation");
260
+ if (response.ok) {
261
+ console.log("Blumessage chat initialized successfully");
262
+ setIsInitialized(true);
263
+ setError(null);
264
+ }
265
+ else {
266
+ console.error("Blumessage chat initialization failed:", response.status, response.statusText);
267
+ errorMessage = "Unable to connect - invalid API key";
268
+ setError(errorMessage);
269
+ setIsInitialized(false);
270
+ if (onError) {
271
+ onError(errorMessage, "api_key_validation");
272
+ }
251
273
  }
252
- _a.label = 8;
253
- case 8: return [3 /*break*/, 10];
254
- case 9:
274
+ return [3 /*break*/, 3];
275
+ case 2:
255
276
  err_1 = _a.sent();
256
277
  console.error("Blumessage chat initialization error:", err_1);
257
278
  errorMessage = "Unable to connect - network error";
@@ -260,13 +281,52 @@ export var BlumessageChat = function (_a) {
260
281
  if (onError) {
261
282
  onError(errorMessage, "network_error");
262
283
  }
263
- return [3 /*break*/, 10];
264
- case 10: return [2 /*return*/];
284
+ return [3 /*break*/, 3];
285
+ case 3: return [2 /*return*/];
265
286
  }
266
287
  });
267
288
  }); };
268
289
  validateApiKey();
269
- }, [apiKey, conversationId]);
290
+ }, [apiKey]);
291
+ // Handle initial message loading (only runs once on mount)
292
+ useEffect(function () {
293
+ var loadInitialMessages = function () { return __awaiter(void 0, void 0, void 0, function () {
294
+ var historyMessages;
295
+ return __generator(this, function (_a) {
296
+ switch (_a.label) {
297
+ case 0:
298
+ if (!isInitialLoad.current) return [3 /*break*/, 5];
299
+ isInitialLoad.current = false;
300
+ if (!(initialMessages.length > 0 && conversationId)) return [3 /*break*/, 1];
301
+ // Warn if both are provided - initialMessages takes precedence
302
+ console.warn('Both initialMessages and conversationId provided. Using initialMessages and ignoring stored conversation.');
303
+ setMessages(initialMessages);
304
+ return [3 /*break*/, 5];
305
+ case 1:
306
+ if (!(initialMessages.length > 0)) return [3 /*break*/, 2];
307
+ // Use provided initial messages
308
+ setMessages(initialMessages);
309
+ return [3 /*break*/, 5];
310
+ case 2:
311
+ if (!conversationId) return [3 /*break*/, 4];
312
+ return [4 /*yield*/, fetchConversationHistory(conversationId)];
313
+ case 3:
314
+ historyMessages = _a.sent();
315
+ setMessages(historyMessages); // Will be empty array if fetch failed
316
+ return [3 /*break*/, 5];
317
+ case 4:
318
+ // No conversation ID and no initial messages - start with empty array
319
+ setMessages([]);
320
+ _a.label = 5;
321
+ case 5: return [2 /*return*/];
322
+ }
323
+ });
324
+ }); };
325
+ // Only load initial messages after API key is validated
326
+ if (isInitialized) {
327
+ loadInitialMessages();
328
+ }
329
+ }, [isInitialized]);
270
330
  var handleSendMessage = function () { return __awaiter(void 0, void 0, void 0, function () {
271
331
  var userMessage, currentInput, requestBody, response, apiResponse, assistantMessages, latestAssistantMessage, assistantResponse_1, errorMessage_1, error_2, errorMessage_2;
272
332
  return __generator(this, function (_a) {
@@ -548,23 +608,36 @@ export var BlumessageChat = function (_a) {
548
608
  height: (fullScreen || isMaximized) ? '100%' : actualHeight,
549
609
  fontFamily: 'system-ui, -apple-system, sans-serif',
550
610
  borderRadius: (fullScreen || isMaximized) ? '12px' : '24px'
551
- }, children: [_jsxs("div", { className: "flex items-center px-6 py-4 border-b ".concat(theme === 'dark'
611
+ }, children: [_jsxs("div", { className: "border-b ".concat(theme === 'dark'
552
612
  ? 'bg-gray-900 border-gray-700'
553
- : 'bg-white border-gray-100'), children: [_jsx("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mr-4", style: { backgroundImage: primaryColor }, children: React.createElement(getIconComponent(), { className: "w-6 h-6 text-white" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-lg font-semibold m-0 leading-6 ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-900'), children: name }), _jsx("div", { className: "text-sm m-0 leading-5 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: subtitle })] }), floating && (_jsxs("div", { className: "flex items-center gap-1", children: [maximizeToggleButton && (_jsx("button", { onClick: handleToggleMaximize, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
613
+ : 'bg-white border-gray-100'), style: {
614
+ display: 'flex',
615
+ alignItems: 'center',
616
+ padding: '16px 24px'
617
+ }, children: [_jsx("div", { className: "rounded-full flex items-center justify-center", style: {
618
+ backgroundImage: primaryColor,
619
+ width: '48px',
620
+ height: '48px',
621
+ marginRight: '24px'
622
+ }, children: React.createElement(getIconComponent(), { className: "w-6 h-6 text-white" }) }), _jsxs("div", { style: { flex: 1 }, children: [_jsx("div", { className: "text-lg font-semibold m-0 leading-6 ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-900'), children: name }), _jsx("div", { className: "text-sm m-0 leading-5 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: subtitle })] }), floating && (_jsxs("div", { className: "flex items-center gap-1", children: [maximizeToggleButton && (_jsx("button", { onClick: handleToggleMaximize, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
554
623
  ? 'hover:bg-gray-800 text-gray-400 hover:text-gray-200'
555
624
  : 'hover:bg-gray-100 text-gray-500 hover:text-gray-700'), children: isMaximized ? _jsx(Minimize2, { className: "w-4 h-4" }) : _jsx(Maximize, { className: "w-4 h-4" }) })), _jsx("button", { onClick: handleCloseChat, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
556
625
  ? 'hover:bg-gray-800 text-gray-400 hover:text-gray-200'
557
- : 'hover:bg-gray-100 text-gray-500 hover:text-gray-700'), children: _jsx(X, { className: "w-4 h-4" }) })] }))] }), _jsxs("div", { className: "flex-1 px-6 py-4 flex flex-col gap-4 overflow-y-auto ".concat(theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'), children: [messages.map(function (message) { return (_jsx("div", { className: "flex ".concat(message.role === 'user' ? 'justify-end' : 'justify-start'), children: _jsx("div", { className: "px-4 py-3 rounded-2xl max-w-[80%] text-sm leading-6 ".concat(message.role === 'user'
558
- ? 'text-white'
559
- : theme === 'dark'
560
- ? 'bg-gray-700 text-gray-100 border border-gray-600 shadow-sm'
561
- : 'bg-white text-gray-800 border border-gray-200 shadow-sm'), style: message.role === 'user' ? { backgroundImage: primaryColor } : {}, children: message.content }) }, message.id)); }), isLoading && (_jsx("div", { className: "flex justify-start", children: _jsx("div", { className: "px-4 py-3 rounded-2xl max-w-[80%] text-sm leading-6 shadow-sm ".concat(theme === 'dark'
626
+ : 'hover:bg-gray-100 text-gray-500 hover:text-gray-700'), children: _jsx(X, { className: "w-4 h-4" }) })] }))] }), _jsxs("div", { className: "flex-1 px-6 py-4 flex flex-col gap-4 overflow-y-auto ".concat(theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'), children: [messages.map(function (message) { return (_jsx("div", { className: "flex ".concat(message.role === 'user' ? 'justify-end' : 'justify-start'), children: _jsxs("div", { className: "max-w-[80%] ".concat(message.role === 'user' ? 'text-right' : 'text-left'), children: [_jsx("div", { className: "px-4 py-3 rounded-2xl text-sm leading-6 ".concat(message.role === 'user'
627
+ ? 'text-white'
628
+ : theme === 'dark'
629
+ ? 'bg-gray-700 text-gray-100 border border-gray-600 shadow-sm'
630
+ : 'bg-white text-gray-800 border border-gray-200 shadow-sm'), style: message.role === 'user' ? { backgroundImage: primaryColor } : {}, children: message.content }), showTimestamps && (_jsx("div", { className: "text-xs mt-1 ".concat(theme === 'dark' ? 'text-gray-500' : 'text-gray-400'), style: {
631
+ textAlign: message.role === 'user' ? 'right' : 'left',
632
+ paddingLeft: message.role === 'assistant' ? '16px' : '0',
633
+ paddingRight: message.role === 'user' ? '16px' : '0'
634
+ }, children: formatTimestamp(message.timestamp) }))] }) }, message.id)); }), isLoading && (_jsx("div", { className: "flex justify-start", children: _jsx("div", { className: "px-4 py-3 rounded-2xl max-w-[80%] text-sm leading-6 shadow-sm ".concat(theme === 'dark'
562
635
  ? 'bg-gray-700 text-gray-100 border border-gray-600'
563
- : 'bg-white text-gray-800 border border-gray-200'), children: _jsxs("div", { className: "flex space-x-1", children: [_jsx("div", { className: "w-2 h-2 rounded-full animate-bounce ".concat(theme === 'dark' ? 'bg-gray-400' : 'bg-gray-400'), style: { animationDelay: '0ms' } }), _jsx("div", { className: "w-2 h-2 rounded-full animate-bounce ".concat(theme === 'dark' ? 'bg-gray-400' : 'bg-gray-400'), style: { animationDelay: '150ms' } }), _jsx("div", { className: "w-2 h-2 rounded-full animate-bounce ".concat(theme === 'dark' ? 'bg-gray-400' : 'bg-gray-400'), style: { animationDelay: '300ms' } })] }) }) })), messages.length === 0 && !isLoading && (_jsx("div", { className: "text-center text-sm py-8 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: "No messages yet. Start a conversation!" })), _jsx("div", { ref: messagesEndRef })] }), _jsx("div", { className: "px-6 py-4 border-t ".concat(theme === 'dark'
636
+ : 'bg-white text-gray-800 border border-gray-200'), children: _jsxs("div", { className: "flex space-x-1", children: [_jsx("div", { className: "w-2 h-2 rounded-full blumessage-animate-bounce ".concat(theme === 'dark' ? 'bg-gray-400' : 'bg-gray-400'), style: { animationDelay: '0ms' } }), _jsx("div", { className: "w-2 h-2 rounded-full blumessage-animate-bounce ".concat(theme === 'dark' ? 'bg-gray-400' : 'bg-gray-400'), style: { animationDelay: '150ms' } }), _jsx("div", { className: "w-2 h-2 rounded-full blumessage-animate-bounce ".concat(theme === 'dark' ? 'bg-gray-400' : 'bg-gray-400'), style: { animationDelay: '300ms' } })] }) }) })), messages.length === 0 && !isLoading && (_jsx("div", { className: "text-center text-sm py-8 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: "No messages yet. Start a conversation!" })), _jsx("div", { ref: messagesEndRef })] }), _jsx("div", { className: "px-6 py-4 border-t ".concat(theme === 'dark'
564
637
  ? 'bg-gray-900 border-gray-700'
565
638
  : 'bg-white border-gray-100'), children: _jsxs("div", { className: "flex items-center rounded-full px-4 py-3 border ".concat(theme === 'dark'
566
639
  ? 'bg-gray-800 border-gray-600'
567
- : 'bg-gray-50 border-gray-200'), children: [_jsx("input", { className: "flex-1 border-none bg-transparent outline-none text-sm font-inherit ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-700'), type: "text", placeholder: placeholder, value: inputValue, onChange: function (e) { return setInputValue(e.target.value); }, onKeyPress: handleKeyPress }), _jsx("button", { className: "w-8 h-8 rounded-full border-none flex items-center justify-center cursor-pointer ml-2 text-white transition-all disabled:opacity-50 disabled:cursor-not-allowed", style: { backgroundImage: primaryColor }, onClick: handleSendMessage, disabled: !inputValue.trim() || isLoading, children: isLoading ? (_jsx(Loader2, { className: "w-4 h-4 animate-spin" })) : (_jsx(Send, { className: "w-4 h-4" })) })] }) })] }));
640
+ : 'bg-gray-50 border-gray-200'), children: [_jsx("input", { className: "flex-1 border-none bg-transparent outline-none text-sm font-inherit ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-700'), type: "text", placeholder: placeholder, value: inputValue, onChange: function (e) { return setInputValue(e.target.value); }, onKeyPress: handleKeyPress }), _jsx("button", { className: "w-8 h-8 rounded-full border-none flex items-center justify-center cursor-pointer ml-2 text-white transition-all disabled:opacity-50 disabled:cursor-not-allowed", style: { backgroundImage: primaryColor }, onClick: handleSendMessage, disabled: !inputValue.trim() || isLoading, children: isLoading ? (_jsx(Loader2, { className: "w-4 h-4 blumessage-animate-spin" })) : (_jsx(Send, { className: "w-4 h-4" })) })] }) })] }));
568
641
  if (floating) {
569
642
  return (_jsx("div", { style: getChatPositionStyles(), children: chatContent }));
570
643
  }
@@ -584,7 +657,12 @@ export var BlumessageChat = function (_a) {
584
657
  borderRadius: (fullScreen || isMaximized) ? '12px' : '24px'
585
658
  }, children: [_jsxs("div", { className: "flex items-center px-6 py-4 border-b ".concat(theme === 'dark'
586
659
  ? 'bg-gray-900 border-gray-700'
587
- : 'bg-white border-gray-100'), children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-red-500 flex items-center justify-center mr-4", children: _jsx(AlertTriangle, { className: "w-6 h-6 text-white" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-lg font-semibold m-0 leading-6 ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-900'), children: "Connection Error" }), _jsx("div", { className: "text-sm m-0 leading-5 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: "Unable to connect" })] }), floating && (_jsxs("div", { className: "flex items-center gap-1", children: [maximizeToggleButton && (_jsx("button", { onClick: handleToggleMaximize, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
660
+ : 'bg-white border-gray-100'), children: [_jsx("div", { className: "rounded-full flex items-center justify-center", style: {
661
+ backgroundColor: '#ef4444',
662
+ width: '48px',
663
+ height: '48px',
664
+ marginRight: '24px'
665
+ }, children: _jsx(AlertTriangle, { className: "w-6 h-6 text-white" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-lg font-semibold m-0 leading-6 ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-900'), children: "Connection Error" }), _jsx("div", { className: "text-sm m-0 leading-5 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: "Unable to connect" })] }), floating && (_jsxs("div", { className: "flex items-center gap-1", children: [maximizeToggleButton && (_jsx("button", { onClick: handleToggleMaximize, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
588
666
  ? 'hover:bg-gray-800 text-gray-400 hover:text-gray-200'
589
667
  : 'hover:bg-gray-100 text-gray-500 hover:text-gray-700'), children: isMaximized ? _jsx(Minimize2, { className: "w-4 h-4" }) : _jsx(Maximize, { className: "w-4 h-4" }) })), _jsx("button", { onClick: handleCloseChat, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
590
668
  ? 'hover:bg-gray-800 text-gray-400 hover:text-gray-200'
@@ -606,7 +684,12 @@ export var BlumessageChat = function (_a) {
606
684
  borderRadius: (fullScreen || isMaximized) ? '12px' : '24px'
607
685
  }, children: [_jsxs("div", { className: "flex items-center px-6 py-4 border-b ".concat(theme === 'dark'
608
686
  ? 'bg-gray-900 border-gray-700'
609
- : 'bg-white border-gray-100'), children: [_jsx("div", { className: "w-12 h-12 rounded-full flex items-center justify-center mr-4", style: { backgroundImage: primaryColor }, children: _jsx(Loader2, { className: "w-6 h-6 text-white animate-spin" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-lg font-semibold m-0 leading-6 ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-900'), children: name }), _jsx("div", { className: "text-sm m-0 leading-5 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: subtitle })] }), floating && (_jsxs("div", { className: "flex items-center gap-1", children: [maximizeToggleButton && (_jsx("button", { onClick: handleToggleMaximize, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
687
+ : 'bg-white border-gray-100'), children: [_jsx("div", { className: "rounded-full flex items-center justify-center", style: {
688
+ backgroundImage: primaryColor,
689
+ width: '48px',
690
+ height: '48px',
691
+ marginRight: '24px'
692
+ }, children: _jsx(Loader2, { className: "w-6 h-6 text-white animate-spin" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-lg font-semibold m-0 leading-6 ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-900'), children: name }), _jsx("div", { className: "text-sm m-0 leading-5 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: subtitle })] }), floating && (_jsxs("div", { className: "flex items-center gap-1", children: [maximizeToggleButton && (_jsx("button", { onClick: handleToggleMaximize, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
610
693
  ? 'hover:bg-gray-800 text-gray-400 hover:text-gray-200'
611
694
  : 'hover:bg-gray-100 text-gray-500 hover:text-gray-700'), children: isMaximized ? _jsx(Minimize2, { className: "w-4 h-4" }) : _jsx(Maximize, { className: "w-4 h-4" }) })), _jsx("button", { onClick: handleCloseChat, className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors ".concat(theme === 'dark'
612
695
  ? 'hover:bg-gray-800 text-gray-400 hover:text-gray-200'
@@ -35,6 +35,7 @@ export interface BlumessageChatProps {
35
35
  onChatWidgetClosed?: () => void;
36
36
  onError?: (error: string, context?: string) => void;
37
37
  persistent?: boolean;
38
+ showTimestamps?: boolean;
38
39
  floating?: boolean;
39
40
  buttonText?: string;
40
41
  buttonPosition?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumessage/react-chat",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "A React TypeScript chat widget component with floating button, theming, and Blumessage API integration",
5
5
  "license": "MIT",
6
6
  "author": "Blumessage <contact@blumessage.com>",