Skip to content

Commit f7538f3

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 002149b + 8d5c7fd commit f7538f3

File tree

15 files changed

+129
-52
lines changed

15 files changed

+129
-52
lines changed

README.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,9 @@ TBD: 交流群二维码
3232

3333
## UI 展示
3434

35-
<table style="border-collapse: collapse; border: 1px solid black;">
3635
<tr>
37-
<td style="padding: 5px;background-color:#fff;"><img src= "TBD" alt="SQLBot Demo1" /></td>
38-
<td style="padding: 5px;background-color:#fff;"><img src= "TBD" alt="SQLBot Demo2" /></td>
36+
<img alt="q&a" src="https://github.com/user-attachments/assets/55526514-52f3-4cfe-98ec-08a986259280" />
3937
</tr>
40-
<tr>
41-
<td style="padding: 5px;background-color:#fff;"><img src= "TBD" alt="SQLBot Demo3" /></td>
42-
<td style="padding: 5px;background-color:#fff;"><img src= "TBD" alt="SQLBot Demo4" /></td>
43-
</tr>
44-
</table>
4538

4639
## Star History
4740

backend/apps/chat/task/llm.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ def select_datasource(self):
439439
_ds = self.out_ds_instance.get_ds(data['id'])
440440
self.ds = _ds
441441
self.chat_question.engine = _ds.type
442+
self.chat_question.db_schema = self.out_ds_instance.get_db_schema(self.ds.id)
442443
_engine_type = self.chat_question.engine
443444
_chat.engine_type = _ds.type
444445
else:
@@ -448,6 +449,7 @@ def select_datasource(self):
448449
raise Exception(f"Datasource configuration with id {_datasource} not found")
449450
self.ds = CoreDatasource(**_ds.model_dump())
450451
self.chat_question.engine = _ds.type_name if _ds.type != 'excel' else 'PostgreSQL'
452+
self.chat_question.db_schema = get_table_schema(session=self.session, current_user=self.current_user, ds=self.ds)
451453
_engine_type = self.chat_question.engine
452454
_chat.engine_type = _ds.type_name
453455
# save chat
@@ -698,6 +700,8 @@ def check_save_sql(self, res: str) -> str:
698700
def check_save_chart(self, res: str) -> Dict[str, Any]:
699701

700702
json_str = extract_nested_json(res)
703+
if json_str is None:
704+
raise Exception("Cannot parse chart config from answer")
701705
data = orjson.loads(json_str)
702706

703707
chart: Dict[str, Any] = {}
@@ -834,7 +838,8 @@ def run_task(self, in_chat: bool = True):
834838
self.ds.id) if self.out_ds_instance else get_table_schema(session=self.session,
835839
current_user=self.current_user,
836840
ds=self.ds)
837-
841+
else:
842+
self.validate_history_ds()
838843
# generate sql
839844
sql_res = self.generate_sql()
840845
full_sql_text = ''
@@ -1023,7 +1028,25 @@ def run_analysis_or_predict_task(self, action_type: str):
10231028
finally:
10241029
# end
10251030
pass
1026-
1031+
1032+
def validate_history_ds(self):
1033+
_ds = self.ds
1034+
if not self.current_assistant:
1035+
current_ds = self.session.exec(CoreDatasource, _ds.id)
1036+
if not current_ds:
1037+
raise Exception('ds is invalid')
1038+
else:
1039+
try:
1040+
_ds_list: list[dict] = get_assistant_ds(session=self.session, llm_service=self)
1041+
match_ds = any(item.get("id") == _ds.id for item in _ds_list)
1042+
if not match_ds:
1043+
type = self.current_assistant.type
1044+
msg = f"ds is invalid [please check ds list and public ds list]" if type == 0 else f"ds is invalid [please check ds api]"
1045+
raise Exception(msg)
1046+
except Exception as e:
1047+
raise Exception(f"ds is invalid [{str(e)}]")
1048+
1049+
10271050

10281051
def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
10291052
"""Execute SQL query using SQLDatabase

backend/apps/system/crud/user.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ def get_user_by_account(*, session: Session, account: str) -> BaseUserDTO | None
2626
@cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="user_id")
2727
async def get_user_info(*, session: Session, user_id: int) -> UserInfoDTO | None:
2828
db_user: UserModel = get_db_user(session = session, user_id = user_id)
29+
if not db_user:
30+
return None
2931
userInfo = UserInfoDTO.model_validate(db_user.model_dump())
3032
userInfo.isAdmin = userInfo.id == 1 and userInfo.account == 'admin'
3133
if userInfo.isAdmin:

backend/apps/system/middleware/auth.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
from common.core import security
1414
from common.core.config import settings
1515
from common.core.schemas import TokenPayload
16+
from common.utils.locale import I18n
1617
from common.utils.utils import SQLBotLogUtil
1718
from common.utils.whitelist import whiteUtils
1819
from fastapi.security.utils import get_authorization_scheme_param
20+
from common.core.deps import get_i18n
1921
class TokenMiddleware(BaseHTTPMiddleware):
2022

2123

@@ -29,27 +31,31 @@ async def dispatch(self, request, call_next):
2931
return await call_next(request)
3032
assistantTokenKey = settings.ASSISTANT_TOKEN_KEY
3133
assistantToken = request.headers.get(assistantTokenKey)
34+
trans = await get_i18n(request)
3235
#if assistantToken and assistantToken.lower().startswith("assistant "):
3336
if assistantToken:
3437
validator: tuple[any] = await self.validateAssistant(assistantToken)
3538
if validator[0]:
3639
request.state.current_user = validator[1]
3740
request.state.assistant = validator[2]
3841
return await call_next(request)
39-
return JSONResponse(f"Unauthorized:[{validator[1]}]", status_code=401, headers={"Access-Control-Allow-Origin": "*"})
42+
message = trans('i18n_permission.authenticate_invalid', msg = validator[1])
43+
return JSONResponse(message, status_code=401, headers={"Access-Control-Allow-Origin": "*"})
4044
#validate pass
4145
tokenkey = settings.TOKEN_KEY
4246
token = request.headers.get(tokenkey)
43-
validate_pass, data = await self.validateToken(token)
47+
validate_pass, data = await self.validateToken(token, trans)
4448
if validate_pass:
4549
request.state.current_user = data
4650
return await call_next(request)
47-
return JSONResponse(f"Unauthorized:[{data}]", status_code=401, headers={"Access-Control-Allow-Origin": "*"})
51+
52+
message = trans('i18n_permission.authenticate_invalid', msg = data)
53+
return JSONResponse(message, status_code=401, headers={"Access-Control-Allow-Origin": "*"})
4854

4955
def is_options(self, request: Request):
5056
return request.method == "OPTIONS"
5157

52-
async def validateToken(self, token: Optional[str]):
58+
async def validateToken(self, token: Optional[str], trans: I18n):
5359
if not token:
5460
return False, f"Miss Token[{settings.TOKEN_KEY}]!"
5561
schema, param = get_authorization_scheme_param(token)
@@ -63,17 +69,15 @@ async def validateToken(self, token: Optional[str]):
6369
with Session(engine) as session:
6470
session_user = await get_user_info(session = session, user_id = token_data.id)
6571
if not session_user:
66-
raise Exception(f"User not found with id: {token_data.id}")
72+
message = trans('i18n_not_exist', msg = trans('i18n_user.account'))
73+
raise Exception(message)
6774
session_user = UserInfoDTO.model_validate(session_user)
6875
if session_user.status != 1:
69-
raise Exception(f"User is not active!")
76+
message = trans('i18n_login.user_disable', msg = trans('i18n_concat_admin'))
77+
raise Exception(message)
7078
if not session_user.oid or session_user.oid == 0:
71-
raise Exception(f"User default space is not set!")
72-
""" if token_data.oid != session_user.oid:
73-
raise HTTPException(
74-
status_code=401,
75-
detail="Default space has been changed, please login again!"
76-
) """
79+
message = trans('i18n_login.no_associated_ws', msg = trans('i18n_concat_admin'))
80+
raise Exception(message)
7781
return True, session_user
7882
except Exception as e:
7983
SQLBotLogUtil.exception(f"Token validation error: {str(e)}")

backend/common/core/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
)
1010
from pydantic_core import MultiHostUrl
1111
from pydantic_settings import BaseSettings, SettingsConfigDict
12-
from typing_extensions import Self
1312

1413

1514
def parse_cors(v: Any) -> list[str] | str:

backend/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
},
2525
"i18n_permission": {
2626
"only_admin": "Only administrators can call this!",
27-
"no_permission": "No permission to access {url}{msg}"
27+
"no_permission": "No permission to access {url}{msg}",
28+
"authenticate_invalid": "Authenticate invalid [{msg}]"
2829
},
2930
"i18n_llm": {
3031
"validate_error": "Validation failed [{msg}]",

backend/locales/zh-CN.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
},
2525
"i18n_permission": {
2626
"only_admin": "仅支持管理员调用!",
27-
"no_permission": "无权调用{url}{msg}"
27+
"no_permission": "无权调用{url}{msg}",
28+
"authenticate_invalid": "认证无效【{msg}】"
2829

2930
},
3031
"i18n_llm": {

backend/template.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ template:
5454
]
5555
```
5656
57+
### 提供表结构如下:
58+
{schema}
59+
5760
### 响应, 请直接返回JSON结果:
5861
```json
5962
6063
user: |
61-
### 提供表结构如下:
62-
{schema}
63-
6464
### 问题:
6565
{question}
6666

frontend/src/utils/request.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class HttpService {
182182
setTimeout(() => {
183183
wsCache.delete('user.token')
184184
window.location.reload()
185-
}, 1000)
185+
}, 2000)
186186
return
187187
// break
188188
case 403:

frontend/src/views/chat/index.vue

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
:class="{ 'assistant-popover-sidebar': isAssistant }"
2222
>
2323
<el-popover
24-
:visible="isAssistant ? floatPopoverVisible : null"
2524
:width="280"
2625
placement="bottom-start"
2726
:popper-style="{ ...defaultFloatPopoverStyle }"
@@ -48,6 +47,31 @@
4847
@on-click-side-bar-btn="hideSideBar"
4948
/>
5049
</el-popover>
50+
51+
<el-drawer
52+
v-model="floatPopoverVisible"
53+
:with-header="false"
54+
direction="ltr"
55+
size="278"
56+
modal-class="assistant-popover_sidebar"
57+
:before-close="hideSideBar"
58+
>
59+
<ChatListContainer
60+
ref="floatPopoverRef"
61+
v-model:chat-list="chatList"
62+
v-model:current-chat-id="currentChatId"
63+
v-model:current-chat="currentChat"
64+
v-model:loading="loading"
65+
:in-popover="false"
66+
@go-empty="goEmpty"
67+
@on-chat-created="onChatCreated"
68+
@on-click-history="onClickHistory"
69+
@on-chat-deleted="onChatDeleted"
70+
@on-chat-renamed="onChatRenamed"
71+
@on-click-side-bar-btn="hideSideBar"
72+
/>
73+
</el-drawer>
74+
5175
<el-tooltip effect="dark" :content="t('qa.new_chat')" placement="bottom">
5276
<el-button link type="primary" class="icon-btn" @click="createNewChatSimple">
5377
<el-icon>
@@ -1092,3 +1116,15 @@ onMounted(() => {
10921116
}
10931117
}
10941118
</style>
1119+
1120+
<style lang="less">
1121+
.assistant-popover_sidebar {
1122+
.ed-drawer {
1123+
height: 100% !important;
1124+
margin-top: 0 !important;
1125+
}
1126+
.ed-drawer__body {
1127+
padding: 0;
1128+
}
1129+
}
1130+
</style>

0 commit comments

Comments
 (0)