Embed Text or Voice Chat to Any Website
Use this tool to embed an n8n text chat and ElevenLabs AI conversational agent to any website. Use it in combination with the InfraNodus knowledge base to create custom knowledge AI chatbots in n8n.
I created this widget because I needed to give users an option to embed both the n8n text chat bot and the ElevenLabs voice chat options on a website with just one embed code. Using this widget, the user can choose which modality they're going to use when interacting with content: voice or text conversation. It also has a minimal presence on your website.
To use it with n8n, you will need an active Embed chat trigger workflow. This will enable users to chat with your content using text. To use it with ElevenLabs, you will need an AI conversational agent set up. This will enable your users to talk to your content using voice. To use it with InfraNodus, you will need to create a graph that can be used to visually interact with your content.
Embed Code Generator
Text shown next to the widget button
Text shown when a widget option is activated
Leave empty to hide the graph option
Leave empty to hide the Telegram option
Embed Code
<script>
(function() {
// Configuration
const primaryColor = '#10b981';
const position = 'bottom-right';
const promptMessage = 'talk to Dmitry Paranyushkin\'s books';
const chatText = 'chat conversation';
const callText = 'voice conversation';
const graphText = 'knowledge graph';
const telegramText = 'Telegram';
const chatUrl = 'https://infranodus.app.n8n.cloud/webhook/0a59eb8e-d075-45f5-b374-c8315877bc28/chat';
const callUrl = 'https://elevenlabs.io/app/talk-to?agent_id=agent_01jvx2jt9pf9vbs7k64s6dn50y';
const graphUrl = 'https://infranodus.com/Deemeetree/dmitrys_books?background=dark&show_analytics=1&most_influential=bc2&maxnodes=200&labelsize=proportional&edgestype=curve&drawedges=true&drawnodes=true&labelsizeratio=2&dynamic=highlight&cutgraph=1&selected=highlight';
const telegramUrl = '';
const buttonLabel = 'talk to this content';
const backToWidgetText = 'back to widget';
const chatConfig = {"showWelcomeScreen":false,"defaultLanguage":"en","initialMessages":["Talk to the Books of Dmitry Paranyushkin","What would you like to discuss?"],"title":"Talk to the Books of Dmitry Paranyushkin","subtitle":"What would you like to discuss?","footer":"","getStarted":"New Conversation","inputPlaceholder":"type your message..."};
// Position styles
const positions = {
'bottom-right': 'bottom: 24px; right: 24px;',
'bottom-left': 'bottom: 24px; left: 24px;',
'top-right': 'top: 24px; right: 24px;',
'top-left': 'top: 24px; left: 24px;'
};
// Create widget
const widget = document.createElement('div');
widget.id = 'interaction-widget-container';
widget.style.cssText = 'position: fixed; z-index: 999999; ' + positions[position];
const wrapper = document.createElement('div');
wrapper.style.cssText = 'display: flex; align-items: center; gap: 12px;';
if (buttonLabel) {
const label = document.createElement('span');
label.textContent = buttonLabel;
label.style.cssText = 'background: rgba(0,0,0,0.7); color: white; padding: 8px 16px; border-radius: 24px; font-size: 14px; cursor: pointer;';
label.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
button.click();
};
wrapper.appendChild(label);
}
const button = document.createElement('button');
button.style.cssText = 'width: 56px; height: 56px; border-radius: 50%; background: ' + primaryColor + '; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(0,0,0,0.15);';
button.innerHTML = '<svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: white;"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
const popup = document.createElement('div');
popup.style.cssText = 'position: absolute; ' + (position.includes('bottom') ? 'bottom: 70px;' : 'top: 70px;') + ' ' + (position.includes('right') ? 'right: 0;' : 'left: 0;') + ' background: white; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.12); padding: 16px; min-width: 280px; display: none;';
let popupHtml = `
<h3 style="font-size: 14px; font-weight: 500; color: #374151; margin: 0 0 12px 0;">${promptMessage}</h3>
<button class="chat-btn" style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #f9fafb; border: none; border-radius: 8px; cursor: pointer; width: 100%; margin-bottom: 8px; font-size: 14px;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: ${primaryColor};"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
${chatText}
</button>
<button class="call-btn" style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #f9fafb; border: none; border-radius: 8px; cursor: pointer; width: 100%; margin-bottom: 8px; font-size: 14px;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #3b82f6;"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
${callText}
</button>
`;
if (graphUrl) {
popupHtml += `
<button class="graph-btn" style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #f9fafb; border: none; border-radius: 8px; cursor: pointer; width: 100%; margin-bottom: 8px; font-size: 14px;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #8b5cf6;"><path d="M12 2v7m0 4v7m-7-8h7m4 0h7M5.7 5.7l4.95 4.95m2.7 2.7l4.95 4.95m0-12.7l-4.95 4.95m-2.7 2.7L5.7 18.3"/></svg>
${graphText}
</button>
`;
}
if (telegramUrl) {
popupHtml += `
<button class="telegram-btn" style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #f9fafb; border: none; border-radius: 8px; cursor: pointer; width: 100%; font-size: 14px;">
<svg viewBox="0 0 24 24" style="width: 20px; height: 20px; fill: #0088cc;"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.56c-.21 2.27-1.13 7.75-1.6 10.29-.2 1.08-.59 1.44-.97 1.47-.82.07-1.45-.54-2.24-.97-1.24-.68-1.94-1.1-3.15-1.77-1.39-.77-.49-1.19.3-1.88.21-.18 3.85-3.53 3.92-3.83.01-.04.02-.18-.07-.26s-.22-.05-.31-.03c-.13.03-2.21 1.41-6.24 4.13-.59.41-1.12.61-1.6.6-.53-.01-1.54-.3-2.29-.54-.92-.3-1.65-.46-1.59-.97.03-.27.4-.54 1.11-.83 4.34-1.89 7.24-3.14 8.69-3.75 4.14-1.72 5-2.02 5.56-2.03.12 0 .39.03.57.17.15.12.19.28.21.44-.01.12-.03.31-.05.47z"/></svg>
${telegramText}
</button>
`;
}
popup.innerHTML = popupHtml;
let isOpen = false;
button.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
isOpen = !isOpen;
popup.style.display = isOpen ? 'block' : 'none';
button.innerHTML = isOpen
? '<svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: white;"><path d="M19 9l-7 7-7-7"/></svg>'
: '<svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: white;"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
};
// Function to extract agent ID from call URL
function extractAgentId(url) {
try {
const urlObj = new URL(url);
const agentId = urlObj.searchParams.get("agent_id");
return agentId || "agent_01jvx2jt9pf9vbs7k64s6dn50y"; // fallback to default
} catch (error) {
console.error("Error extracting agent ID:", error);
return "agent_01jvx2jt9pf9vbs7k64s6dn50y"; // fallback to default
}
}
// Function to inject the ElevenLabs convai widget
function injectElevenLabsWidget() {
try {
const agentId = extractAgentId(callUrl);
// Don't hide the widget completely, just clear its contents and show the back link
widget.innerHTML = '';
// Create back to widget link
const backLink = document.createElement('a');
backLink.innerHTML = '↩'; // Undo character (↩)
backLink.href = '#';
backLink.style.cssText = 'position: fixed; ' +
(position.includes('right') ? 'right: 4px;' : 'left: 4px;') +
' bottom: 4px; color: ' + primaryColor +
'; font-size: 20px; cursor: pointer; z-index: 999999; background: rgba(255,255,255,0.8); width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);';
backLink.onclick = function(e) {
e.preventDefault();
// Remove the back link
backLink.remove();
// Close ElevenLabs widget if it exists
const convaiElements = document.querySelectorAll('elevenlabs-convai');
convaiElements.forEach(el => el.remove());
// Close n8n chat if it exists
const n8nChatContainer = document.querySelector('.n8n-chat');
if (n8nChatContainer) {
n8nChatContainer.remove();
}
// Restore the original widget
wrapper.appendChild(button);
widget.appendChild(wrapper);
widget.appendChild(popup);
return false;
};
document.body.appendChild(backLink);
// Create the ElevenLabs convai element
const convaiElement = document.createElement('elevenlabs-convai');
convaiElement.setAttribute('agent-id', agentId);
document.body.appendChild(convaiElement);
// Add the ElevenLabs script
const script = document.createElement('script');
script.src = 'https://elevenlabs.io/convai-widget/index.js';
script.async = true;
script.type = 'text/javascript';
document.body.appendChild(script);
console.log('ElevenLabs convai widget injected with agent ID: ' + agentId);
return true;
} catch (error) {
console.error("Error injecting ElevenLabs widget:", error);
return false;
}
}
// Function to inject the n8n chat script
function injectN8nChat() {
try {
// Don't hide the widget completely, just clear its contents and show the back link
widget.innerHTML = '';
// Create back to widget link
const backLink = document.createElement('a');
backLink.innerHTML = '↩'; // Undo character (↩)
backLink.href = '#';
backLink.style.cssText = 'position: fixed; ' +
(position.includes('right') ? 'right: 4px;' : 'left: 4px;') +
' bottom: 4px; color: ' + primaryColor +
'; font-size: 20px; cursor: pointer; z-index: 999999; background: rgba(255,255,255,0.8); width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 5px rgba(0,0,0,0.1);';
backLink.onclick = function(e) {
e.preventDefault();
// Remove the back link
backLink.remove();
// Close ElevenLabs widget if it exists
const convaiElements = document.querySelectorAll('elevenlabs-convai');
convaiElements.forEach(el => el.remove());
// Close n8n chat if it exists
const n8nChatContainer = document.querySelector('.n8n-chat');
if (n8nChatContainer) {
n8nChatContainer.remove();
}
// Restore the original widget
wrapper.appendChild(button);
widget.appendChild(wrapper);
widget.appendChild(popup);
return false;
};
document.body.appendChild(backLink);
// Add the n8n chat stylesheet
const link = document.createElement('link');
link.href = 'https://cdn.jsdelivr.net/npm/@n8n/chat/dist/style.css';
link.rel = 'stylesheet';
document.head.appendChild(link);
// Add the n8n chat script
const script = document.createElement('script');
script.type = 'module';
script.textContent = `
import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js';
// Create the chat with the webhook URL and additional configuration
createChat({
webhookUrl: '${chatUrl}',
showWelcomeScreen: ${chatConfig.showWelcomeScreen},
defaultLanguage: '${chatConfig.defaultLanguage}',
initialMessages: [
'${chatConfig.initialMessages?.[0] || ""}',
'${chatConfig.initialMessages?.[1] || ""}'
],
i18n: {
${chatConfig.defaultLanguage}: {
title: "${chatConfig.title || ""}",
subtitle: "${chatConfig.subtitle || ""}",
footer: "${chatConfig.footer || ""}",
getStarted: '${chatConfig.getStarted || ""}',
inputPlaceholder: '${chatConfig.inputPlaceholder || ""}',
},
},
});
console.log('N8n chat created with webhook URL: ${chatUrl}');
`;
document.body.appendChild(script);
console.log('N8n chat script injected');
return true;
} catch (error) {
console.error("Error injecting n8n chat:", error);
return false;
}
}
// Add event listeners after the popup is added to the DOM
setTimeout(() => {
const chatBtn = popup.querySelector('.chat-btn');
const callBtn = popup.querySelector('.call-btn');
const graphBtn = popup.querySelector('.graph-btn');
const telegramBtn = popup.querySelector('.telegram-btn');
if (chatBtn) {
chatBtn.onclick = function() {
injectN8nChat();
};
}
if (callBtn) {
callBtn.onclick = function() {
injectElevenLabsWidget();
};
}
if (graphBtn) {
graphBtn.onclick = function() {
window.open(graphUrl, '_blank');
isOpen = false;
popup.style.display = 'none';
button.innerHTML = '<svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: white;"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
};
}
if (telegramBtn) {
telegramBtn.onclick = function() {
window.open(telegramUrl, '_blank');
isOpen = false;
popup.style.display = 'none';
button.innerHTML = '<svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: white;"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
};
}
// Close popup when clicking outside
document.addEventListener('click', function(e) {
if (!widget.contains(e.target) && isOpen) {
isOpen = false;
popup.style.display = 'none';
button.innerHTML = '<svg viewBox="0 0 24 24" style="width: 24px; height: 24px; fill: white;"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
}
});
}, 100);
wrapper.appendChild(button);
widget.appendChild(wrapper);
widget.appendChild(popup);
document.body.appendChild(widget);
})();
</script>
Add this code just before the closing </body> tag of your website. The widget will appear in the corner you selected.
How It Works
- • Chat: Injects the n8n chat widget directly on your page
- • Voice: Injects the ElevenLabs convai widget on your page
- • Graph: Opens the InfraNodus knowledge graph in a new tab
- • Telegram: Opens your Telegram bot/channel in a new tab