This commit is contained in:
dataeaseShu
2025-09-03 17:30:16 +08:00
6 changed files with 128 additions and 70 deletions
+3 -2
View File
@@ -399,7 +399,8 @@ class LLMService:
]
""" _ds_list = self.session.exec(select(CoreDatasource).options(
load_only(CoreDatasource.id, CoreDatasource.name, CoreDatasource.description))).all() """
if not _ds_list:
raise SingleMessageError('No available datasource configuration found')
ignore_auto_select = _ds_list and len(_ds_list) == 1
# ignore auto select ds
@@ -1175,7 +1176,7 @@ class LLMService:
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]"
msg = f"[please check ds list and public ds list]" if type == 0 else f"[please check ds api]"
raise SingleMessageError(msg)
except Exception as e:
raise SingleMessageError(f"ds is invalid [{str(e)}]")
+58 -44
View File
@@ -102,10 +102,62 @@ def get_session(ds: CoreDatasource | AssistantOutDsSchema):
return session
def check_connection(trans: Optional[Trans], ds: CoreDatasource, is_raise: bool = False):
db = DB.get_db(ds.type)
if db.connect_type == ConnectType.sqlalchemy:
conn = get_engine(ds, 10)
def check_connection(trans: Optional[Trans], ds: CoreDatasource | AssistantOutDsSchema, is_raise: bool = False):
if isinstance(ds, CoreDatasource):
db = DB.get_db(ds.type)
if db.connect_type == ConnectType.sqlalchemy:
conn = get_engine(ds, 10)
try:
with conn.connect() as connection:
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
else:
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration)))
if ds.type == 'dm':
with dmPython.connect(user=conf.username, password=conf.password, server=conf.host,
port=conf.port) as conn, conn.cursor() as cursor:
try:
cursor.execute('select 1', timeout=10).fetchall()
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
elif ds.type == 'doris':
with pymysql.connect(user=conf.username, passwd=conf.password, host=conf.host,
port=conf.port, db=conf.database, connect_timeout=10,
read_timeout=10) as conn, conn.cursor() as cursor:
try:
cursor.execute('select 1')
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
elif ds.type == 'redshift':
with redshift_connector.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username,
password=conf.password,
timeout=10) as conn, conn.cursor() as cursor:
try:
cursor.execute('select 1')
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
else:
conn = get_ds_engine(ds)
try:
with conn.connect() as connection:
SQLBotLogUtil.info("success")
@@ -115,46 +167,8 @@ def check_connection(trans: Optional[Trans], ds: CoreDatasource, is_raise: bool
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
else:
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration)))
if ds.type == 'dm':
with dmPython.connect(user=conf.username, password=conf.password, server=conf.host,
port=conf.port) as conn, conn.cursor() as cursor:
try:
cursor.execute('select 1', timeout=10).fetchall()
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
elif ds.type == 'doris':
with pymysql.connect(user=conf.username, passwd=conf.password, host=conf.host,
port=conf.port, db=conf.database, connect_timeout=10,
read_timeout=10) as conn, conn.cursor() as cursor:
try:
cursor.execute('select 1')
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
elif ds.type == 'redshift':
with redshift_connector.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username,
password=conf.password,
timeout=10) as conn, conn.cursor() as cursor:
try:
cursor.execute('select 1')
SQLBotLogUtil.info("success")
return True
except Exception as e:
SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}")
if is_raise:
raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}')
return False
return False
def get_version(ds: CoreDatasource | AssistantOutDsSchema):
+1 -1
View File
@@ -44,7 +44,7 @@ def get_assistant_ds(session: Session, llm_service) -> list[dict]:
if public_list:
stmt = stmt.where(CoreDatasource.id.in_(public_list))
else:
raise RuntimeError('no public datasource valid')
return []
""" private_list: list[int] = config.get('private_list') or None
if private_list:
stmt = stmt.where(~CoreDatasource.id.in_(private_list)) """
+14 -6
View File
@@ -28,7 +28,7 @@ template:
不要编造<m-schema>内没有提供给你的表结构
</rule>
<rule>
生成的SQL必须符合<db-engine>提供数据库引擎的规范
生成的SQL必须符合<db-engine>内提供数据库引擎的规范
</rule>
<rule>
若用户提问中提供了参考SQL,你需要判断该SQL是否是查询语句
@@ -74,10 +74,14 @@ template:
如数据库引擎是 MySQL、Doris,则在表名、字段名、别名外层加反引号;
如数据库引擎是 Microsoft SQL Server,则在schema、表名、字段名、别名外层加方括号。
<example>
以PostgreSQL为例,查询Schema为TEST表TABLE下所有数据,则生成的SQL为:
SELECT "id" FROM "TEST"."TABLE"
以PostgreSQL为例,查询Schema为TEST表TABLE下前1000条id字段,则生成的SQL为:
SELECT "id" FROM "TEST"."TABLE" LIMIT 1000
- 注意在表名外双引号的位置,千万不要生成为:
SELECT "id" FROM "TEST.TABLE"
SELECT "id" FROM "TEST.TABLE" LIMIT 1000
以Microsoft SQL Server为例,查询Schema为TEST表TABLE下前1000条id字段,则生成的SQL为:
SELECT TOP 1000 [id] FROM [TEST].[TABLE]
- 注意在表名外方括号的位置,千万不要生成为:
SELECT TOP 1000 [id] FROM [TEST.TABLE]
</example>
</rule>
<rule>
@@ -95,9 +99,13 @@ template:
</rule>
<rule>
如果用户没有指定数据条数的限制,输出的查询SQL需要加上1000条的数据条数限制
</rule>
<rule>
如果用户指定的限制大于1000,则按1000处理
<example>
以PostgreSQL为例,查询Schema为TEST表TABLE下id字段,则生成的SQL为:
SELECT "id" FROM "TEST"."TABLE" LIMIT 1000
以Microsoft SQL Server为例,查询Schema为TEST表TABLE下id字段,则生成的SQL为:
SELECT TOP 1000 [id] FROM [TEST].[TABLE]
</example>
</rule>
<rule>
若需关联多表,优先使用<m-schema>中标记为"Primary key"/"ID"/"主键"的字段作为关联条件。
+51 -2
View File
@@ -157,7 +157,55 @@
closeviewport.classList.remove('sqlbot-assistant-viewportnone')
}
}
const drag = (e) => {
if (data.float_icon_drag) {
chat_button.setAttribute('draggable', 'true')
let startX = 0
let startY = 0
const img = new Image()
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='
chat_button.addEventListener('dragstart', (e) => {
startX = e.clientX - chat_button.offsetLeft
startY = e.clientY - chat_button.offsetTop
e.dataTransfer.setDragImage(img, 0, 0)
})
chat_button.addEventListener('drag', (e) => {
if (e.clientX && e.clientY) {
const left = e.clientX - startX
const top = e.clientY - startY
const maxX = window.innerWidth - chat_button.offsetWidth
const maxY = window.innerHeight - chat_button.offsetHeight
chat_button.style.left = Math.min(Math.max(0, left), maxX) + 'px'
chat_button.style.top = Math.min(Math.max(0, top), maxY) + 'px'
}
})
let touchStartX = 0
let touchStartY = 0
chat_button.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX - chat_button.offsetLeft
touchStartY = e.touches[0].clientY - chat_button.offsetTop
e.preventDefault()
})
chat_button.addEventListener('touchmove', (e) => {
const left = e.touches[0].clientX - touchStartX
const top = e.touches[0].clientY - touchStartY
const maxX = window.innerWidth - chat_button.offsetWidth
const maxY = window.innerHeight - chat_button.offsetHeight
chat_button.style.left = Math.min(Math.max(0, left), maxX) + 'px'
chat_button.style.top = Math.min(Math.max(0, top), maxY) + 'px'
e.preventDefault()
})
}
/* const drag = (e) => {
if (['touchmove', 'touchstart'].includes(e.type)) {
chat_button.style.top = e.touches[0].clientY - chat_button_img.clientHeight / 2 + 'px'
chat_button.style.left = e.touches[0].clientX - chat_button_img.clientHeight / 2 + 'px'
@@ -169,6 +217,7 @@
chat_button.style.height = chat_button_img.clientHeight + 'px'
}
if (data.float_icon_drag) {
chat_button.setAttribute('draggable', 'true')
chat_button.addEventListener('drag', drag)
chat_button.addEventListener('dragover', (e) => {
e.preventDefault()
@@ -176,7 +225,7 @@
chat_button.addEventListener('dragend', drag)
chat_button.addEventListener('touchstart', drag)
chat_button.addEventListener('touchmove', drag)
}
} */
viewport.onclick = viewport_func
closeviewport.onclick = viewport_func
}
+1 -15
View File
@@ -5,20 +5,6 @@ SQLBOT_BASE=/opt
SQLBOT_WEB_PORT=8000
SQLBOT_MCP_PORT=8001
# 数据库配置
## 是否使用外部数据库
SQLBOT_EXTERNAL_DB=false
## 数据库地址
SQLBOT_DB_HOST=localhost
## 数据库端口 (仅使用外部数据库时才生效)
SQLBOT_DB_PORT=5432
## SQLBot 数据库库名
SQLBOT_DB_DB=sqlbot
## 数据库用户名
SQLBOT_DB_USER=root
## 数据库密码,密码如包含特殊字符,请用双引号引起来,例如 SQLBOT_DB_PASSWORD="Test@4&^%*^"
SQLBOT_DB_PASSWORD=Password123@pg
# 其他配置
## 普通用户默认密码
SQLBOT_DEFAULT_PWD=SQLBot@123456
@@ -31,4 +17,4 @@ SQLBOT_LOG_LEVEL="INFO"
## 缓存类型
SQLBOT_CACHE_TYPE="memory"
## MCP 图片存储路径
SQLBOT_SERVER_IMAGE_HOST=https://YOUR_SERVER_IP:MCP_PORT/images/
SQLBOT_SERVER_IMAGE_HOST=https://YOUR_SERVER_IP:MCP_PORT/images/