;(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 = `
`
}
/**
* 初始化引导
* @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)
})()