This commit is contained in:
dataeaseShu
2025-07-30 15:51:00 +08:00
25 changed files with 49 additions and 126 deletions
-16
View File
@@ -1,16 +1,7 @@
# Before running, please copy .env.example to .env
DOMAIN=localhost
FRONTEND_HOST=http://localhost:5173
ENVIRONMENT=local
PROJECT_NAME="SQLBot"
STACK_NAME=SQLBot
# Backend
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173"
SECRET_KEY=y5txe1mRmS_JpOrUzFzHEu-kIQn3lf7ll0AOv9DQh0s
FIRST_SUPERUSER=[email protected]
FIRST_SUPERUSER_PASSWORD=123456 # Change this to your pwd
TOKEN_KEY="X-SQLBOT-TOKEN"
DEFAULT_PWD="SQLBot@123456"
@@ -22,7 +13,6 @@ LOG_FORMAT="%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s"
SQL_DEBUG=False
CACHE_TYPE="memory"
CACHE_REDIS_URL="redis://127.0.0.1:6379"
# Postgres
POSTGRES_SERVER=localhost
@@ -31,12 +21,6 @@ POSTGRES_DB=sqlbot
POSTGRES_USER=root
POSTGRES_PASSWORD=132456 # Change this to your pwd
SENTRY_DSN=
# Configure these with your own Docker registry images
DOCKER_IMAGE_BACKEND=backend
DOCKER_IMAGE_FRONTEND=frontend
MCP_IMAGE_PATH=/opt/sqlbot/images
MCP_IMAGE_HOST=http://localhost:3000
SERVER_IMAGE_HOST=https://sqlbot.fit2cloud.cn/images/
+11 -2
View File
@@ -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
@@ -834,7 +836,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 +1026,13 @@ class LLMService:
finally:
# end
pass
def validate_history_ds(self):
_invalid_ds = False
_ds = self.ds
_assistant = self.current_assistant
if _invalid_ds:
yield orjson.dumps({'content': 'ds is invalid', 'type': 'error'}).decode() + '\n\n'
def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
"""Execute SQL query using SQLDatabase
+1 -55
View File
@@ -1,19 +1,14 @@
import secrets
import warnings
from typing import Annotated, Any, Literal
from pydantic import (
AnyUrl,
BeforeValidator,
EmailStr,
HttpUrl,
PostgresDsn,
computed_field,
model_validator,
)
from pydantic_core import MultiHostUrl
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing_extensions import Self
def parse_cors(v: Any) -> list[str] | str:
@@ -31,12 +26,12 @@ class Settings(BaseSettings):
env_ignore_empty=True,
extra="ignore",
)
PROJECT_NAME: str = "SQLBot"
API_V1_STR: str = "/api/v1"
SECRET_KEY: str = secrets.token_urlsafe(32)
# 60 minutes * 24 hours * 8 days = 8 days
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
FRONTEND_HOST: str = "http://localhost:5173"
ENVIRONMENT: Literal["local", "staging", "production"] = "local"
BACKEND_CORS_ORIGINS: Annotated[
list[AnyUrl] | str, BeforeValidator(parse_cors)
@@ -49,8 +44,6 @@ class Settings(BaseSettings):
self.FRONTEND_HOST
]
PROJECT_NAME: str
SENTRY_DSN: HttpUrl | None = None
POSTGRES_SERVER: str
POSTGRES_PORT: int = 5432
POSTGRES_USER: str
@@ -81,55 +74,8 @@ class Settings(BaseSettings):
path=self.POSTGRES_DB,
)
SMTP_TLS: bool = True
SMTP_SSL: bool = False
SMTP_PORT: int = 587
SMTP_HOST: str | None = None
SMTP_USER: str | None = None
SMTP_PASSWORD: str | None = None
EMAILS_FROM_EMAIL: EmailStr | None = None
EMAILS_FROM_NAME: EmailStr | None = None
MCP_IMAGE_PATH: str
MCP_IMAGE_HOST: str
SERVER_IMAGE_HOST: str
@model_validator(mode="after")
def _set_default_emails_from(self) -> Self:
if not self.EMAILS_FROM_NAME:
self.EMAILS_FROM_NAME = self.PROJECT_NAME
return self
EMAIL_RESET_TOKEN_EXPIRE_HOURS: int = 48
@computed_field # type: ignore[prop-decorator]
@property
def emails_enabled(self) -> bool:
return bool(self.SMTP_HOST and self.EMAILS_FROM_EMAIL)
EMAIL_TEST_USER: EmailStr = "[email protected]"
FIRST_SUPERUSER: EmailStr
FIRST_SUPERUSER_PASSWORD: str
def _check_default_secret(self, var_name: str, value: str | None) -> None:
if value == "changethis":
message = (
f'The value of {var_name} is "changethis", '
"for security, please change it, at least for deployments."
)
if self.ENVIRONMENT == "local":
warnings.warn(message, stacklevel=1)
else:
raise ValueError(message)
@model_validator(mode="after")
def _enforce_non_default_secrets(self) -> Self:
self._check_default_secret("SECRET_KEY", self.SECRET_KEY)
self._check_default_secret("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD)
self._check_default_secret(
"FIRST_SUPERUSER_PASSWORD", self.FIRST_SUPERUSER_PASSWORD
)
return self
settings = Settings() # type: ignore
-4
View File
@@ -1,5 +1,4 @@
from fastapi.concurrency import asynccontextmanager
import sentry_sdk
from fastapi import FastAPI
from fastapi.routing import APIRoute
from starlette.middleware.cors import CORSMiddleware
@@ -38,9 +37,6 @@ def custom_generate_unique_id(route: APIRoute) -> str:
return f"{tag}-{route.name}"
if settings.SENTRY_DSN and settings.ENVIRONMENT != "local":
sentry_sdk.init(dsn=str(settings.SENTRY_DSN), enable_tracing=True)
app = FastAPI(
title=settings.PROJECT_NAME,
openapi_url=f"{settings.API_V1_STR}/openapi.json",
+1
View File
@@ -6,6 +6,7 @@ template:
任务:
根据给定的表结构(M-Schema)和用户问题生成符合{engine}数据库引擎规范的sql语句,以及sql中所用到的表名(不要包含schema和database,用数组返回)。
你必须遵守以下规则:
- 不要编造没有提供给你的表结构
- 生成的SQL必须符合{engine}的规范。
- 你的回答必须使用如下JSON格式返回:
{{"success":true,"sql":"生成的SQL语句","tables":["表名1","表名2",...]}}
+6 -7
View File
@@ -22,7 +22,7 @@
<path d="M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z" fill="#ffffff"></path>
</svg>
</div>
<div class="sqlbot-assistant-title"> 🌟 遇见问题,不再有障碍!</div>
<p>你好,我是你的智能小助手。<br/>
点我,开启高效解答模式,让问题变成过去式。</p>
@@ -217,9 +217,9 @@
bottom: 0!important;
}
}
/* 引导 */
#sqlbot-assistant .sqlbot-assistant-mask {
position: fixed;
z-index: 10001;
@@ -289,7 +289,7 @@
right: 20px;
top: 20px;
cursor: pointer;
}
#sqlbot-assistant-chat-container {
width: 460px;
@@ -303,7 +303,7 @@
right: 0 !important;
}
}
#sqlbot-assistant .sqlbot-assistant-chat-button{
position: fixed;
${data.x_type}: ${data.x_value}px;
@@ -362,7 +362,7 @@
from {
height: 0;;
}
to {
height: 600px;
}
@@ -433,7 +433,6 @@
}
if (event.data?.busi == 'ready' && event.data?.ready) {
const certificate = parsrCertificate(data)
console.log(certificate)
params = {
busi: 'certificate',
certificate,
@@ -58,8 +58,6 @@ const valueText = (field: string, val: string, options: any[]) => {
return val
}
export const convertFilterText = (conditions: any[], options: any[]) => {
console.log('conditions', conditions, options)
const result: any[] = []
conditions.forEach((condition) => {
const field = condition.field
+1 -1
View File
@@ -44,7 +44,7 @@
"add_dashboard_name_tips": "请输入仪表板名称",
"existing_dashboard": "存量仪表板",
"add_success": "添加成功",
"no_data": "没找到相关内容",
"no_data": "没有找到相关内容",
"new_tab": "新建Tab",
"length_limit64": "字段长度需要在1-64之间",
"sort_column": "排序字段",
+1 -1
View File
@@ -31,7 +31,7 @@ export const watchRouter = (router: any) => {
return
}
if (to.path === '/login') {
console.log(from)
console.info(from)
next('/chat')
} else {
next()
@@ -133,9 +133,6 @@ const sendMessage = async () => {
_list.push(line)
}
}
// console.log(_list)
for (const str of _list) {
let data
try {
@@ -161,7 +158,7 @@ const sendMessage = async () => {
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.log(data.msg)
console.info(data.msg)
break
case 'error':
currentRecord.error = data.content
@@ -137,9 +137,6 @@ const sendMessage = async () => {
_list.push(line)
}
}
// console.log(_list)
for (const str of _list) {
let data
try {
@@ -165,7 +162,7 @@ const sendMessage = async () => {
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.log(data.msg)
console.info(data.msg)
break
case 'brief':
_currentChat.value.brief = data.brief
@@ -133,9 +133,6 @@ const sendMessage = async () => {
_list.push(line)
}
}
// console.log(_list)
for (const str of _list) {
let data
try {
@@ -160,7 +157,7 @@ const sendMessage = async () => {
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.log(data.msg)
console.info(data.msg)
break
case 'error':
currentRecord.error = data.content
@@ -8,10 +8,10 @@ const props = defineProps<{
const { t } = useI18n()
function clickAnalysis() {
console.log('analysis_record_id: ' + props.message?.record?.analysis_record_id)
console.info('analysis_record_id: ' + props.message?.record?.analysis_record_id)
}
function clickPredict() {
console.log('predict_record_id: ' + props.message?.record?.predict_record_id)
console.info('predict_record_id: ' + props.message?.record?.predict_record_id)
}
</script>
+2 -2
View File
@@ -461,11 +461,11 @@ const currentChatEngineType = computed(() => {
})
function onChatDeleted(id: number) {
console.log('deleted', id)
console.info('deleted', id)
}
function onChatRenamed(chat: Chat) {
console.log('renamed', chat)
console.info('renamed', chat)
}
const chatListSideBarShow = ref<boolean>(true)
@@ -52,7 +52,7 @@ const initOptions = {
}
const handleInit = (editor: any) => {
editorRef.value = editor
console.log('TinyMCE 初始化完成', editor)
console.info('TinyMCE 初始化完成', editor)
}
</script>
+2 -2
View File
@@ -126,11 +126,11 @@ const refreshData = () => {
}
const panelClick = () => {
console.log('panelClick')
console.info('panelClick')
}
const smartClick = () => {
console.log('smartClick')
console.info('smartClick')
}
const deleteHandler = (item: any) => {
+2 -3
View File
@@ -114,11 +114,10 @@ const state = reactive({
},
})
const handleSearch = (e: any) => {
console.log('search', e)
console.info('search', e)
}
const editHandler = (id: any) => {
termApi.query(id).then((res: any) => {
console.log('term detail', res)
state.form = res
dialogFormVisible.value = true
})
@@ -181,7 +180,7 @@ const addTerm = () => {
}
const editTerm = () => {
termApi.edit(state.form).then((res: any) => {
console.log('edit term', res)
console.info('edit term', res)
dialogFormVisible.value = false
search()
ElMessage({
+1 -1
View File
@@ -31,7 +31,7 @@ import Professional from './Professional.vue'
const activeName = ref('0')
const handleClick = (tab: any, event: MouseEvent) => {
console.log(tab, event)
console.info(tab, event)
}
</script>
+1 -1
View File
@@ -164,7 +164,7 @@ const beforeClose = () => {
}
const handleActive = (row: any) => {
console.log('row', row)
console.info('row', row)
}
const handlePrivate = (row: any) => {
+5 -2
View File
@@ -35,7 +35,7 @@ const activeName = ref('')
const activeType = ref('')
const modelFormRef = ref()
const cardRefs = ref<any[]>([])
const showCardError = ref(true) // if you don`t want card mask error, just change this to false
// const showCardError = ref(true) // if you don`t want card mask error, just change this to false
reactive({
form: {
id: '',
@@ -71,6 +71,8 @@ const defaultModelListWithSearch = computed(() => {
})
const modelCheckHandler = async (item: any) => {
console.log(item)
/**
const response = await modelApi.check(item)
const reader = response.body.getReader()
const decoder = new TextDecoder()
@@ -110,6 +112,7 @@ const modelCheckHandler = async (item: any) => {
currentRef?.showErrorMask(checkMsg)
}
})
*/
}
const duplicateName = async (item: any) => {
const res = await modelApi.queryAll()
@@ -201,7 +204,7 @@ const deleteHandler = (item: any) => {
customClass: 'confirm-no_icon',
autofocus: false,
callback: (val: string) => {
console.log(val)
console.info(val)
},
})
return
@@ -93,7 +93,7 @@ const filterTypeChange = () => {
initEnumOptions()
}
const initEnumOptions = () => {
console.log('initEnumOptions')
console.info('initEnumOptions')
}
const selectItem = ({ field_name, id }: any) => {
+1 -1
View File
@@ -552,7 +552,7 @@ const handleEditPassword = (id: any) => {
const handleConfirmPassword = () => {
passwordRef.value.validate((val: any) => {
if (val) {
console.log(val)
console.info(val)
}
})
dialogVisiblePassword.value = false
+4 -4
View File
@@ -152,10 +152,10 @@ const state = reactive({
},
})
const handleSearch = (e: any) => {
console.log('search', e)
console.info('search', e)
}
const editHandler = (id: any) => {
console.log('editHandler', id)
console.info('editHandler', id)
/* userApi.query(id).then((res: any) => {
console.log('term detail', res)
state.form = res
@@ -163,7 +163,7 @@ const editHandler = (id: any) => {
}) */
}
const deleteHandler = (id: any) => {
console.log('deleteHandler', id)
console.info('deleteHandler', id)
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
@@ -222,7 +222,7 @@ const addTerm = () => {
}
const editTerm = () => {
userApi.edit(state.form).then((res: any) => {
console.log('edit term', res)
console.info('edit term', res)
dialogFormVisible.value = false
search()
ElMessage({
+2 -2
View File
@@ -7,8 +7,8 @@ import path from 'path'
import svgLoader from 'vite-svg-loader'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
console.log(mode)
console.log(env)
console.info(mode)
console.info(env)
return {
base: './',
plugins: [
+1 -4
View File
@@ -17,7 +17,7 @@ http.createServer((req, res) => {
toPost(req, res);
}
}).listen(port, () => {
console.log(`Server listening on: http://localhost:${port}`);
console.info(`Server listening on: http://localhost:${port}`);
});
function getOptions(type, axis, data) {
@@ -50,9 +50,7 @@ function getOptions(type, axis, data) {
// 创建 Chart 和配置
async function GenerateCharts(obj) {
console.log(obj)
const options = getOptions(obj.type, JSON.parse(obj.axis), JSON.parse(obj.data));
console.log(options);
const chart = await createChart(options);
// 导出
@@ -69,7 +67,6 @@ async function GenerateCharts(obj) {
function toGet(req, res) {
let data = 'GET请求内容:\n' + util.inspect(url.parse(req.url));
res.end(data);
console.log(data);
}
//获取POST请求内容、cookie