From ce8b3755bc667e84382964265f70db795cea9f1d Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Tue, 14 Jul 2026 15:29:43 +0800 Subject: [PATCH 1/2] fix: Embedded security vulnerabilities --- backend/apps/system/api/assistant.py | 84 +- backend/apps/system/crud/assistant.py | 16 + backend/apps/system/crud/assistant_manage.py | 12 + backend/apps/system/middleware/auth.py | 26 +- frontend/public/assistant.js | 895 ------------------- frontend/src/views/embedded/index.vue | 25 +- frontend/src/views/embedded/page.vue | 31 +- 7 files changed, 164 insertions(+), 925 deletions(-) delete mode 100644 frontend/public/assistant.js diff --git a/backend/apps/system/api/assistant.py b/backend/apps/system/api/assistant.py index 4dbf1aa09..a8df58bf3 100644 --- a/backend/apps/system/api/assistant.py +++ b/backend/apps/system/api/assistant.py @@ -1,6 +1,7 @@ import json import os -from datetime import timedelta +from datetime import datetime, timedelta, timezone +from zoneinfo import ZoneInfo from typing import List, Optional from fastapi import APIRouter, Form, HTTPException, Path, Query, Request, Response, UploadFile @@ -22,30 +23,83 @@ from common.core.security import create_access_token from common.core.sqlbot_cache import clear_cache from common.utils.utils import get_origin_from_referer, origin_match_domain - router = APIRouter(tags=["system_assistant"], prefix="/system/assistant") from common.audit.models.log_model import OperationType, OperationModules from common.audit.schemas.logger_decorator import LogConfig, system_log - +from sqlbot_xpack.core import decrypt_embedded_sign @router.get("/info/{id}", include_in_schema=False) -async def info(request: Request, response: Response, session: SessionDep, trans: Trans, id: int) -> AssistantModel: +async def info(request: Request, response: Response, session: SessionDep, trans: Trans, id: int, virtual: Optional[int] = Query(None)): if not id: raise Exception('miss assistant id') db_model = await get_assistant_info(session=session, assistant_id=id) if not db_model: raise RuntimeError(f"assistant application not exist") db_model = AssistantModel.model_validate(db_model) - - origin = request.headers.get("origin") or get_origin_from_referer(request) - if not origin: - raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=origin or '')) - origin = origin.rstrip('/') - if not origin_match_domain(origin, db_model.domain): - raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=origin or '')) - + + # 校验 SQLBOT-EMBEDDED-SIGN 请求头 + sign_header = request.headers.get("SQLBOT-EMBEDDED-SIGN") + if not sign_header: + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin='')) + + sign_data = await decrypt_embedded_sign(sign_header) + + # 校验 assistant_id 与 id 参数一致 + if str(sign_data.get("assistant_id")) != str(id): + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin='')) + + # 校验 target(来源域名)是否合法 + target = sign_data.get("target", "") + if not origin_match_domain(target, db_model.domain): + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or '')) + + # 校验 sign_time 是否在 10 秒内 + sign_time_str = sign_data.get("sign_time", "") + sign_time = datetime.fromisoformat(sign_time_str) + now_utc = datetime.now(timezone.utc) + sign_time_utc = sign_time.astimezone(timezone.utc) + if abs((now_utc - sign_time_utc).total_seconds()) > 10: + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or '')) + + # 校验是否为真实浏览器请求(非自动化工具) + if sign_data.get("webdriver", False): + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or '')) + + # 校验 User-Agent 一致性(签名中的 navigator.userAgent 与请求头一致) + sign_user_agent = sign_data.get("user_agent", "") + request_user_agent = request.headers.get("User-Agent", "") + if sign_user_agent != request_user_agent: + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or '')) + + # 校验 timezone 与 sign_time 偏移一致性(防时区伪造) + tz_name = sign_data.get("timezone", "") + tz = ZoneInfo(tz_name) + sign_time_naive = sign_time.replace(tzinfo=None) + if tz.utcoffset(sign_time_naive) != sign_time.utcoffset(): + raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or '')) + + origin = target.rstrip('/') + response.headers["Access-Control-Allow-Origin"] = origin - return db_model + + + assistant_oid = 1 + if (db_model.type == 0): + configuration = db_model.configuration + config_obj = json.loads(configuration) if configuration else {} + assistant_oid = config_obj.get('oid', 1) + + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + assistantDict = { + "id": virtual, "account": 'sqlbot-inner-assistant', "oid": assistant_oid, "assistant_id": id + } + access_token = create_access_token( + assistantDict, expires_delta=access_token_expires + ) + + result = db_model.model_dump() + result["token"] = access_token + return result @router.get("/app/{appId}", include_in_schema=False) @@ -67,7 +121,7 @@ async def getApp(request: Request, response: Response, session: SessionDep, tran return db_model -@router.get("/validator", response_model=AssistantValidator, include_in_schema=False) +""" @router.get("/validator", response_model=AssistantValidator, include_in_schema=False) async def validator(session: SessionDep, id: int, virtual: Optional[int] = Query(None)): if not id: raise Exception('miss assistant id') @@ -89,7 +143,7 @@ async def validator(session: SessionDep, id: int, virtual: Optional[int] = Query access_token = create_access_token( assistantDict, expires_delta=access_token_expires ) - return AssistantValidator(True, True, True, access_token) + return AssistantValidator(True, True, True, access_token) """ @router.get('/picture/{file_id}', summary=f"{PLACEHOLDER_PREFIX}assistant_picture_api", description=f"{PLACEHOLDER_PREFIX}assistant_picture_api") diff --git a/backend/apps/system/crud/assistant.py b/backend/apps/system/crud/assistant.py index 3cecbba21..0c9d60083 100644 --- a/backend/apps/system/crud/assistant.py +++ b/backend/apps/system/crud/assistant.py @@ -22,6 +22,21 @@ from common.core.response_middleware import ResponseMiddleware +def _update_cors_middleware_instance(app: FastAPI, updated_origins: list[str]): + """遍历 middleware 栈,找到 CORSMiddleware 实例并更新其 allow_origins。 + + 仅修改 middleware.kwargs 不会影响已构建的中间件实例, + 需要直接更新实例的 allow_origins 属性。 + """ + stack = getattr(app, 'middleware_stack', None) + while stack is not None and hasattr(stack, 'app'): + if isinstance(stack, CORSMiddleware): + stack.allow_origins = updated_origins + return + stack = stack.app + + + @cache(namespace=CacheNamespace.EMBEDDED_INFO, cacheName=CacheName.ASSISTANT_INFO, keyExpression="assistant_id") async def get_assistant_info(*, session: Session, assistant_id: int) -> AssistantModel | None: db_model = session.get(AssistantModel, assistant_id) @@ -98,6 +113,7 @@ def init_dynamic_cors(app: FastAPI): updated_origins = list(set(settings.all_cors_origins + unique_domains)) if cors_middleware: cors_middleware.kwargs['allow_origins'] = updated_origins + _update_cors_middleware_instance(app, updated_origins) if response_middleware: for instance in ResponseMiddleware.instances: instance.update_allow_origins(updated_origins) diff --git a/backend/apps/system/crud/assistant_manage.py b/backend/apps/system/crud/assistant_manage.py index adec96793..f9dce3675 100644 --- a/backend/apps/system/crud/assistant_manage.py +++ b/backend/apps/system/crud/assistant_manage.py @@ -12,6 +12,17 @@ from common.core.response_middleware import ResponseMiddleware +def _update_cors_middleware_instance(app: FastAPI, updated_origins: list[str]): + """遍历 middleware 栈,找到 CORSMiddleware 实例并更新其 allow_origins。""" + stack = getattr(app, 'middleware_stack', None) + while stack is not None and hasattr(stack, 'app'): + if isinstance(stack, CORSMiddleware): + stack.allow_origins = updated_origins + return + stack = stack.app + + + def dynamic_upgrade_cors(request: Request, session: Session): list_result = session.exec(select(AssistantModel).order_by(AssistantModel.create_time)).all() seen = set() @@ -37,6 +48,7 @@ def dynamic_upgrade_cors(request: Request, session: Session): updated_origins = list(set(settings.all_cors_origins + unique_domains)) if cors_middleware: cors_middleware.kwargs['allow_origins'] = updated_origins + _update_cors_middleware_instance(app, updated_origins) if response_middleware: for instance in ResponseMiddleware.instances: instance.update_allow_origins(updated_origins) diff --git a/backend/apps/system/middleware/auth.py b/backend/apps/system/middleware/auth.py index 423f9ea78..feeb246aa 100644 --- a/backend/apps/system/middleware/auth.py +++ b/backend/apps/system/middleware/auth.py @@ -1,15 +1,17 @@ import base64 import json +import re from typing import Optional from fastapi import Request from fastapi.responses import JSONResponse +from starlette.responses import Response import jwt from sqlmodel import Session from starlette.middleware.base import BaseHTTPMiddleware from apps.system.crud.apikey_manage import get_api_key from apps.system.models.system_model import ApiKeyModel, AssistantModel -from common.core.db import engine +from common.core.db import engine from apps.system.crud.assistant import get_assistant_info, get_assistant_user from apps.system.crud.user import get_user_by_account, get_user_info from apps.system.schemas.system_schema import AssistantHeader, UserInfoDTO @@ -17,7 +19,7 @@ from common.core.config import settings from common.core.schemas import TokenPayload from common.utils.locale import I18n -from common.utils.utils import SQLBotLogUtil, get_origin_from_referer +from common.utils.utils import SQLBotLogUtil, get_origin_from_referer, origin_match_domain from common.utils.whitelist import whiteUtils from fastapi.security.utils import get_authorization_scheme_param from common.core.deps import get_i18n @@ -31,6 +33,26 @@ def __init__(self, app): async def dispatch(self, request, call_next): if self.is_options(request) or whiteUtils.is_whitelisted(request.url.path): + # 动态处理 /system/assistant/info/{id} 的 CORS 预检 + if request.method == "OPTIONS": + origin = request.headers.get("origin", "") + if origin: + match = re.search(r'/system/assistant/info/(\d+)', request.url.path) + if match: + assistant_id = int(match.group(1)) + with Session(engine) as session: + db_model = session.get(AssistantModel, assistant_id) + if db_model and origin_match_domain(origin, db_model.domain): + return Response( + status_code=200, + headers={ + "Access-Control-Allow-Origin": origin, + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "*", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "600", + }, + ) return await call_next(request) assistantTokenKey = settings.ASSISTANT_TOKEN_KEY assistantToken = request.headers.get(assistantTokenKey) diff --git a/frontend/public/assistant.js b/frontend/public/assistant.js deleted file mode 100644 index 5d74d5c72..000000000 --- a/frontend/public/assistant.js +++ /dev/null @@ -1,895 +0,0 @@ -;(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) => { - let srcUrl = `${data.domain_url}/#/assistant?id=${data.id}&online=${!!data.online}&name=${encodeURIComponent(data.name)}` - if (data.userFlag) { - srcUrl += `&userFlag=${data.userFlag || ''}` - } - if (data.history) { - srcUrl += `&history=${data.history}` - } - if (data.lang) { - srcUrl += `&lang=${data.lang}` - } - return ` -
- -
-
- - - -
-
- - - -
-
- - - -
-
-` - } - - function getHighestZIndexValue() { - try { - let maxZIndex = -Infinity - let foundAny = false - - const allElements = document.all || document.querySelectorAll('*') - - for (let i = 0; i < allElements.length; i++) { - const element = allElements[i] - - if (!element || element.nodeType !== 1) continue - - const styles = window.getComputedStyle(element) - - const position = styles.position - if (position === 'static') continue - - const zIndex = styles.zIndex - let zIndexValue - - if (zIndex === 'auto') { - zIndexValue = 0 - } else { - zIndexValue = parseInt(zIndex, 10) - if (isNaN(zIndexValue)) continue - } - - foundAny = true - - // 快速返回:如果找到很大的z-index,很可能就是最大值 - /* if (zIndexValue > 10000) { - return zIndexValue; - } */ - - if (zIndexValue > maxZIndex) { - maxZIndex = zIndexValue - } - } - return foundAny ? maxZIndex : 0 - } catch (error) { - console.warn('获取最高z-index时出错,返回默认值0:', error) - return 0 - } - } - - /** - * 初始化引导 - * @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' - function resizeImg() { - const rate = window.outerWidth / window.innerWidth - chat_button_img.style.width = `${30 * (1 / rate)}px` - chat_button_img.style.height = `${30 * (1 / rate)}px` - } - resizeImg() - window.addEventListener('resize', resizeImg) - // 对话框元素 - 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) { - const maxZIndex = getHighestZIndexValue() - const zIndex = Math.max((maxZIndex || 0) + 1, 10000) - const maskZIndex = zIndex + 1 - 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: ${maskZIndex}; - 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: ${maskZIndex}; - } - #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: ${maskZIndex}; - } - #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: ${zIndex}; - } - #sqlbot-assistant #sqlbot-assistant-chat-container{ - z-index: ${zIndex}; - 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) { - params = { - eventName, - messageId: id, - hostOrigin: window.location.origin, - } - if (data.type === 1) { - const certificate = parsrCertificate(data) - params['busi'] = 'certificate' - params['certificate'] = certificate - } - const contentWindow = iframe.contentWindow - contentWindow.postMessage(params, url) - } - } - }) - } - function loadScript(src, id) { - const domain_url = getDomain(src) - const online = getParam(src, 'online') - const userFlag = getParam(src, 'userFlag') - const history = getParam(src, 'history') - const lang = getParam(src, 'lang') - 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' - tempData['userFlag'] = userFlag - tempData['history'] = history - tempData['lang'] = lang - initsqlbot_assistant(tempData) - registerMessageEvent(id, tempData) - }) - .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]['setLang'] = (lang) => { - if (lang != null && typeof lang != 'string') { - throw new Error('The parameter can only be of type string') - } - const iframe = document.getElementById(`sqlbot-assistant-chat-iframe-${id}`) - if (iframe) { - const url = iframe.src - const eventName = 'sqlbot_assistant_event' - const params = { - busi: 'setLang', - lang, - eventName, - messageId: id, - } - const contentWindow = iframe.contentWindow - contentWindow.postMessage(params, url) - } - } - window.sqlbot_assistant_handler[id]['refresh'] = (online, userFlag) => { - 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) - } - if (userFlag != null) { - new_url = updateParam(new_url, 'userFlag', userFlag) - } - iframe.src = 'about:blank' - setTimeout(() => { - iframe.src = new_url - }, 500) - } - } - window.sqlbot_assistant_handler[id]['destroy'] = () => { - const sqlbot_root_id = 'sqlbot-assistant-root-' + id - const container_div = document.getElementById(sqlbot_root_id) - if (container_div) { - const root_div = container_div.parentNode - if (root_div?.parentNode) { - root_div.parentNode.removeChild(root_div) - } - } - - const scriptDom = document.getElementById(`sqlbot-assistant-float-script-${id}`) - if (scriptDom) { - scriptDom.parentNode.removeChild(scriptDom) - } - const propName = script_id_prefix + id + '-state' - if (window[propName]) { - delete window[propName] - } - delete window.sqlbot_assistant_handler[id] - } - window.sqlbot_assistant_handler[id]['setHistory'] = (show) => { - if (show != null && typeof show != '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: 'setHistory', - show, - eventName, - messageId: id, - } - const contentWindow = iframe.contentWindow - contentWindow.postMessage(params, url) - } - } - window.sqlbot_assistant_handler[id]['createConversation'] = (param) => { - const iframe = document.getElementById(`sqlbot-assistant-chat-iframe-${id}`) - if (iframe) { - const url = iframe.src - const eventName = 'sqlbot_assistant_event' - const params = { - busi: 'createConversation', - param, - eventName, - messageId: id, - } - const contentWindow = iframe.contentWindow - contentWindow.postMessage(params, url) - } - } - } - // 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) -})() diff --git a/frontend/src/views/embedded/index.vue b/frontend/src/views/embedded/index.vue index 9bfe191e1..23cd372f4 100644 --- a/frontend/src/views/embedded/index.vue +++ b/frontend/src/views/embedded/index.vue @@ -20,7 +20,7 @@
{ chatRef.value?.showFloatPopover() } -const validator = ref({ +/* const validator = ref({ id: '', valid: false, id_match: false, token: '', -}) +}) */ const appName = ref('') const loading = ref(true) +const tokenReady = ref(false) const eventName = 'sqlbot_assistant_event' + +let resolveTokenReady: (() => void) | null = null +const tokenReadyPromise = new Promise((resolve) => { + resolveTokenReady = resolve +}) const communicationCb = async (event: any) => { if (event.data?.eventName === eventName) { if (event.data?.messageId !== route.query.id) { return } + if (event.data['sqlbot_embedded_token']) { + assistantStore.setToken(event.data['sqlbot_embedded_token']) + resolveTokenReady?.() + tokenReady.value = true + } if (event.data?.busi == 'certificate') { const certificate = event.data['certificate'] assistantStore.setType(1) @@ -201,13 +212,13 @@ onBeforeMount(async () => { const now = Date.now() assistantStore.setFlag(now) assistantStore.setId(assistantId?.toString() || '') - const param = { + /* const param = { id: assistantId, virtual: userFlag || assistantStore.getFlag, online, } validator.value = await assistantApi.validate(param) - assistantStore.setToken(validator.value.token) + assistantStore.setToken(validator.value.token) */ assistantStore.setAssistant(true) loading.value = false @@ -220,6 +231,8 @@ onBeforeMount(async () => { } window.parent.postMessage(readyData, '*') + await tokenReadyPromise + request.get(`/system/assistant/${assistantId}`).then((res) => { if (res.name) { appName.value = res.name diff --git a/frontend/src/views/embedded/page.vue b/frontend/src/views/embedded/page.vue index 8e398b347..b20dee9b9 100644 --- a/frontend/src/views/embedded/page.vue +++ b/frontend/src/views/embedded/page.vue @@ -4,7 +4,7 @@ :class="dynamicType === 4 ? 'sqlbot--embedded-page' : 'sqlbot-embedded-assistant-page'" > void) | null = null +const tokenReadyPromise = new Promise((resolve) => { + resolveTokenReady = resolve +}) const communicationCb = async (event: any) => { if (event.data?.eventName === eventName) { if (event.data?.messageId !== route.query.id) { return } + if ( + event.data['type'] && + parseInt(event.data['type']) !== 4 && + event.data['sqlbot_embedded_token'] + ) { + assistantStore.setToken(event.data['sqlbot_embedded_token']) + resolveTokenReady?.() + tokenReady.value = true + } if (event.data?.busi == 'certificate') { const type = parseInt(event.data['type']) const certificate = event.data['certificate'] @@ -84,6 +99,7 @@ const communicationCb = async (event: any) => { if (type === 4) { assistantStore.setToken(certificate) assistantStore.setAssistant(true) + tokenReady.value = true try { await userStore.info() } catch (e) { @@ -201,18 +217,19 @@ onBeforeMount(async () => { registerReady(assistantId) return } - const param = { + /* const param = { id: assistantId, virtual: userFlag || assistantStore.getFlag, online, } validator.value = await assistantApi.validate(param) - assistantStore.setToken(validator.value.token) + assistantStore.setToken(validator.value.token) */ assistantStore.setAssistant(true) - loading.value = false registerReady(assistantId) + await tokenReadyPromise + loading.value = false request.get(`/system/assistant/${assistantId}`).then((res) => { if (res?.configuration) { const rawData = JSON.parse(res?.configuration) From 0633d559ed75adabfddd3cff7dfec087f2f30fc2 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Tue, 14 Jul 2026 17:18:45 +0800 Subject: [PATCH 2/2] perf: Optimize embedded APIs --- backend/apps/system/api/assistant.py | 4 +- frontend/src/api/assistant.ts | 2 +- frontend/src/api/embedded.ts | 2 +- frontend/src/views/embedded/index.vue | 82 +++++++++++++-------------- frontend/src/views/embedded/page.vue | 73 ++++++++++++------------ 5 files changed, 79 insertions(+), 84 deletions(-) diff --git a/backend/apps/system/api/assistant.py b/backend/apps/system/api/assistant.py index a8df58bf3..c665778df 100644 --- a/backend/apps/system/api/assistant.py +++ b/backend/apps/system/api/assistant.py @@ -313,13 +313,13 @@ async def update(request: Request, session: SessionDep, editor: AssistantDTO): dynamic_upgrade_cors(request=request, session=session) -@router.get("/{id}", response_model=AssistantModel, summary=f"{PLACEHOLDER_PREFIX}assistant_query_api", description=f"{PLACEHOLDER_PREFIX}assistant_query_api") +""" @router.get("/{id}", response_model=AssistantModel, summary=f"{PLACEHOLDER_PREFIX}assistant_query_api", description=f"{PLACEHOLDER_PREFIX}assistant_query_api") async def get_one(session: SessionDep, id: int = Path(description="ID")): db_model = await get_assistant_info(session=session, assistant_id=id) if not db_model: raise ValueError(f"AssistantModel with id {id} not found") db_model = AssistantModel.model_validate(db_model) - return db_model + return db_model """ @router.delete("/{id}", summary=f"{PLACEHOLDER_PREFIX}assistant_del_api", description=f"{PLACEHOLDER_PREFIX}assistant_del_api") diff --git a/frontend/src/api/assistant.ts b/frontend/src/api/assistant.ts index bb955e09f..b883976f0 100644 --- a/frontend/src/api/assistant.ts +++ b/frontend/src/api/assistant.ts @@ -6,6 +6,6 @@ export const assistantApi = { add: (data: any) => request.post('/system/assistant', data), edit: (data: any) => request.put('/system/assistant', data), delete: (id: number) => request.delete(`/system/assistant/${id}`), - query: (id: number) => request.get(`/system/assistant/${id}`), + // query: (id: number) => request.get(`/system/assistant/${id}`), validate: (data: any) => request.get('/system/assistant/validator', { params: data }), } diff --git a/frontend/src/api/embedded.ts b/frontend/src/api/embedded.ts index 0733088ae..2e51008ae 100644 --- a/frontend/src/api/embedded.ts +++ b/frontend/src/api/embedded.ts @@ -5,7 +5,7 @@ export const getAdvancedApplicationList = () => request.get('/system/assistant/advanced_application') export const updateAssistant = (data: any) => request.put('/system/assistant', data) export const saveAssistant = (data: any) => request.post('/system/assistant', data) -export const getOne = (id: any) => request.get(`/system/assistant/${id}`) +// export const getOne = (id: any) => request.get(`/system/assistant/${id}`) export const delOne = (id: any) => request.delete(`/system/assistant/${id}`) export const dsApi = (id: any) => request.get(`/datasource/ws/${id}`) diff --git a/frontend/src/views/embedded/index.vue b/frontend/src/views/embedded/index.vue index 23cd372f4..12d4706d8 100644 --- a/frontend/src/views/embedded/index.vue +++ b/frontend/src/views/embedded/index.vue @@ -20,7 +20,7 @@
{ }) */ const appName = ref('') const loading = ref(true) -const tokenReady = ref(false) const eventName = 'sqlbot_assistant_event' -let resolveTokenReady: (() => void) | null = null -const tokenReadyPromise = new Promise((resolve) => { +let resolveTokenReady: ((data: any) => any) | null = null +const tokenReadyPromise = new Promise((resolve) => { resolveTokenReady = resolve }) const communicationCb = async (event: any) => { @@ -94,8 +92,8 @@ const communicationCb = async (event: any) => { } if (event.data['sqlbot_embedded_token']) { assistantStore.setToken(event.data['sqlbot_embedded_token']) - resolveTokenReady?.() - tokenReady.value = true + const originData = event.data['sqlbot_origin_data'] + resolveTokenReady?.(originData) } if (event.data?.busi == 'certificate') { const certificate = event.data['certificate'] @@ -220,8 +218,6 @@ onBeforeMount(async () => { validator.value = await assistantApi.validate(param) assistantStore.setToken(validator.value.token) */ assistantStore.setAssistant(true) - loading.value = false - window.addEventListener('message', communicationCb) const readyData = { eventName: 'sqlbot_assistant_event', @@ -231,46 +227,44 @@ onBeforeMount(async () => { } window.parent.postMessage(readyData, '*') - await tokenReadyPromise - - request.get(`/system/assistant/${assistantId}`).then((res) => { - if (res.name) { - appName.value = res.name + const res = await tokenReadyPromise + loading.value = false + if (res?.name) { + appName.value = res.name + } + if (res?.configuration) { + const rawData = JSON.parse(res?.configuration) + assistantStore.setAutoDs(rawData?.auto_ds) + if (rawData.logo) { + logo.value = baseUrl + rawData.logo } - if (res?.configuration) { - const rawData = JSON.parse(res?.configuration) - assistantStore.setAutoDs(rawData?.auto_ds) - if (rawData.logo) { - logo.value = baseUrl + rawData.logo - } - for (const key in customSet) { - if ( - Object.prototype.hasOwnProperty.call(customSet, key) && - ![null, undefined].includes(rawData[key]) - ) { - customSet[key] = rawData[key] - configuredKeys.add(key) - } - } - - if (!rawData.theme) { - const { customColor, themeColor } = appearanceStore - const currentColor = - themeColor === 'custom' && customColor - ? customColor - : themeColor === 'blue' - ? '#3370ff' - : '#1CBA90' - customSet.theme = currentColor || customSet.theme + for (const key in customSet) { + if ( + Object.prototype.hasOwnProperty.call(customSet, key) && + ![null, undefined].includes(rawData[key]) + ) { + customSet[key] = rawData[key] + configuredKeys.add(key) } + } - nextTick(() => { - setPageCustomColor(customSet.theme) - setPageHeaderFontColor(customSet.header_font_color) - }) + if (!rawData.theme) { + const { customColor, themeColor } = appearanceStore + const currentColor = + themeColor === 'custom' && customColor + ? customColor + : themeColor === 'blue' + ? '#3370ff' + : '#1CBA90' + customSet.theme = currentColor || customSet.theme } - }) + + nextTick(() => { + setPageCustomColor(customSet.theme) + setPageHeaderFontColor(customSet.header_font_color) + }) + } }) onBeforeUnmount(() => { diff --git a/frontend/src/views/embedded/page.vue b/frontend/src/views/embedded/page.vue index b20dee9b9..68245ef04 100644 --- a/frontend/src/views/embedded/page.vue +++ b/frontend/src/views/embedded/page.vue @@ -23,7 +23,6 @@ import { useAssistantStore } from '@/stores/assistant' import { useAppearanceStoreWithOut } from '@/stores/appearance' import { useI18n } from 'vue-i18n' import { i18n } from '@/i18n' -import { request } from '@/utils/request' import { setCurrentColor } from '@/utils/utils' import { useUserStore } from '@/stores/user' const userStore = useUserStore() @@ -74,8 +73,8 @@ const divLoading = ref(true) const tokenReady = ref(false) const eventName = 'sqlbot_embedded_event' -let resolveTokenReady: (() => void) | null = null -const tokenReadyPromise = new Promise((resolve) => { +let resolveTokenReady: ((data: any) => void) | null = null +const tokenReadyPromise = new Promise((resolve) => { resolveTokenReady = resolve }) const communicationCb = async (event: any) => { @@ -83,13 +82,16 @@ const communicationCb = async (event: any) => { if (event.data?.messageId !== route.query.id) { return } + const assistantTypeObj = event.data['type'] if ( - event.data['type'] && + assistantTypeObj !== null && + assistantTypeObj !== undefined && parseInt(event.data['type']) !== 4 && event.data['sqlbot_embedded_token'] ) { assistantStore.setToken(event.data['sqlbot_embedded_token']) - resolveTokenReady?.() + const originData = event.data['sqlbot_origin_data'] + resolveTokenReady?.(originData) tokenReady.value = true } if (event.data?.busi == 'certificate') { @@ -228,42 +230,41 @@ onBeforeMount(async () => { registerReady(assistantId) - await tokenReadyPromise + const res = await tokenReadyPromise loading.value = false - request.get(`/system/assistant/${assistantId}`).then((res) => { - if (res?.configuration) { - const rawData = JSON.parse(res?.configuration) - assistantStore.setAutoDs(rawData?.auto_ds) - if (rawData.logo) { - logo.value = baseUrl + rawData.logo - } - rawData['name'] = rawData['name'] || res['name'] - for (const key in customSet) { - if ( - Object.prototype.hasOwnProperty.call(customSet, key) && - ![null, undefined].includes(rawData[key]) - ) { - customSet[key] = rawData[key] - configuredKeys.add(key) - } - } - if (!rawData.theme) { - const { customColor, themeColor } = appearanceStore - const currentColor = - themeColor === 'custom' && customColor - ? customColor - : themeColor === 'blue' - ? '#3370ff' - : '#1CBA90' - customSet.theme = currentColor || customSet.theme + if (res?.configuration) { + const rawData = JSON.parse(res?.configuration) + assistantStore.setAutoDs(rawData?.auto_ds) + if (rawData.logo) { + logo.value = baseUrl + rawData.logo + } + rawData['name'] = rawData['name'] || res['name'] + for (const key in customSet) { + if ( + Object.prototype.hasOwnProperty.call(customSet, key) && + ![null, undefined].includes(rawData[key]) + ) { + customSet[key] = rawData[key] + configuredKeys.add(key) } + } - nextTick(() => { - setPageCustomColor(customSet.theme) - }) + if (!rawData.theme) { + const { customColor, themeColor } = appearanceStore + const currentColor = + themeColor === 'custom' && customColor + ? customColor + : themeColor === 'blue' + ? '#3370ff' + : '#1CBA90' + customSet.theme = currentColor || customSet.theme } - }) + + nextTick(() => { + setPageCustomColor(customSet.theme) + }) + } }) onBeforeUnmount(() => {