UNPKG

adrit-mcb

Version:

A customizable floating chatbot widget for React applications

292 lines (277 loc) 19.8 kB
'use strict'; var React = require('react'); var generativeAi = require('@google/generative-ai'); var styled = require('styled-components'); var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } function __makeTemplateObject(cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * Creates a Google AI handler for the ChatbotWidget. * * @example * const aiHandler = createGoogleAIHandler('your-api-key'); * <ChatbotWidget aiHandler={aiHandler} /> */ function createGoogleAIHandler(apiKey, modelId) { var _this = this; if (modelId === void 0) { modelId = 'gemini-2.0-flash'; } if (!apiKey) { console.warn('[adrit-mcb] createGoogleAIHandler called with empty apiKey.'); } var genAI = new generativeAi.GoogleGenerativeAI(apiKey); var model = genAI.getGenerativeModel({ model: modelId }); return function (messages) { return __awaiter(_this, void 0, void 0, function () { var chat, lastMessage, result, response, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 4]); chat = model.startChat({ history: messages.slice(0, -1).map(function (msg) { return ({ role: msg.role === 'assistant' ? 'model' : 'user', parts: [{ text: msg.content }], }); }), generationConfig: { maxOutputTokens: 1000, }, }); lastMessage = messages[messages.length - 1]; return [4 /*yield*/, chat.sendMessage(lastMessage.content)]; case 1: result = _a.sent(); return [4 /*yield*/, result.response]; case 2: response = _a.sent(); return [2 /*return*/, response.text()]; case 3: error_1 = _a.sent(); console.error('Error calling Google AI:', error_1); return [2 /*return*/, 'Sorry, I encountered an error processing your request.']; case 4: return [2 /*return*/]; } }); }); }; } /** * Gets the API key from environment variables, supporting multiple build tools: * - Vite: import.meta.env.VITE_GEMINI_API_KEY * - Create React App: process.env.REACT_APP_GEMINI_API_KEY * - Node.js: process.env.GEMINI_API_KEY * - Direct variable: GEMINI_API_KEY */ function getApiKey() { // Check for Vite environment variables if (typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)) }) !== 'undefined' && undefined) { return (undefined.VITE_GEMINI_API_KEY || undefined.GEMINI_API_KEY || ''); } // Check for Create React App environment variables if (typeof process !== 'undefined' && process.env) { return (process.env.REACT_APP_GEMINI_API_KEY || process.env.GEMINI_API_KEY || ''); } // Check for global variable (for direct browser usage) if (typeof window !== 'undefined' && window.GEMINI_API_KEY) { return window.GEMINI_API_KEY; } return ''; } /** * A ready-to-use handler that automatically tries to get the API key from environment variables. * For Vite: Use VITE_GEMINI_API_KEY in your .env file * For Create React App: Use REACT_APP_GEMINI_API_KEY in your .env file * For Node.js: Use GEMINI_API_KEY in your environment */ var defaultAIHandler = createGoogleAIHandler(getApiKey()); var FloatingButton = styled.button(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background-color: ", ";\n border: none;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.3s ease;\n z-index: 1000;\n\n &:hover {\n transform: scale(1.05);\n }\n"], ["\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background-color: ", ";\n border: none;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.3s ease;\n z-index: 1000;\n\n &:hover {\n transform: scale(1.05);\n }\n"])), function (props) { return props.$buttonColor; }); var ChatContainer = styled.div(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n position: fixed;\n bottom: 90px;\n right: 20px;\n width: 350px;\n height: 500px;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);\n display: flex;\n flex-direction: column;\n transition: transform 0.3s ease, opacity 0.3s ease;\n z-index: 1000;\n \n ", "\n"], ["\n position: fixed;\n bottom: 90px;\n right: 20px;\n width: 350px;\n height: 500px;\n background-color: white;\n border-radius: 12px;\n box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);\n display: flex;\n flex-direction: column;\n transition: transform 0.3s ease, opacity 0.3s ease;\n z-index: 1000;\n \n ", "\n"])), function (props) { return !props.$isOpen && styled.css(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n transform: scale(0.9);\n opacity: 0;\n pointer-events: none;\n "], ["\n transform: scale(0.9);\n opacity: 0;\n pointer-events: none;\n "]))); }); var ChatHeader = styled.div(templateObject_4 || (templateObject_4 = __makeTemplateObject(["\n background-color: ", ";\n padding: 16px;\n border-radius: 12px 12px 0 0;\n color: white;\n font-weight: 600;\n"], ["\n background-color: ", ";\n padding: 16px;\n border-radius: 12px 12px 0 0;\n color: white;\n font-weight: 600;\n"])), function (props) { return props.$themeColor; }); var ChatFooter = styled.div(templateObject_5 || (templateObject_5 = __makeTemplateObject(["\n padding: 5px;\n background-color: #f0f0f0;\n border-radius: 12px;\n color: black;\n font-style: italic;\n font-size: 8px;\n text-align: center;\n \n"], ["\n padding: 5px;\n background-color: #f0f0f0;\n border-radius: 12px;\n color: black;\n font-style: italic;\n font-size: 8px;\n text-align: center;\n \n"]))); var MessagesContainer = styled.div(templateObject_6 || (templateObject_6 = __makeTemplateObject(["\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n"], ["\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n"]))); var MessageBubble = styled.div(templateObject_7 || (templateObject_7 = __makeTemplateObject(["\n max-width: 80%;\n padding: 12px 16px;\n border-radius: 12px;\n align-self: ", ";\n background-color: ", ";\n color: ", ";\n"], ["\n max-width: 80%;\n padding: 12px 16px;\n border-radius: 12px;\n align-self: ", ";\n background-color: ", ";\n color: ", ";\n"])), function (props) { return props.$isUser ? 'flex-end' : 'flex-start'; }, function (props) { return props.$isUser ? props.$themeColor : '#f0f0f0'; }, function (props) { return props.$isUser ? 'white' : 'black'; }); var InputContainer = styled.div(templateObject_8 || (templateObject_8 = __makeTemplateObject(["\n padding: 16px;\n border-top: 1px solid #eee;\n display: flex;\n gap: 8px;\n"], ["\n padding: 16px;\n border-top: 1px solid #eee;\n display: flex;\n gap: 8px;\n"]))); var Input = styled.input(templateObject_9 || (templateObject_9 = __makeTemplateObject(["\n flex: 1;\n padding: 12px;\n border: 1px solid #ddd;\n border-radius: 8px;\n font-size: 14px;\n outline: none;\n \n &:focus {\n border-color: #0070f3;\n }\n"], ["\n flex: 1;\n padding: 12px;\n border: 1px solid #ddd;\n border-radius: 8px;\n font-size: 14px;\n outline: none;\n \n &:focus {\n border-color: #0070f3;\n }\n"]))); var SendButton = styled.button(templateObject_10 || (templateObject_10 = __makeTemplateObject(["\n padding: 0 16px;\n border: none;\n border-radius: 8px;\n background-color: ", ";\n color: white;\n cursor: pointer;\n transition: opacity 0.2s ease;\n \n &:hover {\n opacity: 0.9;\n }\n \n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n"], ["\n padding: 0 16px;\n border: none;\n border-radius: 8px;\n background-color: ", ";\n color: white;\n cursor: pointer;\n transition: opacity 0.2s ease;\n \n &:hover {\n opacity: 0.9;\n }\n \n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n"])), function (props) { return props.$themeColor; }); var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5, templateObject_6, templateObject_7, templateObject_8, templateObject_9, templateObject_10; var DEFAULT_BUTTON_COLOR = '#0070f3'; var DEFAULT_THEME_COLOR = '#0070f3'; var DEFAULT_GREETING = '👋 Hi there! How can I help you today?'; var DEFAULT_SYSTEM_INSTRUCTIONS = 'You are a helpful assistant'; var DEFAULT_AI_HANDLER = defaultAIHandler; var ChatbotWidget = function (_a) { var _b = _a.buttonColor, buttonColor = _b === void 0 ? DEFAULT_BUTTON_COLOR : _b, _c = _a.themeColor, themeColor = _c === void 0 ? DEFAULT_THEME_COLOR : _c, _d = _a.greeting, greeting = _d === void 0 ? DEFAULT_GREETING : _d, _e = _a.systemInstructions, systemInstructions = _e === void 0 ? DEFAULT_SYSTEM_INSTRUCTIONS : _e, _f = _a.aiHandler, aiHandler = _f === void 0 ? DEFAULT_AI_HANDLER : _f; var _g = React.useState({ isOpen: false, messages: [], loading: false, }), state = _g[0], setState = _g[1]; var _h = React.useState(''), inputValue = _h[0], setInputValue = _h[1]; var messagesEndRef = React.useRef(null); var scrollToBottom = React.useCallback(function () { var _a; (_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: 'smooth' }); }, []); React.useEffect(function () { if (state.isOpen && state.messages.length === 0) { setState(function (prev) { return (__assign(__assign({}, prev), { messages: [{ id: '0', text: greeting, sender: 'ai', timestamp: new Date() }] })); }); } }, [state.isOpen, greeting]); React.useEffect(function () { scrollToBottom(); }, [state.messages, scrollToBottom]); var toggleChat = React.useCallback(function () { setState(function (prev) { return (__assign(__assign({}, prev), { isOpen: !prev.isOpen })); }); }, []); var handleInputChange = React.useCallback(function (e) { setInputValue(e.target.value); }, []); var sendMessage = React.useCallback(function () { return __awaiter(void 0, void 0, void 0, function () { var userMessage, messages, response, aiMessage_1, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!inputValue.trim() || state.loading) return [2 /*return*/]; userMessage = { id: Date.now().toString(), text: inputValue.trim(), sender: 'user', timestamp: new Date() }; setState(function (prev) { return (__assign(__assign({}, prev), { messages: __spreadArray(__spreadArray([], prev.messages, true), [userMessage], false), loading: true })); }); setInputValue(''); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); messages = __spreadArray(__spreadArray([ { role: 'system', content: systemInstructions } ], state.messages.map(function (msg) { return ({ role: msg.sender === 'user' ? 'user' : 'assistant', content: msg.text }); }), true), [ { role: 'user', content: userMessage.text } ], false); return [4 /*yield*/, aiHandler(messages)]; case 2: response = _a.sent(); aiMessage_1 = { id: (Date.now() + 1).toString(), text: response, sender: 'ai', timestamp: new Date() }; setState(function (prev) { return (__assign(__assign({}, prev), { messages: __spreadArray(__spreadArray([], prev.messages, true), [aiMessage_1], false), loading: false })); }); return [3 /*break*/, 4]; case 3: error_1 = _a.sent(); console.error('Error generating response:', error_1); setState(function (prev) { return (__assign(__assign({}, prev), { loading: false })); }); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }, [inputValue, state.loading, state.messages, systemInstructions]); var handleKeyPress = React.useCallback(function (e) { if (e.key === 'Enter') { sendMessage(); } }, [sendMessage]); return (React.createElement(React.Fragment, null, React.createElement(FloatingButton, { "$buttonColor": buttonColor, onClick: toggleChat, "aria-label": state.isOpen ? 'Close chat' : 'Open chat' }, state.isOpen ? '✕' : '💬'), React.createElement(ChatContainer, { "$isOpen": state.isOpen, "$themeColor": themeColor }, React.createElement(ChatHeader, { "$themeColor": themeColor }, "AI Chat"), React.createElement(MessagesContainer, null, state.messages.map(function (message) { return (React.createElement(MessageBubble, { key: message.id, "$isUser": message.sender === 'user', "$themeColor": themeColor }, message.text)); }), React.createElement("div", { ref: messagesEndRef })), React.createElement(InputContainer, null, React.createElement(Input, { value: inputValue, onChange: handleInputChange, onKeyPress: handleKeyPress, placeholder: "Type your message...", disabled: state.loading }), React.createElement(SendButton, { onClick: sendMessage, disabled: !inputValue.trim() || state.loading, "$themeColor": themeColor }, state.loading ? '...' : 'Send')), React.createElement(ChatFooter, null, "Powered by ", React.createElement("a", { href: 'https://dorbai.com' }, "DORB AI"))))); }; exports.ChatbotWidget = ChatbotWidget; exports.createGoogleAIHandler = createGoogleAIHandler; exports.defaultAIHandler = defaultAIHandler; //# sourceMappingURL=index.js.map