;(function () { window.sqlbot_assistant_handler = window.sqlbot_assistant_handler || {} const defaultData = { id: '1', show_guide: false, float_icon: '', domain_url: 'http://localhost:5173', header_font_color: 'rgb(100, 106, 115)', x_type: 'right', y_type: 'bottom', x_val: '30', y_val: '30', float_icon_drag: false, } const script_id_prefix = 'sqlbot-assistant-float-script-' const guideHtml = `
🌟 遇见问题,不再有障碍!

你好,我是你的智能小助手。
点我,开启高效解答模式,让问题变成过去式。

` const chatButtonHtml = (data) => `
` const getChatContainerHtml = (data) => { return `
` } /** * 初始化引导 * @param {*} root */ const initGuide = (root) => { root.insertAdjacentHTML('beforeend', guideHtml) const button = root.querySelector('.sqlbot-assistant-button') const close_icon = root.querySelector('.sqlbot-assistant-close') const close_func = () => { root.removeChild(root.querySelector('.sqlbot-assistant-tips')) root.removeChild(root.querySelector('.sqlbot-assistant-mask')) localStorage.setItem('sqlbot_assistant_mask_tip', true) } button.onclick = close_func close_icon.onclick = close_func } const initChat = (root, data) => { // 添加对话icon root.insertAdjacentHTML('beforeend', chatButtonHtml(data)) // 添加对话框 root.insertAdjacentHTML('beforeend', getChatContainerHtml(data)) // 按钮元素 const chat_button = root.querySelector('.sqlbot-assistant-chat-button') let chat_button_img = root.querySelector('.sqlbot-assistant-chat-button > svg') if (data.float_icon) { chat_button_img = root.querySelector('.sqlbot-assistant-chat-button > img') } chat_button_img.style.display = 'block' // 对话框元素 const chat_container = root.querySelector('#sqlbot-assistant-chat-container') // 引导层 const mask_content = root.querySelector('.sqlbot-assistant-mask > .sqlbot-assistant-content') const mask_tips = root.querySelector('.sqlbot-assistant-tips') chat_button_img.onload = (event) => { if (mask_content) { mask_content.style.width = chat_button_img.width + 'px' mask_content.style.height = chat_button_img.height + 'px' if (data.x_type == 'left') { mask_tips.style.marginLeft = (chat_button_img.naturalWidth > 500 ? 500 : chat_button_img.naturalWidth) - 64 + 'px' } else { mask_tips.style.marginRight = (chat_button_img.naturalWidth > 500 ? 500 : chat_button_img.naturalWidth) - 64 + 'px' } } } const viewport = root.querySelector('.sqlbot-assistant-openviewport') const closeviewport = root.querySelector('.sqlbot-assistant-closeviewport') const close_func = () => { chat_container.style['display'] = chat_container.style['display'] == 'block' ? 'none' : 'block' chat_button.style['display'] = chat_container.style['display'] == 'block' ? 'none' : 'block' } close_icon = chat_container.querySelector('.sqlbot-assistant-chat-close') chat_button.onclick = close_func close_icon.onclick = close_func const viewport_func = () => { if (chat_container.classList.contains('sqlbot-assistant-enlarge')) { chat_container.classList.remove('sqlbot-assistant-enlarge') viewport.classList.remove('sqlbot-assistant-viewportnone') closeviewport.classList.add('sqlbot-assistant-viewportnone') } else { chat_container.classList.add('sqlbot-assistant-enlarge') viewport.classList.add('sqlbot-assistant-viewportnone') closeviewport.classList.remove('sqlbot-assistant-viewportnone') } } if (data.float_icon_drag) { chat_button.setAttribute('draggable', 'true') let startX = 0 let startY = 0 const img = new Image() img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=' chat_button.addEventListener('dragstart', (e) => { startX = e.clientX - chat_button.offsetLeft startY = e.clientY - chat_button.offsetTop e.dataTransfer.setDragImage(img, 0, 0) }) chat_button.addEventListener('drag', (e) => { if (e.clientX && e.clientY) { const left = e.clientX - startX const top = e.clientY - startY const maxX = window.innerWidth - chat_button.offsetWidth const maxY = window.innerHeight - chat_button.offsetHeight chat_button.style.left = Math.min(Math.max(0, left), maxX) + 'px' chat_button.style.top = Math.min(Math.max(0, top), maxY) + 'px' } }) let touchStartX = 0 let touchStartY = 0 chat_button.addEventListener('touchstart', (e) => { touchStartX = e.touches[0].clientX - chat_button.offsetLeft touchStartY = e.touches[0].clientY - chat_button.offsetTop e.preventDefault() }) chat_button.addEventListener('touchmove', (e) => { const left = e.touches[0].clientX - touchStartX const top = e.touches[0].clientY - touchStartY const maxX = window.innerWidth - chat_button.offsetWidth const maxY = window.innerHeight - chat_button.offsetHeight chat_button.style.left = Math.min(Math.max(0, left), maxX) + 'px' chat_button.style.top = Math.min(Math.max(0, top), maxY) + 'px' e.preventDefault() }) } /* const drag = (e) => { if (['touchmove', 'touchstart'].includes(e.type)) { chat_button.style.top = e.touches[0].clientY - chat_button_img.clientHeight / 2 + 'px' chat_button.style.left = e.touches[0].clientX - chat_button_img.clientHeight / 2 + 'px' } else { chat_button.style.top = e.y - chat_button_img.clientHeight / 2 + 'px' chat_button.style.left = e.x - chat_button_img.clientHeight / 2 + 'px' } chat_button.style.width = chat_button_img.clientHeight + 'px' chat_button.style.height = chat_button_img.clientHeight + 'px' } if (data.float_icon_drag) { chat_button.setAttribute('draggable', 'true') chat_button.addEventListener('drag', drag) chat_button.addEventListener('dragover', (e) => { e.preventDefault() }) chat_button.addEventListener('dragend', drag) chat_button.addEventListener('touchstart', drag) chat_button.addEventListener('touchmove', drag) } */ viewport.onclick = viewport_func closeviewport.onclick = viewport_func } /** * 第一次进来的引导提示 */ function initsqlbot_assistant(data) { const sqlbot_div = document.createElement('div') const root = document.createElement('div') const sqlbot_root_id = 'sqlbot-assistant-root-' + data.id root.id = sqlbot_root_id initsqlbot_assistantStyle(sqlbot_div, sqlbot_root_id, data) sqlbot_div.appendChild(root) document.body.appendChild(sqlbot_div) const sqlbot_assistant_mask_tip = localStorage.getItem('sqlbot_assistant_mask_tip') if (sqlbot_assistant_mask_tip == null && data.show_guide) { initGuide(root) } initChat(root, data) } // 初始化全局样式 function initsqlbot_assistantStyle(root, sqlbot_assistantId, data) { style = document.createElement('style') style.type = 'text/css' style.innerText = ` /* 放大 */ #sqlbot-assistant .sqlbot-assistant-enlarge { width: 50%!important; height: 100%!important; bottom: 0!important; right: 0 !important; } @media only screen and (max-width: 768px){ #sqlbot-assistant .sqlbot-assistant-enlarge { width: 100%!important; height: 100%!important; right: 0 !important; bottom: 0!important; } } /* 引导 */ #sqlbot-assistant .sqlbot-assistant-mask { position: fixed; z-index: 10001; background-color: transparent; height: 100%; width: 100%; top: 0; left: 0; } #sqlbot-assistant .sqlbot-assistant-mask .sqlbot-assistant-content { width: 64px; height: 64px; box-shadow: 1px 1px 1px 9999px rgba(0,0,0,.6); position: absolute; ${data.x_type}: ${data.x_val}px; ${data.y_type}: ${data.y_val}px; z-index: 10001; } #sqlbot-assistant .sqlbot-assistant-tips { position: fixed; ${data.x_type}:calc(${data.x_val}px + 75px); ${data.y_type}: calc(${data.y_val}px + 0px); padding: 22px 24px 24px; border-radius: 6px; color: #ffffff; font-size: 14px; background: #3370FF; z-index: 10001; } #sqlbot-assistant .sqlbot-assistant-tips .sqlbot-assistant-arrow { position: absolute; background: #3370FF; width: 10px; height: 10px; pointer-events: none; transform: rotate(45deg); box-sizing: border-box; /* left */ ${data.x_type}: -5px; ${data.y_type}: 33px; border-left-color: transparent; border-bottom-color: transparent } #sqlbot-assistant .sqlbot-assistant-tips .sqlbot-assistant-title { font-size: 20px; font-weight: 500; margin-bottom: 8px; } #sqlbot-assistant .sqlbot-assistant-tips .sqlbot-assistant-button { text-align: right; margin-top: 24px; } #sqlbot-assistant .sqlbot-assistant-tips .sqlbot-assistant-button button { border-radius: 4px; background: #FFF; padding: 3px 12px; color: #3370FF; cursor: pointer; outline: none; border: none; } #sqlbot-assistant .sqlbot-assistant-tips .sqlbot-assistant-button button::after{ border: none; } #sqlbot-assistant .sqlbot-assistant-tips .sqlbot-assistant-close { position: absolute; right: 20px; top: 20px; cursor: pointer; } #sqlbot-assistant-chat-container { width: 460px; height: 640px; display:none; } @media only screen and (max-width: 768px) { #sqlbot-assistant-chat-container { width: 100%; height: 70%; right: 0 !important; } } #sqlbot-assistant .sqlbot-assistant-chat-button{ position: fixed; ${data.x_type}: ${data.x_val}px; ${data.y_type}: ${data.y_val}px; cursor: pointer; z-index:10000; } #sqlbot-assistant #sqlbot-assistant-chat-container{ z-index:10000;position: relative; border-radius: 8px; //border: 1px solid #ffffff; background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1; box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10); position: fixed;bottom: 16px;right: 16px;overflow: hidden; } .ed-overlay-dialog { margin-top: 50px; } .ed-drawer { margin-top: 50px; } #sqlbot-assistant #sqlbot-assistant-chat-container .sqlbot-assistant-operate{ top: 18px; right: 15px; position: absolute; display: flex; align-items: center; line-height: 18px; } #sqlbot-assistant #sqlbot-assistant-chat-container .sqlbot-assistant-operate .sqlbot-assistant-chat-close{ margin-left:15px; cursor: pointer; } #sqlbot-assistant #sqlbot-assistant-chat-container .sqlbot-assistant-operate .sqlbot-assistant-openviewport{ cursor: pointer; } #sqlbot-assistant #sqlbot-assistant-chat-container .sqlbot-assistant-operate .sqlbot-assistant-closeviewport{ cursor: pointer; } #sqlbot-assistant #sqlbot-assistant-chat-container .sqlbot-assistant-viewportnone{ display:none; } #sqlbot-assistant #sqlbot-assistant-chat-container #sqlbot-assistant-chat-iframe-${data.id} { height:100%; width:100%; border: none; } #sqlbot-assistant #sqlbot-assistant-chat-container { animation: appear .4s ease-in-out; } @keyframes appear { from { height: 0;; } to { height: 600px; } }`.replaceAll('#sqlbot-assistant ', `#${sqlbot_assistantId} `) root.appendChild(style) } function getParam(src, key) { const url = new URL(src) return url.searchParams.get(key) } function parsrCertificate(config) { const certificateList = config.certificate if (!certificateList?.length) { return null } const list = certificateList.map((item) => formatCertificate(item)).filter((item) => !!item) return JSON.stringify(list) } function isEmpty(obj) { return obj == null || typeof obj == 'undefined' } function formatCertificate(item) { const { type, source, target, target_key, target_val } = item let source_val = null if (type.toLocaleLowerCase() == 'localstorage') { source_val = localStorage.getItem(source) } if (type.toLocaleLowerCase() == 'sessionstorage') { source_val = sessionStorage.getItem(source) } if (type.toLocaleLowerCase() == 'cookie') { source_val = getCookie(source) } if (type.toLocaleLowerCase() == 'custom') { source_val = source } if (isEmpty(source_val)) { return null } return { target, key: target_key || source, value: (target_val && eval(target_val)) || source_val, } } function getCookie(key) { if (!key || !document.cookie) { return null } const cookies = document.cookie.split(';') for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim() if (cookie.startsWith(key + '=')) { return decodeURIComponent(cookie.substring(key.length + 1)) } } return null } function registerMessageEvent(id, data) { const iframe = document.getElementById(`sqlbot-assistant-chat-iframe-${id}`) const url = iframe.src const eventName = 'sqlbot_assistant_event' window.addEventListener('message', (event) => { if (event.data?.eventName === eventName) { if (event.data?.messageId !== id) { return } if (event.data?.busi == 'ready' && event.data?.ready) { const certificate = parsrCertificate(data) params = { busi: 'certificate', certificate, eventName, messageId: id, } const contentWindow = iframe.contentWindow contentWindow.postMessage(params, url) } } }) } function loadScript(src, id) { const domain_url = getDomain(src) const online = getParam(src, 'online') let url = `${domain_url}/api/v1/system/assistant/info/${id}` if (domain_url.includes('5173')) { url = url.replace('5173', '8000') } fetch(url) .then((response) => response.json()) .then((res) => { if (!res.data) { throw new Error(res) } const data = res.data const config_json = data.configuration let tempData = Object.assign(defaultData, data) if (tempData.configuration) { delete tempData.configuration } if (config_json) { const config = JSON.parse(config_json) if (config) { delete config.id tempData = Object.assign(tempData, config) } } tempData['id'] = id tempData['domain_url'] = domain_url if (tempData['float_icon'] && !tempData['float_icon'].startsWith('http://')) { tempData['float_icon'] = `${domain_url}/api/v1/system/assistant/picture/${tempData['float_icon']}` if (domain_url.includes('5173')) { tempData['float_icon'] = tempData['float_icon'].replace('5173', '8000') } } tempData['online'] = online && online.toString().toLowerCase() == 'true' initsqlbot_assistant(tempData) if (data.type == 1) { registerMessageEvent(id, tempData) // postMessage the certificate to iframe } }) .catch((e) => { showMsg('嵌入失败', e.message) }) } function getDomain(src) { return src.substring(0, src.indexOf('/assistant.js')) } function init() { const sqlbotScripts = document.querySelectorAll(`script[id^="${script_id_prefix}"]`) const scriptsArray = Array.from(sqlbotScripts) const src_list = scriptsArray.map((script) => script.src) src_list.forEach((src) => { const id = getParam(src, 'id') window.sqlbot_assistant_handler[id] = window.sqlbot_assistant_handler[id] || {} window.sqlbot_assistant_handler[id]['id'] = id const propName = script_id_prefix + id + '-state' if (window[propName]) { return true } window[propName] = true loadScript(src, id) expposeGlobalMethods(id) }) } function showMsg(title, content) { // 检查并创建容器(如果不存在) let container = document.getElementById('messageContainer') if (!container) { container = document.createElement('div') container.id = 'messageContainer' container.style.position = 'fixed' container.style.bottom = '20px' container.style.right = '20px' container.style.zIndex = '1000' document.body.appendChild(container) } else { // 如果容器已存在,先移除旧弹窗 const oldMessage = container.querySelector('div') if (oldMessage) { oldMessage.style.transform = 'translateX(120%)' oldMessage.style.opacity = '0' setTimeout(() => { container.removeChild(oldMessage) }, 300) } } // 创建弹窗元素 const messageBox = document.createElement('div') messageBox.style.width = '240px' messageBox.style.minHeight = '100px' messageBox.style.background = 'linear-gradient(135deg, #ff6b6b, #ff8e8e)' messageBox.style.borderRadius = '8px' messageBox.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)' messageBox.style.padding = '15px' messageBox.style.color = 'white' messageBox.style.fontFamily = 'Arial, sans-serif' messageBox.style.display = 'flex' messageBox.style.flexDirection = 'column' messageBox.style.transform = 'translateX(120%)' messageBox.style.transition = 'transform 0.3s ease-out' messageBox.style.opacity = '0' messageBox.style.transition = 'opacity 0.3s ease, transform 0.3s ease' messageBox.style.overflow = 'hidden' // 创建标题元素 const titleElement = document.createElement('div') titleElement.style.fontSize = '18px' titleElement.style.fontWeight = 'bold' titleElement.style.marginBottom = '10px' titleElement.style.borderBottom = '1px solid rgba(255, 255, 255, 0.3)' titleElement.style.paddingBottom = '8px' titleElement.textContent = title // 创建内容元素 const contentElement = document.createElement('div') contentElement.style.fontSize = '14px' contentElement.style.flexGrow = '1' contentElement.style.overflow = 'auto' contentElement.textContent = content // 组装元素 messageBox.appendChild(titleElement) messageBox.appendChild(contentElement) // 添加到容器 container.appendChild(messageBox) // 触发显示动画 setTimeout(() => { messageBox.style.transform = 'translateX(0)' messageBox.style.opacity = '1' }, 10) // 3秒后自动隐藏 setTimeout(() => { messageBox.style.transform = 'translateX(120%)' messageBox.style.opacity = '0' setTimeout(() => { container.removeChild(messageBox) // 如果容器是空的,也移除容器 if (container.children.length === 0) { document.body.removeChild(container) } }, 300) }, 5000) } /* function hideMsg() { const container = document.getElementById('messageContainer'); if (container) { const messageBox = container.querySelector('div'); if (messageBox) { messageBox.style.transform = 'translateX(120%)'; messageBox.style.opacity = '0'; setTimeout(() => { container.removeChild(messageBox); // 如果容器是空的,也移除容器 if (container.children.length === 0) { document.body.removeChild(container); } }, 300); } } } */ function updateParam(target_url, key, newValue) { try { const url = new URL(target_url) const [hashPath, hashQuery] = url.hash.split('?') let searchParams if (hashQuery) { searchParams = new URLSearchParams(hashQuery) } else { searchParams = url.searchParams } searchParams.set(key, newValue) if (hashQuery) { url.hash = `${hashPath}?${searchParams.toString()}` } else { url.search = searchParams.toString() } return url.toString() } catch (e) { console.error('Invalid URL:', target_url) return target_url } } function expposeGlobalMethods(id) { window.sqlbot_assistant_handler[id]['setOnline'] = (online) => { if (online != null && typeof online != 'boolean') { throw new Error('The parameter can only be of type boolean') } const iframe = document.getElementById(`sqlbot-assistant-chat-iframe-${id}`) if (iframe) { const url = iframe.src const eventName = 'sqlbot_assistant_event' const params = { busi: 'setOnline', online, eventName, messageId: id, } const contentWindow = iframe.contentWindow contentWindow.postMessage(params, url) } } window.sqlbot_assistant_handler[id]['refresh'] = (online) => { if (online != null && typeof online != 'boolean') { throw new Error('The parameter can only be of type boolean') } const iframe = document.getElementById(`sqlbot-assistant-chat-iframe-${id}`) if (iframe) { const url = iframe.src let new_url = updateParam(url, 't', Date.now()) if (online != null) { new_url = updateParam(new_url, 'online', online) } iframe.src = 'about:blank' setTimeout(() => { iframe.src = new_url }, 500) } } } // window.addEventListener('load', init) const executeWhenReady = (fn) => { if ( document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll) ) { setTimeout(fn, 0) } else { const onReady = () => { document.removeEventListener('DOMContentLoaded', onReady) window.removeEventListener('load', onReady) fn() } document.addEventListener('DOMContentLoaded', onReady) window.addEventListener('load', onReady) } } executeWhenReady(init) })()