adrit-mcb
Version:
A customizable floating chatbot widget for React applications
265 lines (250 loc) • 18.8 kB
JavaScript
;
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;
};
var _a;
/**
* 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-1.5-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*/];
}
});
}); };
}
/**
* A ready-to-use handler that relies on the `GEMINI_API_KEY` environment variable.
* In browser builds, this usually comes from your bundler's environment variables.
*/
var defaultAIHandler = createGoogleAIHandler((typeof process !== 'undefined' ? process.env.GEMINI_API_KEY : '') ||
(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' ? (_a = undefined) === null || _a === void 0 ? void 0 : _a.GEMINI_API_KEY : '') ||
'');
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;
//# sourceMappingURL=index.js.map