@blumessage/react-chat 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -273,12 +273,133 @@ The `onError` callback provides detailed error context:
273
273
  />
274
274
  ```
275
275
 
276
+ ## External Message Submission
277
+
278
+ The component supports external message submission through a ref, allowing parent components to programmatically send messages and control the chat widget.
279
+
280
+ ### Using the Ref
281
+
282
+ ```tsx
283
+ import React, { useRef } from 'react';
284
+ import { BlumessageChat, BlumessageChatRef } from '@blumessage/react-chat';
285
+
286
+ function App() {
287
+ const chatRef = useRef<BlumessageChatRef>(null);
288
+
289
+ const sendExternalMessage = async () => {
290
+ if (chatRef.current) {
291
+ await chatRef.current.sendMessage("Hello from external component!");
292
+ }
293
+ };
294
+
295
+ const openChat = () => {
296
+ chatRef.current?.openChat();
297
+ };
298
+
299
+ const closeChat = () => {
300
+ chatRef.current?.closeChat();
301
+ };
302
+
303
+ const clearChat = () => {
304
+ chatRef.current?.clearConversation();
305
+ };
306
+
307
+ const getMessages = () => {
308
+ const messages = chatRef.current?.getMessages();
309
+ console.log('Current messages:', messages);
310
+ };
311
+
312
+ const getToken = () => {
313
+ const token = chatRef.current?.getToken();
314
+ console.log('Current conversation token:', token);
315
+ };
316
+
317
+ return (
318
+ <div>
319
+ <button onClick={sendExternalMessage}>Send External Message</button>
320
+ <button onClick={openChat}>Open Chat</button>
321
+ <button onClick={closeChat}>Close Chat</button>
322
+ <button onClick={clearChat}>Clear Chat</button>
323
+ <button onClick={getMessages}>Get Messages</button>
324
+ <button onClick={getToken}>Get Token</button>
325
+
326
+ <BlumessageChat
327
+ ref={chatRef}
328
+ apiKey="your-api-key"
329
+ floating={true}
330
+ />
331
+ </div>
332
+ );
333
+ }
334
+ ```
335
+
336
+ ### Available Ref Methods
337
+
338
+ | Method | Type | Description |
339
+ |--------|------|-------------|
340
+ | `sendMessage(message: string)` | `Promise<void>` | Send a message programmatically |
341
+ | `openChat()` | `void` | Open the floating chat widget |
342
+ | `closeChat()` | `void` | Close the floating chat widget |
343
+ | `clearConversation()` | `void` | Clear all messages and reset conversation |
344
+ | `getMessages()` | `Message[]` | Get current messages array |
345
+ | `getToken()` | `string \| null` | Get current conversation token |
346
+
347
+ ### Complete Example with External Controls
348
+
349
+ ```tsx
350
+ import React, { useRef, useState } from 'react';
351
+ import { BlumessageChat, BlumessageChatRef } from '@blumessage/react-chat';
352
+
353
+ function App() {
354
+ const [externalMessage, setExternalMessage] = useState('');
355
+ const chatRef = useRef<BlumessageChatRef>(null);
356
+
357
+ const handleSendExternalMessage = async () => {
358
+ if (!externalMessage.trim() || !chatRef.current) return;
359
+
360
+ try {
361
+ await chatRef.current.sendMessage(externalMessage);
362
+ setExternalMessage('');
363
+ console.log('External message sent successfully');
364
+ } catch (error) {
365
+ console.error('Failed to send external message:', error);
366
+ }
367
+ };
368
+
369
+ return (
370
+ <div>
371
+ <div style={{ marginBottom: '20px' }}>
372
+ <input
373
+ type="text"
374
+ value={externalMessage}
375
+ onChange={(e) => setExternalMessage(e.target.value)}
376
+ placeholder="Type a message to send externally..."
377
+ onKeyPress={(e) => e.key === 'Enter' && handleSendExternalMessage()}
378
+ />
379
+ <button onClick={handleSendExternalMessage}>Send</button>
380
+ <button onClick={() => chatRef.current?.openChat()}>Open Chat</button>
381
+ <button onClick={() => chatRef.current?.closeChat()}>Close Chat</button>
382
+ <button onClick={() => chatRef.current?.clearConversation()}>Clear Chat</button>
383
+ </div>
384
+
385
+ <BlumessageChat
386
+ ref={chatRef}
387
+ apiKey="your-api-key"
388
+ floating={true}
389
+ onUserMessage={(message) => console.log('User message:', message)}
390
+ onAssistantMessage={(message) => console.log('Assistant message:', message)}
391
+ />
392
+ </div>
393
+ );
394
+ }
395
+ ```
396
+
276
397
  ## TypeScript Support
277
398
 
278
399
  Full TypeScript definitions included:
279
400
 
280
401
  ```typescript
281
- import { BlumessageChat, Message, BlumessageChatProps } from '@blumessage/react-chat';
402
+ import { BlumessageChat, Message, BlumessageChatProps, BlumessageChatRef } from '@blumessage/react-chat';
282
403
 
283
404
  interface Message {
284
405
  id: string;
@@ -286,6 +407,15 @@ interface Message {
286
407
  content: string;
287
408
  timestamp: number;
288
409
  }
410
+
411
+ interface BlumessageChatRef {
412
+ sendMessage: (message: string) => Promise<void>;
413
+ openChat: () => void;
414
+ closeChat: () => void;
415
+ clearConversation: () => void;
416
+ getMessages: () => Message[];
417
+ getToken: () => string | null;
418
+ }
289
419
  ```
290
420
 
291
421
  ## Storage Behavior
@@ -55,7 +55,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
55
55
  return to.concat(ar || Array.prototype.slice.call(from));
56
56
  };
57
57
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
58
- import React, { useState, useEffect, useRef } from "react";
58
+ import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from "react";
59
59
  import ReactMarkdown from 'react-markdown';
60
60
  import remarkGfm from 'remark-gfm';
61
61
  import { MessageCircle, AlertTriangle, Loader2, Send, X, Maximize, Minimize2, Bot, MessageSquare, Phone, Mail, Headphones, Users, User, Heart, Star, Zap } from "lucide-react";
@@ -119,7 +119,7 @@ var useIsMobile = function () {
119
119
  }, []);
120
120
  return isMobile;
121
121
  };
122
- export var BlumessageChat = function (_a) {
122
+ export var BlumessageChat = forwardRef(function (_a, ref) {
123
123
  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, initialToken = _a.token, onTokenChange = _a.onTokenChange, 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, _k = _a.typingText, typingText = _k === void 0 ? "Agent is typing..." : _k, _l = _a.emptyStateText, emptyStateText = _l === void 0 ? "Start a conversation!" : _l, _m = _a.markdown, markdown = _m === void 0 ? true : _m,
124
124
  // Floating button props
125
125
  _o = _a.floating,
@@ -203,7 +203,7 @@ export var BlumessageChat = function (_a) {
203
203
  break;
204
204
  case 'medium':
205
205
  default:
206
- dimensions = { width: '480px', height: '600px' };
206
+ dimensions = { width: '600px', height: '600px' };
207
207
  }
208
208
  return dimensions;
209
209
  };
@@ -563,6 +563,149 @@ export var BlumessageChat = function (_a) {
563
563
  // Default fallback
564
564
  return MessageCircle;
565
565
  };
566
+ // Expose methods to parent component via ref
567
+ useImperativeHandle(ref, function () { return ({
568
+ sendMessage: function (message) { return __awaiter(void 0, void 0, void 0, function () {
569
+ var userMessage, currentInput, requestBody, response, apiResponse, assistantMessages, latestAssistantMessage, assistantResponse_2, errorMessage_3, error_3, errorMessage_4;
570
+ return __generator(this, function (_a) {
571
+ switch (_a.label) {
572
+ case 0:
573
+ if (!message.trim() || isLoading)
574
+ return [2 /*return*/];
575
+ userMessage = {
576
+ id: Date.now().toString(),
577
+ content: message.trim(),
578
+ role: 'user',
579
+ timestamp: Date.now()
580
+ };
581
+ // Add user message to UI immediately
582
+ setMessages(function (prev) { return __spreadArray(__spreadArray([], prev, true), [userMessage], false); });
583
+ currentInput = message.trim();
584
+ setIsLoading(true);
585
+ // Call the callback if provided
586
+ if (onUserMessage) {
587
+ onUserMessage(userMessage);
588
+ }
589
+ _a.label = 1;
590
+ case 1:
591
+ _a.trys.push([1, 6, 7, 8]);
592
+ requestBody = {
593
+ message: currentInput,
594
+ };
595
+ // Include token if we have one
596
+ if (token) {
597
+ requestBody.token = token;
598
+ }
599
+ return [4 /*yield*/, fetch('https://api.blumessage.com/api/v1/conversations', {
600
+ method: 'POST',
601
+ headers: {
602
+ 'Content-Type': 'application/json',
603
+ 'Authorization': "Bearer ".concat(apiKey),
604
+ },
605
+ body: JSON.stringify(requestBody),
606
+ })];
607
+ case 2:
608
+ response = _a.sent();
609
+ if (!response.ok) return [3 /*break*/, 4];
610
+ return [4 /*yield*/, response.json()];
611
+ case 3:
612
+ apiResponse = _a.sent();
613
+ // Update token from response (for both first message and continuing conversations)
614
+ updateConversationToken(apiResponse.token);
615
+ assistantMessages = apiResponse.messages.filter(function (msg) { return msg.role === 'assistant'; });
616
+ latestAssistantMessage = assistantMessages[assistantMessages.length - 1];
617
+ if (latestAssistantMessage) {
618
+ assistantResponse_2 = {
619
+ id: (Date.now() + 1).toString(),
620
+ content: latestAssistantMessage.content,
621
+ role: 'assistant',
622
+ timestamp: latestAssistantMessage.timestamp,
623
+ };
624
+ setMessages(function (prev) { return __spreadArray(__spreadArray([], prev, true), [assistantResponse_2], false); });
625
+ if (onAssistantMessage) {
626
+ onAssistantMessage(assistantResponse_2);
627
+ }
628
+ }
629
+ return [3 /*break*/, 5];
630
+ case 4:
631
+ console.error('Failed to send message to Blumessage API:', response.status, response.statusText);
632
+ // If token is invalid, clear it
633
+ if (response.status === 401 || response.status === 403) {
634
+ updateConversationToken(null);
635
+ }
636
+ errorMessage_3 = {
637
+ id: (Date.now() + 1).toString(),
638
+ content: "Sorry, I'm having trouble connecting right now. Please try again.",
639
+ role: 'assistant',
640
+ timestamp: Date.now(),
641
+ };
642
+ setMessages(function (prev) { return __spreadArray(__spreadArray([], prev, true), [errorMessage_3], false); });
643
+ if (onError) {
644
+ onError("Failed to send message: ".concat(response.status, " ").concat(response.statusText), "message_send");
645
+ }
646
+ _a.label = 5;
647
+ case 5: return [3 /*break*/, 8];
648
+ case 6:
649
+ error_3 = _a.sent();
650
+ console.error('Error sending message to Blumessage API:', error_3);
651
+ errorMessage_4 = {
652
+ id: (Date.now() + 1).toString(),
653
+ content: "Sorry, I'm having trouble connecting right now. Please try again.",
654
+ role: 'assistant',
655
+ timestamp: Date.now(),
656
+ };
657
+ setMessages(function (prev) { return __spreadArray(__spreadArray([], prev, true), [errorMessage_4], false); });
658
+ if (onError) {
659
+ onError("Error sending message: ".concat(error_3), "message_send");
660
+ }
661
+ return [3 /*break*/, 8];
662
+ case 7:
663
+ setIsLoading(false);
664
+ return [7 /*endfinally*/];
665
+ case 8: return [2 /*return*/];
666
+ }
667
+ });
668
+ }); },
669
+ openChat: function () {
670
+ if (!isOpen && !isAnimating) {
671
+ setIsAnimating(true);
672
+ // Small delay to ensure smooth animation start
673
+ setTimeout(function () {
674
+ setIsOpen(true);
675
+ // Call the callback after the chat is open
676
+ if (onChatWidgetOpen) {
677
+ onChatWidgetOpen();
678
+ }
679
+ }, 10);
680
+ // Animation duration
681
+ setTimeout(function () {
682
+ setIsAnimating(false);
683
+ }, 300);
684
+ }
685
+ },
686
+ closeChat: function () {
687
+ if (isOpen && !isAnimating) {
688
+ setIsAnimating(true);
689
+ setIsOpen(false);
690
+ // Reset maximized state when closing
691
+ setIsMaximized(false);
692
+ // Call the callback when closing
693
+ if (onChatWidgetClosed) {
694
+ onChatWidgetClosed();
695
+ }
696
+ // Wait for animation to complete
697
+ setTimeout(function () {
698
+ setIsAnimating(false);
699
+ }, 300);
700
+ }
701
+ },
702
+ clearConversation: function () {
703
+ setMessages([]);
704
+ updateConversationToken(null);
705
+ },
706
+ getMessages: function () { return messages; },
707
+ getToken: function () { return token; },
708
+ }); });
566
709
  // Helper function to get position styles for floating button
567
710
  var getButtonPositionStyles = function () {
568
711
  var baseStyles = {
@@ -701,12 +844,15 @@ export var BlumessageChat = function (_a) {
701
844
  ? 'bg-gray-700 text-gray-100 border border-gray-600'
702
845
  : 'bg-white text-gray-800 border border-gray-200'), children: _jsx("div", { className: "".concat(theme === 'dark' ? 'text-gray-300' : 'text-gray-600'), style: { fontStyle: 'italic' }, children: typingText }) }) })), messages.length === 0 && !isLoading && (_jsx("div", { className: "text-center text-sm py-8 ".concat(theme === 'dark' ? 'text-gray-400' : 'text-gray-500'), children: emptyStateText })), _jsx("div", { ref: messagesEndRef })] }), _jsx("div", { className: "border-t ".concat(theme === 'dark'
703
846
  ? 'bg-gray-900 border-gray-700'
704
- : 'bg-white border-gray-100'), style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: _jsxs("div", { className: "flex items-end rounded-2xl px-4 py-3 border ".concat(theme === 'dark'
847
+ : 'bg-white border-gray-100'), style: { padding: isMobile ? '12px 16px' : '16px 24px' }, children: _jsxs("div", { className: "flex items-center rounded-2xl px-4 py-3 border ".concat(theme === 'dark'
705
848
  ? 'bg-gray-800 border-gray-600'
706
849
  : 'bg-gray-50 border-gray-200'), children: [_jsx("textarea", { className: "flex-1 border-none bg-transparent outline-none text-sm font-inherit resize-none ".concat(theme === 'dark' ? 'text-gray-100' : 'text-gray-700'), placeholder: placeholder, value: inputValue, onChange: function (e) { return setInputValue(e.target.value); }, onKeyDown: handleKeyPress, rows: 1, style: {
707
850
  minHeight: '20px',
708
851
  maxHeight: isMobile ? '100px' : '120px',
709
- overflowY: inputValue.split('\n').length > 4 ? 'auto' : 'hidden'
852
+ overflowY: inputValue.split('\n').length > 4 ? 'auto' : 'hidden',
853
+ lineHeight: '1.5',
854
+ paddingTop: '2px',
855
+ paddingBottom: '2px'
710
856
  }, onInput: function (e) {
711
857
  var target = e.target;
712
858
  target.style.height = 'auto';
@@ -780,4 +926,4 @@ export var BlumessageChat = function (_a) {
780
926
  return (_jsxs(_Fragment, { children: [_jsxs("button", { onClick: handleOpenChat, className: "text-white rounded-full shadow-lg transition-all duration-200 flex items-center justify-center gap-2 ".concat(isMobile ? 'blumessage-mobile-button' : '', " ").concat(buttonText ? 'px-4 py-3 h-12' : 'w-14 h-14'), style: __assign(__assign(__assign({}, getButtonPositionStyles()), buttonStyle), { backgroundImage: primaryColor }), children: [React.createElement(getIconComponent(), { className: "w-6 h-6" }), buttonText && !isMobile && _jsx("span", { className: "text-sm font-medium whitespace-nowrap", children: buttonText })] }), renderChatWindow()] }));
781
927
  }
782
928
  return renderChatWindow();
783
- };
929
+ });
@@ -11,6 +11,14 @@ export interface BlumessageApiResponse {
11
11
  created_at: number;
12
12
  updated_at: number;
13
13
  }
14
+ export interface BlumessageChatRef {
15
+ sendMessage: (message: string) => Promise<void>;
16
+ openChat: () => void;
17
+ closeChat: () => void;
18
+ clearConversation: () => void;
19
+ getMessages: () => Message[];
20
+ getToken: () => string | null;
21
+ }
14
22
  export interface BlumessageChatProps {
15
23
  apiKey: string;
16
24
  placeholder?: string;
@@ -43,4 +51,4 @@ export interface BlumessageChatProps {
43
51
  icon?: string;
44
52
  primaryColor?: string;
45
53
  }
46
- export declare const BlumessageChat: React.FC<BlumessageChatProps>;
54
+ export declare const BlumessageChat: React.ForwardRefExoticComponent<BlumessageChatProps & React.RefAttributes<BlumessageChatRef>>;
@@ -1 +1 @@
1
- export { BlumessageChat, type BlumessageChatProps, type Message, type BlumessageApiResponse } from './BlumessageChat';
1
+ export { BlumessageChat, type BlumessageChatProps, type BlumessageChatRef, type Message, type BlumessageApiResponse } from './BlumessageChat';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumessage/react-chat",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A React TypeScript chat widget component with floating button, theming, and Blumessage API integration",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Blumessage <contact@blumessage.com>",
@@ -63,4 +63,4 @@
63
63
  "webpack-cli": "^5.1.4",
64
64
  "webpack-dev-server": "^4.15.1"
65
65
  }
66
- }
66
+ }