浏览代码

feat: passing the inputs values using `difyChatbotConfig` (#6376)

yoyocircle 9 月之前
父节点
当前提交
284ef52bba

+ 21 - 0
web/app/components/base/chat/embedded-chatbot/hooks.tsx

@@ -31,6 +31,7 @@ import type {
 import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
 import { useToastContext } from '@/app/components/base/toast'
 import { changeLanguage } from '@/i18n/i18next-config'
+import { getProcessedInputsFromUrlParams } from '@/app/components/base/chat/utils'
 
 export const useEmbeddedChatbot = () => {
   const isInstalledApp = false
@@ -109,6 +110,7 @@ export const useEmbeddedChatbot = () => {
   const { t } = useTranslation()
   const newConversationInputsRef = useRef<Record<string, any>>({})
   const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any>>({})
+  const [initInputs, setInitInputs] = useState<Record<string, any>>({})
   const handleNewConversationInputsChange = useCallback((newInputs: Record<string, any>) => {
     newConversationInputsRef.current = newInputs
     setNewConversationInputs(newInputs)
@@ -116,30 +118,49 @@ export const useEmbeddedChatbot = () => {
   const inputsForms = useMemo(() => {
     return (appParams?.user_input_form || []).filter((item: any) => item.paragraph || item.select || item['text-input'] || item.number).map((item: any) => {
       if (item.paragraph) {
+        let value = initInputs[item.paragraph.variable]
+        if (value && item.paragraph.max_length && value.length > item.paragraph.max_length)
+          value = value.slice(0, item.paragraph.max_length)
+
         return {
           ...item.paragraph,
+          default: value || item.default,
           type: 'paragraph',
         }
       }
       if (item.number) {
+        const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
         return {
           ...item.number,
+          default: convertedNumber || item.default,
           type: 'number',
         }
       }
       if (item.select) {
+        const isInputInOptions = item.select.options.includes(initInputs[item.select.variable])
         return {
           ...item.select,
+          default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.default,
           type: 'select',
         }
       }
 
+      let value = initInputs[item['text-input'].variable]
+      if (value && item['text-input'].max_length && value.length > item['text-input'].max_length)
+        value = value.slice(0, item['text-input'].max_length)
+
       return {
         ...item['text-input'],
+        default: value || item.default,
         type: 'text-input',
       }
     })
   }, [appParams])
+
+  useEffect(() => {
+    // init inputs from url params
+    setInitInputs(getProcessedInputsFromUrlParams())
+  }, [])
   useEffect(() => {
     const conversationInputs: Record<string, any> = {}
 

+ 20 - 0
web/app/components/base/chat/utils.ts

@@ -0,0 +1,20 @@
+async function decodeBase64AndDecompress(base64String: string) {
+  const binaryString = atob(base64String)
+  const compressedUint8Array = Uint8Array.from(binaryString, char => char.charCodeAt(0))
+  const decompressedStream = new Response(compressedUint8Array).body.pipeThrough(new DecompressionStream('gzip'))
+  const decompressedArrayBuffer = await new Response(decompressedStream).arrayBuffer()
+  return new TextDecoder().decode(decompressedArrayBuffer)
+}
+
+function getProcessedInputsFromUrlParams(): Record<string, any> {
+  const urlParams = new URLSearchParams(window.location.search)
+  const inputs: Record<string, any> = {}
+  urlParams.forEach(async (value, key) => {
+    inputs[key] = await decodeBase64AndDecompress(decodeURIComponent(value))
+  })
+  return inputs
+}
+
+export {
+  getProcessedInputsFromUrlParams,
+}

+ 40 - 11
web/public/embed.js

@@ -24,27 +24,55 @@
   };
 
   // Main function to embed the chatbot
-  function embedChatbot() {
+  async function embedChatbot() {
     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;
+    }
+
+    const params = new URLSearchParams(await getCompressedInputsFromConfig());
+
     const baseUrl =
       config.baseUrl || `https://${config.isDev ? "dev." : ""}udify.app`;
 
+    // pre-check the length of the URL
+    const iframeUrl = `${baseUrl}/chatbot/${config.token}?${params}`;
+    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 = `${baseUrl}/chatbot/${config.token}`;
+      iframe.src = iframeUrl;
       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; 
+        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;
       `;
 
@@ -106,19 +134,19 @@
       document.head.appendChild(styleSheet);
       styleSheet.sheet.insertRule(`
         #${containerDiv.id} {
-          position: fixed; 
+          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); 
+          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; 
+          z-index: 2147483647;
+          transition: all 0.2s ease-in-out 0s;
         }
       `);
       styleSheet.sheet.insertRule(`
@@ -154,7 +182,8 @@
         } else {
           document.addEventListener('keydown', handleEscKey);
         }
-        
+
+
         resetIframePosition();
       });
 

文件差异内容过多而无法显示
+ 0 - 1
web/public/embed.min.js


部分文件因为文件数量过多而无法显示