v0.1

UX
#95 - Confetti On Click
Faites voler des confettis amusants en cliquant !
Build a Webflow chatbot that dynamically loads help articles from collection lists and updates in real time.
Watch the video for step-by-step implementation instructions
<!-- 💙 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>More scripts in UX