@blumessage/react-chat 1.2.1 → 1.4.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
@@ -15,10 +15,38 @@ A React TypeScript chat widget component with floating button, theming, and Blum
15
15
 
16
16
  ## Installation
17
17
 
18
+ ### NPM Package (Recommended)
19
+
18
20
  ```bash
19
21
  npm i @blumessage/react-chat
20
22
  ```
21
23
 
24
+ ### Browser Versions
25
+
26
+ #### Option 1: Standalone (Includes React)
27
+
28
+ For the simplest setup with everything included:
29
+
30
+ ```html
31
+ <!-- Load the Blumessage Chat component (includes React) -->
32
+ <script src="path/to/index.js"></script>
33
+ ```
34
+
35
+ #### Option 2: External React (Smaller Bundle)
36
+
37
+ For when you already have React loaded:
38
+
39
+ ```html
40
+ <!-- Load React and ReactDOM from CDN -->
41
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
42
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
43
+
44
+ <!-- Load the Blumessage Chat component -->
45
+ <script src="path/to/blumessage-chat.browser.js"></script>
46
+ ```
47
+
48
+ See [BROWSER_USAGE.md](./BROWSER_USAGE.md) for complete browser usage documentation.
49
+
22
50
  ## Quick Start
23
51
 
24
52
  ### Floating Chat Widget (Default)
@@ -273,12 +301,133 @@ The `onError` callback provides detailed error context:
273
301
  />
274
302
  ```
275
303
 
304
+ ## External Message Submission
305
+
306
+ The component supports external message submission through a ref, allowing parent components to programmatically send messages and control the chat widget.
307
+
308
+ ### Using the Ref
309
+
310
+ ```tsx
311
+ import React, { useRef } from 'react';
312
+ import { BlumessageChat, BlumessageChatRef } from '@blumessage/react-chat';
313
+
314
+ function App() {
315
+ const chatRef = useRef<BlumessageChatRef>(null);
316
+
317
+ const sendExternalMessage = async () => {
318
+ if (chatRef.current) {
319
+ await chatRef.current.sendMessage("Hello from external component!");
320
+ }
321
+ };
322
+
323
+ const openChat = () => {
324
+ chatRef.current?.openChat();
325
+ };
326
+
327
+ const closeChat = () => {
328
+ chatRef.current?.closeChat();
329
+ };
330
+
331
+ const clearChat = () => {
332
+ chatRef.current?.clearConversation();
333
+ };
334
+
335
+ const getMessages = () => {
336
+ const messages = chatRef.current?.getMessages();
337
+ console.log('Current messages:', messages);
338
+ };
339
+
340
+ const getToken = () => {
341
+ const token = chatRef.current?.getToken();
342
+ console.log('Current conversation token:', token);
343
+ };
344
+
345
+ return (
346
+ <div>
347
+ <button onClick={sendExternalMessage}>Send External Message</button>
348
+ <button onClick={openChat}>Open Chat</button>
349
+ <button onClick={closeChat}>Close Chat</button>
350
+ <button onClick={clearChat}>Clear Chat</button>
351
+ <button onClick={getMessages}>Get Messages</button>
352
+ <button onClick={getToken}>Get Token</button>
353
+
354
+ <BlumessageChat
355
+ ref={chatRef}
356
+ apiKey="your-api-key"
357
+ floating={true}
358
+ />
359
+ </div>
360
+ );
361
+ }
362
+ ```
363
+
364
+ ### Available Ref Methods
365
+
366
+ | Method | Type | Description |
367
+ |--------|------|-------------|
368
+ | `sendMessage(message: string)` | `Promise<void>` | Send a message programmatically |
369
+ | `openChat()` | `void` | Open the floating chat widget |
370
+ | `closeChat()` | `void` | Close the floating chat widget |
371
+ | `clearConversation()` | `void` | Clear all messages and reset conversation |
372
+ | `getMessages()` | `Message[]` | Get current messages array |
373
+ | `getToken()` | `string \| null` | Get current conversation token |
374
+
375
+ ### Complete Example with External Controls
376
+
377
+ ```tsx
378
+ import React, { useRef, useState } from 'react';
379
+ import { BlumessageChat, BlumessageChatRef } from '@blumessage/react-chat';
380
+
381
+ function App() {
382
+ const [externalMessage, setExternalMessage] = useState('');
383
+ const chatRef = useRef<BlumessageChatRef>(null);
384
+
385
+ const handleSendExternalMessage = async () => {
386
+ if (!externalMessage.trim() || !chatRef.current) return;
387
+
388
+ try {
389
+ await chatRef.current.sendMessage(externalMessage);
390
+ setExternalMessage('');
391
+ console.log('External message sent successfully');
392
+ } catch (error) {
393
+ console.error('Failed to send external message:', error);
394
+ }
395
+ };
396
+
397
+ return (
398
+ <div>
399
+ <div style={{ marginBottom: '20px' }}>
400
+ <input
401
+ type="text"
402
+ value={externalMessage}
403
+ onChange={(e) => setExternalMessage(e.target.value)}
404
+ placeholder="Type a message to send externally..."
405
+ onKeyPress={(e) => e.key === 'Enter' && handleSendExternalMessage()}
406
+ />
407
+ <button onClick={handleSendExternalMessage}>Send</button>
408
+ <button onClick={() => chatRef.current?.openChat()}>Open Chat</button>
409
+ <button onClick={() => chatRef.current?.closeChat()}>Close Chat</button>
410
+ <button onClick={() => chatRef.current?.clearConversation()}>Clear Chat</button>
411
+ </div>
412
+
413
+ <BlumessageChat
414
+ ref={chatRef}
415
+ apiKey="your-api-key"
416
+ floating={true}
417
+ onUserMessage={(message) => console.log('User message:', message)}
418
+ onAssistantMessage={(message) => console.log('Assistant message:', message)}
419
+ />
420
+ </div>
421
+ );
422
+ }
423
+ ```
424
+
276
425
  ## TypeScript Support
277
426
 
278
427
  Full TypeScript definitions included:
279
428
 
280
429
  ```typescript
281
- import { BlumessageChat, Message, BlumessageChatProps } from '@blumessage/react-chat';
430
+ import { BlumessageChat, Message, BlumessageChatProps, BlumessageChatRef } from '@blumessage/react-chat';
282
431
 
283
432
  interface Message {
284
433
  id: string;
@@ -286,6 +435,15 @@ interface Message {
286
435
  content: string;
287
436
  timestamp: number;
288
437
  }
438
+
439
+ interface BlumessageChatRef {
440
+ sendMessage: (message: string) => Promise<void>;
441
+ openChat: () => void;
442
+ closeChat: () => void;
443
+ clearConversation: () => void;
444
+ getMessages: () => Message[];
445
+ getToken: () => string | null;
446
+ }
289
447
  ```
290
448
 
291
449
  ## Storage Behavior
@@ -301,6 +459,32 @@ interface Message {
301
459
  - React 18+
302
460
  - TypeScript 4.5+
303
461
 
462
+ ## Building
463
+
464
+ ### Standard Build (ES Modules)
465
+
466
+ ```bash
467
+ npm run build
468
+ ```
469
+
470
+ ### Browser Versions
471
+
472
+ #### Standalone (Includes React)
473
+
474
+ ```bash
475
+ npm run build:commonjs
476
+ ```
477
+
478
+ This creates `dist/index.js` which includes React and can be used directly in browsers.
479
+
480
+ #### External React (Smaller Bundle)
481
+
482
+ ```bash
483
+ npm run build:browser
484
+ ```
485
+
486
+ This creates `dist/blumessage-chat.browser.js` which requires React to be loaded separately.
487
+
304
488
  ## License
305
489
 
306
490
  UNLICENSED - For use only by customers with an active Blumessage subscription.
@@ -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 = {
@@ -783,4 +926,4 @@ export var BlumessageChat = function (_a) {
783
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()] }));
784
927
  }
785
928
  return renderChatWindow();
786
- };
929
+ });