embed.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /** this file is used to embed the sentio in a website
  2. * the sentioConfig should be defined in the html file before this script is included
  3. * the sentioConfig should contain the token of the chatbot
  4. * the token can be found in the chatbot settings page
  5. */
  6. // attention: This JavaScript script must be placed after the <body> element. Otherwise, the script will not work.
  7. (function () {
  8. // Constants for DOM element IDs and configuration key
  9. const configKey = "sentioConfig";
  10. const buttonId = "sentio-bubble-button";
  11. const iframeId = "sentio-bubble-window";
  12. const config = window[configKey];
  13. // SVG icons for open and close states
  14. const svgIcons = {
  15. open: `<svg id="openIcon" width="24" height="24" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg">
  16. <path d="M 12 2.25 C 6.613281 2.25 2.25 6.613281 2.25 12 L 2.25 13.875 C 2.25 14.5 1.75 15 1.125 15 C 0.5 15 0 14.5 0 13.875 L 0 12 C 0 5.371094 5.371094 0 12 0 C 18.628906 0 24 5.371094 24 12 L 24 18.753906 C 24 21.03125 22.152344 22.878906 19.871094 22.878906 L 14.699219 22.875 C 14.3125 23.546875 13.585938 24 12.75 24 L 11.25 24 C 10.007812 24 9 22.992188 9 21.75 C 9 20.507812 10.007812 19.5 11.25 19.5 L 12.75 19.5 C 13.585938 19.5 14.3125 19.953125 14.699219 20.625 L 19.875 20.628906 C 20.910156 20.628906 21.75 19.789062 21.75 18.753906 L 21.75 12 C 21.75 6.613281 17.386719 2.25 12 2.25 Z M 6.75 9.75 L 7.5 9.75 C 8.328125 9.75 9 10.421875 9 11.25 L 9 16.5 C 9 17.328125 8.328125 18 7.5 18 L 6.75 18 C 5.09375 18 3.75 16.65625 3.75 15 L 3.75 12.75 C 3.75 11.09375 5.09375 9.75 6.75 9.75 Z M 17.25 9.75 C 18.90625 9.75 20.25 11.09375 20.25 12.75 L 20.25 15 C 20.25 16.65625 18.90625 18 17.25 18 L 16.5 18 C 15.671875 18 15 17.328125 15 16.5 L 15 11.25 C 15 10.421875 15.671875 9.75 16.5 9.75 Z M 17.25 9.75 "/>
  17. </svg>`,
  18. close: `<svg id="closeIcon" width="24" height="24" viewBox="0 0 24 24" fill="none" >
  19. <path d="M18 18L6 6M6 18L18 6" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  20. </svg>`
  21. };
  22. // Main function to embed the chatbot
  23. async function embedChatbot() {
  24. if (!config || !config.baseUrl || !config.appId) {
  25. console.error(`${configKey} is empty or token is not provided`);
  26. return;
  27. }
  28. // pre-check the length of the URL
  29. const iframeUrl = `${config.baseUrl}/sentio/${config.appId}`;
  30. // Function to create the iframe for the chatbot
  31. function createIframe() {
  32. const iframe = document.createElement("iframe");
  33. iframe.allow = "fullscreen;microphone";
  34. iframe.title = "sentio bubble window";
  35. iframe.id = iframeId;
  36. iframe.src = iframeUrl;
  37. iframe.style.cssText = `
  38. position: absolute;
  39. display: flex;
  40. flex-direction: column;
  41. justify-content: space-between;
  42. left: unset;
  43. right: 0;
  44. bottom: 0;
  45. width: 24rem;
  46. max-width: calc(100vw - 2rem);
  47. height: 43.75rem;
  48. max-height: calc(100vh - 6rem);
  49. border: none;
  50. z-index: 2147483640;
  51. overflow: hidden;
  52. user-select: none;
  53. `;
  54. return iframe;
  55. }
  56. // Function to reset the iframe position
  57. function resetIframePosition() {
  58. if (window.innerWidth <= 640)
  59. return
  60. const targetIframe = document.getElementById(iframeId);
  61. const targetButton = document.getElementById(buttonId);
  62. if (targetIframe && targetButton) {
  63. const buttonRect = targetButton.getBoundingClientRect();
  64. const buttonInBottom = buttonRect.top - 5 > targetIframe.clientHeight
  65. if (buttonInBottom) {
  66. targetIframe.style.bottom = '0px';
  67. targetIframe.style.top = 'unset';
  68. }
  69. else {
  70. targetIframe.style.bottom = 'unset';
  71. targetIframe.style.top = '0px';
  72. }
  73. const buttonInRight = buttonRect.right > targetIframe.clientWidth;
  74. if (buttonInRight) {
  75. targetIframe.style.right = '0';
  76. targetIframe.style.left = 'unset';
  77. }
  78. else {
  79. targetIframe.style.right = 'unset';
  80. targetIframe.style.left = 0;
  81. }
  82. }
  83. }
  84. // Function to create the chat button
  85. function createButton() {
  86. const containerDiv = document.createElement("div");
  87. // Apply custom properties from config
  88. Object.entries(config.containerProps || {}).forEach(([key, value]) => {
  89. if (key === "className") {
  90. containerDiv.classList.add(...value.split(" "));
  91. } else if (key === "style") {
  92. if (typeof value === "object") {
  93. Object.assign(containerDiv.style, value);
  94. } else {
  95. containerDiv.style.cssText = value;
  96. }
  97. } else if (typeof value === "function") {
  98. containerDiv.addEventListener(
  99. key.replace(/^on/, "").toLowerCase(),
  100. value
  101. );
  102. } else {
  103. containerDiv[key] = value;
  104. }
  105. });
  106. containerDiv.id = buttonId;
  107. // Add styles for the button
  108. const styleSheet = document.createElement("style");
  109. document.head.appendChild(styleSheet);
  110. styleSheet.sheet.insertRule(`
  111. #${containerDiv.id} {
  112. position: fixed;
  113. bottom: var(--${containerDiv.id}-bottom, 1rem);
  114. right: var(--${containerDiv.id}-right, 1rem);
  115. left: var(--${containerDiv.id}-left, unset);
  116. top: var(--${containerDiv.id}-top, unset);
  117. width: var(--${containerDiv.id}-width, 48px);
  118. height: var(--${containerDiv.id}-height, 48px);
  119. border-radius: var(--${containerDiv.id}-border-radius, 25px);
  120. background-color: var(--${containerDiv.id}-bg-color, #155EEF);
  121. box-shadow: var(--${containerDiv.id}-box-shadow, rgba(0, 0, 0, 0.2) 0px 4px 8px 0px);
  122. cursor: pointer;
  123. z-index: 2147483647;
  124. }
  125. `);
  126. // Create display div for the button icon
  127. const displayDiv = document.createElement("div");
  128. displayDiv.style.cssText =
  129. "position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; z-index: 2147483647;";
  130. displayDiv.innerHTML = svgIcons.open;
  131. containerDiv.appendChild(displayDiv);
  132. document.body.appendChild(containerDiv);
  133. // Add click event listener to toggle chatbot
  134. containerDiv.addEventListener("click", function () {
  135. const targetIframe = document.getElementById(iframeId);
  136. if (!targetIframe) {
  137. containerDiv.prepend(createIframe());
  138. resetIframePosition();
  139. this.title = "Exit (ESC)";
  140. displayDiv.innerHTML = svgIcons.close;
  141. document.addEventListener('keydown', handleEscKey);
  142. return;
  143. }
  144. targetIframe.style.display = targetIframe.style.display === "none" ? "block" : "none";
  145. displayDiv.innerHTML = targetIframe.style.display === "none" ? svgIcons.open : svgIcons.close;
  146. if (targetIframe.style.display === "none") {
  147. document.removeEventListener('keydown', handleEscKey);
  148. } else {
  149. document.addEventListener('keydown', handleEscKey);
  150. }
  151. resetIframePosition();
  152. });
  153. // Enable dragging if specified in config
  154. if (config.draggable) {
  155. enableDragging(containerDiv, config.dragAxis || "both");
  156. }
  157. }
  158. // Function to enable dragging of the chat button
  159. function enableDragging(element, axis) {
  160. let isDragging = false;
  161. let startX, startY;
  162. element.addEventListener("mousedown", startDragging);
  163. document.addEventListener("mousemove", drag);
  164. document.addEventListener("mouseup", stopDragging);
  165. function startDragging(e) {
  166. isDragging = true;
  167. startX = e.clientX - element.offsetLeft;
  168. startY = e.clientY - element.offsetTop;
  169. }
  170. function drag(e) {
  171. if (!isDragging) return;
  172. element.style.transition = "none";
  173. element.style.cursor = "grabbing";
  174. // Hide iframe while dragging
  175. const targetIframe = document.getElementById(iframeId);
  176. if (targetIframe) {
  177. targetIframe.style.display = "none";
  178. element.querySelector("div").innerHTML = svgIcons.open;
  179. }
  180. const newLeft = e.clientX - startX;
  181. const newBottom = window.innerHeight - e.clientY - startY;
  182. const elementRect = element.getBoundingClientRect();
  183. const maxX = window.innerWidth - elementRect.width;
  184. const maxY = window.innerHeight - elementRect.height;
  185. // Update position based on drag axis
  186. if (axis === "x" || axis === "both") {
  187. element.style.setProperty(
  188. `--${buttonId}-left`,
  189. `${Math.max(0, Math.min(newLeft, maxX))}px`
  190. );
  191. }
  192. if (axis === "y" || axis === "both") {
  193. element.style.setProperty(
  194. `--${buttonId}-bottom`,
  195. `${Math.max(0, Math.min(newBottom, maxY))}px`
  196. );
  197. }
  198. }
  199. function stopDragging() {
  200. isDragging = false;
  201. element.style.transition = "";
  202. element.style.cursor = "pointer";
  203. }
  204. }
  205. // Create the chat button if it doesn't exist
  206. if (!document.getElementById(buttonId)) {
  207. createButton();
  208. }
  209. }
  210. // Add esc Exit keyboard event triggered
  211. function handleEscKey(event) {
  212. if (event.key === 'Escape') {
  213. const targetIframe = document.getElementById(iframeId);
  214. const button = document.getElementById(buttonId);
  215. if (targetIframe && targetIframe.style.display !== 'none') {
  216. targetIframe.style.display = 'none';
  217. button.querySelector('div').innerHTML = svgIcons.open;
  218. }
  219. }
  220. }
  221. document.addEventListener('keydown', handleEscKey);
  222. // Set the embedChatbot function to run when the body is loaded,Avoid infinite nesting
  223. if (config?.dynamicScript) {
  224. embedChatbot();
  225. } else {
  226. document.body.onload = embedChatbot;
  227. }
  228. })();