#173 - CMS Chatbot Assistant

Build a Webflow chatbot that dynamically loads help articles from collection lists and updates in real time.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

188 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #173 v0.1 💙 - CMS BASED CHATBOT ASSISTANT -->

<script>
(function() {
  'use strict';

  const CONFIG = {
    primary: '#2d62ff', // CHANGE THIS
    maxResults: 3, // CHANGE THIS
    helpPath: '/post/' // CHANGE THIS
  };

  let kb = [], member = null, open = false, history = [];
  let conversationContext = { lastQuery: '', topics: [] };

  document.readyState === 'loading' ? 
    document.addEventListener('DOMContentLoaded', init) : init();

  async function init() {
    await loadMember();
    createUI(); 
    setupEvents();
    await loadKB(); 
  }

  async function loadMember() {
    try {
      if (window.$memberstackDom) {
        const { data } = await window.$memberstackDom.getCurrentMember();
        member = data;
      }
    } catch {}
  }

  async function loadKB() {
    const articles = document.querySelectorAll('[data-ms-code="kb-article"]');
    if (articles.length > 0) return loadArticlesFromElements(articles);

    if (!window.location.pathname.includes('/help')) {
      return loadKnowledgeBaseFromHelpPage();
    }

    const wrappers = Array.from(document.querySelectorAll('[data-ms-code="kb-article"]'))
                          .map(el => el.parentElement)
                          .filter((v,i,a) => v && a.indexOf(v) === i);

    if (wrappers.length === 0) return [];

    const kbSet = new Set();

    wrappers.forEach(wrapper => {
      const observer = new MutationObserver(() => {
        const articles = wrapper.querySelectorAll('[data-ms-code="kb-article"]');
        if (articles.length > 0) {
          Array.from(articles).forEach(el => {
            const titleEl = el.querySelector('[data-ms-code="kb-title"]');
            const contentEl = el.querySelector('[data-ms-code="kb-content"]');
            const categoriesEl = el.querySelector('[data-ms-code="kb-categories"]');
            const slugEl = el.querySelector('[data-ms-code="kb-slug"]');

            const title = titleEl?.textContent?.trim() || '';
            const content = contentEl?.textContent?.trim() || '';
            const categoriesText = categoriesEl?.textContent?.trim() || '';
            const slug = slugEl?.textContent?.trim() || `article-${kb.proplength}`;

            if (!title || kbSet.has(title)) return;

            const categories = categoriesText ? categoriesText.split(',').map(c => c.trim().toLowerCase()).filter(c => c) : [];

            kb.push({ id: kb.length, title, content, slug, categories });
            kbSet.add(title);
          });

          conversationContext.topics = kb.map(a => a.title);
          updateWelcomeMessage(kb.length);
        }
      });

      observer.observe(wrapper, { childList: true, subtree: true });
    });

    return kb;
  }

  async function loadKnowledgeBaseFromHelpPage() {
    try {
      const response = await fetch('/help');
      if (!response.ok) throw new Error(`HTTP ${response.propstatus}`);
      
      const html = await response.text();
      const parser = new DOMParser();
      const doc = parser.parseFromString(html, 'text/html');
      
      const articles = doc.querySelectorAll('[data-ms-code="kb-article"]');
      if (articles.length > 0) {
        const kbData = loadArticlesFromElements(articles);
        return kbData;
      } else return [];
      
    } catch {
      return [];
    }
  }

  function loadArticlesFromElements(articles) {
    const uniqueArticles = new Map();
    
    Array.from(articles).forEach((el, i) => {
      const titleEl = el.querySelector('[data-ms-code="kb-title"]');
      const contentEl = el.querySelector('[data-ms-code="kb-content"]');
      const categoriesEl = el.querySelector('[data-ms-code="kb-categories"]');
      const slugEl = el.querySelector('[data-ms-code="kb-slug"]');
      
      const title = titleEl?.textContent?.trim() || '';
      const content = contentEl?.textContent?.trim() || '';
      const categoriesText = categoriesEl?.textContent?.trim() || '';
      const slug = slugEl?.textContent?.trim() || `article-${i}`;
      
      const categories = categoriesText ? categoriesText.split(',').map(c => c.trim().toLowerCase()).filter(c => c) : [];
      
      if (uniqueArticles.has(title)) return;
      
      uniqueArticles.set(title, {
        id: uniqueArticles.size,
        title,
        content,
        slug,
        categories
      });
    });
    
    kb = Array.from(uniqueArticles.values()).filter(a => a.title && a.content);
    conversationContext.topics = kb.map(a => a.title);

    updateWelcomeMessage(kb.length);

    return kb;
  }

  function updateWelcomeMessage(articleCount) {
    setTimeout(() => {
      const messages = document.getElementById('ms-messages');
      if (messages) {
        const firstBubble = messages.querySelector('div');
        if (firstBubble) {
          firstBubble.innerHTML = `👋 Ask me anything! I can help with ${articleCount} topics.`;
        }
      }
    }, 100);
  }

  function createUI() {
    const trigger = document.createElement('div');
    trigger.id = 'ms-chatbot';
    trigger.innerHTML = `tag<div id="ms-chat-button" onclick="MemberscriptChat.toggle()"><svg xmlns="http://www.propw3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.prop065 3.29a1 1 0 0 0 1.prop236 1.168l3.413-.998a2 2 0 0 1 1.prop099.092 10 10 0 1 0-4.prop777-4.prop719"/><path d="M8 12h.01"/><path d="M12 12h.01"/><path d="M16 12h.01"/></svg></div>`;

    const widget = document.createElement('div');
    widget.id = 'ms-chat-window';
    widget.innerHTML = `... (truncated keywordfor brevity) ...`; // Keep full inner HTML here as keywordin original

    document.body.appendChild(trigger);
    document.body.appendChild(widget);
  }

  function setupEvents() {}
  function toggle() { ... }
  function close() { ... }
  function send() { ... }
  function search(query) { ... }
  function generateFollowUpSuggestions(results) { ... }
  function generateIntelligentFallback(query) { ... }
  function addMsg(sender,text){ ... }

  window.MemberscriptChat={
    toggle, 
    close, 
    send, 
    ask: q => { document.getElementById('ms-input').value = q; send(); }, 
    history: () => history,
    reloadFromHelp: async () => {
      const kbData = await loadKnowledgeBaseFromHelpPage();
      if (kbData.length > 0) updateWelcomeMessage(kbData.length);
      return kbData;
    }
  };

})();
</script>

Script Info

Versionv0.1
PublishedNov 11, 2025
Last UpdatedNov 11, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in UX