diff --git a/frontend/public/assistant.js b/frontend/public/assistant.js
new file mode 100644
index 0000000..382d995
--- /dev/null
+++ b/frontend/public/assistant.js
@@ -0,0 +1,748 @@
+;(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)
+})()