From b07dea836ce4e8fcbf01052070baa1fff1f29e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E9=AD=82?= <365125264@qq.com> Date: Wed, 10 Jul 2024 09:27:24 +0800 Subject: [PATCH] feat(embed): enhance config and add custom styling support (#5781) --- web/public/embed.js | 289 +++++++++++++++++++++++++++++----------- web/public/embed.min.js | 31 +---- 2 files changed, 213 insertions(+), 107 deletions(-) diff --git a/web/public/embed.js b/web/public/embed.js index 586abbf61cf413..46345d9b84aa88 100644 --- a/web/public/embed.js +++ b/web/public/embed.js @@ -6,85 +6,220 @@ // attention: This JavaScript script must be placed after the element. Otherwise, the script will not work. -document.body.onload = embedChatbot; +(function () { + // Constants for DOM element IDs and configuration key + const configKey = "difyChatbotConfig"; + const buttonId = "dify-chatbot-bubble-button"; + const iframeId = "dify-chatbot-bubble-window"; -async function embedChatbot () { - const difyChatbotConfig = window.difyChatbotConfig; - if (!difyChatbotConfig || !difyChatbotConfig.token) { - console.error('difyChatbotConfig is empty or token is not provided') - return; - } - const isDev = !!difyChatbotConfig.isDev - const baseUrl = difyChatbotConfig.baseUrl || `https://${isDev ? 'dev.' : ''}udify.app` - const openIcon = ` - - ` - const closeIcon = ` - - ` - - // create iframe - function createIframe () { - const iframe = document.createElement('iframe'); - iframe.allow = "fullscreen;microphone" - iframe.title = "dify chatbot bubble window" - iframe.id = 'dify-chatbot-bubble-window' - iframe.src = `${baseUrl}/chatbot/${difyChatbotConfig.token}` - iframe.style.cssText = 'border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; max-height: calc(100vh - 6rem);border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;' - document.body.appendChild(iframe); - } + // SVG icons for open and close states + const svgIcons = { + open: ` + + `, + close: ` + + ` + }; + + // Main function to embed the chatbot + function embedChatbot() { + const config = window[configKey]; + if (!config || !config.token) { + console.error(`${configKey} is empty or token is not provided`); + return; + } + + const baseUrl = + config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`; + + // 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 = `${baseUrl}/chatbot/${config.token}`; + iframe.style.cssText = ` + border: none; position: fixed; flex-direction: column; justify-content: space-between; + box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; + bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; + max-height: calc(100vh - 6rem); border-radius: 0.75rem; display: flex; z-index: 2147483647; + overflow: hidden; left: unset; background-color: #F3F4F6; + `; + + document.body.appendChild(iframe); + } + + // Function to reset the iframe position + function resetIframePosition() { + const targetIframe = document.getElementById(iframeId); + const targetButton = document.getElementById(buttonId); + if (targetIframe && targetButton) { + const buttonRect = targetButton.getBoundingClientRect(); + const buttonBottom = window.innerHeight - buttonRect.bottom; + const buttonRight = window.innerWidth - buttonRect.right; + const buttonLeft = buttonRect.left; + + // Adjust iframe position to stay within viewport + targetIframe.style.bottom = `${ + buttonBottom + buttonRect.height + 5 + targetIframe.clientHeight > window.innerHeight + ? buttonBottom - targetIframe.clientHeight - 5 + : buttonBottom + buttonRect.height + 5 + }px`; + + targetIframe.style.right = `${ + buttonRight + targetIframe.clientWidth > window.innerWidth + ? window.innerWidth - buttonLeft - targetIframe.clientWidth + : buttonRight + }px`; + } + } + + // 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, 50px); + height: var(--${containerDiv.id}-height, 50px); + 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; + transition: all 0.2s ease-in-out 0s; + } + `); + styleSheet.sheet.insertRule(` + #${containerDiv.id}:hover { + transform: var(--${containerDiv.id}-hover-transform, scale(1.1)); + } + `); + + // Create display div for the button icon + const displayDiv = document.createElement("div"); + displayDiv.style.cssText = + "display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;"; + displayDiv.innerHTML = svgIcons.open; + containerDiv.appendChild(displayDiv); + document.body.appendChild(containerDiv); + + // Add click event listener to toggle chatbot + containerDiv.addEventListener("click", function () { + const targetIframe = document.getElementById(iframeId); + if (!targetIframe) { + createIframe(); + resetIframePosition(); + displayDiv.innerHTML = svgIcons.close; + return; + } + targetIframe.style.display = targetIframe.style.display === "none" ? "block" : "none"; + displayDiv.innerHTML = targetIframe.style.display === "none" ? svgIcons.open : svgIcons.close; + + resetIframePosition(); + }); - const targetButton = document.getElementById('dify-chatbot-bubble-button') - if (!targetButton) { - // create button - const containerDiv = document.createElement("div"); - containerDiv.id = 'dify-chatbot-bubble-button' - containerDiv.style.cssText = `position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}`; - const displayDiv = document.createElement('div'); - displayDiv.style.cssText = 'display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;'; - displayDiv.innerHTML = openIcon - containerDiv.appendChild(displayDiv); - document.body.appendChild(containerDiv); - // add click event to control iframe display - containerDiv.addEventListener('click', function () { - const targetIframe = document.getElementById('dify-chatbot-bubble-window') - if (!targetIframe) { - createIframe() - displayDiv.innerHTML = closeIcon - return; + // Enable dragging if specified in config + if (config.draggable) { + enableDragging(containerDiv, config.dragAxis || "both"); } - if (targetIframe.style.display === 'none') { - targetIframe.style.display = 'block'; - displayDiv.innerHTML = closeIcon - } else { - targetIframe.style.display = 'none'; - displayDiv.innerHTML = openIcon + } + + // Function to enable dragging of the chat button + function enableDragging(element, axis) { + let isDragging = false; + let startX, startY; + + element.addEventListener("mousedown", startDragging); + document.addEventListener("mousemove", drag); + document.addEventListener("mouseup", stopDragging); + + function startDragging(e) { + isDragging = true; + startX = e.clientX - element.offsetLeft; + startY = e.clientY - element.offsetTop; } - }); + + function drag(e) { + 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"; + element.querySelector("div").innerHTML = svgIcons.open; + } + + const newLeft = e.clientX - startX; + const 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() { + isDragging = false; + element.style.transition = ""; + element.style.cursor = "pointer"; + } + } + + // Create the chat button if it doesn't exist + if (!document.getElementById(buttonId)) { + createButton(); + } } -} + + // Set the embedChatbot function to run when the body is loaded + document.body.onload = embedChatbot; +})(); diff --git a/web/public/embed.min.js b/web/public/embed.min.js index 25985f4f16cc29..51a78a39eb1745 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -1,30 +1 @@ -async function embedChatbot(){const t=window.difyChatbotConfig;if(t&&t.token){var e=!!t.isDev;const o=t.baseUrl||`https://${e?"dev.":""}udify.app`,n=` - - `,i=` - - `;if(!document.getElementById("dify-chatbot-bubble-button")){e=document.createElement("div");e.id="dify-chatbot-bubble-button",e.style.cssText="position: fixed; bottom: 1rem; right: 1rem; width: 50px; height: 50px; border-radius: 25px; background-color: #155EEF; box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 8px 0px; cursor: pointer; z-index: 2147483647; transition: all 0.2s ease-in-out 0s; left: unset; transform: scale(1); :hover {transform: scale(1.1);}";const d=document.createElement("div");d.style.cssText="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",d.innerHTML=n,e.appendChild(d),document.body.appendChild(e),e.addEventListener("click",function(){var e=document.getElementById("dify-chatbot-bubble-window");e?"none"===e.style.display?(e.style.display="block",d.innerHTML=i):(e.style.display="none",d.innerHTML=n):((e=document.createElement("iframe")).allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id="dify-chatbot-bubble-window",e.src=o+"/chatbot/"+t.token,e.style.cssText="border: none; position: fixed; flex-direction: column; justify-content: space-between; box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; max-height: calc(100vh - 6rem);border-radius: 0.75rem; display: flex; z-index: 2147483647; overflow: hidden; left: unset; background-color: #F3F4F6;",document.body.appendChild(e),d.innerHTML=i)})}}else console.error("difyChatbotConfig is empty or token is not provided")}document.body.onload=embedChatbot; \ No newline at end of file +!function(){const e="difyChatbotConfig",t="dify-chatbot-bubble-button",n="dify-chatbot-bubble-window",o={open:'\n \n ',close:'\n \n '};document.body.onload=function(){const i=window[e];if(!i||!i.token)return void console.error(`${e} is empty or token is not provided`);const d=i.baseUrl||`https://${i.isDev?"dev.":""}udify.app`;function r(){const e=document.getElementById(n),o=document.getElementById(t);if(e&&o){const t=o.getBoundingClientRect(),n=window.innerHeight-t.bottom,i=window.innerWidth-t.right,d=t.left;e.style.bottom=`${n+t.height+5+e.clientHeight>window.innerHeight?n-e.clientHeight-5:n+t.height+5}px`,e.style.right=`${i+e.clientWidth>window.innerWidth?window.innerWidth-d-e.clientWidth:i}px`}}document.getElementById(t)||function(){const e=document.createElement("div");Object.entries(i.containerProps||{}).forEach(([t,n])=>{"className"===t?e.classList.add(...n.split(" ")):"style"===t?"object"==typeof n?Object.assign(e.style,n):e.style.cssText=n:"function"==typeof n?e.addEventListener(t.replace(/^on/,"").toLowerCase(),n):e[t]=n}),e.id=t;const s=document.createElement("style");document.head.appendChild(s),s.sheet.insertRule(`\n #${e.id} {\n position: fixed; \n bottom: var(--${e.id}-bottom, 1rem);\n right: var(--${e.id}-right, 1rem);\n left: var(--${e.id}-left, unset);\n top: var(--${e.id}-top, unset);\n width: var(--${e.id}-width, 50px);\n height: var(--${e.id}-height, 50px);\n border-radius: var(--${e.id}-border-radius, 25px); \n background-color: var(--${e.id}-bg-color, #155EEF);\n box-shadow: var(--${e.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px);\n cursor: pointer;\n z-index: 2147483647; \n transition: all 0.2s ease-in-out 0s; \n }\n `),s.sheet.insertRule(`\n #${e.id}:hover {\n transform: var(--${e.id}-hover-transform, scale(1.1));\n }\n `);const l=document.createElement("div");l.style.cssText="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",l.innerHTML=o.open,e.appendChild(l),document.body.appendChild(e),e.addEventListener("click",function(){const e=document.getElementById(n);if(!e)return function(){const e=document.createElement("iframe");e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=n,e.src=`${d}/chatbot/${i.token}`,e.style.cssText="\n border: none; position: fixed; flex-direction: column; justify-content: space-between; \n box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px; \n bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem; \n max-height: calc(100vh - 6rem); border-radius: 0.75rem; display: flex; z-index: 2147483647; \n overflow: hidden; left: unset; background-color: #F3F4F6;\n ",document.body.appendChild(e)}(),r(),void(l.innerHTML=o.close);e.style.display="none"===e.style.display?"block":"none",l.innerHTML="none"===e.style.display?o.open:o.close,r()}),i.draggable&&function(e,i){let d,r,s=!1;e.addEventListener("mousedown",function(t){s=!0,d=t.clientX-e.offsetLeft,r=t.clientY-e.offsetTop}),document.addEventListener("mousemove",function(l){if(!s)return;e.style.transition="none",e.style.cursor="grabbing";const c=document.getElementById(n);c&&(c.style.display="none",e.querySelector("div").innerHTML=o.open);const a=l.clientX-d,h=window.innerHeight-l.clientY-r,p=e.getBoundingClientRect(),u=window.innerWidth-p.width,C=window.innerHeight-p.height;"x"!==i&&"both"!==i||e.style.setProperty(`--${t}-left`,`${Math.max(0,Math.min(a,u))}px`),"y"!==i&&"both"!==i||e.style.setProperty(`--${t}-bottom`,`${Math.max(0,Math.min(h,C))}px`)}),document.addEventListener("mouseup",function(){s=!1,e.style.transition="",e.style.cursor="pointer"})}(e,i.dragAxis||"both")}()}}();