Merge remote-tracking branch 'origin/main'
# Conflicts: # frontend/src/router/index.ts
@@ -0,0 +1,28 @@
|
||||
"""028_ds_oid
|
||||
|
||||
Revision ID: e96b16d3daab
|
||||
Revises: b049c9f8ca5b
|
||||
Create Date: 2025-07-17 14:40:48.522033
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e96b16d3daab'
|
||||
down_revision = 'b049c9f8ca5b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('core_datasource', sa.Column('oid', sa.BigInteger(), nullable=True))
|
||||
op.execute('update core_datasource set oid = 1')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('core_datasource', 'oid')
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,54 @@
|
||||
"""029_modify_chat
|
||||
|
||||
Revision ID: 77d4c39ec22f
|
||||
Revises: e96b16d3daab
|
||||
Create Date: 2025-07-17 17:05:13.392973
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '77d4c39ec22f'
|
||||
down_revision = 'e96b16d3daab'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('chat', sa.Column('oid', sa.BigInteger(), nullable=True))
|
||||
op.execute('update chat set oid = 1')
|
||||
op.alter_column('chat', 'create_time',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
type_=sa.DateTime(),
|
||||
existing_nullable=True)
|
||||
op.alter_column('chat_record', 'create_time',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
type_=sa.DateTime(),
|
||||
existing_nullable=True)
|
||||
op.alter_column('chat_record', 'finish_time',
|
||||
existing_type=postgresql.TIMESTAMP(timezone=True),
|
||||
type_=sa.DateTime(),
|
||||
existing_nullable=True)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('chat_record', 'finish_time',
|
||||
existing_type=sa.DateTime(),
|
||||
type_=postgresql.TIMESTAMP(timezone=True),
|
||||
existing_nullable=True)
|
||||
op.alter_column('chat_record', 'create_time',
|
||||
existing_type=sa.DateTime(),
|
||||
type_=postgresql.TIMESTAMP(timezone=True),
|
||||
existing_nullable=True)
|
||||
op.alter_column('chat', 'create_time',
|
||||
existing_type=sa.DateTime(),
|
||||
type_=postgresql.TIMESTAMP(timezone=True),
|
||||
existing_nullable=True)
|
||||
op.drop_column('chat', 'oid')
|
||||
# ### end Alembic commands ###
|
||||
@@ -4,7 +4,6 @@ from typing import List
|
||||
import orjson
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import load_only
|
||||
from sqlmodel import select
|
||||
|
||||
from apps.chat.models.chat_model import Chat, ChatRecord, CreateChat, ChatInfo, RenameChat, ChatQuestion
|
||||
from apps.datasource.models.datasource import CoreDatasource
|
||||
@@ -13,8 +12,9 @@ from common.utils.utils import extract_nested_json
|
||||
|
||||
|
||||
def list_chats(session: SessionDep, current_user: CurrentUser) -> List[Chat]:
|
||||
chart_list = session.exec(
|
||||
select(Chat).where(Chat.create_by == current_user.id).order_by(Chat.create_time.desc())).all()
|
||||
oid = current_user.oid if current_user.oid is not None else 1
|
||||
chart_list = session.query(Chat).filter(and_(Chat.create_by == current_user.id, Chat.oid == oid)).order_by(
|
||||
Chat.create_time.desc()).all()
|
||||
return chart_list
|
||||
|
||||
|
||||
@@ -149,6 +149,7 @@ def create_chat(session: SessionDep, current_user: CurrentUser, create_chat_obj:
|
||||
|
||||
chat = Chat(create_time=datetime.datetime.now(),
|
||||
create_by=current_user.id,
|
||||
oid=current_user.oid if current_user.oid is not None else 1,
|
||||
brief=create_chat_obj.question.strip()[:20])
|
||||
ds: CoreDatasource | None = None
|
||||
if create_chat_obj.datasource:
|
||||
|
||||
@@ -17,7 +17,8 @@ from apps.template.filter.generator import get_permissions_template
|
||||
class Chat(SQLModel, table=True):
|
||||
__tablename__ = "chat"
|
||||
id: Optional[int] = Field(sa_column=Column(BigInteger, Identity(always=True), primary_key=True))
|
||||
create_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True))
|
||||
oid: Optional[int] = Field(sa_column=Column(BigInteger, nullable=True, default=1))
|
||||
create_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True))
|
||||
create_by: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
brief: str = Field(max_length=64, nullable=True)
|
||||
chat_type: str = Field(max_length=20, default="chat") # chat, datasource
|
||||
@@ -31,8 +32,8 @@ class ChatRecord(SQLModel, table=True):
|
||||
chat_id: int = Field(sa_column=Column(BigInteger, nullable=False))
|
||||
ai_modal_id: Optional[int] = Field(sa_column=Column(BigInteger))
|
||||
first_chat: bool = Field(sa_column=Column(Boolean, nullable=True, default=False))
|
||||
create_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True))
|
||||
finish_time: datetime = Field(sa_column=Column(DateTime(timezone=True), nullable=True))
|
||||
create_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True))
|
||||
finish_time: datetime = Field(sa_column=Column(DateTime(timezone=False), nullable=True))
|
||||
create_by: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
datasource: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
engine_type: str = Field(max_length=64)
|
||||
@@ -66,6 +67,7 @@ class ChatRecord(SQLModel, table=True):
|
||||
analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
|
||||
|
||||
|
||||
class CreateChat(BaseModel):
|
||||
id: int = None
|
||||
question: str = None
|
||||
|
||||
@@ -846,6 +846,8 @@ def run_analysis_or_predict_task(llm_service: LLMService, action_type: str, base
|
||||
try:
|
||||
llm_service.set_record(save_analysis_predict_record(llm_service.session, base_record, action_type))
|
||||
|
||||
yield orjson.dumps({'type': 'id', 'id': llm_service.get_record().id}).decode() + '\n\n'
|
||||
|
||||
if action_type == 'analysis':
|
||||
# generate analysis
|
||||
analysis_res = llm_service.generate_analysis()
|
||||
|
||||
@@ -19,8 +19,8 @@ path = "/opt/sqlbot/data/excel"
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
async def datasource_list(session: SessionDep):
|
||||
return get_datasource_list(session=session)
|
||||
async def datasource_list(session: SessionDep, user: CurrentUser):
|
||||
return get_datasource_list(session=session, user=user)
|
||||
|
||||
|
||||
@router.post("/get/{id}")
|
||||
@@ -44,8 +44,8 @@ async def choose_tables(session: SessionDep, id: int, tables: List[CoreTable]):
|
||||
|
||||
|
||||
@router.post("/update", response_model=CoreDatasource)
|
||||
async def update(session: SessionDep, ds: CoreDatasource):
|
||||
return update_ds(session, ds)
|
||||
async def update(session: SessionDep,user: CurrentUser, ds: CoreDatasource):
|
||||
return update_ds(session,user, ds)
|
||||
|
||||
|
||||
@router.post("/delete/{id}", response_model=CoreDatasource)
|
||||
|
||||
@@ -22,10 +22,10 @@ from ..models.datasource import CoreDatasource, CreateDatasource, CoreTable, Cor
|
||||
DatasourceConf, TableAndFields
|
||||
|
||||
|
||||
def get_datasource_list(session: SessionDep):
|
||||
statement = select(CoreDatasource).order_by(CoreDatasource.create_time.desc())
|
||||
datasource_list = session.exec(statement).fetchall()
|
||||
return datasource_list
|
||||
def get_datasource_list(session: SessionDep, user: CurrentUser):
|
||||
oid = user.oid if user.oid is not None else 1
|
||||
return session.query(CoreDatasource).filter(CoreDatasource.oid == oid).order_by(
|
||||
CoreDatasource.create_time.desc()).all()
|
||||
|
||||
|
||||
def get_ds(session: SessionDep, id: int):
|
||||
@@ -45,14 +45,15 @@ def check_status(session: SessionDep, ds: CoreDatasource):
|
||||
return False
|
||||
|
||||
|
||||
def check_name(session: SessionDep, ds: CoreDatasource):
|
||||
def check_name(session: SessionDep, user: CurrentUser, ds: CoreDatasource):
|
||||
if ds.id is not None:
|
||||
ds_list = session.query(CoreDatasource).filter(
|
||||
and_(CoreDatasource.name == ds.name, CoreDatasource.id != ds.id)).all()
|
||||
and_(CoreDatasource.name == ds.name, CoreDatasource.id != ds.id, CoreDatasource.oid == user.oid)).all()
|
||||
if ds_list is not None and len(ds_list) > 0:
|
||||
raise 'Name exist'
|
||||
else:
|
||||
ds_list = session.query(CoreDatasource).filter(CoreDatasource.name == ds.name).all()
|
||||
ds_list = session.query(CoreDatasource).filter(
|
||||
and_(CoreDatasource.name == ds.name, CoreDatasource.oid == user.oid)).all()
|
||||
if ds_list is not None and len(ds_list) > 0:
|
||||
raise 'Name exist'
|
||||
|
||||
@@ -60,10 +61,11 @@ def check_name(session: SessionDep, ds: CoreDatasource):
|
||||
def create_ds(session: SessionDep, user: CurrentUser, create_ds: CreateDatasource):
|
||||
ds = CoreDatasource()
|
||||
deepcopy_ignore_extra(create_ds, ds)
|
||||
check_name(session, ds)
|
||||
check_name(session, user, ds)
|
||||
ds.create_time = datetime.datetime.now()
|
||||
# status = check_status(session, ds)
|
||||
ds.create_by = user.id
|
||||
ds.oid = user.oid if user.oid is not None else 1
|
||||
ds.status = "Success"
|
||||
ds.type_name = db_type_relation()[ds.type]
|
||||
record = CoreDatasource(**ds.model_dump())
|
||||
@@ -85,9 +87,9 @@ def chooseTables(session: SessionDep, id: int, tables: List[CoreTable]):
|
||||
updateNum(session, ds)
|
||||
|
||||
|
||||
def update_ds(session: SessionDep, ds: CoreDatasource):
|
||||
def update_ds(session: SessionDep, user: CurrentUser, ds: CoreDatasource):
|
||||
ds.id = int(ds.id)
|
||||
check_name(session, ds)
|
||||
check_name(session, user, ds)
|
||||
status = check_status(session, ds)
|
||||
ds.status = "Success" if status is True else "Fail"
|
||||
record = session.exec(select(CoreDatasource).where(CoreDatasource.id == ds.id)).first()
|
||||
|
||||
@@ -18,6 +18,7 @@ class CoreDatasource(SQLModel, table=True):
|
||||
create_by: int = Field(sa_column=Column(BigInteger()))
|
||||
status: str = Field(max_length=64, nullable=True)
|
||||
num: str = Field(max_length=256, nullable=True)
|
||||
oid: int = Field(sa_column=Column(BigInteger()))
|
||||
|
||||
|
||||
class CoreTable(SQLModel, table=True):
|
||||
@@ -53,6 +54,7 @@ class CreateDatasource(BaseModel):
|
||||
create_by: int = 0
|
||||
status: str = ''
|
||||
num: str = ''
|
||||
oid: int = 1
|
||||
tables: List[CoreTable] = []
|
||||
|
||||
|
||||
|
||||
@@ -3,20 +3,33 @@
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import jwt
|
||||
from fastapi import HTTPException, status, APIRouter
|
||||
from fastapi.responses import StreamingResponse
|
||||
# from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pydantic import ValidationError
|
||||
from sqlmodel import select
|
||||
|
||||
from apps.chat.api.chat import create_chat
|
||||
from apps.chat.models.chat_model import ChatMcp, CreateChat, ChatStart
|
||||
from apps.chat.task.llm import LLMService, run_task
|
||||
from apps.datasource.crud.datasource import get_datasource_list
|
||||
from apps.system.crud.user import authenticate
|
||||
from apps.system.models.system_model import AiModelDetail
|
||||
from apps.system.crud.user import get_db_user
|
||||
from apps.system.models.system_model import UserWsModel
|
||||
from apps.system.models.user import UserModel
|
||||
from apps.system.schemas.system_schema import BaseUserDTO
|
||||
from apps.system.schemas.system_schema import UserInfoDTO
|
||||
from common.core import security
|
||||
from common.core.config import settings
|
||||
from common.core.deps import SessionDep, get_current_user
|
||||
from common.core.schemas import Token
|
||||
from common.core.deps import SessionDep
|
||||
from common.core.schemas import TokenPayload, XOAuth2PasswordBearer, Token
|
||||
from common.core.security import create_access_token
|
||||
|
||||
reusable_oauth2 = XOAuth2PasswordBearer(
|
||||
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["mcp"], prefix="/mcp")
|
||||
|
||||
|
||||
@@ -35,21 +48,24 @@ router = APIRouter(tags=["mcp"], prefix="/mcp")
|
||||
# ))
|
||||
|
||||
|
||||
@router.get("/ds_list", operation_id="get_datasource_list")
|
||||
async def datasource_list(session: SessionDep):
|
||||
return get_datasource_list(session=session)
|
||||
|
||||
|
||||
@router.get("/model_list", operation_id="get_model_list")
|
||||
async def get_model_list(session: SessionDep):
|
||||
return session.query(AiModelDetail).all()
|
||||
# @router.get("/ds_list", operation_id="get_datasource_list")
|
||||
# async def datasource_list(session: SessionDep):
|
||||
# return get_datasource_list(session=session)
|
||||
#
|
||||
#
|
||||
# @router.get("/model_list", operation_id="get_model_list")
|
||||
# async def get_model_list(session: SessionDep):
|
||||
# return session.query(AiModelDetail).all()
|
||||
|
||||
|
||||
@router.post("/mcp_start", operation_id="mcp_start")
|
||||
async def mcp_start(session: SessionDep, chat: ChatStart):
|
||||
user = authenticate(session=session, account=chat.username, password=chat.password)
|
||||
user: BaseUserDTO = authenticate(session=session, account=chat.username, password=chat.password)
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="Incorrect account or password")
|
||||
|
||||
if not user.oid or user.oid == 0:
|
||||
raise HTTPException(status_code=400, detail="No associated workspace, Please contact the administrator")
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
user_dict = user.to_dict()
|
||||
t = Token(access_token=create_access_token(
|
||||
@@ -61,9 +77,36 @@ async def mcp_start(session: SessionDep, chat: ChatStart):
|
||||
|
||||
@router.post("/mcp_question", operation_id="mcp_question")
|
||||
async def mcp_question(session: SessionDep, chat: ChatMcp):
|
||||
user = await get_current_user(session, chat.token)
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
chat.token, settings.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||
)
|
||||
token_data = TokenPayload(**payload)
|
||||
except (InvalidTokenError, ValidationError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Could not validate credentials",
|
||||
)
|
||||
# session_user = await get_user_info(session=session, user_id=token_data.id)
|
||||
|
||||
llm_service = LLMService(session, user, chat)
|
||||
db_user: UserModel = get_db_user(session=session, user_id=token_data.id)
|
||||
session_user = UserInfoDTO.model_validate(db_user.model_dump())
|
||||
session_user.isAdmin = session_user.id == 1 and session_user.account == 'admin'
|
||||
if session_user.isAdmin:
|
||||
session_user = session_user
|
||||
ws_model: UserWsModel = session.exec(
|
||||
select(UserWsModel).where(UserWsModel.uid == session_user.id, UserWsModel.oid == session_user.oid)).first()
|
||||
session_user.weight = ws_model.weight if ws_model else -1
|
||||
|
||||
session_user = UserInfoDTO.model_validate(session_user)
|
||||
if not session_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
if session_user.status != 1:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
||||
# ask
|
||||
llm_service = LLMService(session, session_user, chat)
|
||||
llm_service.init_record()
|
||||
|
||||
return StreamingResponse(run_task(llm_service, False), media_type="text/event-stream")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from sqlmodel import func, or_, select, delete as sqlmodel_delete
|
||||
from apps.system.crud.user import get_db_user, single_delete, user_ws_options
|
||||
from apps.system.crud.user import clean_user_cache, get_db_user, single_delete, user_ws_options
|
||||
from apps.system.models.system_model import UserWsModel
|
||||
from apps.system.models.user import UserModel
|
||||
from apps.system.schemas.auth import CacheName, CacheNamespace
|
||||
@@ -95,13 +95,14 @@ async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Tran
|
||||
return await user_ws_options(session, current_user.id, trans)
|
||||
|
||||
@router.put("/ws/{oid}")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
# @clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
|
||||
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
|
||||
if not any(x.id == oid for x in ws_list):
|
||||
raise HTTPException(f"oid [{oid}] is invalid!")
|
||||
user_model: UserModel = get_db_user(session = session, user_id = current_user.id)
|
||||
user_model.oid = oid
|
||||
await clean_user_cache(user_model.id)
|
||||
session.add(user_model)
|
||||
session.commit()
|
||||
|
||||
@@ -173,13 +174,14 @@ async def batch_del(session: SessionDep, id_list: list[int]):
|
||||
await single_delete(session, id)
|
||||
|
||||
@router.put("/language")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
|
||||
lang = language.language
|
||||
if lang not in ["zh-CN", "en"]:
|
||||
return {"message": "Language not supported"}
|
||||
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
|
||||
db_user.language = lang
|
||||
await clean_user_cache(db_user.id)
|
||||
session.add(db_user)
|
||||
session.commit()
|
||||
|
||||
@@ -194,11 +196,12 @@ async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
|
||||
session.commit()
|
||||
|
||||
@router.put("/pwd")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, editor: PwdEditor):
|
||||
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
|
||||
if not verify_md5pwd(editor.pwd, db_user.password):
|
||||
raise HTTPException("pwd error")
|
||||
db_user.password = md5pwd(editor.new_pwd)
|
||||
await clean_user_cache(db_user.id)
|
||||
session.add(db_user)
|
||||
session.commit()
|
||||
@@ -1,12 +1,27 @@
|
||||
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
|
||||
from typing import Optional, Set, Any, Dict, Tuple, Callable
|
||||
from inspect import Parameter, signature
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 锁管理
|
||||
_cache_locks = {}
|
||||
|
||||
@asynccontextmanager
|
||||
async def _get_cache_lock(key: str):
|
||||
lock = _cache_locks.setdefault(key, asyncio.Lock())
|
||||
async with lock:
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if key in _cache_locks and not lock.locked():
|
||||
del _cache_locks[key]
|
||||
|
||||
def should_skip_param(param: Parameter) -> bool:
|
||||
"""判断参数是否应该被忽略(依赖注入参数)"""
|
||||
return (
|
||||
@@ -26,9 +41,7 @@ def custom_key_builder(
|
||||
cacheName: Optional[str] = None,
|
||||
keyExpression: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
完全兼容FastAPICache的键生成器
|
||||
"""
|
||||
"""完全兼容FastAPICache的键生成器"""
|
||||
if cacheName:
|
||||
base_key = f"{namespace}:{cacheName}:"
|
||||
|
||||
@@ -38,7 +51,6 @@ def custom_key_builder(
|
||||
bound_args = sig.bind_partial(*args, **kwargs)
|
||||
bound_args.apply_defaults()
|
||||
|
||||
|
||||
if keyExpression.startswith("args["):
|
||||
import re
|
||||
match = re.match(r"args\[(\d+)\]", keyExpression)
|
||||
@@ -47,7 +59,6 @@ def custom_key_builder(
|
||||
value = bound_args.args[index]
|
||||
base_key += f"{value}:"
|
||||
else:
|
||||
|
||||
parts = keyExpression.split('.')
|
||||
value = bound_args.arguments[parts[0]]
|
||||
for part in parts[1:]:
|
||||
@@ -58,37 +69,27 @@ def custom_key_builder(
|
||||
logger.warning(f"Failed to evaluate keyExpression '{keyExpression}': {str(e)}")
|
||||
|
||||
return base_key
|
||||
# 获取函数签名
|
||||
sig = signature(func)
|
||||
|
||||
# 自动识别要跳过的参数
|
||||
sig = signature(func)
|
||||
auto_skip_args = {
|
||||
name for name, param in sig.parameters.items()
|
||||
if should_skip_param(param)
|
||||
}
|
||||
|
||||
# 合并用户指定的额外跳过参数
|
||||
skip_args = auto_skip_args.union(additional_skip_args or set())
|
||||
|
||||
# 过滤kwargs
|
||||
filtered_kwargs = {
|
||||
k: v for k, v in kwargs.items() if k not in skip_args
|
||||
}
|
||||
|
||||
# 过滤args - 将位置参数映射到它们的参数名
|
||||
bound_args = sig.bind_partial(*args, **kwargs)
|
||||
bound_args.apply_defaults()
|
||||
|
||||
filtered_args = []
|
||||
for i, (name, value) in enumerate(bound_args.arguments.items()):
|
||||
# 只处理位置参数 (在args中的参数)
|
||||
if i < len(args) and name not in skip_args:
|
||||
filtered_args.append(value)
|
||||
filtered_args = tuple(filtered_args)
|
||||
|
||||
# 获取默认键生成器
|
||||
default_key_builder = FastAPICache.get_key_builder()
|
||||
# 调用默认键生成器(严格按照其要求的参数格式)
|
||||
return default_key_builder(
|
||||
func=func,
|
||||
namespace=namespace,
|
||||
@@ -105,9 +106,7 @@ def cache(
|
||||
cacheName: Optional[str] = None,
|
||||
keyExpression: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
完全兼容的缓存装饰器
|
||||
"""
|
||||
"""完全兼容的缓存装饰器"""
|
||||
def decorator(func):
|
||||
if key_builder is None:
|
||||
used_key_builder = partial(
|
||||
@@ -121,24 +120,23 @@ def cache(
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
# 准备键生成器参数
|
||||
key_builder_args = {
|
||||
"func": func,
|
||||
"namespace": namespace,
|
||||
"args": args,
|
||||
"kwargs": kwargs
|
||||
}
|
||||
cache_key = used_key_builder(
|
||||
func=func,
|
||||
namespace=namespace or "",
|
||||
args=args,
|
||||
kwargs=kwargs
|
||||
)
|
||||
|
||||
# 生成缓存键
|
||||
cache_key = used_key_builder(**key_builder_args)
|
||||
logger.debug(f"Generated cache key: {cache_key}")
|
||||
|
||||
# 使用原始缓存装饰器
|
||||
return await original_cache(
|
||||
expire=expire,
|
||||
namespace=namespace,
|
||||
key_builder=lambda *_, **__: cache_key # 直接使用预生成的key
|
||||
)(func)(*args, **kwargs)
|
||||
async with _get_cache_lock(cache_key):
|
||||
backend = FastAPICache.get_backend()
|
||||
cached_value = await backend.get(cache_key)
|
||||
if cached_value is not None:
|
||||
return cached_value
|
||||
|
||||
result = await func(*args, **kwargs)
|
||||
await backend.set(cache_key, result, expire)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
@@ -147,17 +145,10 @@ def clear_cache(
|
||||
cacheName: Optional[str] = None,
|
||||
keyExpression: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
清除缓存的装饰器,参数与 @cache 保持一致
|
||||
使用方式:
|
||||
@clear_cache(namespace="user", cacheName="info", keyExpression="user_id")
|
||||
async def update_user(user_id: int):
|
||||
...
|
||||
"""
|
||||
"""清除缓存的装饰器"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
# 1. 生成缓存键(复用 custom_key_builder 逻辑)
|
||||
cache_key = custom_key_builder(
|
||||
func=func,
|
||||
namespace=namespace or "",
|
||||
@@ -167,13 +158,10 @@ def clear_cache(
|
||||
keyExpression=keyExpression,
|
||||
)
|
||||
|
||||
logger.debug(f"Clearing cache for key: {cache_key}")
|
||||
# 2. 清除缓存
|
||||
if await FastAPICache.get_backend().get(cache_key):
|
||||
async with _get_cache_lock(cache_key):
|
||||
await FastAPICache.clear(key=cache_key)
|
||||
|
||||
# 3. 执行原函数
|
||||
return await func(*args, **kwargs)
|
||||
result = await func(*args, **kwargs)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
@@ -129,11 +129,11 @@ template:
|
||||
predict:
|
||||
system: |
|
||||
### 说明:
|
||||
你是一个数据分析师,你的任务是根据给定的数据进行数据预测,我将以json格式给你一组数据,你帮我预测之后的数据(一段可以展示趋势的数据,至少2个周期),用json格式返回。
|
||||
你是一个数据分析师,你的任务是根据给定的数据进行数据预测,我将以json格式给你一组数据,你帮我预测之后的数据(一段可以展示趋势的数据,至少2个周期),用json格式返回,返回的格式需要与传入的数据格式保持一致。
|
||||
```json
|
||||
|
||||
无法预测或者不支持预测的数据请直接返回:"抱歉,该数据无法进行预测。(有原因则返回无法预测的原因)"
|
||||
不需要返回原有数据,直接返回预测的部份
|
||||
无法预测或者不支持预测的数据请直接返回(不需要返回JSON格式):"抱歉,该数据无法进行预测。(有原因则返回无法预测的原因)"
|
||||
如果可以预测,则不需要返回原有数据,直接返回预测的部份
|
||||
|
||||
user: |
|
||||
### 请使用 i18n: {lang} 对应的语言输出你的结果
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"element-plus": "^2.10.1",
|
||||
"element-plus-secondary": "^1.0.0",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
const getChatContainerHtml = (data) => {
|
||||
return `
|
||||
<div id="sqlbot-assistant-chat-container">
|
||||
<iframe id="sqlbot-assistant-chat" allow="microphone" src="${data.domain_url}/#/assistant?id=${data.id}"></iframe>
|
||||
<iframe id="sqlbot-assistant-chat-iframe-${data.id}" allow="microphone" src="${data.domain_url}/#/assistant?id=${data.id}"></iframe>
|
||||
<div class="sqlbot-assistant-operate">
|
||||
<div class="sqlbot-assistant-closeviewport sqlbot-assistant-viewportnone">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
@@ -311,7 +311,7 @@
|
||||
#sqlbot-assistant #sqlbot-assistant-chat-container .sqlbot-assistant-viewportnone{
|
||||
display:none;
|
||||
}
|
||||
#sqlbot-assistant #sqlbot-assistant-chat-container #sqlbot-assistant-chat{
|
||||
#sqlbot-assistant #sqlbot-assistant-chat-container #sqlbot-assistant-chat-iframe-${data.id}{
|
||||
height:100%;
|
||||
width:100%;
|
||||
border: none;
|
||||
@@ -334,7 +334,79 @@
|
||||
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)
|
||||
console.log(certificate)
|
||||
params = {
|
||||
busi: 'certificate',
|
||||
certificate,
|
||||
eventName,
|
||||
messageId: id,
|
||||
}
|
||||
const contentWindow = iframe.contentWindow
|
||||
contentWindow.postMessage(params, url)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
function loadScript(src, id) {
|
||||
const domain_url = getDomain(src)
|
||||
let url = `${domain_url}/api/v1/system/assistant/info/${id}`
|
||||
@@ -353,6 +425,10 @@
|
||||
tempData = Object.assign(tempData, config)
|
||||
}
|
||||
initsqlbot_assistant(tempData)
|
||||
if (data.type == 1) {
|
||||
registerMessageEvent(id, tempData)
|
||||
// postMessage the certificate to iframe
|
||||
}
|
||||
})
|
||||
}
|
||||
function getDomain(src) {
|
||||
|
||||
@@ -14,3 +14,4 @@ export const workspaceUwsDelete = (data: any) => request.delete('/system/workspa
|
||||
export const workspaceDelete = (id: any) => request.delete(`/system/workspace/${id}`)
|
||||
export const workspaceList = () => request.get('/system/workspace')
|
||||
export const workspaceDetail = (id: any) => request.get(`/system/workspace/${id}`)
|
||||
export const uwsOption = (params: any) => request.get('system/workspace/uws/option', { params })
|
||||
|
||||
|
After Width: | Height: | Size: 788 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00004 10.5759L13.4212 5.15476C13.5514 5.02459 13.7624 5.02459 13.8926 5.15476L14.364 5.62616C14.4942 5.75634 14.4942 5.96739 14.364 6.09757L8.47145 11.9901C8.2111 12.2505 7.78899 12.2505 7.52864 11.9901L1.63608 6.09757C1.50591 5.96739 1.50591 5.75634 1.63608 5.62616L2.10749 5.15476C2.23766 5.02459 2.44872 5.02459 2.57889 5.15476L8.00004 10.5759Z" fill="#646A73"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00004 10.5759L13.4212 5.15476C13.5514 5.02459 13.7624 5.02459 13.8926 5.15476L14.364 5.62616C14.4942 5.75634 14.4942 5.96739 14.364 6.09757L8.47145 11.9901C8.2111 12.2505 7.78899 12.2505 7.52864 11.9901L1.63608 6.09757C1.50591 5.96739 1.50591 5.75634 1.63608 5.62616L2.10749 5.15476C2.23766 5.02459 2.44872 5.02459 2.57889 5.15476L8.00004 10.5759Z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 455 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.25 9.75H6.75C4.67893 9.75 2.25 11.1986 2.25 13.8V15.825C2.25 16.1978 2.58579 16.5 3 16.5H15C15.4142 16.5 15.75 16.1978 15.75 15.825V13.8C15.75 11.1964 13.3211 9.75 11.25 9.75Z" fill="#1CBA90"/>
|
||||
<path d="M5.25 5.25C5.25 7.32107 6.92893 9 9 9C11.0711 9 12.75 7.32107 12.75 5.25C12.75 3.17893 11.0711 1.5 9 1.5C6.92893 1.5 5.25 3.17893 5.25 5.25Z" fill="#1CBA90"/>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.25 9.75H6.75C4.67893 9.75 2.25 11.1986 2.25 13.8V15.825C2.25 16.1978 2.58579 16.5 3 16.5H15C15.4142 16.5 15.75 16.1978 15.75 15.825V13.8C15.75 11.1964 13.3211 9.75 11.25 9.75Z" />
|
||||
<path d="M5.25 5.25C5.25 7.32107 6.92893 9 9 9C11.0711 9 12.75 7.32107 12.75 5.25C12.75 3.17893 11.0711 1.5 9 1.5C6.92893 1.5 5.25 3.17893 5.25 5.25Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 438 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 8.66667H10C11.841 8.66667 14 9.95237 14 12.2667V14.0667C14 14.398 13.7015 14.6667 13.3333 14.6667H2.66667C2.29848 14.6667 2 14.398 2 14.0667V12.2667C2 9.95432 4.15905 8.66667 6 8.66667ZM12.6667 13.3333V12.2222C12.6667 10.8212 11.2473 10 10 10H6C4.77998 10 3.33333 10.7811 3.33333 12.2222V13.3333H12.6667ZM8 8C6.15905 8 4.66667 6.50762 4.66667 4.66667C4.66667 2.82572 6.15905 1.33333 8 1.33333C9.84095 1.33333 11.3333 2.82572 11.3333 4.66667C11.3333 6.50762 9.84095 8 8 8ZM8 6.66667C9.10457 6.66667 10 5.77123 10 4.66667C10 3.5621 9.10457 2.66667 8 2.66667C6.89543 2.66667 6 3.5621 6 4.66667C6 5.77123 6.89543 6.66667 8 6.66667Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 761 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.8057 13.6388C11.7701 14.4894 10.4447 15 9.00003 15C5.68632 15 3.00003 12.3137 3.00003 9C3.00003 8.69123 3.02335 8.38791 3.06833 8.0917L3.77428 8.50919C4.05573 8.67564 4.40051 8.42728 4.3318 8.10761L3.79257 5.59892C3.74659 5.385 3.52799 5.25572 3.31837 5.31849L0.860228 6.05455C0.546995 6.14835 0.495468 6.57013 0.77691 6.73657L1.6979 7.28123C1.56848 7.83317 1.50003 8.40859 1.50003 9C1.50003 13.1421 4.85789 16.5 9.00003 16.5C11.0063 16.5 12.8286 15.7122 14.1745 14.4291L12.8057 13.6388Z" fill="currentColor"/>
|
||||
<path d="M5.4246 4.1812C6.42315 3.43909 7.66031 3 9.00003 3C12.3137 3 15 5.68629 15 9C15 9.21238 14.989 9.42219 14.9675 9.62887L14.145 9.16094C13.8608 8.99924 13.5203 9.25334 13.5943 9.57182L14.1756 12.0711C14.2251 12.2842 14.4459 12.4098 14.6544 12.3435L17.0999 11.5663C17.4115 11.4673 17.4559 11.0447 17.1717 10.883L16.365 10.424C16.4536 9.96297 16.5 9.48691 16.5 9C16.5 4.85786 13.1422 1.5 9.00003 1.5C7.09588 1.5 5.35746 2.20961 4.03481 3.3788L5.4246 4.1812Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 3V12H15V3H3ZM1.5 2.25C1.5 1.83562 1.8375 1.5 2.25 1.5H15.75C16.1625 1.5 16.5 1.83562 16.5 2.25V12.75C16.5 13.1644 16.1625 13.5 15.75 13.5H2.25C1.8375 13.5 1.5 13.1644 1.5 12.75V2.25Z" fill="currentColor"/>
|
||||
<path d="M11.5789 5.09473L12.6394 6.15523L9.0881 9.70648L7.35635 7.9751L6.15523 9.1766L5.09473 8.11573L7.35635 5.8541L9.08773 7.58548L11.5789 5.09473Z" fill="currentColor"/>
|
||||
<path d="M12.75 15H5.25V16.5H12.75V15Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 597 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.93782 14.5935C2.1193 13.7061 1.49299 12.6387 1.125 11.4573L2.43707 9.66437C2.72657 9.26876 2.72657 8.73124 2.43707 8.33563L1.125 6.54267C1.49299 5.36128 2.1193 4.29386 2.93782 3.40655L5.14275 3.6464C5.63031 3.69944 6.09604 3.43052 6.29385 2.98174L7.18964 0.949401C7.77221 0.818869 8.37806 0.75 9 0.75C9.62194 0.75 10.2278 0.818869 10.8104 0.949401L11.7062 2.98174C11.904 3.43052 12.3697 3.69944 12.8573 3.6464L15.0622 3.40655C15.8807 4.29386 16.507 5.36128 16.875 6.54267L15.5629 8.33563C15.2734 8.73124 15.2734 9.26876 15.5629 9.66437L16.875 11.4573C16.507 12.6387 15.8807 13.7061 15.0622 14.5935L12.8573 14.3536C12.3697 14.3006 11.904 14.5695 11.7062 15.0183L10.8104 17.0506C10.2278 17.1811 9.62194 17.25 9 17.25C8.37806 17.25 7.77221 17.1811 7.18964 17.0506L6.29385 15.0183C6.09604 14.5695 5.63031 14.3006 5.14275 14.3536L2.93782 14.5935ZM9 12C10.6569 12 12 10.6569 12 9C12 7.34315 10.6569 6 9 6C7.34315 6 6 7.34315 6 9C6 10.6569 7.34315 12 9 12Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.2276 13.6116C6.57261 13.7278 6.65136 14.0653 6.40198 14.3271C6.02398 14.7099 3.73835 15.0264 3.0596 15.1258C2.92985 15.1453 2.81023 15.1843 2.68573 15.1134C2.50685 15.0137 2.47685 14.8389 2.51098 14.606C2.60285 13.976 2.89348 12.3729 3.33823 11.2067C3.37798 11.1024 3.50885 11.0859 3.61273 11.0942C3.82873 11.0942 3.94535 11.1902 4.0241 11.4107C4.32335 12.2717 4.87648 12.9249 5.67448 13.3701C5.84923 13.4661 6.03636 13.5448 6.2276 13.6116ZM15.8674 2.2348C15.9086 2.49017 15.9379 2.7418 15.9337 3.0013C15.8962 4.2913 15.6142 5.53142 15.1459 6.73442C14.586 8.17255 13.7651 9.43742 12.6165 10.4844C12.5212 10.5707 12.492 10.6614 12.5044 10.7807C12.546 11.168 12.5749 11.9304 12.6165 12.3182C12.7076 13.2204 12.3926 13.9577 11.6587 14.4812C10.8709 15.0414 10.0537 15.5608 9.24561 16.088C8.73561 16.418 8.10936 16.1169 8.05123 15.5154C7.98486 14.8232 7.93498 13.7559 7.88548 13.0637C7.86036 12.7134 7.86898 12.7134 7.53298 12.668C6.63298 12.5525 5.93248 12.1281 5.45173 11.3619C5.20273 10.9663 5.07823 10.5294 5.03698 10.0719C5.02423 9.93205 4.96648 9.89905 4.83785 9.8908C4.11223 9.85367 3.0116 9.8083 2.28598 9.75467C1.68485 9.70967 1.37773 9.07892 1.70098 8.55167C1.92523 8.18042 2.16598 7.81817 2.40223 7.45142C2.65498 7.0558 2.9036 6.6643 3.15673 6.26867C3.62098 5.54792 4.28848 5.16055 5.14273 5.1523C5.6321 5.14817 6.49648 5.2348 6.98548 5.28805C7.08111 5.2963 7.14748 5.2843 7.20936 5.21417C8.34561 3.95305 9.74698 3.1003 11.3392 2.53142C12.2354 2.21102 13.1668 1.9995 14.1135 1.90142C14.5942 1.85155 15.0754 1.83505 15.552 1.92167C15.8134 1.97117 15.8261 1.98355 15.8674 2.2348ZM14.2687 3.39317C13.4385 3.47942 12.6307 3.66317 11.844 3.94442C10.4002 4.45967 9.24036 5.20105 8.33136 6.2098C7.92036 6.67255 7.39498 6.8293 6.82236 6.77942C6.68282 6.76378 6.54344 6.74678 6.40423 6.72842C5.99035 6.67978 5.57408 6.65436 5.15735 6.6523C4.82248 6.6553 4.6166 6.7723 4.42048 7.0768L4.04548 7.66405C3.90155 7.8898 3.75743 8.11543 3.6131 8.34092C3.92585 8.35967 4.24085 8.37767 4.55885 8.3938C4.91885 8.41705 5.60885 8.50255 5.9411 8.78005C6.32998 9.10367 6.49273 9.50942 6.53061 9.93655C6.55423 10.196 6.61686 10.3978 6.72186 10.5643C6.95473 10.9355 7.25061 11.1193 7.73361 11.1816C7.79623 11.1902 7.82511 11.1939 7.86261 11.1996C8.00661 11.222 8.12136 11.2464 8.26198 11.2963C8.54061 11.3957 8.79711 11.5625 9.00298 11.8213C9.18673 12.0523 9.28236 12.665 9.33298 12.9152C9.35586 13.0273 9.42523 13.9352 9.44286 14.1699C9.89734 13.8759 10.3457 13.5724 10.7876 13.2598C11.0715 13.0573 11.1626 12.8499 11.1251 12.4775C11.1064 12.3058 11.0265 11.0672 11.0126 10.9363C10.9511 10.346 11.1555 9.7843 11.6062 9.37555C12.5479 8.51717 13.2532 7.46192 13.7482 6.1903C14.1195 5.23705 14.3415 4.30142 14.4124 3.37967C14.3662 3.38342 14.3182 3.38792 14.2687 3.39317ZM11.8125 7.12517C11.5141 7.12517 11.228 7.00665 11.017 6.79567C10.806 6.58469 10.6875 6.29854 10.6875 6.00017C10.6875 5.70181 10.806 5.41566 11.017 5.20468C11.228 4.9937 11.5141 4.87517 11.8125 4.87517C12.1109 4.87517 12.397 4.9937 12.608 5.20468C12.819 5.41566 12.9375 5.70181 12.9375 6.00017C12.9375 6.29854 12.819 6.58469 12.608 6.79567C12.397 7.00665 12.1109 7.12517 11.8125 7.12517Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -25,13 +25,16 @@ const routerList = computed(() => {
|
||||
if (showSysmenu.value) {
|
||||
return router.getRoutes().filter((route) => route.path.includes('/system') && !route.redirect)
|
||||
}
|
||||
|
||||
return router.getRoutes().filter((route) => {
|
||||
return (
|
||||
!route.path.includes('canvas') &&
|
||||
!route.path.includes('member') &&
|
||||
!route.path.includes('permission') &&
|
||||
!route.path.includes('preview') &&
|
||||
route.path !== '/login' &&
|
||||
!route.path.includes('/system') &&
|
||||
!route.redirect &&
|
||||
(route.path.includes('set') || !route.redirect) &&
|
||||
route.path !== '/:pathMatch(.*)*' &&
|
||||
!route.path.includes('dsTable')
|
||||
)
|
||||
@@ -45,17 +48,28 @@ const routerList = computed(() => {
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
<style lang="less">
|
||||
.ed-menu-vertical {
|
||||
--ed-menu-item-height: 40px;
|
||||
--ed-menu-bg-color: transparent;
|
||||
--ed-menu-base-level-padding: 4px;
|
||||
border-right: none;
|
||||
.ed-menu-item {
|
||||
height: 40px;
|
||||
height: 40px !important;
|
||||
border-radius: 6px !important;
|
||||
&.is-active {
|
||||
background-color: #fff !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.ed-sub-menu .ed-sub-menu__title:hover {
|
||||
border-radius: 6px;
|
||||
&.is-active.is-active.is-active {
|
||||
background-color: #1cba901a;
|
||||
}
|
||||
|
||||
.ed-sub-menu.is-active {
|
||||
.ed-sub-menu__title {
|
||||
color: #1cba90 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ import model from '@/assets/svg/icon_dataset_filled.svg'
|
||||
import ds from '@/assets/svg/ds.svg'
|
||||
import dashboard from '@/assets/svg/dashboard.svg'
|
||||
import chat from '@/assets/svg/chat.svg'
|
||||
import setting from '@/assets/svg/icon_setting_filled.svg'
|
||||
import icon_user from '@/assets/svg/icon_user.svg'
|
||||
|
||||
const iconMap = {
|
||||
icon_ai: model,
|
||||
ds,
|
||||
dashboard,
|
||||
chat,
|
||||
user,
|
||||
setting,
|
||||
icon_user: icon_user,
|
||||
workspace,
|
||||
} as { [key: string]: any }
|
||||
@@ -38,7 +41,9 @@ const MenuItem = defineComponent({
|
||||
const router = useRouter()
|
||||
|
||||
const handleMenuClick = (e: any) => {
|
||||
router.push(e.index)
|
||||
if (e.index) {
|
||||
router.push(e.redirect || e.index)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -50,7 +55,7 @@ const MenuItem = defineComponent({
|
||||
if (children?.length) {
|
||||
return h(
|
||||
ElSubMenu,
|
||||
{ index: path, onClick: (e: any) => handleMenuClick(e) },
|
||||
{ index: path, onClick: () => handleMenuClick(props.menu) },
|
||||
{
|
||||
title: () => titleWithIcon(props),
|
||||
default: () => children.map((ele: any) => h(MenuItem, { menu: ele })),
|
||||
|
||||
@@ -43,6 +43,7 @@ const handleDefaultWorkspaceChange = (item: any) => {
|
||||
currentWorkspace.value = { id: item.id, name: item.name }
|
||||
userApi.ws_change(item.id).then(() => {
|
||||
ElMessage.success(t('common.switch_success'))
|
||||
location.reload()
|
||||
})
|
||||
emit('selectWorkspace', item)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"common": {
|
||||
"input_content": "Please Input Content",
|
||||
"empty": " ",
|
||||
"may_not_exist": "No results yet, the user may not exist",
|
||||
"back": "Back",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
@@ -94,6 +95,7 @@
|
||||
"type": "Data Source Type",
|
||||
"status": "Status",
|
||||
"actions": "Actions",
|
||||
"local_excelcsv": "Local Excel/CSV",
|
||||
"pieces_in_total": "Showing {ms} records",
|
||||
"test_connection": "Test Connection",
|
||||
"check": "check",
|
||||
@@ -308,7 +310,44 @@
|
||||
"there_are": "There are",
|
||||
"2_dashboards": "{msg} dashboards",
|
||||
"smart_data_centers": "{msg} smart data centers",
|
||||
"confirm_to_delete": "This data source is in use. Deleting it will cause these resources to not work properly. Confirm to delete?"
|
||||
"confirm_to_delete": "This data source is in use. Deleting it will cause these resources to not work properly. Confirm to delete?",
|
||||
"id_account_to_add": "Search user ID/account to add",
|
||||
"find_user": "Find user",
|
||||
"add_successfully": "Add successfully",
|
||||
"member_management": "Member management",
|
||||
"permission_configuration": "Permission configuration",
|
||||
"set": "Set",
|
||||
"operate_with_caution": "After deletion, the users under the workspace will be removed and all resources will be deleted. Please operate with caution."
|
||||
},
|
||||
"permission": {
|
||||
"search_rule_group": "Search rule group",
|
||||
"add_rule_group": "Add rule group",
|
||||
"permission_rule": "Permission rule",
|
||||
"restricted_user": "Restricted user",
|
||||
"set_rule": "Set rule",
|
||||
"set_user": "Set user",
|
||||
"2": "{msg}",
|
||||
"238_people": "{msg} people",
|
||||
"no_permission_rule": "No permission rule",
|
||||
"set_permission_rule": "Set permission rule",
|
||||
"basic_information": "Basic information",
|
||||
"rule_group_name": "Rule group name",
|
||||
"rule_name": "Rule name",
|
||||
"type": "Type",
|
||||
"data_source": "Data source",
|
||||
"data_table": "Data table",
|
||||
"select_restricted_user": "Select restricted user",
|
||||
"rule_group_1": "Delete rule group: {msg}?",
|
||||
"no_rule": "No rule",
|
||||
"row_permission": "Row permission",
|
||||
"column_permission": "Column permission",
|
||||
"rule_rule_1": "Delete rule: {msg}?",
|
||||
"no_content": "No content",
|
||||
"edit_column_permission": "Edit column permission",
|
||||
"edit_row_permission": "Edit row permission",
|
||||
"add_column_permission": "Add column permission",
|
||||
"add_row_permission": "Add row permission",
|
||||
"no_fields_yet": "No fields yet"
|
||||
},
|
||||
"chat": {
|
||||
"chart_type": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"menu": {
|
||||
"menu": {
|
||||
"Data Q&A": "智能问数",
|
||||
"Data Connections": "数据源",
|
||||
"Dashboard": "仪表盘",
|
||||
@@ -7,6 +7,7 @@
|
||||
},
|
||||
"common": {
|
||||
"input_content": "请输入内容",
|
||||
"may_not_exist": "暂无结果,用户可能不存在",
|
||||
"empty": "",
|
||||
"back": "返回",
|
||||
"confirm": "确认",
|
||||
@@ -94,10 +95,12 @@
|
||||
"data_predict": "数据分析",
|
||||
"chat_search": "搜索",
|
||||
"thinking": "思考中",
|
||||
"thinking_step": "思考过程"
|
||||
"thinking_step": "思考过程",
|
||||
"ask_again": "重新生成"
|
||||
},
|
||||
"ds": {
|
||||
"title": "数据源",
|
||||
"local_excelcsv": "本地 Excel/CSV",
|
||||
"add": "添加数据源",
|
||||
"delete": "删除数据源",
|
||||
"name": "数据源名称",
|
||||
@@ -328,7 +331,43 @@
|
||||
"there_are": "有 ",
|
||||
"2_dashboards": "{msg} 个仪表板",
|
||||
"smart_data_centers": "{msg} 个智能问数",
|
||||
"confirm_to_delete": "正在使用此数据源,删除后会导致这些资源无法正常使用,确认删除?"
|
||||
"confirm_to_delete": "正在使用此数据源,删除后会导致这些资源无法正常使用,确认删除?",
|
||||
"id_account_to_add": "搜索用户ID/账号添加",
|
||||
"find_user": "查找用户",
|
||||
"add_successfully": "添加成功",
|
||||
"member_management": "成员管理",
|
||||
"permission_configuration": "权限配置",
|
||||
"set": "设置",
|
||||
"operate_with_caution": "删除后,该工作空间下的用户将被移除,所有资源也将被删除,请谨慎操作。"
|
||||
},
|
||||
"permission": {
|
||||
"search_rule_group": "搜索规则组",
|
||||
"add_rule_group": "添加规则组",
|
||||
"permission_rule": "权限规则",
|
||||
"restricted_user": "受限用户",
|
||||
"set_rule": "设置规则",
|
||||
"set_user": "设置用户",
|
||||
"2": "{msg} 个",
|
||||
"238_people": "{msg} 人",
|
||||
"no_permission_rule": "暂无权限规则",
|
||||
"set_permission_rule": "设置权限规则",
|
||||
"rule_group_name": "规则组名称",
|
||||
"rule_name": "规则名称",
|
||||
"type": "类型",
|
||||
"data_source": "数据源",
|
||||
"data_table": "数据表",
|
||||
"select_restricted_user": "选择受限用户",
|
||||
"rule_group_1": "是否删除规则组:{msg}?",
|
||||
"no_rule": "暂无规则",
|
||||
"row_permission": "行权限",
|
||||
"column_permission": "列权限",
|
||||
"rule_rule_1": "是否删除规则:{msg}?",
|
||||
"no_content": "暂无内容",
|
||||
"edit_column_permission": "编辑列权限",
|
||||
"edit_row_permission": "编辑行权限",
|
||||
"add_column_permission": "添加列权限",
|
||||
"add_row_permission": "添加行权限",
|
||||
"no_fields_yet": "暂无字段"
|
||||
},
|
||||
"chat": {
|
||||
"chart_type": {
|
||||
@@ -366,4 +405,4 @@
|
||||
"back_community": "还原至社区版",
|
||||
"confirm_tips": "确定还原至社区版?"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,14 @@ import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
|
||||
import Dashboard from '@/views/dashboard/index.vue'
|
||||
import Model from '@/views/system/model/Model.vue'
|
||||
import assistant from '@/views/embedded/assistant.vue'
|
||||
import Member from '@/views/system/member/index.vue'
|
||||
import Permission from '@/views/system/permission/index.vue'
|
||||
import User from '@/views/system/user/User.vue'
|
||||
import Workspace from '@/views/system/workspace/index.vue'
|
||||
import { i18n } from '@/i18n'
|
||||
import { watchRouter } from './watch'
|
||||
|
||||
const t = i18n.global.t
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [
|
||||
@@ -31,7 +36,7 @@ const router = createRouter({
|
||||
path: 'index',
|
||||
name: 'chat',
|
||||
component: chat,
|
||||
meta: { title: 'Data Q&A', icon: 'chat' },
|
||||
meta: { title: t('menu.Data Q&A'), icon: 'chat' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -56,7 +61,7 @@ const router = createRouter({
|
||||
path: 'index',
|
||||
name: 'ds',
|
||||
component: Datasource,
|
||||
meta: { title: 'Data Connections', icon: 'ds' },
|
||||
meta: { title: t('menu.Data Connections'), icon: 'ds' },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -69,7 +74,27 @@ const router = createRouter({
|
||||
path: 'index',
|
||||
name: 'dashboard',
|
||||
component: Dashboard,
|
||||
meta: { title: 'Dashboard', icon: 'dashboard' },
|
||||
meta: { title: t('dashboard.dashboard'), icon: 'dashboard' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/set',
|
||||
component: LayoutDsl,
|
||||
redirect: '/set/member',
|
||||
meta: { title: t('workspace.set'), icon: 'setting' },
|
||||
children: [
|
||||
{
|
||||
path: '/set/member',
|
||||
name: 'member',
|
||||
component: Member,
|
||||
meta: { title: t('workspace.member_management') },
|
||||
},
|
||||
{
|
||||
path: '/set/permission',
|
||||
name: 'permission',
|
||||
component: Permission,
|
||||
meta: { title: t('workspace.permission_configuration') },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -126,13 +151,13 @@ const router = createRouter({
|
||||
path: 'user',
|
||||
name: 'user',
|
||||
component: User,
|
||||
meta: { title: '用户管理', icon: 'user' },
|
||||
meta: { title: t('user.user_management'), icon: 'user' },
|
||||
},
|
||||
{
|
||||
path: 'workspace',
|
||||
name: 'workspace',
|
||||
component: Workspace,
|
||||
meta: { title: '工作空间', icon: 'workspace' },
|
||||
meta: { title: t('user.workspace'), icon: 'workspace' },
|
||||
},
|
||||
{
|
||||
path: 'model',
|
||||
|
||||
@@ -9,6 +9,8 @@ interface AssistantState {
|
||||
token: string
|
||||
assistant: boolean
|
||||
flag: number
|
||||
type: number
|
||||
certificate: string
|
||||
}
|
||||
|
||||
export const AssistantStore = defineStore('assistant', {
|
||||
@@ -17,9 +19,14 @@ export const AssistantStore = defineStore('assistant', {
|
||||
token: '',
|
||||
assistant: false,
|
||||
flag: 0,
|
||||
type: 0,
|
||||
certificate: '',
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getCertificate(): string {
|
||||
return this.certificate
|
||||
},
|
||||
getToken(): string {
|
||||
return this.token
|
||||
},
|
||||
@@ -29,8 +36,17 @@ export const AssistantStore = defineStore('assistant', {
|
||||
getFlag(): number {
|
||||
return this.flag
|
||||
},
|
||||
getType(): number {
|
||||
return this.type
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setCertificate(certificate: string) {
|
||||
this.certificate = certificate
|
||||
},
|
||||
setType(type: number) {
|
||||
this.type = type
|
||||
},
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
},
|
||||
|
||||
@@ -2,25 +2,23 @@ import MarkdownIt from 'markdown-it'
|
||||
import hljs from 'highlight.js'
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
html: false,
|
||||
linkify: false,
|
||||
breaks: true,
|
||||
typographer: true,
|
||||
quotes: '“”‘’',
|
||||
highlight: (str: any, lang: any): string => {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
try {
|
||||
return `
|
||||
<pre>
|
||||
<code class="hljs">
|
||||
${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}
|
||||
</code>
|
||||
</pre>
|
||||
`
|
||||
return `<pre class="hljs">
|
||||
<code>${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}</code>
|
||||
</pre>`
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
return '<pre><code class="hljs">' + md.utils.escapeHtml(str) + '</code></pre>'
|
||||
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { datetimeFormat } from '@/utils/utils.ts'
|
||||
import type { ChatMessage } from '@/api/chat.ts'
|
||||
|
||||
defineProps<{
|
||||
message: ChatMessage
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tool-container">
|
||||
<div class="tool-btns">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="tool-times">
|
||||
<div></div>
|
||||
<div class="time">
|
||||
{{ datetimeFormat(message?.record?.create_time) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tool-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
row-gap: 8px;
|
||||
|
||||
min-height: 22px;
|
||||
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.tool-times {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: rgba(100, 106, 115, 1);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -53,7 +53,6 @@ function clickQuestion(question: string): void {
|
||||
|
||||
<style scoped lang="less">
|
||||
.recommend-questions {
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
@@ -69,7 +68,7 @@ function clickQuestion(question: string): void {
|
||||
.question-grid {
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
grid-template-columns: repeat(2, calc(50% - 6px));
|
||||
}
|
||||
|
||||
.question {
|
||||
|
||||
@@ -192,6 +192,7 @@ defineExpose({ sendMessage, index: index.value, chatList: _chatList })
|
||||
:loading="_loading"
|
||||
>
|
||||
<MdComponent :message="message.record?.analysis" style="margin-top: 12px" />
|
||||
<slot></slot>
|
||||
<slot name="footer"></slot>
|
||||
</BaseAnswer>
|
||||
</template>
|
||||
|
||||
@@ -70,9 +70,9 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="base-answer-block">
|
||||
<el-button v-if="loading || hasReasoning" class="thinking-btn" @click="clickShow">
|
||||
<el-button v-if="message.isTyping || hasReasoning" class="thinking-btn" @click="clickShow">
|
||||
<div class="thinking-btn-inner">
|
||||
<span v-if="loading">{{ t('qa.thinking') }}</span>
|
||||
<span v-if="message.isTyping">{{ t('qa.thinking') }}</span>
|
||||
<span v-else>{{ t('qa.thinking_step') }}</span>
|
||||
<span class="btn-icon">
|
||||
<el-icon v-if="show">
|
||||
@@ -140,7 +140,14 @@ onMounted(() => {
|
||||
line-height: 22px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(143, 149, 158, 1);
|
||||
color: rgba(143, 149, 158, 1) !important;
|
||||
|
||||
.markdown-body {
|
||||
color: rgba(143, 149, 158, 1) !important;
|
||||
line-height: 22px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(31, 35, 41, 0.15);
|
||||
@@ -154,6 +161,11 @@ onMounted(() => {
|
||||
|
||||
.answer-container {
|
||||
width: 100%;
|
||||
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: rgba(31, 35, 41, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -193,6 +193,7 @@ defineExpose({ sendMessage, index: index.value, chatList: _chatList })
|
||||
<template>
|
||||
<BaseAnswer v-if="message" :message="message" :reasoning-name="['predict']" :loading="_loading">
|
||||
<MdComponent :message="message.record?.predict_content" style="margin-top: 12px" />
|
||||
<slot></slot>
|
||||
<slot name="footer"></slot>
|
||||
</BaseAnswer>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChatMessage } from '@/api/chat.ts'
|
||||
import DisplayChartBlock from '@/views/chat/component/DisplayChartBlock.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { concat } from 'lodash-es'
|
||||
import type { ChartTypes } from '@/views/chat/component/BaseChart.ts'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
message: ChatMessage
|
||||
isPredict?: boolean
|
||||
}>(),
|
||||
{
|
||||
isPredict: false,
|
||||
}
|
||||
)
|
||||
|
||||
const dataObject = computed<{
|
||||
fields: Array<string>
|
||||
data: Array<{ [key: string]: any }>
|
||||
}>(() => {
|
||||
if (props.message?.record?.data) {
|
||||
if (typeof props.message?.record?.data === 'string') {
|
||||
return JSON.parse(props.message.record.data)
|
||||
} else {
|
||||
return props.message.record.data
|
||||
}
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const data = computed(() => {
|
||||
if (props.isPredict) {
|
||||
let _list = []
|
||||
if (
|
||||
props.message?.record?.predict_data &&
|
||||
typeof props.message?.record?.predict_data === 'string'
|
||||
) {
|
||||
if (
|
||||
props.message?.record?.predict_data.length > 0 &&
|
||||
props.message?.record?.predict_data.trim().startsWith('[') &&
|
||||
props.message?.record?.predict_data.trim().endsWith(']')
|
||||
) {
|
||||
try {
|
||||
_list = JSON.parse(props.message?.record?.predict_data)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (props.message?.record?.predict_data.length > 0) {
|
||||
_list = props.message?.record?.predict_data
|
||||
}
|
||||
}
|
||||
|
||||
if (_list.length == 0) {
|
||||
return _list
|
||||
}
|
||||
|
||||
if (dataObject.value.data && dataObject.value.data.length > 0) {
|
||||
return concat(dataObject.value.data, _list)
|
||||
}
|
||||
|
||||
return _list
|
||||
} else {
|
||||
return dataObject.value.data
|
||||
}
|
||||
})
|
||||
|
||||
const chartRef = ref()
|
||||
|
||||
const chartObject = computed<{
|
||||
type: ChartTypes
|
||||
title: string
|
||||
axis: {
|
||||
x: { name: string; value: string }
|
||||
y: { name: string; value: string }
|
||||
series: { name: string; value: string }
|
||||
}
|
||||
columns: Array<{ name: string; value: string }>
|
||||
}>(() => {
|
||||
if (props.message?.record?.chart) {
|
||||
return JSON.parse(props.message.record.chart)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
const currentChartType = ref<ChartTypes | undefined>(undefined)
|
||||
|
||||
const chartType = computed<ChartTypes>({
|
||||
get() {
|
||||
if (currentChartType.value) {
|
||||
return currentChartType.value
|
||||
}
|
||||
return chartObject.value.type ?? 'table'
|
||||
},
|
||||
set(v) {
|
||||
currentChartType.value = v
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="
|
||||
(!isPredict && (message?.record?.sql || message?.record?.chart)) ||
|
||||
(isPredict && message?.record?.chart && data.length > 0)
|
||||
"
|
||||
class="chart-component-container"
|
||||
>
|
||||
<div class="header-bar">todo</div>
|
||||
<div v-if="message?.record?.chart" class="chart-block">
|
||||
<DisplayChartBlock
|
||||
:id="message.record.id"
|
||||
ref="chartRef"
|
||||
:chart-type="chartType"
|
||||
:message="message"
|
||||
:data="data"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart-component-container {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 16px;
|
||||
|
||||
border: 1px solid rgba(222, 224, 227, 1);
|
||||
border-radius: 12px;
|
||||
|
||||
.header-bar {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.chart-block {
|
||||
height: 352px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -45,6 +45,7 @@ function clickPredict() {
|
||||
|
||||
.prefix-title {
|
||||
color: rgba(28, 186, 144, 1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -61,29 +61,26 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="message?.record">
|
||||
<div v-if="message.record.chart" class="chart-base-container">
|
||||
<div v-if="data?.length > 0">
|
||||
<ChartComponent
|
||||
v-if="message.record.id"
|
||||
:id="id ?? 'default_chat_id'"
|
||||
ref="chartRef"
|
||||
:type="chartType"
|
||||
:columns="chartObject?.columns"
|
||||
:x="xAxis"
|
||||
:y="yAxis"
|
||||
:series="series"
|
||||
:data="data"
|
||||
/>
|
||||
</div>
|
||||
<el-empty v-else description="No Data" />
|
||||
</div>
|
||||
<div v-if="message.record?.chart" class="chart-base-container">
|
||||
<ChartComponent
|
||||
v-if="message.record.id && data?.length > 0"
|
||||
:id="id ?? 'default_chat_id'"
|
||||
ref="chartRef"
|
||||
:type="chartType"
|
||||
:columns="chartObject?.columns"
|
||||
:x="xAxis"
|
||||
:y="yAxis"
|
||||
:series="series"
|
||||
:data="data"
|
||||
/>
|
||||
<el-empty v-else description="No Data" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart-base-container {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: rgba(224, 224, 226, 0.29);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import md from '@/utils/markdown.ts'
|
||||
import 'highlight.js/styles/github-dark-dimmed.css'
|
||||
import 'highlight.js/styles/github-dark-dimmed.min.css'
|
||||
import 'github-markdown-css/github-markdown-light.css'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -13,7 +14,22 @@ const renderMd = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-dompurify-html="renderMd"></div>
|
||||
<div v-dompurify-html="renderMd" class="markdown-body md-render-container"></div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
<style lang="less">
|
||||
.md-render-container {
|
||||
.hljs {
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
//ul {
|
||||
// padding-left: 16px;
|
||||
//}
|
||||
//ol {
|
||||
// padding-left: 16px;
|
||||
//}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -116,25 +116,66 @@
|
||||
@finish="onChartAnswerFinish"
|
||||
@error="onChartAnswerError"
|
||||
>
|
||||
<div style="height: 100px; width: 100%; background: grey">todo</div>
|
||||
<div style="padding: 0 22px; display: flex; justify-content: flex-end">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
:disabled="isTyping"
|
||||
@click="clickAnalysis(message.record?.id)"
|
||||
>
|
||||
{{ t('chat.data_analysis') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
:disabled="isTyping"
|
||||
@click="clickPredict(message.record?.id)"
|
||||
>
|
||||
{{ t('chat.data_predict') }}
|
||||
</el-button>
|
||||
<ChartBlock style="margin-top: 12px" :message="message" />
|
||||
<div
|
||||
v-if="message.record?.error && message.record?.error?.trim().length > 0"
|
||||
class="error-container"
|
||||
>
|
||||
{{ message.record?.error }}
|
||||
</div>
|
||||
<ChatToolBar :message="message">
|
||||
<div class="tool-btns">
|
||||
<el-tooltip effect="dark" :content="t('qa.ask_again')" placement="top">
|
||||
<el-button
|
||||
class="tool-btn"
|
||||
text
|
||||
:disabled="isTyping"
|
||||
@click="askAgain(message)"
|
||||
>
|
||||
<el-icon size="18">
|
||||
<icon_replace_outlined />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<template v-if="message.record?.chart">
|
||||
<div class="divider"></div>
|
||||
<div>
|
||||
<el-button
|
||||
class="tool-btn"
|
||||
text
|
||||
:disabled="isTyping"
|
||||
@click="clickAnalysis(message.record?.id)"
|
||||
>
|
||||
<span class="tool-btn-inner">
|
||||
<el-icon size="18">
|
||||
<icon_screen_outlined />
|
||||
</el-icon>
|
||||
<span class="btn-text">
|
||||
{{ t('chat.data_analysis') }}
|
||||
</span>
|
||||
</span>
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
class="tool-btn"
|
||||
text
|
||||
:disabled="isTyping"
|
||||
@click="clickPredict(message.record?.id)"
|
||||
>
|
||||
<span class="tool-btn-inner">
|
||||
<el-icon size="18">
|
||||
<icon_start_outlined />
|
||||
</el-icon>
|
||||
<span class="btn-text">
|
||||
{{ t('chat.data_predict') }}
|
||||
</span>
|
||||
</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</ChatToolBar>
|
||||
<template #footer>
|
||||
<RecommendQuestion
|
||||
:questions="message.recommended_question"
|
||||
@@ -156,7 +197,15 @@
|
||||
:message="message"
|
||||
@finish="onAnalysisAnswerFinish"
|
||||
@error="onAnalysisAnswerError"
|
||||
/>
|
||||
>
|
||||
<div
|
||||
v-if="message.record?.error && message.record?.error?.trim().length > 0"
|
||||
class="error-container"
|
||||
>
|
||||
{{ message.record?.error }}
|
||||
</div>
|
||||
<ChatToolBar :message="message" />
|
||||
</AnalysisAnswer>
|
||||
<PredictAnswer
|
||||
v-if="
|
||||
message?.record?.predict_record_id !== undefined &&
|
||||
@@ -170,7 +219,16 @@
|
||||
:message="message"
|
||||
@finish="onPredictAnswerFinish"
|
||||
@error="onPredictAnswerError"
|
||||
/>
|
||||
>
|
||||
<ChartBlock style="margin-top: 12px" :message="message" is-predict />
|
||||
<div
|
||||
v-if="message.record?.error && message.record?.error?.trim().length > 0"
|
||||
class="error-container"
|
||||
>
|
||||
{{ message.record?.error }}
|
||||
</div>
|
||||
<ChatToolBar :message="message" />
|
||||
</PredictAnswer>
|
||||
</template>
|
||||
</ChatRow>
|
||||
</template>
|
||||
@@ -226,13 +284,18 @@ import ChartAnswer from './answer/ChartAnswer.vue'
|
||||
import AnalysisAnswer from './answer/AnalysisAnswer.vue'
|
||||
import PredictAnswer from './answer/PredictAnswer.vue'
|
||||
import UserChat from './chat-block/UserChat.vue'
|
||||
import ChartBlock from './chat-block/ChartBlock.vue'
|
||||
import RecommendQuestion from './RecommendQuestion.vue'
|
||||
import ChatListContainer from './ChatListContainer.vue'
|
||||
import ChatCreator from '@/views/chat/ChatCreator.vue'
|
||||
import ChatToolBar from './ChatToolBar.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { endsWith, find, startsWith } from 'lodash-es'
|
||||
import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg'
|
||||
import icon_sidebar_outlined from '@/assets/svg/icon_sidebar_outlined.svg'
|
||||
import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg'
|
||||
import icon_screen_outlined from '@/assets/svg/icon_screen_outlined.svg'
|
||||
import icon_start_outlined from '@/assets/svg/icon_start_outlined.svg'
|
||||
import logo_fold from '@/assets/LOGO-fold.svg'
|
||||
import logo from '@/assets/LOGO.svg'
|
||||
|
||||
@@ -536,6 +599,13 @@ function onAnalysisAnswerError() {
|
||||
isTyping.value = false
|
||||
}
|
||||
|
||||
function askAgain(message: ChatMessage) {
|
||||
inputMessage.value = message.record?.question ?? ''
|
||||
nextTick(() => {
|
||||
sendMessage()
|
||||
})
|
||||
}
|
||||
|
||||
async function clickAnalysis(id?: number) {
|
||||
const baseRecord = find(currentChat.value.records, (value) => id === value.id)
|
||||
if (baseRecord == undefined) {
|
||||
@@ -750,11 +820,52 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-container {
|
||||
color: var(--ed-text-color-primary);
|
||||
font-size: 12px;
|
||||
line-height: 1.7692307692;
|
||||
padding: 16px 22px;
|
||||
.error-container {
|
||||
margin-top: 12px;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(31, 35, 41, 1);
|
||||
}
|
||||
|
||||
.tool-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
column-gap: 16px;
|
||||
row-gap: 8px;
|
||||
|
||||
.tool-btn {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: rgba(100, 106, 115, 1);
|
||||
|
||||
.tool-btn-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
&:active {
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
border-left: 1px solid rgba(31, 35, 41, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-content-block {
|
||||
|
||||
@@ -133,6 +133,7 @@ const closeField = () => {
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
emits('refresh')
|
||||
datasourceApi.tableList(props.info.id).then((res) => {
|
||||
tableList.value = res
|
||||
if (!currentTable.value.table_name) return
|
||||
@@ -173,7 +174,7 @@ const changeStatus = (row: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
const emits = defineEmits(['back'])
|
||||
const emits = defineEmits(['back', 'refresh'])
|
||||
const back = () => {
|
||||
emits('back')
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { datasourceApi } from '@/api/datasource'
|
||||
import { useEmitt } from '@/utils/useEmitt'
|
||||
import Card from './Card.vue'
|
||||
import DelMessageBox from './DelMessageBox.vue'
|
||||
import { dsTypeWithImgSort } from './js/ds-type'
|
||||
import { dsTypeWithImg } from './js/ds-type'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Datasource {
|
||||
@@ -41,7 +41,7 @@ const activeType = ref('')
|
||||
const datasourceFormRef = ref()
|
||||
|
||||
const datasourceList = shallowRef([] as Datasource[])
|
||||
const defaultDatasourceList = shallowRef(dsTypeWithImgSort as (Datasource & { img: string })[])
|
||||
const defaultDatasourceList = shallowRef(dsTypeWithImg as (Datasource & { img: string })[])
|
||||
|
||||
const currentDefaultDatasource = ref('')
|
||||
const datasourceListWithSearch = computed(() => {
|
||||
@@ -115,6 +115,10 @@ const refresh = () => {
|
||||
search()
|
||||
}
|
||||
|
||||
const refreshData = () => {
|
||||
search()
|
||||
}
|
||||
|
||||
const panelClick = () => {
|
||||
console.log('panelClick')
|
||||
}
|
||||
@@ -329,11 +333,17 @@ const back = () => {
|
||||
:active-name="activeName"
|
||||
:active-type="activeType"
|
||||
@refresh="refresh"
|
||||
@close="beforeClose"
|
||||
@change-active-step="(val: number) => (activeStep = val)"
|
||||
></DatasourceForm>
|
||||
</el-drawer>
|
||||
</div>
|
||||
<DataTable v-if="currentDataTable" :info="currentDataTable" @back="back"></DataTable>
|
||||
<DataTable
|
||||
v-if="currentDataTable"
|
||||
:info="currentDataTable"
|
||||
@refresh="refreshData"
|
||||
@back="back"
|
||||
></DataTable>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -11,7 +11,7 @@ import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
import FixedSizeList from 'element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { useCache } from '@/utils/useCache'
|
||||
import { dsType, haveSchema } from '@/views/ds/js/ds-type'
|
||||
import { haveSchema } from '@/views/ds/js/ds-type'
|
||||
import { setSize } from '@/utils/utils'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import icon_fileExcel_colorful from '@/assets/datasource/icon_excel.png'
|
||||
@@ -34,7 +34,7 @@ const props = withDefaults(
|
||||
|
||||
const { wsCache } = useCache()
|
||||
const dsFormRef = ref<FormInstance>()
|
||||
const emit = defineEmits(['refresh', 'changeActiveStep'])
|
||||
const emit = defineEmits(['refresh', 'changeActiveStep', 'close'])
|
||||
const isCreate = ref(true)
|
||||
const isEditTable = ref(false)
|
||||
const checkList = ref<any>([])
|
||||
@@ -125,6 +125,7 @@ const close = () => {
|
||||
dialogVisible.value = false
|
||||
isCreate.value = true
|
||||
emit('changeActiveStep', 0)
|
||||
emit('close')
|
||||
isEditTable.value = false
|
||||
checkList.value = []
|
||||
tableList.value = []
|
||||
@@ -460,7 +461,19 @@ defineExpose({
|
||||
class="model-form"
|
||||
:class="(!isCreate || activeStep === 2) && 'edit-form'"
|
||||
>
|
||||
<div v-if="isCreate && activeStep !== 2" class="model-name">{{ activeName }}</div>
|
||||
<div v-if="isCreate && activeStep !== 2" class="model-name">
|
||||
{{ activeName }}
|
||||
<span
|
||||
v-if="form.type !== 'excel'"
|
||||
style="margin-left: 8px; color: #8f959e; font-size: 12px; margin-top: 7px"
|
||||
>
|
||||
<span>{{ t('ds.form.support_version') }}: </span>
|
||||
<span v-if="form.type === 'sqlServer'">2012+</span>
|
||||
<span v-else-if="form.type === 'oracle'">12+</span>
|
||||
<span v-else-if="form.type === 'mysql'">5.6+</span>
|
||||
<span v-else-if="form.type === 'pg'">9.6+</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-content">
|
||||
<el-form
|
||||
v-show="activeStep === 1"
|
||||
@@ -536,28 +549,6 @@ defineExpose({
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.type')" prop="type">
|
||||
<el-select
|
||||
v-model="form.type"
|
||||
:placeholder="$t('datasource.Please_select') + $t('common.empty') + t('ds.type')"
|
||||
:disabled="!isCreate"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dsType"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<span v-if="form.type !== 'excel'">
|
||||
<span>{{ t('ds.form.support_version') }}: </span>
|
||||
<span v-if="form.type === 'sqlServer'">2012+</span>
|
||||
<span v-else-if="form.type === 'oracle'">12+</span>
|
||||
<span v-else-if="form.type === 'mysql'">5.6+</span>
|
||||
<span v-else-if="form.type === 'pg'">9.6+</span>
|
||||
</span>
|
||||
|
||||
<div v-if="form.type !== 'excel'" style="margin-top: 16px">
|
||||
<el-form-item :label="t('ds.form.host')" prop="host">
|
||||
<el-input
|
||||
|
||||
@@ -3,28 +3,22 @@ import excel from '@/assets/datasource/icon_excel.png'
|
||||
import oracle from '@/assets/datasource/icon_oracle.png'
|
||||
import pg from '@/assets/datasource/icon_PostgreSQL.png'
|
||||
import sqlServer from '@/assets/datasource/icon_SQL_Server.png'
|
||||
import { i18n } from '@/i18n'
|
||||
|
||||
const t = i18n.global.t
|
||||
export const dsType = [
|
||||
{ label: t('ds.local_excelcsv'), value: 'excel' },
|
||||
{ label: 'MySQL', value: 'mysql' },
|
||||
{ label: 'PostgreSQL', value: 'pg' },
|
||||
{ label: 'Microsoft SQL Server', value: 'sqlServer' },
|
||||
{ label: 'Oracle', value: 'oracle' },
|
||||
{ label: 'Excel/CSV', value: 'excel' },
|
||||
{ label: 'PostgreSQL', value: 'pg' },
|
||||
{ label: 'SQL Server', value: 'sqlServer' },
|
||||
]
|
||||
|
||||
export const dsTypeWithImg = [
|
||||
{ name: '本地 Excel/CSV', type: 'excel', img: excel },
|
||||
{ name: t('ds.local_excelcsv'), type: 'excel', img: excel },
|
||||
{ name: 'MySQL', type: 'mysql', img: mysql_ds },
|
||||
{ name: 'SQL Server', type: 'sqlServer', img: sqlServer },
|
||||
{ name: 'Oracle', type: 'oracle', img: oracle },
|
||||
{ name: 'PostgreSQL', type: 'pg', img: pg },
|
||||
]
|
||||
|
||||
export const dsTypeWithImgSort = [
|
||||
{ name: '本地 Excel/CSV', type: 'excel', img: excel },
|
||||
{ name: 'PostgreSQL', type: 'pg', img: pg },
|
||||
{ name: 'Oracle', type: 'oracle', img: oracle },
|
||||
{ name: 'MySQL', type: 'mysql', img: mysql_ds },
|
||||
{ name: 'SQL Server', type: 'sqlServer', img: sqlServer },
|
||||
]
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
|
||||
import ChatComponent from '@/views/chat/index.vue'
|
||||
import AssistantGif from '@/assets/img/assistant.gif'
|
||||
import history from '@/assets/svg/chart/history.svg'
|
||||
@@ -101,6 +101,21 @@ const validator = ref({
|
||||
token: '',
|
||||
})
|
||||
const loading = ref(true)
|
||||
const eventName = 'sqlbot_assistant_event'
|
||||
const communicationCb = async (event: any) => {
|
||||
if (event.data?.eventName === eventName) {
|
||||
if (event.data?.messageId !== route.query.id) {
|
||||
return
|
||||
}
|
||||
if (event.data?.busi == 'certificate') {
|
||||
const certificate = event.data['certificate']
|
||||
console.log(certificate)
|
||||
assistantStore.setType(1)
|
||||
assistantStore.setCertificate(certificate)
|
||||
// store certificate to pinia
|
||||
}
|
||||
}
|
||||
}
|
||||
onBeforeMount(async () => {
|
||||
const assistantId = route.query.id
|
||||
const online = route.query.online
|
||||
@@ -116,6 +131,19 @@ onBeforeMount(async () => {
|
||||
assistantStore.setToken(validator.value.token)
|
||||
assistantStore.setAssistant(true)
|
||||
loading.value = false
|
||||
|
||||
window.addEventListener('message', communicationCb)
|
||||
const readyData = {
|
||||
eventName: 'sqlbot_assistant_event',
|
||||
busi: 'ready',
|
||||
ready: true,
|
||||
messageId: assistantId,
|
||||
}
|
||||
window.parent.postMessage(readyData, '*')
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('message', communicationCb)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,490 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import { uwsOption, workspaceUserList, workspaceUwsDelete, workspaceCreate } from '@/api/workspace'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import assigned from '@/assets/svg/icon_assigned_outlined.svg'
|
||||
import SuccessFilled from '@/assets/svg/gou_icon.svg'
|
||||
import CircleCloseFilled from '@/assets/svg/icon_ban_filled.svg'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const multipleSelectionAll = ref<any[]>([])
|
||||
const keyword = ref('')
|
||||
const keywordsMember = ref('')
|
||||
const userStore = useUserStore()
|
||||
|
||||
const workspaceForm = reactive({
|
||||
name: '',
|
||||
id: '',
|
||||
})
|
||||
const selectable = (row: any) => ![userStore.getUid].includes(row.id)
|
||||
const fieldListWithSearch = computed(() => {
|
||||
if (!keyword.value) return fieldList.value
|
||||
return fieldList.value.filter((ele: any) =>
|
||||
ele.name.toLowerCase().includes(keywordsMember.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
const fieldDialog = ref<boolean>(false)
|
||||
const multipleTableRef = ref()
|
||||
const isIndeterminate = ref(true)
|
||||
const checkAll = ref(false)
|
||||
const fieldList = ref<any>([])
|
||||
const pageInfo = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const cancelDelete = () => {
|
||||
handleToggleRowSelection(false)
|
||||
multipleSelectionAll.value = []
|
||||
checkAll.value = false
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
const deleteBatchUser = () => {
|
||||
ElMessageBox.confirm(
|
||||
t('workspace.selected_2_members', { msg: multipleSelectionAll.value.length }),
|
||||
{
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
}
|
||||
).then(() => {
|
||||
workspaceUwsDelete({
|
||||
uid_list: multipleSelectionAll.value.map((ele) => ele.id),
|
||||
}).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
})
|
||||
multipleSelectionAll.value = []
|
||||
search()
|
||||
})
|
||||
})
|
||||
}
|
||||
const deleteHandler = (row: any) => {
|
||||
ElMessageBox.confirm(t('workspace.member_feng_yibudao', { msg: row.name }), {
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
}).then(() => {
|
||||
workspaceUwsDelete({
|
||||
uid_list: [row.id],
|
||||
}).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
})
|
||||
search()
|
||||
})
|
||||
})
|
||||
}
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
const ids = fieldList.value.map((ele: any) => ele.id)
|
||||
multipleSelectionAll.value = [
|
||||
...multipleSelectionAll.value.filter((ele) => !ids.includes(ele.id)),
|
||||
...val,
|
||||
]
|
||||
isIndeterminate.value = !(val.length === 0 || val.length === fieldList.value.length)
|
||||
checkAll.value = val.length === fieldList.value.length
|
||||
}
|
||||
const handleCheckAllChange = (val: any) => {
|
||||
isIndeterminate.value = false
|
||||
handleSelectionChange(val ? fieldList.value : [])
|
||||
if (val) {
|
||||
handleToggleRowSelection()
|
||||
} else {
|
||||
multipleTableRef.value.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleRowSelection = (check: boolean = true) => {
|
||||
let i = 0
|
||||
const ids = multipleSelectionAll.value.map((ele: any) => ele.id)
|
||||
for (const key in fieldList.value) {
|
||||
if (ids.includes((fieldList.value[key] as any).id)) {
|
||||
i += 1
|
||||
multipleTableRef.value.toggleRowSelection(fieldList.value[key], check)
|
||||
}
|
||||
}
|
||||
checkAll.value = i === fieldList.value.length
|
||||
isIndeterminate.value = !(i === 0 || i === fieldList.value.length)
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
workspaceUserList(
|
||||
{ keyword: keywordsMember.value },
|
||||
pageInfo.currentPage,
|
||||
pageInfo.pageSize
|
||||
).then((res) => {
|
||||
fieldList.value = res.items
|
||||
pageInfo.total = res.total
|
||||
})
|
||||
}
|
||||
|
||||
const closeField = () => {
|
||||
fieldDialog.value = false
|
||||
afterFind.value = false
|
||||
userInfo.value.id = ''
|
||||
userInfo.value.name = ''
|
||||
userInfo.value.account = ''
|
||||
}
|
||||
|
||||
const saveField = () => {
|
||||
workspaceCreate({
|
||||
uid_list: [userInfo.value.id],
|
||||
weight: 0,
|
||||
}).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
search()
|
||||
fieldDialog.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const userInfo = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
account: '',
|
||||
})
|
||||
const afterFind = ref(false)
|
||||
const findUser = () => {
|
||||
uwsOption({ keyword: workspaceForm.name })
|
||||
.then((res: any) => {
|
||||
userInfo.value = res || {}
|
||||
})
|
||||
.finally(() => {
|
||||
afterFind.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const addWorkspace = () => {
|
||||
afterFind.value = false
|
||||
workspaceForm.name = ''
|
||||
fieldDialog.value = true
|
||||
}
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
pageInfo.pageSize = val
|
||||
search()
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
pageInfo.currentPage = val
|
||||
search()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="member">
|
||||
<div class="tool-left">
|
||||
<span class="page-title">{{ $t('workspace.member_management') }}</span>
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
style="width: 240px; margin-right: 12px"
|
||||
:placeholder="$t('user.name_account_email')"
|
||||
@keyup.enter="search"
|
||||
@blur="search"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button type="primary" @click="addWorkspace()">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ $t('workspace.add_member') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<div class="preview-or-schema">
|
||||
<el-table
|
||||
ref="multipleTableRef"
|
||||
:data="fieldListWithSearch"
|
||||
:selectable="selectable"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="name" :label="$t('user.name')" width="160" />
|
||||
<el-table-column prop="account" :label="$t('user.account')" width="160" />
|
||||
<el-table-column prop="status" :label="$t('user.user_status')">
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="user-status-container"
|
||||
:class="[scope.row.status ? 'active' : 'disabled']"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<SuccessFilled v-if="scope.row.status" />
|
||||
<CircleCloseFilled v-else />
|
||||
</el-icon>
|
||||
<span>{{ $t(`user.${scope.row.status ? 'enabled' : 'disabled'}`) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" :label="$t('user.email')" width="204" />
|
||||
<el-table-column prop="weight" :label="$t('workspace.member_type')" width="120">
|
||||
<template #default="scope">
|
||||
<span>{{
|
||||
scope.row.weight === 1
|
||||
? t('workspace.administrator')
|
||||
: t('workspace.ordinary_member')
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" width="80" :label="t('ds.actions')">
|
||||
<template #default="scope">
|
||||
<div class="field-comment">
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('workspace.remove')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="deleteHandler(scope.row)">
|
||||
<assigned></assigned>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<EmptyBackground
|
||||
v-if="!keywordsMember && !fieldListWithSearch.length"
|
||||
:description="$t('workspace.no_user')"
|
||||
img-type="noneWhite"
|
||||
/>
|
||||
|
||||
<EmptyBackground
|
||||
v-if="!!keywordsMember && !fieldListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
/>
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pageInfo.currentPage"
|
||||
v-model:page-size="pageInfo.pageSize"
|
||||
:page-sizes="[10, 20, 30]"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pageInfo.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="multipleSelectionAll.length" class="bottom-select">
|
||||
<el-checkbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAllChange"
|
||||
>
|
||||
{{ $t('datasource.select_all') }}
|
||||
</el-checkbox>
|
||||
|
||||
<button class="danger-button" @click="deleteBatchUser">{{ $t('workspace.remove') }}</button>
|
||||
|
||||
<span class="selected">{{
|
||||
$t('user.selected_2_items', { msg: multipleSelectionAll.length })
|
||||
}}</span>
|
||||
|
||||
<el-button text @click="cancelDelete">
|
||||
{{ $t('common.cancel') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="fieldDialog"
|
||||
:title="$t('workspace.add_member')"
|
||||
width="600"
|
||||
modal-class="add-user_dialog"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@before-closed="closeField"
|
||||
>
|
||||
<el-input
|
||||
v-model="workspaceForm.name"
|
||||
clearable
|
||||
:placeholder="$t('workspace.id_account_to_add')"
|
||||
>
|
||||
<template #append>
|
||||
<span style="cursor: pointer" @click="findUser">{{ t('workspace.find_user') }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="user">
|
||||
<template v-if="userInfo.id">
|
||||
<div class="name">{{ userInfo.name }}</div>
|
||||
<div class="account">{{ userInfo.account }}</div>
|
||||
</template>
|
||||
<div v-else-if="afterFind" class="empty">{{ $t('common.may_not_exist') }}</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button secondary @click="closeField">{{ t('common.cancel') }}</el-button>
|
||||
<el-button v-if="!userInfo.id" disabled type="info">{{ t('model.add') }}</el-button>
|
||||
<el-button v-else type="primary" @click="saveField">{{ t('model.add') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.member {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.tool-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.page-title {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
.table-content {
|
||||
height: calc(100% - 104px);
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.preview-or-schema {
|
||||
.user-status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
.ed-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.field-comment {
|
||||
height: 24px;
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-num {
|
||||
margin: 12px 0;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-select {
|
||||
position: absolute;
|
||||
height: 64px;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
border-top: 1px solid #1f232926;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 24px;
|
||||
|
||||
.danger-button {
|
||||
border: 1px solid var(--ed-color-danger);
|
||||
color: var(--ed-color-danger);
|
||||
border-radius: var(--ed-border-radius-base);
|
||||
min-width: 80px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 0 16px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.add-user_dialog {
|
||||
.ed-input-group__append {
|
||||
background-color: #fff;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.user {
|
||||
height: 126px;
|
||||
font-weight: 400;
|
||||
padding-top: 16px;
|
||||
|
||||
.name {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
.account {
|
||||
color: #8f959e;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #8f959e;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -46,7 +46,7 @@ const handleModelClick = (item: any) => {
|
||||
:class="activeName === ele.name && 'isActive'"
|
||||
@click="handleModelClick(ele)"
|
||||
>
|
||||
<img width="32px" height="32px" :src="ele.icon" />
|
||||
<img width="18px" height="18px" :src="ele.icon" />
|
||||
<span class="name">{{ ele.name }}</span>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" setup>
|
||||
import delIcon from '@/assets/svg/icon_delete.svg'
|
||||
import icon_key_outlined from '@/assets/svg/icon-key_outlined.svg'
|
||||
import icon_member_outlined from '@/assets/svg/icon_member_outlined.svg'
|
||||
import Lock from '@/assets/permission/icon_custom-tools_colorful.png'
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
name: string
|
||||
type: string
|
||||
num: string
|
||||
id?: string
|
||||
}>(),
|
||||
{
|
||||
name: '-',
|
||||
type: '-',
|
||||
id: '-',
|
||||
num: '-',
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits(['edit', 'del', 'setUser', 'setRule'])
|
||||
|
||||
const handleEdit = () => {
|
||||
emits('edit')
|
||||
}
|
||||
|
||||
const handleDel = () => {
|
||||
emits('del')
|
||||
}
|
||||
|
||||
const setUser = () => {
|
||||
emits('setUser')
|
||||
}
|
||||
|
||||
// const setRule = () => {
|
||||
// emits('setRule')
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="name-icon">
|
||||
<img :src="Lock" width="32px" height="32px" />
|
||||
<span class="name">{{ name }}</span>
|
||||
</div>
|
||||
<div class="type-value">
|
||||
<span class="type">{{ $t('permission.permission_rule') }}</span>
|
||||
<span class="value"> {{ $t('permission.2', { msg: type }) }}</span>
|
||||
</div>
|
||||
<div class="type-value">
|
||||
<span class="type">{{ $t('permission.restricted_user') }}</span>
|
||||
<span class="value"> {{ $t('permission.238_people', { msg: type }) }}</span>
|
||||
</div>
|
||||
<div class="methods">
|
||||
<el-button secondary @click="handleEdit">
|
||||
<template #icon>
|
||||
<icon_key_outlined></icon_key_outlined>
|
||||
</template>
|
||||
{{ $t('permission.set_rule') }}
|
||||
</el-button>
|
||||
<el-button secondary @click="setUser">
|
||||
<template #icon>
|
||||
<icon_member_outlined></icon_member_outlined>
|
||||
</template>
|
||||
{{ $t('permission.set_user') }}
|
||||
</el-button>
|
||||
<el-button secondary @click="handleDel">
|
||||
<template #icon>
|
||||
<delIcon></delIcon>
|
||||
</template>
|
||||
{{ $t('dashboard.delete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card {
|
||||
width: 371px;
|
||||
height: 176px;
|
||||
border: 1px solid #dee0e3;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin: 0 16px 16px 0;
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 24px 0px #1f232914;
|
||||
}
|
||||
|
||||
.name-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.type-value {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
.type {
|
||||
color: #646a73;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.methods {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,579 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
|
||||
import icon_down_outlined from '@/assets/svg/icon_down_outlined.svg'
|
||||
import Card from './Card.vue'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const keywords = ref('')
|
||||
const activeStep = ref(0)
|
||||
const dialogFormVisible = ref(false)
|
||||
const ruleConfigvVisible = ref(false)
|
||||
const editRule = ref(0)
|
||||
const termFormRef = ref()
|
||||
const columnFormRef = ref()
|
||||
const drawerTitle = ref('')
|
||||
const dialogTitle = ref('')
|
||||
const tableData = ref<any[]>([])
|
||||
const ruleList = ref<any[]>([
|
||||
{
|
||||
id: 1,
|
||||
num: '10',
|
||||
type: '90',
|
||||
name: 'jkjkj',
|
||||
},
|
||||
])
|
||||
const form = reactive({
|
||||
name: '',
|
||||
id: '',
|
||||
})
|
||||
const tableColumnData = ref<any[]>([])
|
||||
const searchColumn = ref('')
|
||||
|
||||
const columnForm = reactive({
|
||||
name: '',
|
||||
id: '',
|
||||
dataTable: '',
|
||||
datasource: '',
|
||||
})
|
||||
|
||||
const options = ref<any[]>([])
|
||||
const ruleListWithSearch = computed(() => {
|
||||
if (!keywords.value) return ruleList.value
|
||||
return ruleList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const setDrawerTitle = () => {
|
||||
if (activeStep.value === 0) {
|
||||
drawerTitle.value = t('permission.add_rule_group')
|
||||
} else {
|
||||
if (editRule.value === 1) {
|
||||
drawerTitle.value = t('permission.set_permission_rule')
|
||||
}
|
||||
|
||||
if (editRule.value === 2) {
|
||||
drawerTitle.value = t('permission.select_restricted_user')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const userTypeList = [
|
||||
{
|
||||
name: t('permission.row_permission'),
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: t('permission.column_permission'),
|
||||
value: 0,
|
||||
},
|
||||
]
|
||||
|
||||
const handleAddPermission = (val: any) => {
|
||||
if (val === 1) {
|
||||
handleRowPermission()
|
||||
} else {
|
||||
handleColumnPermission(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRowPermission = () => {
|
||||
console.log('handleRowPermission')
|
||||
}
|
||||
const handleColumnPermission = (row: any) => {
|
||||
dialogFormVisible.value = true
|
||||
dialogTitle.value = row?.id
|
||||
? t('permission.edit_column_permission')
|
||||
: t('permission.add_column_permission')
|
||||
}
|
||||
const beforeClose = () => {
|
||||
ruleConfigvVisible.value = false
|
||||
activeStep.value = 0
|
||||
}
|
||||
const handleSearch = () => {}
|
||||
const editHandler = (row: any) => {
|
||||
editRule.value = 0
|
||||
setDrawerTitle()
|
||||
ruleConfigvVisible.value = true
|
||||
console.log('row', row)
|
||||
}
|
||||
const handleEditRule = (row: any) => {
|
||||
editRule.value = 1
|
||||
setDrawerTitle()
|
||||
console.log(row)
|
||||
}
|
||||
const deleteHandler = (row: any) => {
|
||||
console.log(row)
|
||||
}
|
||||
const setUser = (row: any) => {
|
||||
editRule.value = 2
|
||||
setDrawerTitle()
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('permission.rule_group_name'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const closeForm = () => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const saveHandler = () => {}
|
||||
|
||||
const columnRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('permission.rule_name'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
|
||||
dataTable: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('permission.rule_group_name'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="permission">
|
||||
<div class="tool-left">
|
||||
<span class="page-title">{{ $t('workspace.permission_configuration') }}</span>
|
||||
<div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
style="width: 240px; margin-right: 12px"
|
||||
:placeholder="$t('permission.search_rule_group')"
|
||||
@keyup.enter="handleSearch"
|
||||
@blur="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button type="primary" @click="editHandler(null)">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ $t('permission.add_rule_group') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !ruleListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
/>
|
||||
|
||||
<div v-else class="card-content">
|
||||
<Card
|
||||
v-for="ele in ruleListWithSearch"
|
||||
:id="ele.id"
|
||||
:key="ele.id"
|
||||
:name="ele.name"
|
||||
:type="ele.type"
|
||||
:num="ele.num"
|
||||
@edit="handleEditRule(ele)"
|
||||
@del="deleteHandler(ele)"
|
||||
@set-user="setUser(ele)"
|
||||
></Card>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="ruleConfigvVisible"
|
||||
:close-on-click-modal="false"
|
||||
size="calc(100% - 100px)"
|
||||
modal-class="permission-drawer-fullscreen"
|
||||
direction="btt"
|
||||
:before-close="beforeClose"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<span style="white-space: nowrap">{{ drawerTitle }}</span>
|
||||
<div v-if="!editRule" class="flex-center" style="width: 100%">
|
||||
<el-steps custom style="max-width: 500px; flex: 1" :active="activeStep" align-center>
|
||||
<el-step>
|
||||
<template #title> {{ $t('permission.set_permission_rule') }} </template>
|
||||
</el-step>
|
||||
<el-step>
|
||||
<template #title> {{ $t('permission.select_restricted_user') }} </template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</div>
|
||||
<el-icon style="cursor: pointer" @click="close">
|
||||
<icon_close_outlined></icon_close_outlined>
|
||||
</el-icon>
|
||||
</template>
|
||||
|
||||
<div class="drawer-content">
|
||||
<div class="title">
|
||||
{{ $t('ds.form.base_info') }}
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="termFormRef"
|
||||
:model="form"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
class="form-content_error"
|
||||
>
|
||||
<el-form-item prop="name" :label="t('permission.rule_group_name')">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') +
|
||||
$t('common.empty') +
|
||||
$t('permission.rule_group_name')
|
||||
"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item class="add-permission_form">
|
||||
<template #label>
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
{{ t('permission.permission_rule') }}
|
||||
|
||||
<el-popover popper-class="system-permission_user" placement="bottom">
|
||||
<template #reference>
|
||||
<el-button class="add-btn" type="primary">
|
||||
{{ $t('model.add') }}
|
||||
<el-icon style="margin-left: 4px" size="16">
|
||||
<icon_down_outlined></icon_down_outlined>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="popover">
|
||||
<div class="popover-content">
|
||||
<div
|
||||
v-for="ele in userTypeList"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
@click="handleAddPermission(ele.value)"
|
||||
>
|
||||
<div class="model-name">{{ ele.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
<div class="table-content">
|
||||
<el-table
|
||||
:empty-text="$t('permission.no_rule')"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="name" :label="$t('permission.rule_name')" />
|
||||
<el-table-column prop="type" :label="$t('permission.type')" />
|
||||
<el-table-column prop="datasource" :label="$t('permission.data_source')" />
|
||||
<el-table-column prop="data_table" :label="$t('permission.data_table')" />
|
||||
<el-table-column fixed="right" width="150" :label="$t('ds.actions')">
|
||||
<template #default="scope">
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('datasource.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="editHandler(scope.row)">
|
||||
<IconOpeEdit></IconOpeEdit>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('dashboard.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="deleteHandler(scope.row)">
|
||||
<IconOpeDelete></IconOpeDelete>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer
|
||||
v-model="dialogFormVisible"
|
||||
:title="dialogTitle"
|
||||
destroy-on-close
|
||||
size="896px"
|
||||
modal-class="column-form_drawer"
|
||||
:before-close="closeForm"
|
||||
>
|
||||
<el-form
|
||||
ref="columnFormRef"
|
||||
:model="columnForm"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
:rules="columnRules"
|
||||
class="form-content_error"
|
||||
>
|
||||
<el-form-item prop="name" :label="t('permission.rule_name')">
|
||||
<el-input
|
||||
v-model="columnForm.name"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('permission.rule_name')
|
||||
"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="dataTable" :label="t('permission.data_table')">
|
||||
<el-select
|
||||
v-model="columnForm.datasource"
|
||||
style="width: 416px"
|
||||
:placeholder="
|
||||
$t('datasource.Please_select') + $t('common.empty') + $t('permission.data_source')
|
||||
"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="columnForm.dataTable"
|
||||
style="width: 416px; margin-left: auto"
|
||||
:disabled="!columnForm.datasource"
|
||||
:placeholder="
|
||||
$t('datasource.Please_select') + $t('common.empty') + $t('permission.data_table')
|
||||
"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('permission.set_rule')">
|
||||
<el-input
|
||||
v-model="searchColumn"
|
||||
:placeholder="$t('permission.search_rule_group')"
|
||||
autocomplete="off"
|
||||
><template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined />
|
||||
</el-icon> </template
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="table-content">
|
||||
<el-table
|
||||
:empty-text="$t('permission.no_fields_yet')"
|
||||
:data="tableColumnData"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="name" :label="$t('datasource.field_name')" />
|
||||
<el-table-column prop="type" :label="$t('datasource.field_notes')" />
|
||||
<el-table-column fixed="right" width="150" :label="$t('ds.actions')">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.checked" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeForm">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveHandler">
|
||||
{{ $t('common.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.permission {
|
||||
.tool-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.page-title {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.permission-drawer-fullscreen {
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
height: calc(100% - 20px);
|
||||
|
||||
.add-btn:hover {
|
||||
.ed-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
.add-permission_form {
|
||||
.ed-form-item__label {
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
border: 1px solid #1f232926;
|
||||
border-top: none;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
max-height: calc(100vh - 400px);
|
||||
.ed-table__empty-text {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: #646a73;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-form_drawer {
|
||||
.table-content {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
border: 1px solid #1f232926;
|
||||
border-top: none;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
max-height: calc(100vh - 400px);
|
||||
.ed-table__empty-text {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-permission_user.system-permission_user {
|
||||
padding: 0;
|
||||
width: 120px !important;
|
||||
min-width: 120px !important;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border: 1px solid #dee0e3;
|
||||
|
||||
.popover {
|
||||
.popover-content {
|
||||
padding: 4px;
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
left: 0;
|
||||
top: 40px;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #1f232926;
|
||||
}
|
||||
}
|
||||
.popover-item {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin: 9px 0 0 0;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.done {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
color: var(--ed-color-primary);
|
||||
|
||||
.done {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
<el-table-column prop="create_time" width="180" sortable :label="$t('user.creation_time')">
|
||||
<template #default="scope">
|
||||
<span>{{ formatTimestamp(scope.row.create_time) }}</span>
|
||||
<span>{{ formatTimestamp(scope.row.create_time, 'YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" width="150" :label="$t('ds.actions')">
|
||||
@@ -165,8 +165,7 @@
|
||||
:title="dialogTitle"
|
||||
destroy-on-close
|
||||
size="600px"
|
||||
modle-class="sqlbot-drawer"
|
||||
@close="onFormClose"
|
||||
:before-close="onFormClose"
|
||||
>
|
||||
<el-form
|
||||
ref="termFormRef"
|
||||
@@ -343,21 +342,23 @@ const filterOption = ref<any[]>([
|
||||
property: { placeholder: t('common.empty') + t('user.workspace') },
|
||||
},
|
||||
])
|
||||
|
||||
const defaultForm = {
|
||||
id: '',
|
||||
name: '',
|
||||
account: '',
|
||||
oid: 0,
|
||||
email: '',
|
||||
status: '',
|
||||
phoneNumber: '',
|
||||
oid_list: [],
|
||||
}
|
||||
const options = ref<any[]>([])
|
||||
const state = reactive<any>({
|
||||
tableData: [],
|
||||
filterTexts: [],
|
||||
conditions: [],
|
||||
form: {
|
||||
id: '',
|
||||
name: '',
|
||||
account: '',
|
||||
oid: 0,
|
||||
email: '',
|
||||
status: '',
|
||||
phoneNumber: '',
|
||||
oid_list: [],
|
||||
},
|
||||
form: { ...defaultForm },
|
||||
pageInfo: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
@@ -616,14 +617,19 @@ const search = () => {
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
userApi.add(state.form).then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
userApi
|
||||
.add(state.form)
|
||||
.then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('common.save_success'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
state.form = { ...defaultForm }
|
||||
})
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
userApi.edit(state.form).then(() => {
|
||||
@@ -721,7 +727,8 @@ onMounted(() => {
|
||||
.sqlbot-table {
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 200px);
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow-y: auto;
|
||||
|
||||
&.is-filter {
|
||||
max-height: calc(100vh - 256px);
|
||||
|
||||
@@ -87,6 +87,13 @@ const currentTable = ref<any>({})
|
||||
const init = () => {
|
||||
workspaceList().then((res) => {
|
||||
tableList.value = res
|
||||
if (currentTable.value.id) {
|
||||
tableList.value.forEach((ele: any) => {
|
||||
if (ele.id === currentTable.value.id) {
|
||||
currentTable.value.name = ele.name
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
@@ -119,7 +126,7 @@ const deleteBatchUser = () => {
|
||||
t('workspace.selected_2_members', { msg: multipleSelectionAll.value.length }),
|
||||
{
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
confirmButtonText: t('workspace.remove'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
@@ -141,7 +148,7 @@ const deleteBatchUser = () => {
|
||||
const deleteHandler = (row: any) => {
|
||||
ElMessageBox.confirm(t('workspace.member_feng_yibudao', { msg: row.name }), {
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
confirmButtonText: t('workspace.remove'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
@@ -231,6 +238,7 @@ const saveField = () => {
|
||||
const delWorkspace = (row: any) => {
|
||||
ElMessageBox.confirm(t('workspace.workspace_de_workspace', { msg: row.name }), {
|
||||
confirmButtonType: 'danger',
|
||||
tip: t('workspace.operate_with_caution'),
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
@@ -413,7 +421,7 @@ const handleCurrentChange = (val: number) => {
|
||||
? t('workspace.administrator')
|
||||
: t('workspace.ordinary_member')
|
||||
}}</span>
|
||||
<el-icon size="16">
|
||||
<el-icon style="display: none" size="16">
|
||||
<arrow_down></arrow_down>
|
||||
</el-icon>
|
||||
</div>
|
||||
@@ -533,7 +541,7 @@ const handleCurrentChange = (val: number) => {
|
||||
</el-form>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeField">{{ t('common.cancel') }}</el-button>
|
||||
<el-button secondary @click="closeField">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveField">{{
|
||||
t(workspaceForm.id ? 'common.save' : 'model.add')
|
||||
}}</el-button>
|
||||
@@ -707,6 +715,7 @@ const handleCurrentChange = (val: number) => {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
.ed-icon {
|
||||
display: block !important;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||