From bf01e41fd990aba6b9b67c905eb7a1bdd8c6a9ee Mon Sep 17 00:00:00 2001 From: Panpan Date: Mon, 28 Apr 2025 14:38:59 +0800 Subject: [PATCH] feat: improve embedded chatbot styles (#18692) --- .../chat/embedded-chatbot/header/index.tsx | 72 +++++++++- web/i18n/de-DE/share-app.ts | 2 + web/i18n/en-US/share-app.ts | 2 + web/i18n/es-ES/share-app.ts | 2 + web/i18n/fa-IR/share-app.ts | 2 + web/i18n/fr-FR/share-app.ts | 2 + web/i18n/hi-IN/share-app.ts | 2 + web/i18n/it-IT/share-app.ts | 2 + web/i18n/ja-JP/share-app.ts | 2 + web/i18n/ko-KR/share-app.ts | 2 + web/i18n/pl-PL/share-app.ts | 2 + web/i18n/pt-BR/share-app.ts | 2 + web/i18n/ro-RO/share-app.ts | 2 + web/i18n/ru-RU/share-app.ts | 2 + web/i18n/sl-SI/share-app.ts | 2 + web/i18n/th-TH/share-app.ts | 2 + web/i18n/tr-TR/share-app.ts | 2 + web/i18n/uk-UA/share-app.ts | 2 + web/i18n/vi-VN/share-app.ts | 2 + web/i18n/zh-Hans/share-app.ts | 2 + web/i18n/zh-Hant/share-app.ts | 2 + web/public/embed.js | 132 ++++++++++++++---- web/public/embed.min.js | 42 +++--- 23 files changed, 236 insertions(+), 50 deletions(-) diff --git a/web/app/components/base/chat/embedded-chatbot/header/index.tsx b/web/app/components/base/chat/embedded-chatbot/header/index.tsx index c38a4546dd..49444d2d73 100644 --- a/web/app/components/base/chat/embedded-chatbot/header/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/header/index.tsx @@ -1,6 +1,6 @@ import type { FC } from 'react' -import React from 'react' -import { RiResetLeftLine } from '@remixicon/react' +import React, { useCallback, useEffect, useState } from 'react' +import { RiCollapseDiagonal2Line, RiExpandDiagonal2Line, RiResetLeftLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import type { Theme } from '../theme/theme-context' import { CssTransform } from '../theme/utils' @@ -36,6 +36,44 @@ const Header: FC = ({ currentConversationId, inputsForms, } = useEmbeddedChatbotContext() + + const isClient = typeof window !== 'undefined' + const isIframe = isClient ? window.self !== window.top : false + const [parentOrigin, setParentOrigin] = useState('') + const [showToggleExpandButton, setShowToggleExpandButton] = useState(false) + const [expanded, setExpanded] = useState(false) + + const handleMessageReceived = useCallback((event: MessageEvent) => { + let currentParentOrigin = parentOrigin + if (!currentParentOrigin && event.data.type === 'dify-chatbot-config') { + currentParentOrigin = event.origin + setParentOrigin(event.origin) + } + if (event.origin !== currentParentOrigin) + return + if (event.data.type === 'dify-chatbot-config') + setShowToggleExpandButton(event.data.payload.isToggledByButton && !event.data.payload.isDraggable) + }, [parentOrigin]) + + useEffect(() => { + if (!isIframe) return + + const listener = (event: MessageEvent) => handleMessageReceived(event) + window.addEventListener('message', listener) + + window.parent.postMessage({ type: 'dify-chatbot-iframe-ready' }, '*') + + return () => window.removeEventListener('message', listener) + }, [isIframe, handleMessageReceived]) + + const handleToggleExpand = useCallback(() => { + if (!isIframe || !showToggleExpandButton) return + setExpanded(!expanded) + window.parent.postMessage({ + type: 'dify-chatbot-expand-change', + }, parentOrigin) + }, [isIframe, parentOrigin, showToggleExpandButton, expanded]) + if (!isMobile) { return (
@@ -59,6 +97,21 @@ const Header: FC = ({ {currentConversationId && ( )} + { + showToggleExpandButton && ( + + + { + expanded + ? + : + } + + + ) + } {currentConversationId && allowResetChat && ( = ({
+ { + showToggleExpandButton && ( + + + { + expanded + ? + : + } + + + ) + } {currentConversationId && allowResetChat && ( @@ -22,6 +23,53 @@ `; + + const originalIframeStyleText = ` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${buttonId}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${buttonId}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + 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; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + ` + + const expandedIframeStyleText = ` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${buttonId}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${buttonId}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + min-width: 24rem; + width: 48%; + max-width: calc(100vw - 2rem); + min-height: 43.75rem; + height: 88%; + max-height: calc(100vh - 6rem); + border: none; + z-index: 2147483640; + overflow: hidden; + user-select: none; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + ` + // Main function to embed the chatbot async function embedChatbot() { let isDragging = false @@ -71,6 +119,7 @@ const baseUrl = config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`; + const targetOrigin = new URL(baseUrl).origin; // pre-check the length of the URL const iframeUrl = `${baseUrl}/chatbot/${config.token}?${params}`; @@ -92,23 +141,7 @@ 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; - `; + iframe.style.cssText = originalIframeStyleText; return iframe; } @@ -121,29 +154,70 @@ const targetButton = document.getElementById(buttonId); if (targetIframe && targetButton) { const buttonRect = targetButton.getBoundingClientRect(); + // We don't necessarily need iframeRect anymore with the center logic - const buttonInBottom = buttonRect.top - 5 > targetIframe.clientHeight; + const viewportCenterY = window.innerHeight / 2; + const buttonCenterY = buttonRect.top + buttonRect.height / 2; - if (buttonInBottom) { - targetIframe.style.bottom = "0px"; - targetIframe.style.top = "unset"; + if (buttonCenterY < viewportCenterY) { + targetIframe.style.top = `var(--${buttonId}-bottom, 1rem)`; + targetIframe.style.bottom = 'unset'; } else { - targetIframe.style.bottom = "unset"; - targetIframe.style.top = "0px"; + targetIframe.style.bottom = `var(--${buttonId}-bottom, 1rem)`; + targetIframe.style.top = 'unset'; } - const buttonInRight = buttonRect.right > targetIframe.clientWidth; + const viewportCenterX = window.innerWidth / 2; + const buttonCenterX = buttonRect.left + buttonRect.width / 2; - if (buttonInRight) { - targetIframe.style.right = "0"; - targetIframe.style.left = "unset"; + if (buttonCenterX < viewportCenterX) { + targetIframe.style.left = `var(--${buttonId}-right, 1rem)`; + targetIframe.style.right = 'unset'; } else { - targetIframe.style.right = "unset"; - targetIframe.style.left = 0; + targetIframe.style.right = `var(--${buttonId}-right, 1rem)`; + targetIframe.style.left = 'unset'; } } } + function toggleExpand() { + isExpanded = !isExpanded; + + const targetIframe = document.getElementById(iframeId); + if (!targetIframe) return; + + if (isExpanded) { + targetIframe.style.cssText = expandedIframeStyleText; + } else { + targetIframe.style.cssText = originalIframeStyleText; + } + resetIframePosition(); + } + + window.addEventListener('message', (event) => { + if (event.origin !== targetOrigin) return; + + const targetIframe = document.getElementById(iframeId); + if (!targetIframe || event.source !== targetIframe.contentWindow) return; + + if (event.data.type === 'dify-chatbot-iframe-ready') { + targetIframe.contentWindow?.postMessage( + { + type: 'dify-chatbot-config', + payload: { + isToggledByButton: true, + isDraggable: !!config.draggable, + }, + }, + targetOrigin + ); + } + + if (event.data.type === 'dify-chatbot-expand-change') { + toggleExpand(); + } + }); + // Function to create the chat button function createButton() { const containerDiv = document.createElement("div"); diff --git a/web/public/embed.min.js b/web/public/embed.min.js index c0aeac4487..8bcdfcbbec 100644 --- a/web/public/embed.min.js +++ b/web/public/embed.min.js @@ -1,20 +1,24 @@ -(()=>{let t="difyChatbotConfig",m="dify-chatbot-bubble-button",h="dify-chatbot-bubble-window",p=window[t];async function e(){let u=!1;if(p&&p.token){var e=new URLSearchParams({...await(async()=>{var e=p?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await o(t)})),n})(),...await(async()=>{var e=p?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await o(t)})),n})()});let t=`${p.baseUrl||`https://${p.isDev?"dev.":""}udify.app`}/chatbot/${p.token}?`+e;e=s();async function o(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=h,e.src=t,e.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; - `,e}function d(){var e,t;window.innerWidth<=640||(e=document.getElementById(h),t=document.getElementById(m),e&&t&&((t=t.getBoundingClientRect()).top-5>e.clientHeight?(e.style.bottom="0px",e.style.top="unset"):(e.style.bottom="unset",e.style.top="0px"),t.right>e.clientWidth?(e.style.right="0",e.style.left="unset"):(e.style.right="unset",e.style.left=0)))}function n(){let n=document.createElement("div");Object.entries(p.containerProps||{}).forEach(([e,t])=>{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=m;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` +(()=>{let t="difyChatbotConfig",h="dify-chatbot-bubble-button",m="dify-chatbot-bubble-window",y=window[t],a=!1,l=` + position: absolute; + display: flex; + flex-direction: column; + justify-content: space-between; + top: unset; + right: var(--${h}-right, 1rem); /* Align with dify-chatbot-bubble-button. */ + bottom: var(--${h}-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */ + left: unset; + 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; + transition-property: width, height; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + `;async function e(){let u=!1;if(y&&y.token){var e=new URLSearchParams({...await(async()=>{var e=y?.inputs||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n[e]=await i(t)})),n})(),...await(async()=>{var e=y?.systemVariables||{};let n={};return await Promise.all(Object.entries(e).map(async([e,t])=>{n["sys."+e]=await i(t)})),n})()}),n=y.baseUrl||`https://${y.isDev?"dev.":""}udify.app`;let o=new URL(n).origin,t=`${n}/chatbot/${y.token}?`+e;n=s();async function i(e){e=(new TextEncoder).encode(e),e=new Response(new Blob([e]).stream().pipeThrough(new CompressionStream("gzip"))).arrayBuffer(),e=new Uint8Array(await e);return btoa(String.fromCharCode(...e))}function s(){var e=document.createElement("iframe");return e.allow="fullscreen;microphone",e.title="dify chatbot bubble window",e.id=m,e.src=t,e.style.cssText=l,e}function d(){var e,t,n;window.innerWidth<=640||(e=document.getElementById(m),t=document.getElementById(h),e&&t&&(t=t.getBoundingClientRect(),n=window.innerHeight/2,t.top+t.height/2{"className"===e?n.classList.add(...t.split(" ")):"style"===e?"object"==typeof t?Object.assign(n.style,t):n.style.cssText=t:"function"==typeof t?n.addEventListener(e.replace(/^on/,"").toLowerCase(),t):n[e]=t}),n.id=h;var e=document.createElement("style"),e=(document.head.appendChild(e),e.sheet.insertRule(` #${n.id} { position: fixed; bottom: var(--${n.id}-bottom, 1rem); @@ -29,10 +33,10 @@ cursor: pointer; z-index: 2147483647; } - `),document.createElement("div"));function t(){var e;u||((e=document.getElementById(h))?(e.style.display="none"===e.style.display?"block":"none","none"===e.style.display?y("open"):y("close"),"none"===e.style.display?document.removeEventListener("keydown",l):document.addEventListener("keydown",l),d()):(n.appendChild(s()),d(),this.title="Exit (ESC)",y("close"),document.addEventListener("keydown",l)))}if(e.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",e.innerHTML=` + `),document.createElement("div"));function t(){var e;u||((e=document.getElementById(m))?(e.style.display="none"===e.style.display?"block":"none","none"===e.style.display?p("open"):p("close"),"none"===e.style.display?document.removeEventListener("keydown",b):document.addEventListener("keydown",b),d()):(n.appendChild(s()),d(),this.title="Exit (ESC)",p("close"),document.addEventListener("keydown",b)))}if(e.style.cssText="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;",e.innerHTML=` - `,n.appendChild(e),document.body.appendChild(n),n.addEventListener("click",t),n.addEventListener("touchend",t),p.draggable){var r=n;var a=p.dragAxis||"both";let s,d,t,l;function o(e){u=!1,l=("touchstart"===e.type?(s=e.touches[0].clientX-r.offsetLeft,d=e.touches[0].clientY-r.offsetTop,t=e.touches[0].clientX,e.touches[0]):(s=e.clientX-r.offsetLeft,d=e.clientY-r.offsetTop,t=e.clientX,e)).clientY,document.addEventListener("mousemove",i),document.addEventListener("touchmove",i,{passive:!1}),document.addEventListener("mouseup",c),document.addEventListener("touchend",c),e.preventDefault()}function i(n){var o="touchmove"===n.type?n.touches[0]:n,i=o.clientX-t,o=o.clientY-l;if(u=8{u=!1},0),r.style.transition="",r.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}r.addEventListener("mousedown",o),r.addEventListener("touchstart",o)}}e.style.display="none",document.body.appendChild(e),2048{u=!1},0),a.style.transition="",a.style.cursor="pointer",document.removeEventListener("mousemove",i),document.removeEventListener("touchmove",i),document.removeEventListener("mouseup",c),document.removeEventListener("touchend",c)}a.addEventListener("mousedown",o),a.addEventListener("touchstart",o)}}n.style.display="none",document.body.appendChild(n),2048{var t,n;e.origin===o&&(t=document.getElementById(m))&&e.source===t.contentWindow&&("dify-chatbot-iframe-ready"===e.data.type&&t.contentWindow?.postMessage({type:"dify-chatbot-config",payload:{isToggledByButton:!0,isDraggable:!!y.draggable}},o),"dify-chatbot-expand-change"===e.data.type)&&(a=!a,n=document.getElementById(m))&&(a?n.style.cssText="\n position: absolute;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n top: unset;\n right: var(--dify-chatbot-bubble-button-right, 1rem); /* Align with dify-chatbot-bubble-button. */\n bottom: var(--dify-chatbot-bubble-button-bottom, 1rem); /* Align with dify-chatbot-bubble-button. */\n left: unset;\n min-width: 24rem;\n width: 48%;\n max-width: calc(100vw - 2rem);\n min-height: 43.75rem;\n height: 88%;\n max-height: calc(100vh - 6rem);\n border: none;\n z-index: 2147483640;\n overflow: hidden;\n user-select: none;\n transition-property: width, height;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n ":n.style.cssText=l,d())}),document.getElementById(h)||r()}else console.error(t+" is empty or token is not provided")}function p(e="open"){"open"===e?(document.getElementById("openIcon").style.display="block",document.getElementById("closeIcon").style.display="none"):(document.getElementById("openIcon").style.display="none",document.getElementById("closeIcon").style.display="block")}function b(e){"Escape"===e.key&&(e=document.getElementById(m))&&"none"!==e.style.display&&(e.style.display="none",p("open"))}h,h,document.addEventListener("keydown",b),y?.dynamicScript?e():document.body.onload=e})(); \ No newline at end of file