feat: Assistant api

This commit is contained in:
fit2cloud-chenyw
2025-07-09 18:29:07 +08:00
parent 32f0495f91
commit 4bd69b845f
9 changed files with 212 additions and 8 deletions
@@ -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')
+2 -1
View File
@@ -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)
+93
View File
@@ -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
+14 -1
View File
@@ -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"
__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"
+33 -1
View File
@@ -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
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
)
+2 -1
View File
@@ -25,7 +25,8 @@ wlist = [
"/system/license",
"/system/config/key",
"/images/*",
"/sse"
"/sse",
"/system/assistant/validator/*"
]
class WhitelistChecker:
+11
View File
@@ -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}`),
}
+2 -2
View File
@@ -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,
},
+20 -2
View File
@@ -8,7 +8,7 @@
<h4>SQLBot 小助手</h4>
</div>
</div>
<chat-component ref="chatRef" class="sqlbot-chat-container" />
<chat-component v-if="!loading" ref="chatRef" class="sqlbot-chat-container" />
</div>
<div class="sqlbot-top-btn">
<el-icon style="cursor: pointer" @click="openHistory">
@@ -54,12 +54,17 @@
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeMount, 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'
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
import { useRoute } from 'vue-router'
import { assistantApi } from '@/api/assistant'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const route = useRoute()
const chatRef = ref()
const chatList = ref<Array<any>>([])
@@ -81,6 +86,19 @@ const EditPen = (chat: any) => {
const Delete = (chat: any) => {
chatRef.value?.onChatDeleted(chat.id)
}
const validator = ref({
id: '',
valid: false,
id_match: false,
token: '',
})
const loading = ref(true)
onBeforeMount(async () => {
const assistantId = route.params.id
validator.value = await assistantApi.validate(assistantId)
userStore.setToken(validator.value.token)
loading.value = false
})
</script>
<style lang="less" scoped>