diff --git a/backend/alembic/versions/022_assistant_ddl.py b/backend/alembic/versions/022_assistant_ddl.py new file mode 100644 index 00000000..a5fd8695 --- /dev/null +++ b/backend/alembic/versions/022_assistant_ddl.py @@ -0,0 +1,35 @@ +"""022_assistant_ddl + +Revision ID: e6b20ae73606 +Revises: 440e9e41da3c +Create Date: 2025-07-09 18:20:27.160183 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = 'e6b20ae73606' +down_revision = '440e9e41da3c' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'sys_assistant', + sa.Column('id', sa.BigInteger(), primary_key=True, nullable=False), + sa.Column('name', sa.String(255), nullable=False), + sa.Column('domain', sa.String(255), nullable=False), + sa.Column('type', sa.Integer(), nullable=False, default=0), + sa.Column('configuration', sa.Text(), nullable=True), + sa.Column('create_time', sa.BigInteger(), default=0, nullable=False) + ) + op.create_index(op.f('ix_sys_assistant_id'), 'sys_assistant', ['id'], unique=False) + + +def downgrade(): + op.drop_index(op.f('ix_sys_assistant_id'), table_name='sys_assistant') + op.drop_table('sys_assistant') diff --git a/backend/apps/api.py b/backend/apps/api.py index ff75d577..35296396 100644 --- a/backend/apps/api.py +++ b/backend/apps/api.py @@ -4,13 +4,14 @@ from apps.chat.api import chat from apps.dashboard.api import dashboard_api from apps.datasource.api import datasource from apps.settings.api import terminology -from apps.system.api import login, user, aimodel, workspace +from apps.system.api import login, user, aimodel, workspace, assistant from apps.mcp import mcp api_router = APIRouter() api_router.include_router(login.router) api_router.include_router(user.router) api_router.include_router(workspace.router) +api_router.include_router(assistant.router) api_router.include_router(aimodel.router) api_router.include_router(terminology.router) api_router.include_router(datasource.router) diff --git a/backend/apps/system/api/assistant.py b/backend/apps/system/api/assistant.py new file mode 100644 index 00000000..ab8f5419 --- /dev/null +++ b/backend/apps/system/api/assistant.py @@ -0,0 +1,93 @@ +from datetime import timedelta +from fastapi import APIRouter, FastAPI, Request +from sqlmodel import Session, select +from apps.system.crud.user import get_user_by_account +from apps.system.models.system_model import AssistantModel +from apps.system.schemas.system_schema import AssistantBase, AssistantDTO, AssistantValidator +from common.core.deps import SessionDep +from common.core.security import create_access_token +from common.utils.time import get_timestamp +from starlette.middleware.cors import CORSMiddleware +from common.core.config import settings +router = APIRouter(tags=["system/assistant"], prefix="/system/assistant") + +@router.get("/validator/{id}", response_model=AssistantValidator) +async def info(session: SessionDep, id: int): + db_model = session.get(AssistantModel, id) + if not db_model: + return AssistantValidator() + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + user = get_user_by_account(session=session, account='admin') + access_token = create_access_token( + user.to_dict(), expires_delta=access_token_expires + ) + return AssistantValidator(True, True, True, access_token) + + +@router.get("", response_model=list[AssistantModel]) +async def query(session: SessionDep): + list_result = session.exec(select(AssistantModel).order_by(AssistantModel.create_time)).all() + return list_result + +@router.post("") +async def add(request: Request, session: SessionDep, creator: AssistantBase): + db_model = AssistantModel.model_validate(creator) + db_model.create_time = get_timestamp() + session.add(db_model) + session.commit() + dynamic_upgrade_cors(request=request, session=session) + + +@router.put("") +async def update(request: Request, session: SessionDep, editor: AssistantDTO): + id = editor.id + db_model = session.get(AssistantModel, id) + if not db_model: + raise ValueError(f"AssistantModel with id {id} not found") + update_data = AssistantModel.model_validate(editor) + db_model.sqlmodel_update(update_data) + session.add(db_model) + session.commit() + dynamic_upgrade_cors(request=request, session=session) + +@router.get("/{id}", response_model=AssistantModel) +async def get_one(session: SessionDep, id: int): + db_model = session.get(AssistantModel, id) + if not db_model: + raise ValueError(f"AssistantModel with id {id} not found") + return db_model + +@router.delete("/{id}") +async def delete(request: Request, session: SessionDep, id: int): + db_model = session.get(AssistantModel, id) + if not db_model: + raise ValueError(f"AssistantModel with id {id} not found") + session.delete(db_model) + session.commit() + dynamic_upgrade_cors(request=request, session=session) + +def dynamic_upgrade_cors(request: Request, session: Session): + list_result = session.exec(select(AssistantModel).order_by(AssistantModel.create_time)).all() + seen = set() + unique_domains = [] + for item in list_result: + if item.domain: + for domain in item.domain.split(','): + domain = domain.strip() + if domain and domain not in seen: + seen.add(domain) + unique_domains.append(domain) + app: FastAPI = request.app + cors_middleware = None + for middleware in app.user_middleware: + if middleware.cls == CORSMiddleware: + cors_middleware = middleware + break + if cors_middleware: + updated_origins = list(set(settings.all_cors_origins + unique_domains)) + cors_middleware.kwargs['allow_origins'] = updated_origins + + + + + diff --git a/backend/apps/system/models/system_model.py b/backend/apps/system/models/system_model.py index dd2eb374..ad344781 100644 --- a/backend/apps/system/models/system_model.py +++ b/backend/apps/system/models/system_model.py @@ -1,4 +1,5 @@ +from typing import Optional from sqlmodel import BigInteger, Field, Text, SQLModel from common.core.models import SnowflakeBase from common.core.schemas import BaseCreatorDTO @@ -39,4 +40,16 @@ class UserWsBaseModel(SQLModel): weight: int = Field(default=0, nullable=False) class UserWsModel(SnowflakeBase, UserWsBaseModel, table=True): - __tablename__ = "sys_user_ws" \ No newline at end of file + __tablename__ = "sys_user_ws" + + +class AssistantBaseModel(SQLModel): + name: str = Field(max_length=255, nullable=False) + type: int = Field(nullable=False, default=0) + domain: str = Field(max_length=255, nullable=False) + configuration: Optional[str] = Field(sa_type = Text(), nullable=True) + create_time: int = Field(default=0, sa_type=BigInteger()) + +class AssistantModel(SnowflakeBase, AssistantBaseModel, table=True): + __tablename__ = "sys_assistant" + \ No newline at end of file diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py index 683f7de6..b524e24b 100644 --- a/backend/apps/system/schemas/system_schema.py +++ b/backend/apps/system/schemas/system_schema.py @@ -1,3 +1,4 @@ +from typing import Optional from pydantic import BaseModel from common.core.schemas import BaseCreatorDTO @@ -51,4 +52,35 @@ class UserWsDTO(UserWsBase): class UserInfoDTO(UserEditor): language: str = "zh-CN" weight: int = 0 - isAdmin: bool = False \ No newline at end of file + isAdmin: bool = False + + +class AssistantBase(BaseModel): + name: str + domain: str + type: int = 0 + configuration: Optional[str] = None +class AssistantDTO(AssistantBase, BaseCreatorDTO): + pass + +class AssistantValidator(BaseModel): + valid: bool = False + id_match: bool = False + domain_match: bool = False + token: Optional[str] = None + + def __init__( + self, + valid: bool = False, + id_match: bool = False, + domain_match: bool = False, + token: Optional[str] = None, + **kwargs + ): + super().__init__( + valid=valid, + id_match=id_match, + domain_match=domain_match, + token=token, + **kwargs + ) \ No newline at end of file diff --git a/backend/common/utils/whitelist.py b/backend/common/utils/whitelist.py index 10c38ff8..137104c9 100644 --- a/backend/common/utils/whitelist.py +++ b/backend/common/utils/whitelist.py @@ -25,7 +25,8 @@ wlist = [ "/system/license", "/system/config/key", "/images/*", - "/sse" + "/sse", + "/system/assistant/validator/*" ] class WhitelistChecker: diff --git a/frontend/src/api/assistant.ts b/frontend/src/api/assistant.ts new file mode 100644 index 00000000..cb82aa05 --- /dev/null +++ b/frontend/src/api/assistant.ts @@ -0,0 +1,11 @@ +import { request } from '@/utils/request' + +export const assistantApi = { + queryAll: (keyword?: string) => + request.get('/system/assistant', { params: keyword ? { keyword } : {} }), + add: (data: any) => request.post('/system/assistant', data), + edit: (data: any) => request.put('/system/assistant', data), + delete: (id: number) => request.delete(`/system/assistant/${id}`), + query: (id: number) => request.get(`/system/assistant/${id}`), + validate: (id: any) => request.get(`/system/assistant/validator/${id}`), +} diff --git a/frontend/src/router/assistant.ts b/frontend/src/router/assistant.ts index 0bcbbf88..fec2ddc4 100644 --- a/frontend/src/router/assistant.ts +++ b/frontend/src/router/assistant.ts @@ -6,12 +6,12 @@ const router = createRouter({ history: createWebHashHistory(), routes: [ { - path: '/', + path: '/:id', name: 'index', component: assistant, }, { - path: '/assistant', + path: '/assistant:id', name: 'assistant', component: assistant, }, diff --git a/frontend/src/views/embedded/assistant.vue b/frontend/src/views/embedded/assistant.vue index f62714b2..fb9f59e6 100644 --- a/frontend/src/views/embedded/assistant.vue +++ b/frontend/src/views/embedded/assistant.vue @@ -8,7 +8,7 @@