langflow-chatbot
Version:
Add a Langflow-powered chatbot to your website.
306 lines (305 loc) • 14.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FloatingChatWidget = void 0;
const ChatWidget_1 = require("./ChatWidget");
const logger_1 = require("../utils/logger");
const uiConstants_1 = require("../config/uiConstants");
/** Default configuration values for the FloatingChatWidget's appearance and behavior. */
const DEFAULT_FLOATING_CONFIG = {
isOpen: false,
position: 'bottom-right',
showCloseButton: true,
showToggleButton: true,
toggleButtonText: 'Chat', // Default text, though current implementation uses an icon
widgetTitle: 'Chatbot',
};
/**
* Provides a floating, toggleable chat interface that wraps the main ChatWidget.
* Manages the visibility and placement of the chat panel and the toggle button.
*/
class FloatingChatWidget {
static validatePosition(pos) {
const allowedPositions = ['bottom-right', 'bottom-left', 'top-right', 'top-left'];
return allowedPositions.includes(pos) ? pos : 'bottom-right';
}
/**
* Constructs a FloatingChatWidget instance.
* @param {LangflowChatClient} chatClient - The client for API interactions.
* @param {boolean} [enableStream=true] - Whether to enable streaming for bot responses (passed to ChatWidget).
* @param {FloatingChatWidgetConfig} [userConfig={}] - User-provided configuration options.
* @param {Logger} [logger] - Optional logger instance. If not provided, a new one is created.
*/
constructor(chatClient, enableStream = true, userConfig = {}, logger) {
this.floatingButton = null;
this.chatContainer = null;
this.chatWidgetInstance = null;
this.isChatVisible = false;
this.chatClient = chatClient;
this.enableStream = enableStream;
const validatedPosition = FloatingChatWidget.validatePosition(userConfig.position ?? DEFAULT_FLOATING_CONFIG.position);
this.logger = logger || new logger_1.Logger(userConfig.logLevel || 'info', 'FloatingChatWidget');
const resolvedFloatingWidgetTitle = userConfig.widgetTitle ?? DEFAULT_FLOATING_CONFIG.widgetTitle;
const mergedFloatingConfig = {
isOpen: userConfig.isOpen ?? DEFAULT_FLOATING_CONFIG.isOpen,
position: validatedPosition,
showCloseButton: userConfig.showCloseButton ?? DEFAULT_FLOATING_CONFIG.showCloseButton,
showToggleButton: userConfig.showToggleButton ?? DEFAULT_FLOATING_CONFIG.showToggleButton,
toggleButtonText: userConfig.toggleButtonText ?? DEFAULT_FLOATING_CONFIG.toggleButtonText,
widgetTitle: resolvedFloatingWidgetTitle,
logLevel: userConfig.logLevel,
initialSessionId: userConfig.initialSessionId,
onSessionIdUpdate: userConfig.onSessionIdUpdate,
datetimeFormat: userConfig.datetimeFormat,
floatingPanelWidth: userConfig.floatingPanelWidth,
floatingWidgetHeaderTemplate: userConfig.floatingWidgetHeaderTemplate,
chatWidgetConfig: {
labels: {
widgetTitle: resolvedFloatingWidgetTitle,
userSender: userConfig.chatWidgetConfig?.labels?.userSender,
botSender: userConfig.chatWidgetConfig?.labels?.botSender,
errorSender: userConfig.chatWidgetConfig?.labels?.errorSender,
systemSender: userConfig.chatWidgetConfig?.labels?.systemSender,
welcomeMessage: userConfig.chatWidgetConfig?.labels?.welcomeMessage,
},
template: {
mainContainerTemplate: userConfig.chatWidgetConfig?.template?.mainContainerTemplate,
inputAreaTemplate: userConfig.chatWidgetConfig?.template?.inputAreaTemplate,
messageTemplate: userConfig.chatWidgetConfig?.template?.messageTemplate,
widgetHeaderTemplate: userConfig.floatingWidgetHeaderTemplate || userConfig.chatWidgetConfig?.template?.widgetHeaderTemplate || uiConstants_1.DEFAULT_FLOATING_WIDGET_HEADER_TEMPLATE,
},
datetimeFormat: userConfig.chatWidgetConfig?.datetimeFormat,
},
containerId: userConfig.containerId,
};
this.config = mergedFloatingConfig;
if (this.logger && typeof this.logger.info === 'function') {
this.logger.info('FloatingChatWidget initialized with config:', this.config);
}
this._createElements();
this._setupEventListeners();
this.isChatVisible = this.config.isOpen;
if (this.config.isOpen) {
this.showChat(true);
}
else {
this.hideChat(true);
}
document.body.appendChild(this.floatingButton);
document.body.appendChild(this.chatContainer);
if (this.config.floatingPanelWidth && this.chatContainer) {
this.chatContainer.style.setProperty('--langflow-floating-panel-width', this.config.floatingPanelWidth);
this.logger.info(`FloatingChatWidget: Panel width set to ${this.config.floatingPanelWidth} via config.`);
}
}
/**
* Creates the necessary DOM elements for the floating widget:
* - The floating toggle button.
* - The chat panel container, including its header (title, minimize button).
* - The host div for the inner ChatWidget instance.
* It then instantiates the inner ChatWidget within the host div.
*/
_createElements() {
if (!document.getElementById('floating-chat-widget-header-style')) {
const style = document.createElement('style');
style.id = 'floating-chat-widget-header-style';
style.textContent = `
.floating-chat-panel .chat-widget-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5em 1em;
border-bottom: 1px solid #eee;
}
.floating-chat-panel .chat-widget-title-text {
font-weight: bold;
font-size: 1em;
flex: 1 1 auto;
text-align: left;
margin-right: 1em;
}
.floating-chat-panel .chat-widget-minimize-button {
background: none;
border: none;
cursor: pointer;
padding: 0.25em;
margin-left: 0.5em;
display: flex;
align-items: center;
}
`;
document.head.appendChild(style);
}
this.floatingButton = document.createElement('div');
this.floatingButton.className = `floating-chat-button ${this.config.position}`;
this.floatingButton.innerHTML = uiConstants_1.SVG_CHAT_ICON;
if (!this.config.showToggleButton) {
this.floatingButton.style.display = 'none';
}
this.chatContainer = document.createElement('div');
this.chatContainer.className = `floating-chat-panel ${this.config.position}`;
const chatWidgetDiv = document.createElement('div');
const chatWidgetInnerId = `chat-widget-inner-container-${Date.now()}-${Math.random().toString(36).substring(2)}`;
chatWidgetDiv.id = chatWidgetInnerId;
chatWidgetDiv.className = 'chat-widget-inner-host';
this.chatContainer.appendChild(chatWidgetDiv);
try {
this.chatWidgetInstance = new ChatWidget_1.ChatWidget(chatWidgetDiv, this.chatClient, this.enableStream, {
labels: this.config.chatWidgetConfig.labels,
template: this.config.chatWidgetConfig.template,
datetimeFormat: this.config.chatWidgetConfig.datetimeFormat,
}, this.logger, this.config.initialSessionId, this.config.onSessionIdUpdate);
if (this.chatWidgetInstance) {
const widgetElement = this.chatWidgetInstance.getWidgetElement();
const minimizeButton = widgetElement.querySelector('.chat-widget-minimize-button');
if (minimizeButton) {
if (this.config.showCloseButton) {
minimizeButton.onclick = () => this.toggleChatVisibility();
}
else {
minimizeButton.style.display = 'none';
}
}
// Listen for the chatReset event from the inner ChatWidget
this.chatResetListener = (event) => {
this.logger.info('FloatingChatWidget: Detected chatReset event from inner ChatWidget. Re-dispatching.');
if (this.chatContainer) {
this.chatContainer.dispatchEvent(new CustomEvent('chatReset', { bubbles: true, composed: true }));
}
};
widgetElement.addEventListener('chatReset', this.chatResetListener);
}
}
catch (error) {
this.logger.error("Failed to instantiate ChatWidget.", error);
chatWidgetDiv.innerHTML = '<p class="chat-load-error">Error: Could not load chat.</p>';
}
}
/**
* Sets up event listeners for the floating widget, primarily for the toggle button.
*/
_setupEventListeners() {
if (this.floatingButton) {
this.floatingButton.addEventListener('click', () => this.toggleChatVisibility());
}
}
/**
* Toggles the visibility of the chat panel and the floating button.
*/
toggleChatVisibility() {
this.isChatVisible = !this.isChatVisible;
if (this.chatContainer && this.floatingButton) {
if (this.isChatVisible) {
this.chatContainer.style.display = 'flex';
this.floatingButton.style.display = 'none';
this.scrollToBottomWhenVisible();
}
else {
this.chatContainer.style.display = 'none';
if (this.config.showToggleButton) {
this.floatingButton.style.display = 'flex';
}
else {
this.floatingButton.style.display = 'none';
}
}
}
}
/**
* Shows the chat panel.
* @param {boolean} [initial=false] - If true, sets display style directly without toggling, for initial setup.
*/
showChat(initial = false) {
if (!this.isChatVisible || initial) {
if (!initial)
this.toggleChatVisibility();
else {
if (this.chatContainer)
this.chatContainer.style.display = 'flex';
if (this.floatingButton) {
this.floatingButton.style.display = 'none';
}
this.isChatVisible = true;
this.scrollToBottomWhenVisible();
}
}
}
/**
* Scrolls to bottom when the panel becomes visible.
* Uses requestAnimationFrame to ensure the panel is fully rendered before scrolling.
*/
scrollToBottomWhenVisible() {
if (this.chatWidgetInstance) {
requestAnimationFrame(() => {
if (this.chatWidgetInstance) {
const displayManager = this.chatWidgetInstance.displayManager;
if (displayManager && typeof displayManager.scrollChatToBottom === 'function') {
displayManager.scrollChatToBottom();
}
}
});
}
}
/**
* Hides the chat panel.
* @param {boolean} [initial=false] - If true, sets display style directly without toggling, for initial setup.
*/
hideChat(initial = false) {
if (this.isChatVisible || initial) {
if (!initial)
this.toggleChatVisibility();
else {
if (this.chatContainer)
this.chatContainer.style.display = 'none';
if (this.floatingButton) {
if (this.config.showToggleButton) {
this.floatingButton.style.display = 'flex';
}
else {
this.floatingButton.style.display = 'none';
}
}
this.isChatVisible = false;
}
}
}
/**
* Destroys the FloatingChatWidget instance.
* This includes destroying the inner ChatWidget instance and removing the floating widget's DOM elements.
*/
destroy() {
if (this.chatWidgetInstance && typeof this.chatWidgetInstance.destroy === 'function') {
// Remove event listener before destroying the inner widget
const widgetElement = this.chatWidgetInstance.getWidgetElement();
if (widgetElement && this.chatResetListener) {
widgetElement.removeEventListener('chatReset', this.chatResetListener);
this.chatResetListener = undefined;
}
this.chatWidgetInstance.destroy();
}
this.chatWidgetInstance = null;
if (this.floatingButton) {
this.floatingButton.remove();
this.floatingButton = null;
}
if (this.chatContainer) {
this.chatContainer.remove();
this.chatContainer = null;
}
this.logger.info("FloatingChatWidget instance destroyed.");
}
/**
* Returns the main DOM element for the chat panel.
* @returns {HTMLElement | null} The chat panel element.
*/
getPanelElement() {
return this.chatContainer;
}
/**
* Returns the container element for attaching listeners or custom behavior.
* @returns {HTMLElement | null} The container element.
*/
getContainerElement() {
return this.config.containerId ? document.getElementById(this.config.containerId) : null;
}
}
exports.FloatingChatWidget = FloatingChatWidget;