UNPKG

langflow-chatbot

Version:

Add a Langflow-powered chatbot to your website.

306 lines (305 loc) 14.1 kB
"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;