diff --git a/.env.example b/.env.example index e60c0f21..10f5bd88 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,12 @@ FIRST_SUPERUSER_PASSWORD=123456 # Change this to your pwd TOKEN_KEY="X-SQLBOT-TOKEN" DEFAULT_PWD="SQLBot@123456" +ASSISTANT_TOKEN_KEY="X-SQLBOT-ASSISTANT-TOKEN" + +LOG_LEVEL="INFO" # DEBUG, INFO, WARNING, ERROR +LOG_DIR="/opt/sqlbot/logs" +LOG_FORMAT="%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s" +SQL_DEBUG=False # Postgres POSTGRES_SERVER=localhost diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 1c821a4f..db6e9413 100755 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -5,7 +5,6 @@ from os.path import abspath, dirname sys.path.insert(0, dirname(dirname(abspath(__file__)))) import os -from logging.config import fileConfig from alembic import context from sqlalchemy import engine_from_config, pool @@ -16,7 +15,6 @@ config = context.config # Interpret the config file for Python logging. # This line sets up loggers basically. -fileConfig(config.config_file_name) # add your model's MetaData object here # for 'autogenerate' support diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index b4786662..c1d718ce 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -1,6 +1,5 @@ import json import sqlparse -import logging import traceback import warnings from typing import Any, List, Optional, Union, Dict @@ -35,7 +34,7 @@ from apps.system.crud.assistant import AssistantOutDs, AssistantOutDsFactory, ge from apps.system.schemas.system_schema import AssistantOutDsSchema from common.core.config import settings from common.core.deps import CurrentAssistant, SessionDep, CurrentUser -from common.utils.utils import extract_nested_json +from common.utils.utils import SQLBotLogUtil, extract_nested_json warnings.filterwarnings("ignore") @@ -223,7 +222,7 @@ class LLMService: res = self.llm.stream(analysis_msg) token_usage = {} for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -273,7 +272,7 @@ class LLMService: res = self.llm.stream(predict_msg) token_usage = {} for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -323,7 +322,7 @@ class LLMService: token_usage = {} res = self.llm.stream(guess_msg) for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -390,7 +389,7 @@ class LLMService: token_usage = {} res = self.llm.stream(datasource_msg) for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -474,7 +473,7 @@ class LLMService: token_usage = {} res = self.llm.stream(self.sql_message) for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -543,7 +542,7 @@ class LLMService: res = self.llm.stream(msg) token_usage = {} for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -567,7 +566,7 @@ class LLMService: # 'content': msg.content} for msg # in # analysis_msg]).decode()) - print(full_filter_text) + SQLBotLogUtil.info(full_filter_text) return full_filter_text def generate_chart(self): @@ -582,7 +581,7 @@ class LLMService: token_usage = {} res = self.llm.stream(self.chart_message) for chunk in res: - print(chunk) + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' if 'reasoning_content' in chunk.additional_kwargs: reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') @@ -691,7 +690,7 @@ class LLMService: Returns: Query results """ - print(f"Executing SQL on ds_id {self.ds.id}: {sql}") + SQLBotLogUtil.info(f"Executing SQL on ds_id {self.ds.id}: {sql}") return exec_sql(self.ds, sql) @@ -717,7 +716,7 @@ def execute_sql_with_db(db: SQLDatabase, sql: str) -> str: except Exception as e: error_msg = f"SQL execution failed: {str(e)}" - logging.error(error_msg) + SQLBotLogUtil.exception(error_msg) raise RuntimeError(error_msg) @@ -741,7 +740,7 @@ def run_task(llm_service: LLMService, in_chat: bool = True): ds_res = llm_service.select_datasource() for chunk in ds_res: - print(chunk) + SQLBotLogUtil.info(chunk) if in_chat: yield orjson.dumps( {'content': chunk.get('content'), 'reasoning_content': chunk.get('reasoning_content'), @@ -765,7 +764,7 @@ def run_task(llm_service: LLMService, in_chat: bool = True): yield orjson.dumps({'type': 'info', 'msg': 'sql generated'}).decode() + '\n\n' # filter sql - print(full_sql_text) + SQLBotLogUtil.info(full_sql_text) # todo row permission sql_json_str = extract_nested_json(full_sql_text) @@ -785,11 +784,11 @@ def run_task(llm_service: LLMService, in_chat: bool = True): raise Exception("SQL query is empty") sql_result = llm_service.generate_filter(data.get('sql'), data.get('tables')) # maybe no sql and tables - print(sql_result) + SQLBotLogUtil.info(sql_result) sql = llm_service.check_save_sql(res=sql_result) # sql = llm_service.check_save_sql(res=full_sql_text) - print(sql) + SQLBotLogUtil.info(sql) format_sql = sqlparse.format(sql, reindent=True) if in_chat: yield orjson.dumps({'content': format_sql, 'type': 'sql'}).decode() + '\n\n' @@ -815,9 +814,9 @@ def run_task(llm_service: LLMService, in_chat: bool = True): yield orjson.dumps({'type': 'info', 'msg': 'chart generated'}).decode() + '\n\n' # filter chart - print(full_chart_text) + SQLBotLogUtil.info(full_chart_text) chart = llm_service.check_save_chart(res=full_chart_text) - print(chart) + SQLBotLogUtil.info(chart) if in_chat: yield orjson.dumps({'content': orjson.dumps(chart).decode(), 'type': 'chart'}).decode() + '\n\n' else: @@ -856,7 +855,7 @@ def run_task(llm_service: LLMService, in_chat: bool = True): if chart['type'] != 'table': yield '### generated chart picture\n\n' image_url = request_picture(llm_service.record.chat_id, llm_service.record.id, chart, result) - print(image_url) + SQLBotLogUtil.info(image_url) yield f'![{chart["type"]}]({image_url})' except Exception as e: traceback.print_exc() diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index cde525f5..c48c954f 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -15,7 +15,7 @@ from apps.db.db import get_engine, get_tables, get_fields, exec_sql from apps.db.engine import get_engine_config, get_engine_conn from apps.db.type import db_type_relation from common.core.deps import SessionDep, CurrentUser, Trans -from common.utils.utils import deepcopy_ignore_extra +from common.utils.utils import SQLBotLogUtil, deepcopy_ignore_extra from .table import get_tables_by_ds_id from ..crud.field import delete_field_by_ds_id, update_field from ..crud.table import delete_table_by_ds_id, update_table @@ -39,12 +39,12 @@ def check_status(session: SessionDep, ds: CoreDatasource, is_raise: bool = False conn = get_engine(ds) try: with conn.connect() as connection: - print("success") + SQLBotLogUtil.info("success") return True except Exception as e: + SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}") if is_raise: raise HTTPException(status_code=500, detail=f'Connect Failed: {e.args}') - print("Fail:", e) return False diff --git a/backend/apps/system/crud/user.py b/backend/apps/system/crud/user.py index c0549f63..ffad38a8 100644 --- a/backend/apps/system/crud/user.py +++ b/backend/apps/system/crud/user.py @@ -1,13 +1,13 @@ from typing import Optional from sqlmodel import Session, select, delete as sqlmodel_delete -import logging from apps.system.models.system_model import UserWsModel, WorkspaceModel from apps.system.schemas.auth import CacheName, CacheNamespace from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO, UserWs from common.core.deps import SessionDep from common.core.sqlbot_cache import cache, clear_cache from common.utils.locale import I18n +from common.utils.utils import SQLBotLogUtil from ..models.user import UserModel from common.core.security import verify_md5pwd @@ -68,4 +68,4 @@ async def single_delete(session: SessionDep, id: int): #@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id") async def clean_user_cache(id: int): - logging.info(f"User cache for [{id}] has been cleaned") + SQLBotLogUtil.info(f"User cache for [{id}] has been cleaned") diff --git a/backend/apps/system/middleware/auth.py b/backend/apps/system/middleware/auth.py index e298d3a7..365dfff1 100644 --- a/backend/apps/system/middleware/auth.py +++ b/backend/apps/system/middleware/auth.py @@ -1,6 +1,6 @@ from typing import Optional -from fastapi import HTTPException, Request +from fastapi import Request from fastapi.responses import JSONResponse import jwt from sqlmodel import Session @@ -13,6 +13,7 @@ from apps.system.schemas.system_schema import AssistantHeader, UserInfoDTO from common.core import security from common.core.config import settings from common.core.schemas import TokenPayload +from common.utils.utils import SQLBotLogUtil from common.utils.whitelist import whiteUtils from fastapi.security.utils import get_authorization_scheme_param class TokenMiddleware(BaseHTTPMiddleware): @@ -70,6 +71,7 @@ class TokenMiddleware(BaseHTTPMiddleware): ) """ return True, session_user except Exception as e: + SQLBotLogUtil.exception(f"Token validation error: {str(e)}", exc_info=True) return False, e @@ -95,4 +97,6 @@ class TokenMiddleware(BaseHTTPMiddleware): assistant_info = AssistantHeader.model_validate(assistant_info.model_dump(exclude_unset=True)) return True, session_user, assistant_info except Exception as e: + SQLBotLogUtil.exception(f"Assistant validation error: {str(e)}", exc_info=True) + # Return False and the exception message return False, e \ No newline at end of file diff --git a/backend/common/core/config.py b/backend/common/core/config.py index 99c7fbc7..f9026b09 100644 --- a/backend/common/core/config.py +++ b/backend/common/core/config.py @@ -60,6 +60,11 @@ class Settings(BaseSettings): TOKEN_KEY: str = "X-SQLBOT-TOKEN" DEFAULT_PWD: str = "SQLBot@123456" ASSISTANT_TOKEN_KEY: str = "X-SQLBOT-ASSISTANT-TOKEN" + + LOG_LEVEL: str = "INFO" # DEBUG, INFO, WARNING, ERROR + LOG_DIR: str = "logs" + LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s" + SQL_DEBUG: bool = False @computed_field # type: ignore[prop-decorator] @property diff --git a/backend/common/core/response_middleware.py b/backend/common/core/response_middleware.py index 880117d3..59590ef8 100644 --- a/backend/common/core/response_middleware.py +++ b/backend/common/core/response_middleware.py @@ -1,10 +1,10 @@ import json -import logging from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import JSONResponse from starlette.exceptions import HTTPException from starlette.requests import Request from common.core.config import settings +from common.utils.utils import SQLBotLogUtil class ResponseMiddleware(BaseHTTPMiddleware): def __init__(self, app): super().__init__(app) @@ -48,7 +48,7 @@ class ResponseMiddleware(BaseHTTPMiddleware): } ) except Exception as e: - logging.error(f"Response processing error: {str(e)}", exc_info=True) + SQLBotLogUtil.error(f"Response processing error: {str(e)}", exc_info=True) return JSONResponse( status_code=500, content={ @@ -68,6 +68,7 @@ class ResponseMiddleware(BaseHTTPMiddleware): class exception_handler(): @staticmethod async def http_exception_handler(request: Request, exc: HTTPException): + SQLBotLogUtil.exception(f"HTTP Exception: {exc.detail}", exc_info=True) return JSONResponse( status_code=exc.status_code, content={ @@ -81,8 +82,7 @@ class exception_handler(): @staticmethod async def global_exception_handler(request: Request, exc: Exception): - logging.error(f"Error info: {str(exc)}", exc_info=True) - + SQLBotLogUtil.exception(f"Unhandled Exception: {str(exc)}", exc_info=True) return JSONResponse( status_code=500, content={ diff --git a/backend/common/core/sqlbot_cache.py b/backend/common/core/sqlbot_cache.py index 196a7275..ea83cdec 100644 --- a/backend/common/core/sqlbot_cache.py +++ b/backend/common/core/sqlbot_cache.py @@ -1,12 +1,13 @@ -import logging from fastapi_cache import FastAPICache from fastapi_cache.decorator import cache as original_cache from functools import partial, wraps -from typing import Optional, Set, Any, Dict, Tuple, Callable +from typing import Optional, Set, Any, Dict, Tuple from inspect import Parameter, signature from contextlib import asynccontextmanager import asyncio +from common.utils.utils import SQLBotLogUtil + # 锁管理 _cache_locks = {} @@ -65,7 +66,7 @@ def custom_key_builder( base_key += f"{value}:" except (IndexError, KeyError, AttributeError) as e: - logging.warning(f"Failed to evaluate keyExpression '{keyExpression}': {str(e)}") + SQLBotLogUtil.warning(f"Failed to evaluate keyExpression '{keyExpression}': {str(e)}") return base_key @@ -127,19 +128,16 @@ def cache( ) async with _get_cache_lock(cache_key): - logging.info(f"Using cache key: {cache_key}") - print(f"Using cache key: {cache_key}") + SQLBotLogUtil.info(f"Using cache key: {cache_key}") backend = FastAPICache.get_backend() cached_value = await backend.get(cache_key) if cached_value is not None: - logging.info(f"Cache hit for key: {cache_key}, the value is: {cached_value}") - print(f"Cache hit for key: {cache_key}, the value is: {cached_value}") + SQLBotLogUtil.info(f"Cache hit for key: {cache_key}, the value is: {cached_value}") return cached_value result = await func(*args, **kwargs) await backend.set(cache_key, result, expire) - logging.info(f"Cache miss for key: {cache_key}, result cached.") - print(f"Cache miss for key: {cache_key}, result cached.") + SQLBotLogUtil.info(f"Cache miss for key: {cache_key}, result cached.") return result return wrapper @@ -167,8 +165,7 @@ def clear_cache( if await FastAPICache.get_backend().get(cache_key): await FastAPICache.clear(key=cache_key) result = await func(*args, **kwargs) - logging.info(f"Clearing cache for key: {cache_key}") - print(f"Clearing cache for key: {cache_key}") + SQLBotLogUtil.info(f"Clearing cache for key: {cache_key}") return result return await func(*args, **kwargs) diff --git a/backend/common/utils/utils.py b/backend/common/utils/utils.py index f690b27f..bbc4c2a0 100644 --- a/backend/common/utils/utils.py +++ b/backend/common/utils/utils.py @@ -1,6 +1,10 @@ import hashlib +import inspect import logging from datetime import datetime, timedelta, timezone +from logging.handlers import RotatingFileHandler +from pathlib import Path +from common.core.config import settings from typing import Optional import jwt @@ -10,9 +14,6 @@ from jwt.exceptions import InvalidTokenError from common.core import security from common.core.config import settings -logging.basicConfig(level=logging.DEBUG) -logger = logging.getLogger(__name__) - def generate_password_reset_token(email: str) -> str: delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS) @@ -76,3 +77,142 @@ def extract_nested_json(text): def string_to_numeric_hash(text: str, bits: Optional[int] = 64) -> int: hash_bytes = hashlib.sha256(text.encode()).digest() return int.from_bytes(hash_bytes[:bits//8], byteorder='big') + + +def setup_logging(): + # 确保日志目录存在 + log_dir = Path(settings.LOG_DIR) + log_dir.mkdir(parents=True, exist_ok=True) + + # 日志格式 + formatter = logging.Formatter( + f'{settings.LOG_FORMAT}' + ) + + # 控制台日志 + console_handler = logging.StreamHandler() + console_handler.setLevel(settings.LOG_LEVEL) + console_handler.setFormatter(formatter) + + # 文件日志处理器 + file_handlers = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warn': logging.WARNING, + 'error': logging.ERROR + } + + # 主日志记录器 + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) # 设置最低级别 + + # 添加控制台处理器 + root_logger.addHandler(console_handler) + + # 为每个级别创建文件处理器 + for level_name, level in file_handlers.items(): + file_path = log_dir / f"{level_name}.log" + handler = RotatingFileHandler( + file_path, + maxBytes=10 * 1024 * 1024, # 10MB + backupCount=5, + encoding='utf-8' + ) + handler.setLevel(level) + handler.setFormatter(formatter) + + # 添加过滤器只处理特定级别日志 + if level_name == 'debug': + handler.addFilter(lambda record: record.levelno == logging.DEBUG) + elif level_name == 'info': + handler.addFilter(lambda record: record.levelno == logging.INFO) + elif level_name == 'warn': + handler.addFilter(lambda record: record.levelno == logging.WARNING) + elif level_name == 'error': + handler.addFilter(lambda record: record.levelno >= logging.ERROR) + + root_logger.addHandler(handler) + + # SQL日志特殊处理 + if settings.LOG_LEVEL == "DEBUG" and settings.SQL_DEBUG: + sql_logger = logging.getLogger('sqlalchemy.engine') + sql_logger.setLevel(logging.DEBUG) + + sql_handler = RotatingFileHandler( + log_dir / "sql.log", + maxBytes=10 * 1024 * 1024, + backupCount=2, + encoding='utf-8' + ) + sql_handler.setFormatter(formatter) + sql_logger.addHandler(sql_handler) + +setup_logging() + + +class CallerLogger(logging.Logger): + def __init__(self, logger: logging.Logger): + self.logger = logger + super().__init__(logger.name, logger.level) + + def _log(self, level, msg, args, exc_info=None, extra=None, stacklevel=3): + if self.logger.isEnabledFor(level): + self.logger._log(level, msg, args, exc_info=exc_info, extra=extra, stacklevel=stacklevel) + +class SQLBotLogUtil: + + @staticmethod + def _get_logger() -> logging.Logger: + frame = inspect.currentframe() + try: + caller_frame = frame.f_back.f_back + module_name = caller_frame.f_globals.get('__name__', '__main__') + return CallerLogger(logging.getLogger(module_name)) + finally: + del frame + + + @staticmethod + def debug(msg: str, *args, **kwargs): + logger = SQLBotLogUtil._get_logger() + if logger.isEnabledFor(logging.DEBUG): + logger._log(logging.DEBUG, msg, args, **kwargs) + + @staticmethod + def info(msg: str, *args, **kwargs): + logger = SQLBotLogUtil._get_logger() + if logger.isEnabledFor(logging.INFO): + logger._log(logging.INFO, msg, args, **kwargs) + + @staticmethod + def warning(msg: str, *args, **kwargs): + logger = SQLBotLogUtil._get_logger() + if logger.isEnabledFor(logging.WARNING): + logger._log(logging.WARNING, msg, args, **kwargs) + + @staticmethod + def error(msg: str, *args, exc_info: Optional[bool] = None, **kwargs): + logger = SQLBotLogUtil._get_logger() + if logger.isEnabledFor(logging.ERROR): + logger._log( + logging.ERROR, + msg, + args, + exc_info=exc_info if exc_info is not None else True, + **kwargs + ) + + @staticmethod + def exception(msg: str, *args, **kwargs): + logger = SQLBotLogUtil._get_logger() + if logger.isEnabledFor(logging.ERROR): + logger._log(logging.ERROR, msg, args, exc_info=True, **kwargs) + + @staticmethod + def critical(msg: str, *args, **kwargs): + logger = SQLBotLogUtil._get_logger() + if logger.isEnabledFor(logging.CRITICAL): + logger._log(logging.CRITICAL, msg, args, **kwargs) + + + diff --git a/backend/common/utils/whitelist.py b/backend/common/utils/whitelist.py index 10fa0c02..225d94e1 100644 --- a/backend/common/utils/whitelist.py +++ b/backend/common/utils/whitelist.py @@ -2,6 +2,7 @@ import re from typing import List, Pattern from common.core.config import settings +from common.utils.utils import SQLBotLogUtil wlist = [ "/", "/docs", @@ -50,7 +51,7 @@ class WhitelistChecker: try: self._compiled_patterns.append(re.compile(regex_pattern)) except re.error: - print(f"Invalid regular expression pattern: {pattern}") + SQLBotLogUtil.error(f"Invalid regex pattern: {regex_pattern}") def is_whitelisted(self, path: str) -> bool: prefix = settings.API_V1_STR diff --git a/backend/main.py b/backend/main.py index 19ef6f33..54410660 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,3 @@ -import logging from fastapi.concurrency import asynccontextmanager import sentry_sdk from fastapi import FastAPI @@ -18,6 +17,8 @@ import sqlbot_xpack from fastapi_cache import FastAPICache from fastapi_cache.backends.inmemory import InMemoryBackend +from common.utils.utils import SQLBotLogUtil + def run_migrations(): alembic_cfg = Config("alembic.ini") command.upgrade(alembic_cfg, "head") @@ -27,10 +28,10 @@ def run_migrations(): async def lifespan(app: FastAPI): run_migrations() FastAPICache.init(InMemoryBackend()) - print("✅ FastAPICache 初始化完成") init_dynamic_cors(app) + SQLBotLogUtil.info("✅ SQLBot 初始化完成") yield - + SQLBotLogUtil.info("SQLBot 应用关闭") def custom_generate_unique_id(route: APIRoute) -> str: tag = route.tags[0] if route.tags and len(route.tags) > 0 else "" diff --git a/frontend/src/assets/embedded/LOGO.png b/frontend/src/assets/embedded/LOGO.png new file mode 100644 index 00000000..6ca459b6 Binary files /dev/null and b/frontend/src/assets/embedded/LOGO.png differ diff --git a/frontend/src/assets/embedded/answer.png b/frontend/src/assets/embedded/answer.png new file mode 100644 index 00000000..7f27a908 Binary files /dev/null and b/frontend/src/assets/embedded/answer.png differ diff --git a/frontend/src/assets/embedded/disable.png b/frontend/src/assets/embedded/disable.png new file mode 100644 index 00000000..355a68a9 Binary files /dev/null and b/frontend/src/assets/embedded/disable.png differ diff --git a/frontend/src/assets/embedded/icon_expand-down_filled.svg b/frontend/src/assets/embedded/icon_expand-down_filled.svg new file mode 100644 index 00000000..959fa80c --- /dev/null +++ b/frontend/src/assets/embedded/icon_expand-down_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/embedded/icon_new-chat_outlined.svg b/frontend/src/assets/embedded/icon_new-chat_outlined.svg new file mode 100644 index 00000000..fcc09f84 --- /dev/null +++ b/frontend/src/assets/embedded/icon_new-chat_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/embedded/icon_new-chat_outlined_1.svg b/frontend/src/assets/embedded/icon_new-chat_outlined_1.svg new file mode 100644 index 00000000..b060b027 --- /dev/null +++ b/frontend/src/assets/embedded/icon_new-chat_outlined_1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/embedded/icon_rename_outlined.svg b/frontend/src/assets/embedded/icon_rename_outlined.svg new file mode 100644 index 00000000..fd43aabf --- /dev/null +++ b/frontend/src/assets/embedded/icon_rename_outlined.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/embedded/icon_sidebar_outlined.svg b/frontend/src/assets/embedded/icon_sidebar_outlined.svg new file mode 100644 index 00000000..1257b82c --- /dev/null +++ b/frontend/src/assets/embedded/icon_sidebar_outlined.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/assets/embedded/loading.png b/frontend/src/assets/embedded/loading.png new file mode 100644 index 00000000..08576dda Binary files /dev/null and b/frontend/src/assets/embedded/loading.png differ diff --git a/frontend/src/assets/embedded/send.png b/frontend/src/assets/embedded/send.png new file mode 100644 index 00000000..c470d066 Binary files /dev/null and b/frontend/src/assets/embedded/send.png differ diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index e8426871..0b5b64c5 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -400,7 +400,14 @@ "filter_like": "Contains", "filter_not_like": "Does not contain", "filter_in": "Belongs to", - "filter_not_in": "Does not belong to" + "filter_not_in": "Does not belong to", + "enter_a_question": "Please enter a question", + "new_conversation": "New Conversation", + "send": "Send", + "stop_replying": "Stop replying", + "i_am_sqlbot": "Hello, I am SQLBot", + "predict_data_etc": "I can query data, generate charts, detect data anomalies, predict data, etc.", + "intelligent_data_query": "Hurry up and start intelligent data query~" }, "embedded": { "embedded_management": "Embedded management", @@ -446,7 +453,8 @@ "edit_interface_credentials": "Edit interface credentials", "duplicate_name": "Duplicate name", "interface_credentials": "Interface credentials", - "no_credentials_yet": "No credentials yet" + "no_credentials_yet": "No credentials yet", + "intelligent_customer_service": "SQLBot Intelligent Customer Service" }, "chat": { "type": "Chart Type", diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 5449abe1..53d07817 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -458,7 +458,15 @@ "edit_interface_credentials": "编辑接口凭证", "duplicate_name": "重复名称", "interface_credentials": "接口凭证", - "no_credentials_yet": "暂无凭证" + "no_credentials_yet": "暂无凭证", + "intelligent_customer_service": "SQLBot 智能客服", + "enter_a_question": "请输入问题", + "new_conversation": "新建对话", + "send": "发送", + "stop_replying": "停止回答", + "i_am_sqlbot": "你好,我是 SQLBot", + "predict_data_etc": "我可以查询数据、生成图表、检测数据异常、预测数据等", + "intelligent_data_query": "赶快开启智能问数吧~" }, "chat": { "type": "图表类型", diff --git a/frontend/src/views/chat/ChatList.vue b/frontend/src/views/chat/ChatList.vue index d6d1f714..73780aae 100644 --- a/frontend/src/views/chat/ChatList.vue +++ b/frontend/src/views/chat/ChatList.vue @@ -1,7 +1,10 @@ + + diff --git a/frontend/src/views/embedded/index.vue b/frontend/src/views/embedded/index.vue new file mode 100644 index 00000000..e9951c67 --- /dev/null +++ b/frontend/src/views/embedded/index.vue @@ -0,0 +1,264 @@ + + + + diff --git a/frontend/src/views/system/model/ModelForm.vue b/frontend/src/views/system/model/ModelForm.vue index bb7b44cc..57f294ca 100644 --- a/frontend/src/views/system/model/ModelForm.vue +++ b/frontend/src/views/system/model/ModelForm.vue @@ -291,10 +291,10 @@ defineExpose({