mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
Merge branch 'main' of https://github.com/dataease/SQLBot
This commit is contained in:
@@ -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 ###
|
||||
@@ -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
|
||||
|
||||
@@ -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] = []
|
||||
|
||||
|
||||
|
||||
+46
-17
@@ -3,20 +3,29 @@
|
||||
|
||||
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 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 authenticate, get_user_info
|
||||
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 +44,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 +73,26 @@ 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)
|
||||
session_user = UserInfoDTO.model_validate(session_user)
|
||||
if not session_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
llm_service = LLMService(session, user, chat)
|
||||
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,6 +1,8 @@
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from sqlmodel import exists, or_, select
|
||||
from sqlmodel import exists, or_, select, delete as sqlmodel_delete
|
||||
from apps.system.crud.user import clean_user_cache
|
||||
from apps.system.crud.workspace import reset_single_user_oid, reset_user_oid
|
||||
from apps.system.models.system_model import UserWsModel, WorkspaceBase, WorkspaceEditor, WorkspaceModel
|
||||
from apps.system.models.user import UserModel
|
||||
from apps.system.schemas.system_schema import UserWsBase, UserWsDTO, UserWsEditor, UserWsOption, WorkspaceUser
|
||||
@@ -43,7 +45,7 @@ async def option_pager(
|
||||
pagination=pagination,
|
||||
)
|
||||
|
||||
@router.get("/uws/option", response_model=UserWsOption)
|
||||
@router.get("/uws/option", response_model=UserWsOption | None)
|
||||
async def option_user(
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
@@ -60,7 +62,7 @@ async def option_user(
|
||||
if keyword:
|
||||
stmt = stmt.where(
|
||||
or_(
|
||||
UserModel.account == int(keyword),
|
||||
UserModel.account == keyword,
|
||||
UserModel.name == keyword,
|
||||
)
|
||||
)
|
||||
@@ -76,8 +78,6 @@ async def pager(
|
||||
oid: Optional[int] = Query(None, description="空间ID(仅admin用户生效)"),
|
||||
):
|
||||
if current_user.isAdmin:
|
||||
if not oid:
|
||||
raise RuntimeError('oid miss error')
|
||||
workspace_id = oid
|
||||
else:
|
||||
workspace_id = current_user.oid
|
||||
@@ -108,43 +108,55 @@ async def pager(
|
||||
async def create(session: SessionDep, current_user: CurrentUser, creator: UserWsDTO):
|
||||
if not current_user.isAdmin and current_user.weight == 0:
|
||||
raise HTTPException("no permission to execute")
|
||||
oid: int = creator.oid if current_user.isAdmin else current_user.oid
|
||||
oid: int = creator.oid if (current_user.isAdmin and creator.oid) else current_user.oid
|
||||
weight = creator.weight if (current_user.isAdmin and creator.weight) else 0
|
||||
# 判断uid_list以及oid合法性
|
||||
db_model_list = [
|
||||
UserWsModel.model_validate({
|
||||
"oid": oid,
|
||||
"uid": uid,
|
||||
"weight": creator.weight
|
||||
"weight": weight
|
||||
})
|
||||
for uid in creator.uid_list
|
||||
]
|
||||
for uid in creator.uid_list:
|
||||
await reset_single_user_oid(session, uid, oid)
|
||||
await clean_user_cache(uid)
|
||||
|
||||
session.add_all(db_model_list)
|
||||
session.commit()
|
||||
|
||||
@router.put("/uws")
|
||||
async def edit(session: SessionDep, editor: UserWsEditor):
|
||||
if not editor.oid or not editor.uid:
|
||||
raise RuntimeError("param [oid, uid] miss")
|
||||
raise HTTPException("param [oid, uid] miss")
|
||||
db_model = session.exec(select(UserWsModel).where(UserWsModel.uid == editor.uid, UserWsModel.oid == editor.oid)).first()
|
||||
if not db_model:
|
||||
raise RuntimeError("uws not exist")
|
||||
raise HTTPException("uws not exist")
|
||||
if editor.weight == db_model.weight:
|
||||
return
|
||||
|
||||
db_model.weight = editor.weight
|
||||
session.add(db_model)
|
||||
|
||||
await clean_user_cache(editor.uid)
|
||||
session.commit()
|
||||
|
||||
@router.delete("/uws")
|
||||
async def delete(session: SessionDep, current_user: CurrentUser, dto: UserWsBase):
|
||||
if not current_user.isAdmin and current_user.weight == 0:
|
||||
raise HTTPException("no permission to execute")
|
||||
oid: int = dto.oid if current_user.isAdmin else current_user.oid
|
||||
oid: int = dto.oid if (current_user.isAdmin and dto.oid) else current_user.oid
|
||||
db_model_list: list[UserWsModel] = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(dto.uid_list), UserWsModel.oid == oid)).all()
|
||||
if not db_model_list:
|
||||
raise HTTPException(f"UserWsModel not found")
|
||||
for db_model in db_model_list:
|
||||
session.delete(db_model)
|
||||
|
||||
for uid in dto.uid_list:
|
||||
await reset_single_user_oid(session, uid, oid, False)
|
||||
await clean_user_cache(uid)
|
||||
|
||||
session.commit()
|
||||
|
||||
@router.get("", response_model=list[WorkspaceModel])
|
||||
@@ -167,7 +179,7 @@ async def update(session: SessionDep, editor: WorkspaceEditor):
|
||||
id = editor.id
|
||||
db_model = session.get(WorkspaceModel, id)
|
||||
if not db_model:
|
||||
raise ValueError(f"WorkspaceModel with id {id} not found")
|
||||
raise HTTPException(f"WorkspaceModel with id {id} not found")
|
||||
update_data = WorkspaceModel.model_validate(editor)
|
||||
db_model.sqlmodel_update(update_data)
|
||||
session.add(db_model)
|
||||
@@ -177,16 +189,27 @@ async def update(session: SessionDep, editor: WorkspaceEditor):
|
||||
async def get_one(session: SessionDep, trans: Trans, id: int):
|
||||
db_model = session.get(WorkspaceModel, id)
|
||||
if not db_model:
|
||||
raise ValueError(f"WorkspaceModel with id {id} not found")
|
||||
raise HTTPException(f"WorkspaceModel with id {id} not found")
|
||||
if db_model.name.startswith('i18n'):
|
||||
db_model.name = trans(db_model.name)
|
||||
return db_model
|
||||
|
||||
@router.delete("/{id}")
|
||||
async def delete(session: SessionDep, id: int):
|
||||
async def single_delete(session: SessionDep, id: int):
|
||||
db_model = session.get(WorkspaceModel, id)
|
||||
if not db_model:
|
||||
raise ValueError(f"WorkspaceModel with id {id} not found")
|
||||
raise HTTPException(f"WorkspaceModel with id {id} not found")
|
||||
|
||||
user_ws_list = session.exec(select(UserWsModel).where(UserWsModel.oid == id)).all()
|
||||
if user_ws_list:
|
||||
# clean user cache
|
||||
for user_ws in user_ws_list:
|
||||
await clean_user_cache(user_ws.uid)
|
||||
# reset user default oid
|
||||
await reset_user_oid(session, id)
|
||||
# delete user_ws
|
||||
session.exec(sqlmodel_delete(UserWsModel).where(UserWsModel.oid == id))
|
||||
|
||||
session.delete(db_model)
|
||||
session.commit()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
from typing import Optional
|
||||
from sqlmodel import Session, select, delete as sqlmodel_delete
|
||||
|
||||
import logging
|
||||
from apps.system.models.system_model import UserWsModel, WorkspaceModel
|
||||
from apps.system.schemas.auth import CacheName, CacheNamespace
|
||||
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO, UserWs
|
||||
@@ -64,4 +64,8 @@ async def single_delete(session: SessionDep, id: int):
|
||||
del_stmt = sqlmodel_delete(UserWsModel).where(UserWsModel.uid == id)
|
||||
session.exec(del_stmt)
|
||||
session.delete(user_model)
|
||||
session.commit()
|
||||
session.commit()
|
||||
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
async def clean_user_cache(id: int):
|
||||
logging.info(f"User cache for [{id}] has been cleaned")
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
from typing import Optional
|
||||
from sqlmodel import Session, func, select, update
|
||||
|
||||
from apps.system.models.system_model import UserWsModel
|
||||
from apps.system.models.user import UserModel
|
||||
|
||||
async def reset_single_user_oid(session: Session, uid: int, oid: int, add: Optional[bool] = True):
|
||||
user_model = session.get(UserModel, uid)
|
||||
if not user_model:
|
||||
return
|
||||
origin_oid = user_model.oid
|
||||
if add and (not origin_oid or origin_oid == 0):
|
||||
user_model.oid = oid
|
||||
session.add(user_model)
|
||||
if not add and origin_oid and origin_oid == oid:
|
||||
user_model.oid = 0
|
||||
user_ws = session.exec(select(UserWsModel).where(UserWsModel.uid == uid, UserWsModel.oid != oid)).first()
|
||||
if user_ws:
|
||||
user_model.oid = user_ws.oid
|
||||
session.add(user_model)
|
||||
|
||||
async def reset_user_oid(session: Session, oid: int):
|
||||
stmt = (
|
||||
select(
|
||||
UserModel.id,
|
||||
UserModel.oid,
|
||||
func.coalesce(
|
||||
func.array_remove(
|
||||
func.array_agg(UserWsModel.oid),
|
||||
None
|
||||
),
|
||||
[]
|
||||
).label("oid_list")
|
||||
)
|
||||
.join(UserWsModel, UserModel.id == UserWsModel.uid, isouter=True)
|
||||
.where(UserModel.id != 1)
|
||||
.group_by(UserModel.id)
|
||||
)
|
||||
|
||||
user_filter = (
|
||||
select(UserModel.id)
|
||||
.join(UserWsModel, UserModel.id == UserWsModel.uid)
|
||||
.where(UserWsModel.oid == oid)
|
||||
.distinct()
|
||||
)
|
||||
stmt = stmt.where(UserModel.id.in_(user_filter))
|
||||
|
||||
result_user_list = session.exec(stmt)
|
||||
for row in result_user_list:
|
||||
result_dict = {}
|
||||
for item, key in zip(row, row._fields):
|
||||
result_dict[key] = item
|
||||
|
||||
origin_oid = result_dict['oid']
|
||||
oid_list: list = list(filter(lambda x: x != oid, result_dict['oid_list']))
|
||||
if origin_oid not in oid_list:
|
||||
result_dict['oid'] = oid_list[0] if oid_list else 0
|
||||
if result_dict['oid'] != origin_oid:
|
||||
result_dict.pop("oid_list", None)
|
||||
update_stmt = update(UserModel).where(UserModel.id == result_dict['id']).values(oid=result_dict['oid'])
|
||||
session.exec(update_stmt)
|
||||
@@ -50,7 +50,7 @@ class UserWsBase(BaseModel):
|
||||
uid_list: list[int]
|
||||
oid: Optional[int] = None
|
||||
class UserWsDTO(UserWsBase):
|
||||
weight: int = 0
|
||||
weight: Optional[int] = 0
|
||||
|
||||
class UserWsEditor(BaseModel):
|
||||
uid: int
|
||||
|
||||
@@ -14,7 +14,7 @@ class Paginator:
|
||||
result_dict = {}
|
||||
for item, key in zip(row, row._fields):
|
||||
if isinstance(item, SQLModel):
|
||||
result_dict.update(item.dict())
|
||||
result_dict.update(item.model_dump())
|
||||
else:
|
||||
result_dict[key] = item
|
||||
|
||||
|
||||
@@ -132,8 +132,8 @@ template:
|
||||
你是一个数据分析师,你的任务是根据给定的数据进行数据预测,我将以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",
|
||||
|
||||
@@ -11,7 +11,7 @@ export const workspaceUwsUpdate = (data: any) => request.put('/system/workspace/
|
||||
export const workspaceCreate = (data: any) => request.post('/system/workspace', data)
|
||||
export const workspaceUpdate = (data: any) => request.put('/system/workspace', data)
|
||||
export const workspaceUwsDelete = (data: any) => request.delete('/system/workspace/uws', { data })
|
||||
export const workspaceDelete = (data: any) => request.delete('/system/workspace', { data })
|
||||
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 })
|
||||
|
||||
@@ -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" 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 |
@@ -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)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
"data_predict": "数据分析",
|
||||
"chat_search": "搜索",
|
||||
"thinking": "思考中",
|
||||
"thinking_step": "思考过程"
|
||||
"thinking_step": "思考过程",
|
||||
"ask_again": "重新生成"
|
||||
},
|
||||
"ds": {
|
||||
"title": "数据源",
|
||||
|
||||
@@ -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>'
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -154,6 +154,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -117,23 +117,67 @@
|
||||
@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>
|
||||
<div
|
||||
v-if="message.record?.error && message.record?.error?.trim().length > 0"
|
||||
class="error-container"
|
||||
>
|
||||
{{ message.record?.error }}
|
||||
</div>
|
||||
<div class="tool-container">
|
||||
<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>
|
||||
<div class="tool-times">
|
||||
{{ datetimeFormat(message.record?.create_time) }}
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<RecommendQuestion
|
||||
@@ -156,7 +200,20 @@
|
||||
: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>
|
||||
<div class="tool-container">
|
||||
<div class="tool-btns"></div>
|
||||
<div class="tool-times">
|
||||
{{ datetimeFormat(message.record?.create_time) }}
|
||||
</div>
|
||||
</div>
|
||||
</AnalysisAnswer>
|
||||
<PredictAnswer
|
||||
v-if="
|
||||
message?.record?.predict_record_id !== undefined &&
|
||||
@@ -170,7 +227,20 @@
|
||||
:message="message"
|
||||
@finish="onPredictAnswerFinish"
|
||||
@error="onPredictAnswerError"
|
||||
/>
|
||||
>
|
||||
<div
|
||||
v-if="message.record?.error && message.record?.error?.trim().length > 0"
|
||||
class="error-container"
|
||||
>
|
||||
{{ message.record?.error }}
|
||||
</div>
|
||||
<div class="tool-container">
|
||||
<div class="tool-btns"></div>
|
||||
<div class="tool-times">
|
||||
{{ datetimeFormat(message.record?.create_time) }}
|
||||
</div>
|
||||
</div>
|
||||
</PredictAnswer>
|
||||
</template>
|
||||
</ChatRow>
|
||||
</template>
|
||||
@@ -233,8 +303,12 @@ 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'
|
||||
import { datetimeFormat } from '@/utils/utils.ts'
|
||||
|
||||
import { useAssistantStore } from '@/stores/assistant'
|
||||
const assistantStore = useAssistantStore()
|
||||
@@ -536,6 +610,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 +831,72 @@ 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-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
min-height: 22px;
|
||||
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.tool-times {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: rgba(100, 106, 115, 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 {
|
||||
|
||||
@@ -236,9 +236,7 @@ const delWorkspace = (row: any) => {
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
}).then(() => {
|
||||
workspaceDelete({
|
||||
id: row.id,
|
||||
}).then(() => {
|
||||
workspaceDelete(row.id).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
|
||||
Reference in New Issue
Block a user