Custom CTA buttons for AI Chatbot

Hi Max, thanks for this. It works great and the solution is fixed.

Note: We already declared ‘const waitForElement’ in JS code to keep users on the same tab when clicking a link inside the chatbot (I included this code in my original post).

With the help of Claude AI, I was able to find a workaround to maintain the same-tab function with the CTA buttons injected. I’ve shared it below for future reference:

// Consolidated code - no duplicate declarations
const WIDGET_ID = 'e6b5fefa-bada-4235-b3bc-7e9d941415f0';

// Single waitForElement function for both features
const waitForChatElement = (selector, root = document, maxAttempts = 500) =>
  new Promise((resolve) => {
    let attempts = 0;
    const check = () => {
      const element = root.querySelector(selector);
      if (element) {
        resolve(element);
      } else if (attempts < maxAttempts) {
        attempts++;
        setTimeout(check, 100);
      }
    };
    check();
  });

// CTA Button Configuration
const CALL_BUTTON_TEXT = 'Call Us';
const CALL_BUTTON_LINK = 'tel:8887298812';
const SMS_BUTTON_TEXT = 'Text Us';
const SMS_BUTTON_LINK = 'sms:8542239313';

let chatContainer = null;

// Initialize both features
waitForChatElement(`.eapps-ai-chatbot-${WIDGET_ID}-custom-css-root .es-window-body`).then((container) => {
  chatContainer = container;
  
  // Feature 1: Fix links to open in same tab
  const fixLinks = () => {
    const links = chatContainer.querySelectorAll("a[target='_blank']");
    links.forEach((link) => {
      link.target = "_self";
    });
  };
  
  const linkObserver = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.addedNodes.length) {
        fixLinks();
      }
    });
  });
  
  linkObserver.observe(chatContainer, {
    childList: true,
    subtree: true,
  });
  
  fixLinks();
});

// Feature 2: Add CTA buttons
const observeChildNodes = (element, callback) => {
  const observer = new MutationObserver((mutationsList) => {
    for (const mutation of mutationsList) {
      mutation.addedNodes.forEach(callback);
    }
  });
  observer.observe(element, { childList: true });
  return observer;
};

const createCTAButton = (text, link, className) =>
  Object.assign(document.createElement('a'), {
    href: link,
    textContent: text,
    className: `es-btn ${className}`,
    target: '_blank',
  });

const addButtons = (btns, portal) => {
  const lastAssistantMessage = [
    ...portal.querySelectorAll('.es-message-group-assistant'),
  ].pop();
  if (lastAssistantMessage) {
    lastAssistantMessage.after(btns);
  }
};

const firstAssistantMessageSelector =
  '.es-message-group-assistant:not(:first-child):has(.es-message-content-container)';

waitForChatElement(firstAssistantMessageSelector).then((assistantFirstMsg) => {
  const portal = assistantFirstMsg.closest('#__EAAPS_PORTAL');
  let messageContainer = portal.querySelector(
    '[class*="widget-window__MessagesContainer-sc"]'
  );
  
  if (!portal || !messageContainer) return;
  
  const wrapper = document.createElement('div');
  wrapper.className = 'es-cta-buttons';
  
  // Call button
  const callBtn = createCTAButton(
    CALL_BUTTON_TEXT,
    CALL_BUTTON_LINK,
    'es-btn-call'
  );
  
  // SMS button
  const smsBtn = createCTAButton(
    SMS_BUTTON_TEXT,
    SMS_BUTTON_LINK,
    'es-btn-sms'
  );
  
  wrapper.append(callBtn, smsBtn);
  addButtons(wrapper, portal);
  
  // Track new assistant messages
  const observeNewMessages = () => {
    return observeChildNodes(messageContainer, (el) => {
      if (el.classList && el.classList.contains('es-message-group-assistant')) {
        addButtons(wrapper, portal);
      }
    });
  };
  
  let messagesObserver = observeNewMessages();
  
  // Track when chat window opens
  observeChildNodes(portal, (el) => {
    if (el.classList && el.classList.contains('es-window-container')) {
      addButtons(wrapper, portal);
      messagesObserver.disconnect();
      messageContainer = portal.querySelector(
        '[class*="widget-window__MessagesContainer-sc"]'
      );
      messagesObserver = observeNewMessages();
    }
  });
});

Thanks so much!

2 Likes