ultra-chat-bot
Version:
made with ❤️ by Mohamed Majri
1,267 lines (1,250 loc) • 114 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('lucide-react'), require('socket.io-client')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'lucide-react', 'socket.io-client'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.UltraChatBot = {}, global.React, global.LucideReact, global.io));
})(this, (function (exports, React, lucideReact, socket_ioClient) { 'use strict';
var jsxRuntime = {exports: {}};
var reactJsxRuntime_production = {};
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_production;
function requireReactJsxRuntime_production () {
if (hasRequiredReactJsxRuntime_production) return reactJsxRuntime_production;
hasRequiredReactJsxRuntime_production = 1;
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"),
REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
function jsxProd(type, config, maybeKey) {
var key = null;
void 0 !== maybeKey && (key = "" + maybeKey);
void 0 !== config.key && (key = "" + config.key);
if ("key" in config) {
maybeKey = {};
for (var propName in config)
"key" !== propName && (maybeKey[propName] = config[propName]);
} else maybeKey = config;
config = maybeKey.ref;
return {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: void 0 !== config ? config : null,
props: maybeKey
};
}
reactJsxRuntime_production.Fragment = REACT_FRAGMENT_TYPE;
reactJsxRuntime_production.jsx = jsxProd;
reactJsxRuntime_production.jsxs = jsxProd;
return reactJsxRuntime_production;
}
var reactJsxRuntime_development = {};
/**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_development;
function requireReactJsxRuntime_development () {
if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development;
hasRequiredReactJsxRuntime_development = 1;
"production" !== process.env.NODE_ENV &&
(function () {
function getComponentNameFromType(type) {
if (null == type) return null;
if ("function" === typeof type)
return type.$$typeof === REACT_CLIENT_REFERENCE
? null
: type.displayName || type.name || null;
if ("string" === typeof type) return type;
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
case REACT_ACTIVITY_TYPE:
return "Activity";
}
if ("object" === typeof type)
switch (
("number" === typeof type.tag &&
console.error(
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
),
type.$$typeof)
) {
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_CONTEXT_TYPE:
return (type.displayName || "Context") + ".Provider";
case REACT_CONSUMER_TYPE:
return (type._context.displayName || "Context") + ".Consumer";
case REACT_FORWARD_REF_TYPE:
var innerType = type.render;
type = type.displayName;
type ||
((type = innerType.displayName || innerType.name || ""),
(type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef"));
return type;
case REACT_MEMO_TYPE:
return (
(innerType = type.displayName || null),
null !== innerType
? innerType
: getComponentNameFromType(type.type) || "Memo"
);
case REACT_LAZY_TYPE:
innerType = type._payload;
type = type._init;
try {
return getComponentNameFromType(type(innerType));
} catch (x) {}
}
return null;
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
try {
testStringCoercion(value);
var JSCompiler_inline_result = !1;
} catch (e) {
JSCompiler_inline_result = true;
}
if (JSCompiler_inline_result) {
JSCompiler_inline_result = console;
var JSCompiler_temp_const = JSCompiler_inline_result.error;
var JSCompiler_inline_result$jscomp$0 =
("function" === typeof Symbol &&
Symbol.toStringTag &&
value[Symbol.toStringTag]) ||
value.constructor.name ||
"Object";
JSCompiler_temp_const.call(
JSCompiler_inline_result,
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
JSCompiler_inline_result$jscomp$0
);
return testStringCoercion(value);
}
}
function getTaskName(type) {
if (type === REACT_FRAGMENT_TYPE) return "<>";
if (
"object" === typeof type &&
null !== type &&
type.$$typeof === REACT_LAZY_TYPE
)
return "<...>";
try {
var name = getComponentNameFromType(type);
return name ? "<" + name + ">" : "<...>";
} catch (x) {
return "<...>";
}
}
function getOwner() {
var dispatcher = ReactSharedInternals.A;
return null === dispatcher ? null : dispatcher.getOwner();
}
function UnknownOwner() {
return Error("react-stack-top-frame");
}
function hasValidKey(config) {
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) return false;
}
return void 0 !== config.key;
}
function defineKeyPropWarningGetter(props, displayName) {
function warnAboutAccessingKey() {
specialPropKeyWarningShown ||
((specialPropKeyWarningShown = true),
console.error(
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
displayName
));
}
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
function elementRefGetterWithDeprecationWarning() {
var componentName = getComponentNameFromType(this.type);
didWarnAboutElementRef[componentName] ||
((didWarnAboutElementRef[componentName] = true),
console.error(
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
));
componentName = this.props.ref;
return void 0 !== componentName ? componentName : null;
}
function ReactElement(
type,
key,
self,
source,
owner,
props,
debugStack,
debugTask
) {
self = props.ref;
type = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
props: props,
_owner: owner
};
null !== (void 0 !== self ? self : null)
? Object.defineProperty(type, "ref", {
enumerable: false,
get: elementRefGetterWithDeprecationWarning
})
: Object.defineProperty(type, "ref", { enumerable: false, value: null });
type._store = {};
Object.defineProperty(type._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: 0
});
Object.defineProperty(type, "_debugInfo", {
configurable: false,
enumerable: false,
writable: true,
value: null
});
Object.defineProperty(type, "_debugStack", {
configurable: false,
enumerable: false,
writable: true,
value: debugStack
});
Object.defineProperty(type, "_debugTask", {
configurable: false,
enumerable: false,
writable: true,
value: debugTask
});
Object.freeze && (Object.freeze(type.props), Object.freeze(type));
return type;
}
function jsxDEVImpl(
type,
config,
maybeKey,
isStaticChildren,
source,
self,
debugStack,
debugTask
) {
var children = config.children;
if (void 0 !== children)
if (isStaticChildren)
if (isArrayImpl(children)) {
for (
isStaticChildren = 0;
isStaticChildren < children.length;
isStaticChildren++
)
validateChildKeys(children[isStaticChildren]);
Object.freeze && Object.freeze(children);
} else
console.error(
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
);
else validateChildKeys(children);
if (hasOwnProperty.call(config, "key")) {
children = getComponentNameFromType(type);
var keys = Object.keys(config).filter(function (k) {
return "key" !== k;
});
isStaticChildren =
0 < keys.length
? "{key: someKey, " + keys.join(": ..., ") + ": ...}"
: "{key: someKey}";
didWarnAboutKeySpread[children + isStaticChildren] ||
((keys =
0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}"),
console.error(
'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />',
isStaticChildren,
children,
keys,
children
),
(didWarnAboutKeySpread[children + isStaticChildren] = true));
}
children = null;
void 0 !== maybeKey &&
(checkKeyStringCoercion(maybeKey), (children = "" + maybeKey));
hasValidKey(config) &&
(checkKeyStringCoercion(config.key), (children = "" + config.key));
if ("key" in config) {
maybeKey = {};
for (var propName in config)
"key" !== propName && (maybeKey[propName] = config[propName]);
} else maybeKey = config;
children &&
defineKeyPropWarningGetter(
maybeKey,
"function" === typeof type
? type.displayName || type.name || "Unknown"
: type
);
return ReactElement(
type,
children,
self,
source,
getOwner(),
maybeKey,
debugStack,
debugTask
);
}
function validateChildKeys(node) {
"object" === typeof node &&
null !== node &&
node.$$typeof === REACT_ELEMENT_TYPE &&
node._store &&
(node._store.validated = 1);
}
var React$1 = React,
REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"),
REACT_PORTAL_TYPE = Symbol.for("react.portal"),
REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"),
REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"),
REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"),
REACT_CONTEXT_TYPE = Symbol.for("react.context"),
REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"),
REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"),
REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"),
REACT_MEMO_TYPE = Symbol.for("react.memo"),
REACT_LAZY_TYPE = Symbol.for("react.lazy"),
REACT_ACTIVITY_TYPE = Symbol.for("react.activity"),
REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"),
ReactSharedInternals =
React$1.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
hasOwnProperty = Object.prototype.hasOwnProperty,
isArrayImpl = Array.isArray,
createTask = console.createTask
? console.createTask
: function () {
return null;
};
React$1 = {
"react-stack-bottom-frame": function (callStackForError) {
return callStackForError();
}
};
var specialPropKeyWarningShown;
var didWarnAboutElementRef = {};
var unknownOwnerDebugStack = React$1["react-stack-bottom-frame"].bind(
React$1,
UnknownOwner
)();
var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
var didWarnAboutKeySpread = {};
reactJsxRuntime_development.Fragment = REACT_FRAGMENT_TYPE;
reactJsxRuntime_development.jsx = function (type, config, maybeKey, source, self) {
var trackActualOwner =
1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
return jsxDEVImpl(
type,
config,
maybeKey,
false,
source,
self,
trackActualOwner
? Error("react-stack-top-frame")
: unknownOwnerDebugStack,
trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask
);
};
reactJsxRuntime_development.jsxs = function (type, config, maybeKey, source, self) {
var trackActualOwner =
1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
return jsxDEVImpl(
type,
config,
maybeKey,
true,
source,
self,
trackActualOwner
? Error("react-stack-top-frame")
: unknownOwnerDebugStack,
trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask
);
};
})();
return reactJsxRuntime_development;
}
if (process.env.NODE_ENV === 'production') {
jsxRuntime.exports = requireReactJsxRuntime_production();
} else {
jsxRuntime.exports = requireReactJsxRuntime_development();
}
var jsxRuntimeExports = jsxRuntime.exports;
// Geist font family with fallbacks
const GEIST_FONT_FAMILY$2 = '"Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
const Avatar = ({ style, children }) => {
const baseStyles = {
position: 'relative',
display: 'flex',
height: '2.5rem',
width: '2.5rem',
flexShrink: 0,
overflow: 'hidden',
borderRadius: '50%',
...style,
};
return (jsxRuntimeExports.jsx("span", { style: baseStyles, children: children }));
};
const AvatarImage = ({ src, alt }) => {
const [imageLoaded, setImageLoaded] = React.useState(false);
const [imageError, setImageError] = React.useState(false);
const handleLoad = () => {
setImageLoaded(true);
setImageError(false);
};
const handleError = () => {
setImageError(true);
setImageLoaded(false);
};
if (!src || imageError) {
return null;
}
return (jsxRuntimeExports.jsx("img", { src: src, alt: alt, style: {
height: '100%',
width: '100%',
objectFit: 'cover',
display: imageLoaded ? 'block' : 'none',
}, onLoad: handleLoad, onError: handleError }));
};
const AvatarFallback = ({ style, children }) => {
const baseStyles = {
display: 'flex',
height: '100%',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f3f4f6',
color: '#6b7280',
fontSize: '0.875rem',
fontWeight: '500',
fontFamily: GEIST_FONT_FAMILY$2,
...style,
};
return (jsxRuntimeExports.jsx("span", { style: baseStyles, children: children }));
};
// Geist font family with fallbacks
const GEIST_FONT_FAMILY$1 = '"Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
const Button = ({ variant = 'default', size = 'default', className, children, style, ...props }) => {
const baseStyles = {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '0.375rem',
fontSize: '0.875rem',
fontWeight: '500',
fontFamily: GEIST_FONT_FAMILY$1,
transition: 'all 0.2s ease-in-out',
cursor: 'pointer',
border: 'none',
textDecoration: 'none',
outline: 'none',
...getVariantStyles(variant),
...getSizeStyles(size),
};
return (jsxRuntimeExports.jsx("button", { style: {
...baseStyles,
...style,
}, ...props, children: children }));
};
function getVariantStyles(variant) {
switch (variant) {
case 'outline':
return {
backgroundColor: 'transparent',
border: '1px solid #d1d5db',
color: '#374151',
};
case 'ghost':
return {
backgroundColor: 'transparent',
color: '#374151',
};
default:
return {
backgroundColor: '#3b82f6',
color: 'white',
};
}
}
function getSizeStyles(size) {
switch (size) {
case 'sm':
return {
height: '2rem',
paddingLeft: '0.75rem',
paddingRight: '0.75rem',
fontSize: '0.75rem',
};
case 'lg':
return {
height: '2.75rem',
paddingLeft: '2rem',
paddingRight: '2rem',
fontSize: '1rem',
};
case 'icon':
return {
height: '2.5rem',
width: '2.5rem',
padding: 0,
};
default:
return {
height: '2.5rem',
paddingLeft: '1rem',
paddingRight: '1rem',
};
}
}
const Card = ({ style, children }) => {
const baseStyles = {
backgroundColor: '#ffffff',
border: '1px solid #e5e7eb',
borderRadius: '0.5rem',
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
...style,
};
return (jsxRuntimeExports.jsx("div", { style: baseStyles, children: children }));
};
const CardContent = ({ style, children }) => {
const baseStyles = {
padding: '1.5rem',
...style,
};
return (jsxRuntimeExports.jsx("div", { style: baseStyles, children: children }));
};
const CardFooter = ({ style, children }) => {
const baseStyles = {
display: 'flex',
alignItems: 'center',
padding: '1.5rem',
paddingTop: '0',
...style,
};
return (jsxRuntimeExports.jsx("div", { style: baseStyles, children: children }));
};
const CardAction = ({ style, children }) => {
const baseStyles = {
paddingTop: '1rem',
...style,
};
return (jsxRuntimeExports.jsx("div", { style: baseStyles, children: children }));
};
// ChatSessionManager class for handling session management
class ChatSessionManager {
constructor(socketUrl) {
this.socket = null;
this.sessionId = null;
this.chatId = null;
this.isInitialized = false;
this.eventHandlers = new Map();
this.sessionKey = 'chatbot-session';
this.sessionExpiry = 60 * 60 * 1000; // 1 hour for guests
this.socketUrl = socketUrl;
}
// Event handling
on(event, handler) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, []);
}
this.eventHandlers.get(event).push(handler);
}
off(event, handler) {
if (!this.eventHandlers.has(event))
return;
if (!handler) {
this.eventHandlers.delete(event);
}
else {
const handlers = this.eventHandlers.get(event);
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
}
emit(event, ...args) {
const handlers = this.eventHandlers.get(event) || [];
handlers.forEach(handler => handler(...args));
}
// Session management
getStoredSession() {
try {
const stored = localStorage.getItem(this.sessionKey);
if (!stored)
return null;
const session = JSON.parse(stored);
// Check if session has expired (only for guests)
if (!session.email && Date.now() - session.timestamp > this.sessionExpiry) {
this.clearStoredSession();
return null;
}
return session;
}
catch (error) {
this.clearStoredSession();
return null;
}
}
storeSession(sessionId, chatId, email) {
try {
const sessionData = {
sessionId,
chatId,
timestamp: Date.now(),
...(email && { email })
};
localStorage.setItem(this.sessionKey, JSON.stringify(sessionData));
}
catch (error) {
// Handle localStorage errors silently
}
}
clearStoredSession() {
try {
localStorage.removeItem(this.sessionKey);
}
catch (error) {
// Handle localStorage errors silently
}
}
// Session validation
async validateSession(sessionId) {
try {
const response = await fetch(`${this.socketUrl}/session/${sessionId}`);
const data = await response.json();
return data.valid === true;
}
catch (error) {
return false;
}
}
// Initialize session and connection
async initialize(userInfo, options) {
const storedSession = this.getStoredSession();
let sessionId = null;
let chatId = null;
if (storedSession) {
const isValid = await this.validateSession(storedSession.sessionId);
if (isValid) {
sessionId = storedSession.sessionId;
chatId = storedSession.chatId;
}
else {
this.clearStoredSession();
}
}
// Connect to socket
this.socket = socket_ioClient.io(this.socketUrl);
return new Promise((resolve, reject) => {
if (!this.socket) {
reject(new Error('Failed to create socket connection'));
return;
}
this.socket.on('connect', () => {
var _a;
this.emit('connect');
// Join with session info
const joinData = {
name: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.firstname) && (userInfo === null || userInfo === void 0 ? void 0 : userInfo.lastname)
? `${userInfo.firstname} ${userInfo.lastname}`
: "Anonymous Guest",
email: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.email) || null,
firstname: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.firstname) || null,
lastname: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.lastname) || null,
origin: options === null || options === void 0 ? void 0 : options.companyName,
visitedPaths: (options === null || options === void 0 ? void 0 : options.visitedPaths) || [],
currentPath: (options === null || options === void 0 ? void 0 : options.currentPath) || window.location.pathname,
...(sessionId && { sessionId }),
...(chatId && { chatId })
};
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.emit("guest:join", joinData);
});
this.socket.on('disconnect', () => {
this.emit('disconnect');
});
this.socket.on("guest:joined", (data) => {
this.sessionId = data.sessionId;
this.chatId = data.chatId;
this.isInitialized = true;
// Store session
this.storeSession(data.sessionId, data.chatId, userInfo === null || userInfo === void 0 ? void 0 : userInfo.email);
// Set up message handlers
this.setupMessageHandlers(options);
// Handle chat history if provided
if (data.isReconnection && data.chatHistory && data.chatHistory.length > 0) {
this.emit('chatHistoryRestored', data.chatHistory);
}
else if (data.isReconnection) {
// Reconnection but no chat history - emit empty history event
// console.log('🔄 Reconnection detected but no chat history found');
this.emit('chatHistoryRestored', []);
}
resolve({
success: true,
sessionId: data.sessionId,
chatId: data.chatId,
reconnected: data.reconnected || !!sessionId,
isReconnection: data.isReconnection,
chatHistory: data.chatHistory
});
});
this.socket.on('error', (error) => {
this.emit('error', error);
reject(error);
});
// Timeout after 10 seconds
setTimeout(() => {
if (!this.isInitialized) {
reject(new Error('Connection timeout'));
}
}, 10000);
});
}
setupMessageHandlers(options) {
if (!this.socket)
return;
// Message handling
this.socket.on("message", (data) => {
this.emit('message', data);
});
// Message sent confirmation
this.socket.on("message:sent", (data) => {
this.emit('messageSent', data);
});
// Typing indicators
if (options === null || options === void 0 ? void 0 : options.showTypingIndicator) {
this.socket.on("admin:startTyping", (data) => {
this.emit('adminStartTyping', data);
});
this.socket.on("admin:stopTyping", (data) => {
this.emit('adminStopTyping', data);
});
this.socket.on("typing", (data) => {
this.emit('typing', data);
});
}
// Message seen events
this.socket.on("messages:seenByAdmin", (data) => {
this.emit('messagesSeenByAdmin', data);
});
this.socket.on("messages:marked", (data) => {
this.emit('messagesMarked', data);
});
// Chat history events
this.socket.on("guest:chatHistory", (data) => {
this.emit('chatHistory', data.messages);
});
}
// Send message
sendMessage(text, userInfo, options) {
if (!this.socket || !this.isInitialized) {
throw new Error('Session not initialized');
}
const messageData = {
text,
sender: "user",
origin: options === null || options === void 0 ? void 0 : options.companyName,
timestamp: new Date().toISOString(),
paths: (options === null || options === void 0 ? void 0 : options.visitedPaths) || [],
currentPath: (options === null || options === void 0 ? void 0 : options.currentPath) || window.location.pathname,
};
// Add user info if available
if ((userInfo === null || userInfo === void 0 ? void 0 : userInfo.firstname) && (userInfo === null || userInfo === void 0 ? void 0 : userInfo.lastname) && (userInfo === null || userInfo === void 0 ? void 0 : userInfo.email)) {
messageData.firstname = userInfo.firstname;
messageData.lastname = userInfo.lastname;
messageData.email = userInfo.email;
}
this.socket.emit("guest:message", messageData);
}
// Typing indicators
startTyping() {
if (this.socket && this.isInitialized) {
this.socket.emit('guest:startTyping');
}
}
stopTyping() {
if (this.socket && this.isInitialized) {
this.socket.emit('guest:stopTyping');
}
}
// Mark messages as seen
markMessagesSeen(messageIds) {
if (this.socket && this.isInitialized) {
this.socket.emit("guest:markSeen", { messageIds });
}
}
// Request chat history
getChatHistory() {
if (this.socket && this.isInitialized) {
this.socket.emit("guest:getChatHistory");
}
}
// Send path navigation update
updateNavigationPath(currentPath, visitedPaths, userInfo) {
if (this.socket && this.isInitialized) {
const pathData = {
currentPath,
visitedPaths,
timestamp: new Date().toISOString(),
};
// Add user info if available
if ((userInfo === null || userInfo === void 0 ? void 0 : userInfo.firstname) && (userInfo === null || userInfo === void 0 ? void 0 : userInfo.lastname) && (userInfo === null || userInfo === void 0 ? void 0 : userInfo.email)) {
pathData.firstname = userInfo.firstname;
pathData.lastname = userInfo.lastname;
pathData.email = userInfo.email;
}
this.socket.emit("guest:pathUpdate", pathData);
}
}
// Connection status
get isConnected() {
var _a;
return ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.connected) || false;
}
get currentSessionId() {
return this.sessionId;
}
get currentChatId() {
return this.chatId;
}
// Cleanup
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
}
this.isInitialized = false;
this.sessionId = null;
this.chatId = null;
this.eventHandlers.clear();
}
}
// const socketUrl = "http://localhost:5001";
const socketUrl = "https://chat-bot.gentauronline.com";
const fallBackPrimaryColor = "#003299";
const fallBackSecondaryColor = "#efb100";
const audioUrl = "https://cdn.gentaur.com/chat-bots/Voicy_Telegram%20SFX%205.mp3";
// Geist font family with fallbacks
const GEIST_FONT_FAMILY = '"Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
// Function to load Geist font from Google Fonts
const loadGeistFont = () => {
// Check if font is already loaded
if (document.querySelector('link[href*="fonts.googleapis.com"][href*="Geist"]')) {
return;
}
// Create preconnect links for better performance
const preconnect1 = document.createElement('link');
preconnect1.rel = 'preconnect';
preconnect1.href = 'https://fonts.googleapis.com';
const preconnect2 = document.createElement('link');
preconnect2.rel = 'preconnect';
preconnect2.href = 'https://fonts.gstatic.com';
preconnect2.crossOrigin = 'anonymous';
// Create link element for Geist font
const fontLink = document.createElement('link');
fontLink.rel = 'stylesheet';
fontLink.href = 'https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap';
// Add to document head
document.head.appendChild(preconnect1);
document.head.appendChild(preconnect2);
document.head.appendChild(fontLink);
};
// Function to load custom animations
const loadCustomAnimations = () => {
// Check if animations are already loaded
if (document.querySelector('#chatbot-animations')) {
return;
}
const style = document.createElement('style');
style.id = 'chatbot-animations';
style.textContent = `
@keyframes slideIn {
0% {
width: 0;
opacity: 0;
}
100% {
width: 100%;
opacity: 1;
}
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-6px);
}
60% {
transform: translateY(-3px);
}
}
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.9;
}
100% {
transform: scale(1);
opacity: 1;
}
}
`;
document.head.appendChild(style);
};
// Function to darken a hex color
function darkenColor(hex, percent = 20) {
// Remove # if present
hex = hex.replace('#', '');
// Parse RGB values
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
// Darken by reducing RGB values
const darkenedR = Math.max(0, Math.floor(r * (100 - percent) / 100));
const darkenedG = Math.max(0, Math.floor(g * (100 - percent) / 100));
const darkenedB = Math.max(0, Math.floor(b * (100 - percent) / 100));
// Convert back to hex
return `#${darkenedR.toString(16).padStart(2, '0')}${darkenedG.toString(16).padStart(2, '0')}${darkenedB.toString(16).padStart(2, '0')}`;
}
function formatTimeToHHMM() {
const date = new Date();
const hours = date.getUTCHours().toString().padStart(2, "0");
const minutes = date.getUTCMinutes().toString().padStart(2, "0");
return `${hours}:${minutes} UTC`;
}
function formatChatHistoryMessages(chatHistory, botAvatar, userAvatar) {
return chatHistory.map((msg) => ({
id: msg._id || "msg-" + Date.now() + Math.random(),
text: msg.text,
sender: (msg.sender === "admin" || msg.sender === "system" || msg.sender === "bot") ? "bot" : "user",
timestamp: new Date(msg.timestamp),
avatar: (msg.sender === "admin" || msg.sender === "system" || msg.sender === "bot") ? botAvatar : userAvatar,
seenByAdmin: msg.seenByAdmin || false,
seenByGuest: msg.seenByGuest || false,
seenAt: msg.seenAt ? new Date(msg.seenAt) : undefined,
originalSender: msg.sender,
senderName: msg.senderName,
}));
}
const ChatBot = ({ UID, config = {}, onMessageSent, onMessageReceived, onConnect, onDisconnect, className, }) => {
const [serverConfig, setServerConfig] = React.useState({});
const [isConfigLoading, setIsConfigLoading] = React.useState(true);
const mergedConfig = React.useMemo(() => {
return { ...serverConfig, ...config };
}, [serverConfig, config]);
const [isOpen, setIsOpen] = React.useState(false);
const [activeTab, setActiveTab] = React.useState("home");
const [messages, setMessages] = React.useState([]);
const [inputText, setInputText] = React.useState("");
const [isConnected, setIsConnected] = React.useState(false);
const [isAdminTyping, setIsAdminTyping] = React.useState(false);
const [chatSessionManager, setChatSessionManager] = React.useState(null);
const [unseenBotMessageCount, setUnseenBotMessageCount] = React.useState(0);
const [sessionInfo, setSessionInfo] = React.useState({});
const messagesEndRef = React.useRef(null);
React.useRef(null);
const [isHoveringChatButton, setIsHoveringChatButton] = React.useState(false);
const [isAnimating, setIsAnimating] = React.useState(false);
// Typing system refs and state
const typingTimeoutRef = React.useRef(null);
const isCurrentlyTyping = React.useRef(false);
const [hasUserSentMessage, setHasUserSentMessage] = React.useState(false);
// Seen system refs and state
const unseenAdminMessages = React.useRef(new Set());
React.useRef(null);
const pendingPathUpdates = React.useRef([]);
const lastSentPath = React.useRef("");
const pathUpdateTimeoutRef = React.useRef(null);
// Track all visited paths with localStorage persistence and session timeout
const [visitedPaths, setVisitedPaths] = React.useState([]);
const [pathsInitialized, setPathsInitialized] = React.useState(false);
const updateVisitedPaths = (newPath) => {
setVisitedPaths(prev => {
let updated = prev;
if (!prev.includes(newPath)) {
updated = [...prev, newPath];
try {
localStorage.setItem('chatbot-visited-paths', JSON.stringify(updated));
localStorage.setItem('chatbot-paths-timestamp', Date.now().toString());
}
catch (error) {
// Handle localStorage errors silently
}
}
else {
try {
localStorage.setItem('chatbot-paths-timestamp', Date.now().toString());
}
catch (error) {
// Handle localStorage errors silently
}
}
debouncedPathUpdate(newPath, updated);
return updated;
});
};
// Add current path on mount and track path changes
React.useEffect(() => {
// Wait for paths to be initialized from localStorage
if (!pathsInitialized) {
return;
}
const currentPath = window.location.pathname;
// Add current path if not already in the list
updateVisitedPaths(currentPath);
const handlePopState = () => {
const newPath = window.location.pathname;
updateVisitedPaths(newPath);
};
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function (...args) {
originalPushState.apply(history, args);
const newPath = window.location.pathname;
updateVisitedPaths(newPath);
};
history.replaceState = function (...args) {
originalReplaceState.apply(history, args);
const newPath = window.location.pathname;
updateVisitedPaths(newPath);
};
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
history.pushState = originalPushState;
history.replaceState = originalReplaceState;
};
}, [pathsInitialized]);
const isOpenRef = React.useRef(isOpen);
const activeTabRef = React.useRef(activeTab);
const chatSessionManagerRef = React.useRef(chatSessionManager);
React.useEffect(() => {
isOpenRef.current = isOpen;
}, [isOpen]);
React.useEffect(() => {
activeTabRef.current = activeTab;
}, [activeTab]);
React.useEffect(() => {
chatSessionManagerRef.current = chatSessionManager;
if (chatSessionManager && chatSessionManager.isConnected) {
processPendingPathUpdates();
}
}, [chatSessionManager]);
React.useEffect(() => {
if (isConnected && chatSessionManager) {
processPendingPathUpdates();
}
}, [isConnected, chatSessionManager]);
React.useEffect(() => {
return () => {
if (pathUpdateTimeoutRef.current) {
clearTimeout(pathUpdateTimeoutRef.current);
}
};
}, []);
// Function to fetch bot config from server
const fetchBotConfig = async () => {
if (!UID) {
setIsConfigLoading(false);
return;
}
try {
const response = await fetch(`${socketUrl}/bot-config/${UID}`);
if (response.ok) {
const botConfig = await response.json();
const mappedConfig = {
companyName: botConfig.companyName,
botName: botConfig.botName,
homeScreenMessage: botConfig.homeScreenMessage,
presentationMessage: botConfig.presentationMessage,
botAvatar: botConfig.botAvatar,
userAvatar: botConfig.userAvatar,
theme: botConfig.theme,
primaryColor: botConfig.primaryColor,
secondaryColor: botConfig.secondaryColor,
placeholder: botConfig.placeholder,
welcomeMessage: botConfig.welcomeMessage,
socketUrl: socketUrl,
autoConnect: botConfig.autoConnect,
showTypingIndicator: botConfig.showTypingIndicator,
height: botConfig.height,
width: botConfig.width,
position: botConfig.position,
firstname: botConfig.firstname,
lastname: botConfig.lastname,
email: botConfig.email,
audioUrl: botConfig.audioUrl,
enableNotificationSound: botConfig.enableNotificationSound,
};
setServerConfig(mappedConfig);
}
}
catch (error) {
// Error fetching bot config
}
finally {
setIsConfigLoading(false);
}
};
React.useEffect(() => {
fetchBotConfig();
}, [UID]);
// Load Geist font and custom animations when component mounts
React.useEffect(() => {
loadGeistFont();
loadCustomAnimations();
}, []);
// Initialize visited paths from localStorage after config is loaded
React.useEffect(() => {
if (isConfigLoading)
return;
// Check localStorage for existing paths and validate session timeout
if (typeof window !== 'undefined') {
try {
const saved = localStorage.getItem('chatbot-visited-paths');
const lastUpdate = localStorage.getItem('chatbot-paths-timestamp');
if (saved && lastUpdate) {
const sessionTimeout = mergedConfig.sessionTimeoutHours ?
mergedConfig.sessionTimeoutHours * 60 * 60 * 1000 :
2 * 60 * 60 * 1000; // Default: 2 hours in milliseconds
const timeSinceLastUpdate = Date.now() - parseInt(lastUpdate);
// If session is still valid, restore saved paths
if (timeSinceLastUpdate < sessionTimeout) {
const parsedPaths = JSON.parse(saved);
setVisitedPaths(parsedPaths);
}
else {
// Session expired, clear old data
localStorage.removeItem('chatbot-visited-paths');
localStorage.removeItem('chatbot-paths-timestamp');
}
}
}
catch (error) {
// Clear corrupted data
localStorage.removeItem('chatbot-visited-paths');
localStorage.removeItem('chatbot-paths-timestamp');
}
}
// Mark paths as initialized
setPathsInitialized(true);
}, [isConfigLoading, mergedConfig.sessionTimeoutHours]);
React.useEffect(() => {
if (pathsInitialized && typeof window !== 'undefined') {
const currentPath = window.location.pathname;
immediatePathUpdate(currentPath, visitedPaths);
}
}, [pathsInitialized, visitedPaths]);
// Aggressive fallback check for welcome message
React.useEffect(() => {
if (isConfigLoading || !mergedConfig.welcomeMessage)
return;
const fallbackTimer = setTimeout(() => {
setMessages(currentMessages => {
const hasRealMessages = currentMessages.some(msg => !msg.id.startsWith("welcome-"));
const hasWelcomeMessage = currentMessages.some(msg => msg.id.startsWith("welcome-"));
if (!hasRealMessages && !hasWelcomeMessage) {
const welcomeMsg = {
id: "welcome-" + Date.now(),
text: mergedConfig.welcomeMessage,
sender: "bot",
timestamp: new Date(),
avatar: mergedConfig.botAvatar,
};
return [welcomeMsg];
}
return currentMessages;
});
}, 2000);
return () => clearTimeout(fallbackTimer);
}, [isConfigLoading, mergedConfig.welcomeMessage, mergedConfig.botAvatar, chatSessionManager, isConnected]);
// Specific check for session establishment without chat history
React.useEffect(() => {
if (!chatSessionManager || !isConnected || isConfigLoading || !mergedConfig.welcomeMessage)
return;
const sessionEstablishedTimer = setTimeout(() => {
setMessages(currentMessages => {
const hasRealMessages = currentMessages.some(msg => !msg.id.startsWith("welcome-"));
const hasWelcomeMessage = currentMessages.some(msg => msg.id.startsWith("welcome-"));
if (!hasRealMessages && !hasWelcomeMessage) {
const welcomeMsg = {
id: "welcome-" + Date.now(),
text: mergedConfig.welcomeMessage,
sender: "bot",
timestamp: new Date(),
avatar: mergedConfig.botAvatar,
};
return [welcomeMsg];
}
return currentMessages;
});
}, 500);
return () => clearTimeout(sessionEstablishedTimer);
}, [chatSessionManager, isConnected, isConfigLoading, mergedConfig.welcomeMessage, sessionInfo]);
React.useEffect(() => {
if (isConfigLoading)
return;
if (mergedConfig.autoConnect && !chatSessionManager) {
initializeChatSession();
}
return () => {
if (chatSessionManager) {
chatSessionManager.disconnect();
}
};
}, [isConfigLoading, mergedConfig.autoConnect, chatSessionManager]);
React.useEffect(() => {
if (isConfigLoading || !mergedConfig.welcomeMessage) {
return;
}
const timers = [
setTimeout(() => {
setMessages(currentMessages => {
const welcomeExis