langflow-chatbot
Version:
Add a Langflow-powered chatbot to your website.
162 lines (161 loc) • 7.65 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChatDisplayManager = void 0;
const datetimeUtils_1 = require("../utils/datetimeUtils");
/**
* Manages the display of messages and other UI elements within the chat widget.
* Handles DOM manipulations for adding, updating, and removing messages, scrolling, etc.
*/
class ChatDisplayManager {
/**
* Constructs a ChatDisplayManager instance.
* @param {HTMLElement} widgetElement - The main HTML element of the chat widget.
* @param {ChatDisplayManagerConfig} config - Configuration for display behavior and templates.
* @param {Logger} logger - An instance of the Logger for logging messages.
*/
constructor(widgetElement, config, logger) {
this.widgetElement = widgetElement;
this.config = config;
this.logger = logger;
this.chatMessagesContainer = this.widgetElement.querySelector('.chat-messages');
if (!this.chatMessagesContainer) {
this.logger.error("ChatDisplayManager: .chat-messages container not found in widgetElement.");
// It will be harder to operate, but we don't throw here to allow potential recovery or partial functionality.
}
this.datetimeHandler = (0, datetimeUtils_1.createDefaultDatetimeHandler)(this.config.datetimeFormat);
}
/**
* Sets a custom datetime handler function.
* If the new handler is invalid, the existing handler is retained.
* @param {DatetimeHandler} newHandler - The new datetime handler function.
*/
setDatetimeHandler(newHandler) {
if ((0, datetimeUtils_1.isValidDatetimeHandler)(newHandler)) {
this.datetimeHandler = newHandler;
this.logger.info("ChatDisplayManager: Custom datetime handler set successfully.");
}
else {
this.logger.warn("ChatDisplayManager: Attempted to set an invalid or misbehaving datetime handler. Using previous or default handler.");
}
}
/**
* Adds a message to the chat display.
* @param {string} sender - The sender of the message (e.g., user, bot).
* @param {string} message - The message content (HTML or plain text).
* @param {boolean} [isThinking=false] - Whether the message is a "thinking" indicator.
* @param {string} [datetime] - Optional ISO datetime string for the message. Defaults to current time.
* @returns {HTMLElement | null} The created message element, or null if an error occurred.
*/
addMessageToDisplay(sender, message, isThinking = false, datetime) {
if (!this.chatMessagesContainer) {
this.logger.error("Cannot add message, .chat-messages container not found.");
return null;
}
let messageClasses = "message";
if (sender === this.config.userSender) {
messageClasses += " user-message";
}
else if (sender === this.config.botSender) {
messageClasses += " bot-message";
}
else if (sender === this.config.errorSender) {
messageClasses += " error-message";
}
else if (sender === this.config.systemSender) {
messageClasses += " system-message";
}
if (isThinking) {
messageClasses += " thinking";
}
const effectiveDatetime = datetime || new Date().toISOString();
const formattedDatetime = this.datetimeHandler(effectiveDatetime);
// Ensure message content is treated as text to prevent XSS if it's not explicitly HTML.
// For this component, we assume `message` can be HTML as it's used for bot thinking bubbles.
// Proper sanitization should happen before this stage if content is purely user-generated and untrusted.
let populatedTemplate = this.config.messageTemplate
.replace("{{messageClasses}}", messageClasses)
.replace("{{sender}}", sender)
.replace("{{message}}", message)
.replace("{{datetime}}", formattedDatetime);
const tempDiv = document.createElement('div');
tempDiv.innerHTML = populatedTemplate.trim();
const messageElement = tempDiv.firstElementChild;
if (messageElement) {
this.chatMessagesContainer.appendChild(messageElement);
this.scrollChatToBottom();
return messageElement;
}
this.logger.error("ChatDisplayManager: Failed to create message element from template.");
return null;
}
/**
* Updates the content of an existing bot message element, typically used for streaming responses.
* Prioritizes updating the `.message-text-content` span within the element.
* If not found, falls back to updating the first child (if not sender display) or the element itself.
* @param {HTMLElement} messageElement - The bot message HTML element to update.
* @param {string} htmlOrText - The new HTML or text content.
*/
updateBotMessageContent(messageElement, htmlOrText) {
const textContentSpan = messageElement.querySelector('.message-text-content');
if (textContentSpan) {
textContentSpan.innerHTML = htmlOrText;
}
else {
this.logger.warn("ChatDisplayManager: .message-text-content span not found in messageElement. Using fallback logic to update content.");
// Fallback logic inspired by original ChatWidget behavior:
let mainContentArea = messageElement.firstElementChild || messageElement;
// Skip over sender-name-display if it's the first child
if (mainContentArea.classList.contains('sender-name-display')) {
mainContentArea = mainContentArea.nextElementSibling || messageElement;
}
// If after skipping, we still have a valid element, update it. Otherwise, update the root messageElement.
if (mainContentArea && mainContentArea !== messageElement) {
mainContentArea.innerHTML = htmlOrText;
}
else {
messageElement.innerHTML = htmlOrText; // Safest fallback if no specific content area is identified
}
}
this.scrollChatToBottom();
}
/**
* Scrolls the chat messages container to the bottom.
* Uses requestAnimationFrame for smoother rendering.
*/
scrollChatToBottom() {
if (this.chatMessagesContainer) {
requestAnimationFrame(() => {
if (this.chatMessagesContainer) { // Re-check in case the element was removed between frames
this.chatMessagesContainer.scrollTop = this.chatMessagesContainer.scrollHeight;
}
});
}
}
/**
* Removes a specific message element from the display.
* @param {HTMLElement} messageElement - The HTML element of the message to remove.
*/
removeMessageElement(messageElement) {
if (messageElement && messageElement.parentNode) {
messageElement.parentNode.removeChild(messageElement);
}
}
/**
* Removes any message element currently marked with the 'thinking' class.
*/
removeThinkingMessage() {
const thinkingMessage = this.widgetElement.querySelector('.message.thinking');
if (thinkingMessage) {
thinkingMessage.remove();
}
}
/**
* Clears all messages from the chat messages container.
*/
clearMessages() {
if (this.chatMessagesContainer) {
this.chatMessagesContainer.innerHTML = '';
}
}
}
exports.ChatDisplayManager = ChatDisplayManager;