diff --git a/README.md b/README.md
index 11041d56..a952ba30 100644
--- a/README.md
+++ b/README.md
@@ -32,16 +32,9 @@ TBD: 交流群二维码
## UI 展示
-
## Star History
diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py
index e758fa93..b208f4e2 100644
--- a/backend/apps/chat/task/llm.py
+++ b/backend/apps/chat/task/llm.py
@@ -439,6 +439,7 @@ class LLMService:
_ds = self.out_ds_instance.get_ds(data['id'])
self.ds = _ds
self.chat_question.engine = _ds.type
+ self.chat_question.db_schema = self.out_ds_instance.get_db_schema(self.ds.id)
_engine_type = self.chat_question.engine
_chat.engine_type = _ds.type
else:
@@ -448,6 +449,7 @@ class LLMService:
raise Exception(f"Datasource configuration with id {_datasource} not found")
self.ds = CoreDatasource(**_ds.model_dump())
self.chat_question.engine = _ds.type_name if _ds.type != 'excel' else 'PostgreSQL'
+ self.chat_question.db_schema = get_table_schema(session=self.session, current_user=self.current_user, ds=self.ds)
_engine_type = self.chat_question.engine
_chat.engine_type = _ds.type_name
# save chat
@@ -698,6 +700,8 @@ class LLMService:
def check_save_chart(self, res: str) -> Dict[str, Any]:
json_str = extract_nested_json(res)
+ if json_str is None:
+ raise Exception("Cannot parse chart config from answer")
data = orjson.loads(json_str)
chart: Dict[str, Any] = {}
@@ -834,7 +838,8 @@ class LLMService:
self.ds.id) if self.out_ds_instance else get_table_schema(session=self.session,
current_user=self.current_user,
ds=self.ds)
-
+ else:
+ self.validate_history_ds()
# generate sql
sql_res = self.generate_sql()
full_sql_text = ''
@@ -1023,7 +1028,25 @@ class LLMService:
finally:
# end
pass
-
+
+ def validate_history_ds(self):
+ _ds = self.ds
+ if not self.current_assistant:
+ current_ds = self.session.exec(CoreDatasource, _ds.id)
+ if not current_ds:
+ raise Exception('ds is invalid')
+ else:
+ try:
+ _ds_list: list[dict] = get_assistant_ds(session=self.session, llm_service=self)
+ match_ds = any(item.get("id") == _ds.id for item in _ds_list)
+ if not match_ds:
+ type = self.current_assistant.type
+ 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]"
+ raise Exception(msg)
+ except Exception as e:
+ raise Exception(f"ds is invalid [{str(e)}]")
+
+
def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
"""Execute SQL query using SQLDatabase
diff --git a/backend/apps/system/crud/user.py b/backend/apps/system/crud/user.py
index d721ef50..3dcd4ccf 100644
--- a/backend/apps/system/crud/user.py
+++ b/backend/apps/system/crud/user.py
@@ -26,6 +26,8 @@ def get_user_by_account(*, session: Session, account: str) -> BaseUserDTO | None
@cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="user_id")
async def get_user_info(*, session: Session, user_id: int) -> UserInfoDTO | None:
db_user: UserModel = get_db_user(session = session, user_id = user_id)
+ if not db_user:
+ return None
userInfo = UserInfoDTO.model_validate(db_user.model_dump())
userInfo.isAdmin = userInfo.id == 1 and userInfo.account == 'admin'
if userInfo.isAdmin:
diff --git a/backend/apps/system/middleware/auth.py b/backend/apps/system/middleware/auth.py
index 4f4d8706..be44d814 100644
--- a/backend/apps/system/middleware/auth.py
+++ b/backend/apps/system/middleware/auth.py
@@ -13,9 +13,11 @@ from apps.system.schemas.system_schema import AssistantHeader, UserInfoDTO
from common.core import security
from common.core.config import settings
from common.core.schemas import TokenPayload
+from common.utils.locale import I18n
from common.utils.utils import SQLBotLogUtil
from common.utils.whitelist import whiteUtils
from fastapi.security.utils import get_authorization_scheme_param
+from common.core.deps import get_i18n
class TokenMiddleware(BaseHTTPMiddleware):
@@ -29,6 +31,7 @@ class TokenMiddleware(BaseHTTPMiddleware):
return await call_next(request)
assistantTokenKey = settings.ASSISTANT_TOKEN_KEY
assistantToken = request.headers.get(assistantTokenKey)
+ trans = await get_i18n(request)
#if assistantToken and assistantToken.lower().startswith("assistant "):
if assistantToken:
validator: tuple[any] = await self.validateAssistant(assistantToken)
@@ -36,20 +39,23 @@ class TokenMiddleware(BaseHTTPMiddleware):
request.state.current_user = validator[1]
request.state.assistant = validator[2]
return await call_next(request)
- return JSONResponse(f"Unauthorized:[{validator[1]}]", status_code=401, headers={"Access-Control-Allow-Origin": "*"})
+ message = trans('i18n_permission.authenticate_invalid', msg = validator[1])
+ return JSONResponse(message, status_code=401, headers={"Access-Control-Allow-Origin": "*"})
#validate pass
tokenkey = settings.TOKEN_KEY
token = request.headers.get(tokenkey)
- validate_pass, data = await self.validateToken(token)
+ validate_pass, data = await self.validateToken(token, trans)
if validate_pass:
request.state.current_user = data
return await call_next(request)
- return JSONResponse(f"Unauthorized:[{data}]", status_code=401, headers={"Access-Control-Allow-Origin": "*"})
+
+ message = trans('i18n_permission.authenticate_invalid', msg = data)
+ return JSONResponse(message, status_code=401, headers={"Access-Control-Allow-Origin": "*"})
def is_options(self, request: Request):
return request.method == "OPTIONS"
- async def validateToken(self, token: Optional[str]):
+ async def validateToken(self, token: Optional[str], trans: I18n):
if not token:
return False, f"Miss Token[{settings.TOKEN_KEY}]!"
schema, param = get_authorization_scheme_param(token)
@@ -63,17 +69,15 @@ class TokenMiddleware(BaseHTTPMiddleware):
with Session(engine) as session:
session_user = await get_user_info(session = session, user_id = token_data.id)
if not session_user:
- raise Exception(f"User not found with id: {token_data.id}")
+ message = trans('i18n_not_exist', msg = trans('i18n_user.account'))
+ raise Exception(message)
session_user = UserInfoDTO.model_validate(session_user)
if session_user.status != 1:
- raise Exception(f"User is not active!")
+ message = trans('i18n_login.user_disable', msg = trans('i18n_concat_admin'))
+ raise Exception(message)
if not session_user.oid or session_user.oid == 0:
- raise Exception(f"User default space is not set!")
- """ if token_data.oid != session_user.oid:
- raise HTTPException(
- status_code=401,
- detail="Default space has been changed, please login again!"
- ) """
+ message = trans('i18n_login.no_associated_ws', msg = trans('i18n_concat_admin'))
+ raise Exception(message)
return True, session_user
except Exception as e:
SQLBotLogUtil.exception(f"Token validation error: {str(e)}")
diff --git a/backend/common/core/config.py b/backend/common/core/config.py
index 5d16c61a..9de1279b 100644
--- a/backend/common/core/config.py
+++ b/backend/common/core/config.py
@@ -9,7 +9,6 @@ from pydantic import (
)
from pydantic_core import MultiHostUrl
from pydantic_settings import BaseSettings, SettingsConfigDict
-from typing_extensions import Self
def parse_cors(v: Any) -> list[str] | str:
diff --git a/backend/locales/en.json b/backend/locales/en.json
index 7b775419..ffe76fd3 100644
--- a/backend/locales/en.json
+++ b/backend/locales/en.json
@@ -24,7 +24,8 @@
},
"i18n_permission": {
"only_admin": "Only administrators can call this!",
- "no_permission": "No permission to access {url}{msg}"
+ "no_permission": "No permission to access {url}{msg}",
+ "authenticate_invalid": "Authenticate invalid [{msg}]"
},
"i18n_llm": {
"validate_error": "Validation failed [{msg}]",
diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json
index abadd609..2a63da7f 100644
--- a/backend/locales/zh-CN.json
+++ b/backend/locales/zh-CN.json
@@ -24,7 +24,8 @@
},
"i18n_permission": {
"only_admin": "仅支持管理员调用!",
- "no_permission": "无权调用{url}{msg}"
+ "no_permission": "无权调用{url}{msg}",
+ "authenticate_invalid": "认证无效【{msg}】"
},
"i18n_llm": {
diff --git a/backend/template.yaml b/backend/template.yaml
index 60eb5a5b..0c86b1ce 100644
--- a/backend/template.yaml
+++ b/backend/template.yaml
@@ -54,13 +54,13 @@ template:
]
```
+ ### 提供表结构如下:
+ {schema}
+
### 响应, 请直接返回JSON结果:
```json
user: |
- ### 提供表结构如下:
- {schema}
-
### 问题:
{question}
diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts
index 5cbdc90d..e9ae6c17 100644
--- a/frontend/src/utils/request.ts
+++ b/frontend/src/utils/request.ts
@@ -182,7 +182,7 @@ class HttpService {
setTimeout(() => {
wsCache.delete('user.token')
window.location.reload()
- }, 1000)
+ }, 2000)
return
// break
case 403:
diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue
index ecd89429..836f4231 100644
--- a/frontend/src/views/chat/index.vue
+++ b/frontend/src/views/chat/index.vue
@@ -21,7 +21,6 @@
:class="{ 'assistant-popover-sidebar': isAssistant }"
>
+
+
+
+
+
@@ -1092,3 +1116,15 @@ onMounted(() => {
}
}
+
+
diff --git a/frontend/src/views/ds/Datasource.vue b/frontend/src/views/ds/Datasource.vue
index b1c0264e..4273d211 100644
--- a/frontend/src/views/ds/Datasource.vue
+++ b/frontend/src/views/ds/Datasource.vue
@@ -77,6 +77,10 @@ const handleDefaultDatasourceChange = (item: any) => {
}
}
+const changeActiveStep = (val: number) => {
+ activeStep.value = val > 2 ? 2 : val
+}
+
const formatKeywords = (item: string) => {
if (!defaultDatasourceKeywords.value) return item
return item.replaceAll(
@@ -359,7 +363,7 @@ const back = () => {
:active-type="activeType"
@refresh="refresh"
@close="beforeClose"
- @change-active-step="(val: number) => (activeStep = val)"
+ @change-active-step="changeActiveStep"
>
diff --git a/frontend/src/views/ds/DatasourceForm.vue b/frontend/src/views/ds/DatasourceForm.vue
index 0440699e..e81db1b8 100644
--- a/frontend/src/views/ds/DatasourceForm.vue
+++ b/frontend/src/views/ds/DatasourceForm.vue
@@ -9,6 +9,7 @@ import { ElMessage } from 'element-plus-secondary'
import type { FormInstance, FormRules } from 'element-plus-secondary'
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
import FixedSizeList from 'element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs'
+import { debounce } from 'lodash-es'
import { Plus } from '@element-plus/icons-vue'
import { useCache } from '@/utils/useCache'
import { haveSchema } from '@/views/ds/js/ds-type'
@@ -42,6 +43,7 @@ const tableList = ref([])
const excelUploadSuccess = ref(false)
const tableListLoading = ref(false)
const token = wsCache.get('user.token')
+const checkLoading = ref(false)
const request_key = computed(() => {
// eslint-disable-next-line no-undef
return LicenseGenerator.generate()
@@ -339,7 +341,7 @@ const check = () => {
onBeforeUnmount(() => (saveLoading.value = false))
-const next = async (formEl: FormInstance | undefined) => {
+const next = debounce(async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
@@ -349,31 +351,38 @@ const next = async (formEl: FormInstance | undefined) => {
emit('changeActiveStep', props.activeStep + 1)
}
} else {
+ if (checkLoading.value) return
// check status if success do next
const requestObj = buildConf()
- datasourceApi.check(requestObj).then((res: boolean) => {
- if (res) {
- emit('changeActiveStep', props.activeStep + 1)
- // request tables
- datasourceApi.getTablesByConf(requestObj).then((res: any) => {
- tableList.value = res
- })
- } else {
- ElMessage({
- message: t('ds.form.connect.failed'),
- type: 'error',
- showClose: true,
- })
- }
- })
+ checkLoading.value = true
+ datasourceApi
+ .check(requestObj)
+ .then((res: boolean) => {
+ if (res) {
+ emit('changeActiveStep', props.activeStep + 1)
+ // request tables
+ datasourceApi.getTablesByConf(requestObj).then((res: any) => {
+ tableList.value = res
+ })
+ } else {
+ ElMessage({
+ message: t('ds.form.connect.failed'),
+ type: 'error',
+ showClose: true,
+ })
+ }
+ })
+ .finally(() => {
+ checkLoading.value = false
+ })
}
}
})
-}
+}, 300)
-const preview = () => {
+const preview = debounce(() => {
emit('changeActiveStep', props.activeStep - 1)
-}
+}, 200)
const beforeUpload = (rawFile: any) => {
setFile(rawFile)
@@ -479,7 +488,7 @@ defineExpose({