/** this file is used to embed the chatbot in a website * the difyChatbotConfig should be defined in the html file before this script is included * the difyChatbotConfig should contain the token of the chatbot * the token can be found in the chatbot settings page */ // attention: This JavaScript script must be placed after the
element. Otherwise, the script will not work. (function () { // Constants for DOM element IDs and configuration key const configKey = "difyChatbotConfig"; const buttonId = "dify-chatbot-bubble-button"; const iframeId = "dify-chatbot-bubble-window"; const config = window[configKey]; // SVG icons for open and close states const svgIcons = ` `; // Main function to embed the chatbot async function embedChatbot() { let isDragging = false if (!config || !config.token) { console.error(`${configKey} is empty or token is not provided`); return; } async function compressAndEncodeBase64(input) { const uint8Array = new TextEncoder().encode(input); const compressedStream = new Response( new Blob([uint8Array]) .stream() .pipeThrough(new CompressionStream("gzip")) ).arrayBuffer(); const compressedUint8Array = new Uint8Array(await compressedStream); return btoa(String.fromCharCode(...compressedUint8Array)); } async function getCompressedInputsFromConfig() { const inputs = config?.inputs || {}; const compressedInputs = {}; await Promise.all( Object.entries(inputs).map(async ([key, value]) => { compressedInputs[key] = await compressAndEncodeBase64(value); }) ); return compressedInputs; } async function getCompressedSystemVariablesFromConfig() { const systemVariables = config?.systemVariables || {}; const compressedSystemVariables = {}; await Promise.all( Object.entries(systemVariables).map(async ([key, value]) => { compressedSystemVariables[`sys.${key}`] = await compressAndEncodeBase64(value); }) ); return compressedSystemVariables; } const params = new URLSearchParams({ ...await getCompressedInputsFromConfig(), ...await getCompressedSystemVariablesFromConfig() }); const baseUrl = config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`; // pre-check the length of the URL const iframeUrl = `${baseUrl}/chatbot/${config.token}?${params}`; // 1) CREATE the iframe immediately, so it can load in the background: const preloadedIframe = createIframe(); // 2) HIDE it by default: preloadedIframe.style.display = "none"; // 3) APPEND it to the document body right away: document.body.appendChild(preloadedIframe); // ─── End Fix Snippet if(iframeUrl.length > 2048) { console.error("The URL is too long, please reduce the number of inputs to prevent the bot from failing to load"); } // Function to create the iframe for the chatbot function createIframe() { const iframe = document.createElement("iframe"); iframe.allow = "fullscreen;microphone"; iframe.title = "dify chatbot bubble window"; iframe.id = iframeId; iframe.src = iframeUrl; iframe.style.cssText = ` position: absolute; display: flex; flex-direction: column; justify-content: space-between; left: unset; right: 0; bottom: 0; width: 24rem; max-width: calc(100vw - 2rem); height: 43.75rem; max-height: calc(100vh - 6rem); border: none; z-index: 2147483640; overflow: hidden; user-select: none; `; return iframe; } // Function to reset the iframe position function resetIframePosition() { if (window.innerWidth <= 640) return; const targetIframe = document.getElementById(iframeId); const targetButton = document.getElementById(buttonId); if (targetIframe && targetButton) { const buttonRect = targetButton.getBoundingClientRect(); const buttonInBottom = buttonRect.top - 5 > targetIframe.clientHeight; if (buttonInBottom) { targetIframe.style.bottom = "0px"; targetIframe.style.top = "unset"; } else { targetIframe.style.bottom = "unset"; targetIframe.style.top = "0px"; } const buttonInRight = buttonRect.right > targetIframe.clientWidth; if (buttonInRight) { targetIframe.style.right = "0"; targetIframe.style.left = "unset"; } else { targetIframe.style.right = "unset"; targetIframe.style.left = 0; } } } // Function to create the chat button function createButton() { const containerDiv = document.createElement("div"); // Apply custom properties from config Object.entries(config.containerProps || {}).forEach(([key, value]) => { if (key === "className") { containerDiv.classList.add(...value.split(" ")); } else if (key === "style") { if (typeof value === "object") { Object.assign(containerDiv.style, value); } else { containerDiv.style.cssText = value; } } else if (typeof value === "function") { containerDiv.addEventListener( key.replace(/^on/, "").toLowerCase(), value ); } else { containerDiv[key] = value; } }); containerDiv.id = buttonId; // Add styles for the button const styleSheet = document.createElement("style"); document.head.appendChild(styleSheet); styleSheet.sheet.insertRule(` #${containerDiv.id} { position: fixed; bottom: var(--${containerDiv.id}-bottom, 1rem); right: var(--${containerDiv.id}-right, 1rem); left: var(--${containerDiv.id}-left, unset); top: var(--${containerDiv.id}-top, unset); width: var(--${containerDiv.id}-width, 48px); height: var(--${containerDiv.id}-height, 48px); border-radius: var(--${containerDiv.id}-border-radius, 25px); background-color: var(--${containerDiv.id}-bg-color, #155EEF); box-shadow: var(--${containerDiv.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px); cursor: pointer; z-index: 2147483647; } `); // Create display div for the button icon const displayDiv = document.createElement("div"); displayDiv.style.cssText = "position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;"; displayDiv.innerHTML = svgIcons; containerDiv.appendChild(displayDiv); document.body.appendChild(containerDiv); // Add click event listener to toggle chatbot containerDiv.addEventListener("click", handleClick); // Add touch event listener containerDiv.addEventListener("touchend", handleClick); function handleClick() { if (isDragging) return; const targetIframe = document.getElementById(iframeId); if (!targetIframe) { containerDiv.appendChild(createIframe()); resetIframePosition(); this.title = "Exit (ESC)"; setSvgIcon("close"); document.addEventListener("keydown", handleEscKey); return; } targetIframe.style.display = targetIframe.style.display === "none" ? "block" : "none"; targetIframe.style.display === "none" ? setSvgIcon("open") : setSvgIcon("close"); if (targetIframe.style.display === "none") { document.removeEventListener("keydown", handleEscKey); } else { document.addEventListener("keydown", handleEscKey); } resetIframePosition(); } // Enable dragging if specified in config if (config.draggable) { enableDragging(containerDiv, config.dragAxis || "both"); } } // Function to enable dragging of the chat button function enableDragging(element, axis) { let startX, startY, startClientX, startClientY; element.addEventListener("mousedown", startDragging); element.addEventListener("touchstart", startDragging); function startDragging(e) { isDragging = false; if (e.type === "touchstart") { startX = e.touches[0].clientX - element.offsetLeft; startY = e.touches[0].clientY - element.offsetTop; startClientX = e.touches[0].clientX; startClientY = e.touches[0].clientY; } else { startX = e.clientX - element.offsetLeft; startY = e.clientY - element.offsetTop; } document.addEventListener("mousemove", drag); document.addEventListener("touchmove", drag, { passive: false }); document.addEventListener("mouseup", stopDragging); document.addEventListener("touchend", stopDragging); e.preventDefault(); } function drag(e) { const touch = e.type === "touchmove" ? e.touches[0] : e; const deltaX = touch.clientX - startClientX; const deltaY = touch.clientY - startClientY; // Determine whether it is a drag operation if (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8) { isDragging = true; } if (!isDragging) return; element.style.transition = "none"; element.style.cursor = "grabbing"; // Hide iframe while dragging const targetIframe = document.getElementById(iframeId); if (targetIframe) { targetIframe.style.display = "none"; setSvgIcon("open"); } let newLeft, newBottom; if (e.type === "touchmove") { newLeft = e.touches[0].clientX - startX; newBottom = window.innerHeight - e.touches[0].clientY - startY; } else { newLeft = e.clientX - startX; newBottom = window.innerHeight - e.clientY - startY; } const elementRect = element.getBoundingClientRect(); const maxX = window.innerWidth - elementRect.width; const maxY = window.innerHeight - elementRect.height; // Update position based on drag axis if (axis === "x" || axis === "both") { element.style.setProperty( `--${buttonId}-left`, `${Math.max(0, Math.min(newLeft, maxX))}px` ); } if (axis === "y" || axis === "both") { element.style.setProperty( `--${buttonId}-bottom`, `${Math.max(0, Math.min(newBottom, maxY))}px` ); } } function stopDragging() { setTimeout(() => { isDragging = false; }, 0); element.style.transition = ""; element.style.cursor = "pointer"; document.removeEventListener("mousemove", drag); document.removeEventListener("touchmove", drag); document.removeEventListener("mouseup", stopDragging); document.removeEventListener("touchend", stopDragging); } } // Create the chat button if it doesn't exist if (!document.getElementById(buttonId)) { createButton(); } } function setSvgIcon(type = "open") { if (type === "open") { document.getElementById("openIcon").style.display = "block"; document.getElementById("closeIcon").style.display = "none"; } else { document.getElementById("openIcon").style.display = "none"; document.getElementById("closeIcon").style.display = "block"; } } // Add esc Exit keyboard event triggered function handleEscKey(event) { if (event.key === "Escape") { const targetIframe = document.getElementById(iframeId); if (targetIframe && targetIframe.style.display !== "none") { targetIframe.style.display = "none"; setSvgIcon("open"); } } } document.addEventListener("keydown", handleEscKey); // Set the embedChatbot function to run when the body is loaded,Avoid infinite nesting if (config?.dynamicScript) { embedChatbot(); } else { document.body.onload = embedChatbot; } })();