Skip to content

Commit 4bd69b8

Browse files
feat: Assistant api
1 parent 32f0495 commit 4bd69b8

File tree

9 files changed

+212
-8
lines changed

9 files changed

+212
-8
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""022_assistant_ddl
2+
3+
Revision ID: e6b20ae73606
4+
Revises: 440e9e41da3c
5+
Create Date: 2025-07-09 18:20:27.160183
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'e6b20ae73606'
15+
down_revision = '440e9e41da3c'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
op.create_table(
22+
'sys_assistant',
23+
sa.Column('id', sa.BigInteger(), primary_key=True, nullable=False),
24+
sa.Column('name', sa.String(255), nullable=False),
25+
sa.Column('domain', sa.String(255), nullable=False),
26+
sa.Column('type', sa.Integer(), nullable=False, default=0),
27+
sa.Column('configuration', sa.Text(), nullable=True),
28+
sa.Column('create_time', sa.BigInteger(), default=0, nullable=False)
29+
)
30+
op.create_index(op.f('ix_sys_assistant_id'), 'sys_assistant', ['id'], unique=False)
31+
32+
33+
def downgrade():
34+
op.drop_index(op.f('ix_sys_assistant_id'), table_name='sys_assistant')
35+
op.drop_table('sys_assistant')

backend/apps/api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
from apps.dashboard.api import dashboard_api
55
from apps.datasource.api import datasource
66
from apps.settings.api import terminology
7-
from apps.system.api import login, user, aimodel, workspace
7+
from apps.system.api import login, user, aimodel, workspace, assistant
88
from apps.mcp import mcp
99

1010
api_router = APIRouter()
1111
api_router.include_router(login.router)
1212
api_router.include_router(user.router)
1313
api_router.include_router(workspace.router)
14+
api_router.include_router(assistant.router)
1415
api_router.include_router(aimodel.router)
1516
api_router.include_router(terminology.router)
1617
api_router.include_router(datasource.router)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from datetime import timedelta
2+
from fastapi import APIRouter, FastAPI, Request
3+
from sqlmodel import Session, select
4+
from apps.system.crud.user import get_user_by_account
5+
from apps.system.models.system_model import AssistantModel
6+
from apps.system.schemas.system_schema import AssistantBase, AssistantDTO, AssistantValidator
7+
from common.core.deps import SessionDep
8+
from common.core.security import create_access_token
9+
from common.utils.time import get_timestamp
10+
from starlette.middleware.cors import CORSMiddleware
11+
from common.core.config import settings
12+
router = APIRouter(tags=["system/assistant"], prefix="/system/assistant")
13+
14+
@router.get("/validator/{id}", response_model=AssistantValidator)
15+
async def info(session: SessionDep, id: int):
16+
db_model = session.get(AssistantModel, id)
17+
if not db_model:
18+
return AssistantValidator()
19+
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
20+
user = get_user_by_account(session=session, account='admin')
21+
access_token = create_access_token(
22+
user.to_dict(), expires_delta=access_token_expires
23+
)
24+
return AssistantValidator(True, True, True, access_token)
25+
26+
27+
@router.get("", response_model=list[AssistantModel])
28+
async def query(session: SessionDep):
29+
list_result = session.exec(select(AssistantModel).order_by(AssistantModel.create_time)).all()
30+
return list_result
31+
32+
@router.post("")
33+
async def add(request: Request, session: SessionDep, creator: AssistantBase):
34+
db_model = AssistantModel.model_validate(creator)
35+
db_model.create_time = get_timestamp()
36+
session.add(db_model)
37+
session.commit()
38+
dynamic_upgrade_cors(request=request, session=session)
39+
40+
41+
@router.put("")
42+
async def update(request: Request, session: SessionDep, editor: AssistantDTO):
43+
id = editor.id
44+
db_model = session.get(AssistantModel, id)
45+
if not db_model:
46+
raise ValueError(f"AssistantModel with id {id} not found")
47+
update_data = AssistantModel.model_validate(editor)
48+
db_model.sqlmodel_update(update_data)
49+
session.add(db_model)
50+
session.commit()
51+
dynamic_upgrade_cors(request=request, session=session)
52+
53+
@router.get("/{id}", response_model=AssistantModel)
54+
async def get_one(session: SessionDep, id: int):
55+
db_model = session.get(AssistantModel, id)
56+
if not db_model:
57+
raise ValueError(f"AssistantModel with id {id} not found")
58+
return db_model
59+
60+
@router.delete("/{id}")
61+
async def delete(request: Request, session: SessionDep, id: int):
62+
db_model = session.get(AssistantModel, id)
63+
if not db_model:
64+
raise ValueError(f"AssistantModel with id {id} not found")
65+
session.delete(db_model)
66+
session.commit()
67+
dynamic_upgrade_cors(request=request, session=session)
68+
69+
def dynamic_upgrade_cors(request: Request, session: Session):
70+
list_result = session.exec(select(AssistantModel).order_by(AssistantModel.create_time)).all()
71+
seen = set()
72+
unique_domains = []
73+
for item in list_result:
74+
if item.domain:
75+
for domain in item.domain.split(','):
76+
domain = domain.strip()
77+
if domain and domain not in seen:
78+
seen.add(domain)
79+
unique_domains.append(domain)
80+
app: FastAPI = request.app
81+
cors_middleware = None
82+
for middleware in app.user_middleware:
83+
if middleware.cls == CORSMiddleware:
84+
cors_middleware = middleware
85+
break
86+
if cors_middleware:
87+
updated_origins = list(set(settings.all_cors_origins + unique_domains))
88+
cors_middleware.kwargs['allow_origins'] = updated_origins
89+
90+
91+
92+
93+

backend/apps/system/models/system_model.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
from typing import Optional
23
from sqlmodel import BigInteger, Field, Text, SQLModel
34
from common.core.models import SnowflakeBase
45
from common.core.schemas import BaseCreatorDTO
@@ -39,4 +40,16 @@ class UserWsBaseModel(SQLModel):
3940
weight: int = Field(default=0, nullable=False)
4041

4142
class UserWsModel(SnowflakeBase, UserWsBaseModel, table=True):
42-
__tablename__ = "sys_user_ws"
43+
__tablename__ = "sys_user_ws"
44+
45+
46+
class AssistantBaseModel(SQLModel):
47+
name: str = Field(max_length=255, nullable=False)
48+
type: int = Field(nullable=False, default=0)
49+
domain: str = Field(max_length=255, nullable=False)
50+
configuration: Optional[str] = Field(sa_type = Text(), nullable=True)
51+
create_time: int = Field(default=0, sa_type=BigInteger())
52+
53+
class AssistantModel(SnowflakeBase, AssistantBaseModel, table=True):
54+
__tablename__ = "sys_assistant"
55+

backend/apps/system/schemas/system_schema.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Optional
12
from pydantic import BaseModel
23

34
from common.core.schemas import BaseCreatorDTO
@@ -51,4 +52,35 @@ class UserWsDTO(UserWsBase):
5152
class UserInfoDTO(UserEditor):
5253
language: str = "zh-CN"
5354
weight: int = 0
54-
isAdmin: bool = False
55+
isAdmin: bool = False
56+
57+
58+
class AssistantBase(BaseModel):
59+
name: str
60+
domain: str
61+
type: int = 0
62+
configuration: Optional[str] = None
63+
class AssistantDTO(AssistantBase, BaseCreatorDTO):
64+
pass
65+
66+
class AssistantValidator(BaseModel):
67+
valid: bool = False
68+
id_match: bool = False
69+
domain_match: bool = False
70+
token: Optional[str] = None
71+
72+
def __init__(
73+
self,
74+
valid: bool = False,
75+
id_match: bool = False,
76+
domain_match: bool = False,
77+
token: Optional[str] = None,
78+
**kwargs
79+
):
80+
super().__init__(
81+
valid=valid,
82+
id_match=id_match,
83+
domain_match=domain_match,
84+
token=token,
85+
**kwargs
86+
)

backend/common/utils/whitelist.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"/system/license",
2626
"/system/config/key",
2727
"/images/*",
28-
"/sse"
28+
"/sse",
29+
"/system/assistant/validator/*"
2930
]
3031

3132
class WhitelistChecker:

frontend/src/api/assistant.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { request } from '@/utils/request'
2+
3+
export const assistantApi = {
4+
queryAll: (keyword?: string) =>
5+
request.get('/system/assistant', { params: keyword ? { keyword } : {} }),
6+
add: (data: any) => request.post('/system/assistant', data),
7+
edit: (data: any) => request.put('/system/assistant', data),
8+
delete: (id: number) => request.delete(`/system/assistant/${id}`),
9+
query: (id: number) => request.get(`/system/assistant/${id}`),
10+
validate: (id: any) => request.get(`/system/assistant/validator/${id}`),
11+
}

frontend/src/router/assistant.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ const router = createRouter({
66
history: createWebHashHistory(),
77
routes: [
88
{
9-
path: '/',
9+
path: '/:id',
1010
name: 'index',
1111
component: assistant,
1212
},
1313
{
14-
path: '/assistant',
14+
path: '/assistant:id',
1515
name: 'assistant',
1616
component: assistant,
1717
},

frontend/src/views/embedded/assistant.vue

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<h4>SQLBot 小助手</h4>
99
</div>
1010
</div>
11-
<chat-component ref="chatRef" class="sqlbot-chat-container" />
11+
<chat-component v-if="!loading" ref="chatRef" class="sqlbot-chat-container" />
1212
</div>
1313
<div class="sqlbot-top-btn">
1414
<el-icon style="cursor: pointer" @click="openHistory">
@@ -54,12 +54,17 @@
5454
</div>
5555
</template>
5656
<script setup lang="ts">
57-
import { ref } from 'vue'
57+
import { onBeforeMount, ref } from 'vue'
5858
import ChatComponent from '@/views/chat/index.vue'
5959
import AssistantGif from '@/assets/img/assistant.gif'
6060
import history from '@/assets/svg/chart/history.svg'
6161
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg'
6262
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg'
63+
import { useRoute } from 'vue-router'
64+
import { assistantApi } from '@/api/assistant'
65+
import { useUserStore } from '@/stores/user'
66+
const userStore = useUserStore()
67+
const route = useRoute()
6368
6469
const chatRef = ref()
6570
const chatList = ref<Array<any>>([])
@@ -81,6 +86,19 @@ const EditPen = (chat: any) => {
8186
const Delete = (chat: any) => {
8287
chatRef.value?.onChatDeleted(chat.id)
8388
}
89+
const validator = ref({
90+
id: '',
91+
valid: false,
92+
id_match: false,
93+
token: '',
94+
})
95+
const loading = ref(true)
96+
onBeforeMount(async () => {
97+
const assistantId = route.params.id
98+
validator.value = await assistantApi.validate(assistantId)
99+
userStore.setToken(validator.value.token)
100+
loading.value = false
101+
})
84102
</script>
85103

86104
<style lang="less" scoped>

0 commit comments

Comments
 (0)