diff --git a/Dockerfile b/Dockerfile
index 930b6350..a33a28e8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -54,11 +54,9 @@ ENV SQLBOT_HOME=/opt/sqlbot
ENV PYTHONPATH=${SQLBOT_HOME}/app
ENV PATH="${SQLBOT_HOME}/app/.venv/bin:$PATH"
-# ENV POSTGRES_SERVER=localhost
-# ENV POSTGRES_PORT=5432
-# ENV POSTGRES_DB=sqlbot
-# ENV POSTGRES_USER=root
-# ENV POSTGRES_PASSWORD=Password123@pg
+ENV POSTGRES_DB=sqlbot
+ENV POSTGRES_USER=root
+ENV POSTGRES_PASSWORD=Password123@pg
# Copy necessary files from builder
COPY start.sh /opt/sqlbot/app/start.sh
diff --git a/Dockerfile-base b/Dockerfile-base
index 259e560c..89d2b711 100644
--- a/Dockerfile-base
+++ b/Dockerfile-base
@@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libgif-dev \
librsvg2-dev \
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
+ && curl -qL https://www.npmjs.com/install.sh | sh - \
&& apt-get install -y nodejs \
&& curl -L --connect-timeout 60 -m 1800 https://fit2cloud-support.oss-cn-beijing.aliyuncs.com/xpack-license/get-validator-linux | sh \
&& rm -rf /var/lib/apt/lists/* \
diff --git a/README.md b/README.md
index ea21a73c..f28a3624 100644
--- a/README.md
+++ b/README.md
@@ -19,18 +19,19 @@ SQLBot 是一款基于大模型和 RAG 的智能问数系统。SQLBot 的优势
### 安装部署
准备一台 Linux 服务器,执行以下一键安装脚本。
-在运行 SQLBot 前,请确保已安装好 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/)。
+在运行 SQLBot 前,请确保已安装好 [Docker](https://docs.docker.com/get-docker/)。
```bash
-# 创建目录
-mkdir -p /opt/sqlbot
-cd /opt/sqlbot
-
-# 下载 docker-compose.yaml
-curl -o docker-compose.yaml https://raw.githubusercontent.com/dataease/SQLBot/main/docker-compose.yaml
-
-# 启动服务
-docker compose up -d
+docker run -d \
+ --name sqlbot \
+ --restart unless-stopped \
+ -p 8000:8000 \
+ -p 8001:8001 \
+ -v ./data/sqlbot/excel:/opt/sqlbot/data/excel \
+ -v ./data/sqlbot/images:/opt/sqlbot/images \
+ -v ./data/sqlbot/logs:/opt/sqlbot/logs \
+ -v ./data/postgresql:/var/lib/postgresql/data \
+ dataease/sqlbot:v1.1.1
```
你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 SQLBot。
@@ -58,8 +59,8 @@ docker compose up -d
## Roadmap
-- 术语库:开发中...
- SQL 示例库:开发中...
+- 自定义提示词:开发中...
## Star History
diff --git a/backend/apps/chat/curd/chat.py b/backend/apps/chat/curd/chat.py
index cebcc223..01081553 100644
--- a/backend/apps/chat/curd/chat.py
+++ b/backend/apps/chat/curd/chat.py
@@ -71,6 +71,21 @@ def get_chart_config(session: SessionDep, chart_record_id: int):
return {}
+def get_last_execute_sql_error(session: SessionDep, chart_id: int):
+ stmt = select(ChatRecord.error).where(and_(ChatRecord.chat_id == chart_id)).order_by(
+ ChatRecord.create_time.desc()).limit(1)
+ res = session.execute(stmt).scalar()
+ if res:
+ try:
+ obj = orjson.loads(res)
+ if obj.get('type') and obj.get('type') == 'exec-sql-err':
+ return obj.get('traceback')
+ except Exception:
+ pass
+
+ return None
+
+
def get_chat_chart_data(session: SessionDep, chart_record_id: int):
stmt = select(ChatRecord.data).where(and_(ChatRecord.id == chart_record_id))
res = session.execute(stmt)
@@ -701,7 +716,8 @@ def get_old_questions(session: SessionDep, datasource: int):
if not datasource:
return records
stmt = select(ChatRecord.question).where(
- and_(ChatRecord.datasource == datasource, ChatRecord.question.isnot(None), ChatRecord.error.is_(None))).order_by(
+ and_(ChatRecord.datasource == datasource, ChatRecord.question.isnot(None),
+ ChatRecord.error.is_(None))).order_by(
ChatRecord.create_time.desc()).limit(20)
result = session.execute(stmt)
for r in result:
diff --git a/backend/apps/chat/models/chat_model.py b/backend/apps/chat/models/chat_model.py
index 0057e9cd..eb418251 100644
--- a/backend/apps/chat/models/chat_model.py
+++ b/backend/apps/chat/models/chat_model.py
@@ -159,6 +159,7 @@ class ChatInfo(BaseModel):
class AiModelQuestion(BaseModel):
+ question: str = None
ai_modal_id: int = None
ai_modal_name: str = None # Specific model name
engine: str = ""
@@ -171,6 +172,7 @@ class AiModelQuestion(BaseModel):
filter: str = []
sub_query: Optional[list[dict]] = None
terminologies: str = ""
+ error_msg: str = ""
def sql_sys_question(self):
return get_sql_template()['system'].format(engine=self.engine, schema=self.db_schema, question=self.question,
@@ -178,7 +180,7 @@ class AiModelQuestion(BaseModel):
def sql_user_question(self, current_time: str):
return get_sql_template()['user'].format(engine=self.engine, schema=self.db_schema, question=self.question,
- rule=self.rule, current_time=current_time)
+ rule=self.rule, current_time=current_time, error_msg=self.error_msg)
def chart_sys_question(self):
return get_chart_template()['system'].format(sql=self.sql, question=self.question, lang=self.lang)
@@ -226,7 +228,6 @@ class AiModelQuestion(BaseModel):
class ChatQuestion(AiModelQuestion):
- question: str
chat_id: int
diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py
index b99275d8..0237a4c3 100644
--- a/backend/apps/chat/task/llm.py
+++ b/backend/apps/chat/task/llm.py
@@ -24,7 +24,8 @@ from apps.chat.curd.chat import save_question, save_sql_answer, save_sql, \
finish_record, save_analysis_answer, save_predict_answer, save_predict_data, \
save_select_datasource_answer, save_recommend_question_answer, \
get_old_questions, save_analysis_predict_record, rename_chat, get_chart_config, \
- get_chat_chart_data, list_generate_sql_logs, list_generate_chart_logs, start_log, end_log
+ get_chat_chart_data, list_generate_sql_logs, list_generate_chart_logs, start_log, end_log, \
+ get_last_execute_sql_error
from apps.chat.models.chat_model import ChatQuestion, ChatRecord, Chat, RenameChat, ChatLog, OperationEnum
from apps.datasource.crud.datasource import get_table_schema
from apps.datasource.crud.permission import get_row_permission_filters, is_normal_user
@@ -70,6 +71,8 @@ class LLMService:
chunk_list: List[str] = []
future: Future
+ last_execute_sql_error: str = None
+
def __init__(self, current_user: CurrentUser, chat_question: ChatQuestion,
current_assistant: Optional[CurrentAssistant] = None, no_reasoning: bool = False,
config: LLMConfig = None):
@@ -127,6 +130,15 @@ class LLMService:
llm_instance = LLMFactory.create_llm(self.config)
self.llm = llm_instance.llm
+ # get last_execute_sql_error
+ last_execute_sql_error = get_last_execute_sql_error(self.session, self.chat_question.chat_id)
+ if last_execute_sql_error:
+ self.chat_question.error_msg = f'''
+{last_execute_sql_error}
+'''
+ else:
+ self.chat_question.error_msg = ''
+
self.init_messages()
@classmethod
diff --git a/backend/common/core/config.py b/backend/common/core/config.py
index 920ee73f..ac0eb8d7 100644
--- a/backend/common/core/config.py
+++ b/backend/common/core/config.py
@@ -47,7 +47,7 @@ class Settings(BaseSettings):
POSTGRES_SERVER: str = 'localhost'
POSTGRES_PORT: int = 5432
POSTGRES_USER: str = 'root'
- POSTGRES_PASSWORD: str = "123456"
+ POSTGRES_PASSWORD: str = "Password123@pg"
POSTGRES_DB: str = "sqlbot"
SQLBOT_DB_URL: str = ''
# SQLBOT_DB_URL: str = 'mysql+pymysql://root:Password123%40mysql@127.0.0.1:3306/sqlbot'
diff --git a/backend/common/core/sqlbot_cache.py b/backend/common/core/sqlbot_cache.py
index 6c827207..15018019 100644
--- a/backend/common/core/sqlbot_cache.py
+++ b/backend/common/core/sqlbot_cache.py
@@ -68,7 +68,7 @@ def cache(
@wraps(func)
async def wrapper(*args, **kwargs):
- if not settings.CACHE_TYPE or settings.CACHE_TYPE.lower() == "none":
+ if not settings.CACHE_TYPE or settings.CACHE_TYPE.lower() == "none" or not is_cache_initialized():
return await func(*args, **kwargs)
# 生成缓存键
cache_key = used_key_builder(
@@ -96,7 +96,7 @@ def clear_cache(
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
- if not settings.CACHE_TYPE or settings.CACHE_TYPE.lower() == "none":
+ if not settings.CACHE_TYPE or settings.CACHE_TYPE.lower() == "none" or not is_cache_initialized():
return await func(*args, **kwargs)
cache_key = custom_key_builder(
func=func,
@@ -138,4 +138,21 @@ def init_sqlbot_cache():
SQLBotLogUtil.info(f"SQLBot 使用Redis缓存, 可使用多进程模式")
else:
SQLBotLogUtil.warning("SQLBot 未启用缓存, 可使用多进程模式")
-
\ No newline at end of file
+
+
+def is_cache_initialized() -> bool:
+ # 检查必要的属性是否存在
+ if not hasattr(FastAPICache, "_backend") or not hasattr(FastAPICache, "_prefix"):
+ return False
+
+ # 检查属性值是否为 None
+ if FastAPICache._backend is None or FastAPICache._prefix is None:
+ return False
+
+ # 尝试获取后端确认
+ try:
+ backend = FastAPICache.get_backend()
+ return backend is not None
+ except (AssertionError, AttributeError, Exception) as e:
+ SQLBotLogUtil.debug(f"缓存初始化检查失败: {str(e)}")
+ return False
\ No newline at end of file
diff --git a/backend/template.yaml b/backend/template.yaml
index 5d16992c..dd182300 100644
--- a/backend/template.yaml
+++ b/backend/template.yaml
@@ -98,7 +98,7 @@ template:
- 柱状图或折线图:适合展示在横轴的字段优先排序,若SQL包含分类字段,则分类字段次一级排序
- 如果用户没有指定数据条数的限制,输出的查询SQL需要加上1000条的数据条数限制
+ 如果用户没有指定数据条数的限制,输出的查询SQL必须加上1000条的数据条数限制
如果用户指定的限制大于1000,则按1000处理
以PostgreSQL为例,查询Schema为TEST表TABLE下id字段,则生成的SQL为:
@@ -235,6 +235,7 @@ template:
{current_time}
+ {error_msg}
{question}
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 71b177df..73bd99ca 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,6 +1,6 @@
services:
sqlbot:
- image: dataease/sqlbot:v1.0.0
+ image: dataease/sqlbot:v1.1.0
container_name: sqlbot
restart: always
networks:
@@ -10,11 +10,11 @@ services:
- 8001:8001
environment:
# Database configuration
- POSTGRES_SERVER: sqlbot-db
+ POSTGRES_SERVER: localhost
POSTGRES_PORT: 5432
POSTGRES_DB: sqlbot
- POSTGRES_USER: sqlbot
- POSTGRES_PASSWORD: sqlbot
+ POSTGRES_USER: root
+ POSTGRES_PASSWORD: Password123@pg
# Project basic settings
PROJECT_NAME: "SQLBot"
DEFAULT_PWD: "SQLBot@123456"
@@ -31,26 +31,7 @@ services:
- ./data/sqlbot/excel:/opt/sqlbot/data/excel
- ./data/sqlbot/images:/opt/sqlbot/images
- ./data/sqlbot/logs:/opt/sqlbot/logs
- depends_on:
- sqlbot-db:
- condition: service_healthy
-
- sqlbot-db:
- image: postgres:17.5
- container_name: sqlbot-db
- restart: always
- networks:
- - sqlbot-network
- volumes:
- ./data/postgresql:/var/lib/postgresql/data
- environment:
- POSTGRES_DB: sqlbot
- POSTGRES_USER: sqlbot
- POSTGRES_PASSWORD: sqlbot
- healthcheck:
- test: ["CMD-SHELL", "pg_isready"]
- interval: 3s
- timeout: 5s
- retries: 5
+
networks:
sqlbot-network:
diff --git a/frontend/src/stores/dashboard/dashboard.ts b/frontend/src/stores/dashboard/dashboard.ts
index 2504e784..6e593680 100644
--- a/frontend/src/stores/dashboard/dashboard.ts
+++ b/frontend/src/stores/dashboard/dashboard.ts
@@ -13,6 +13,10 @@ export const dashboardStore = defineStore('dashboard', {
canvasViewInfo: {},
fullscreenFlag: false,
dataPrepareState: false,
+ baseMatrixCount: {
+ x: 72,
+ y: 36,
+ },
dashboardInfo: {
id: null,
name: null,
diff --git a/frontend/src/views/dashboard/canvas/CanvasCore.vue b/frontend/src/views/dashboard/canvas/CanvasCore.vue
index e574ebb6..dac6b9e3 100644
--- a/frontend/src/views/dashboard/canvas/CanvasCore.vue
+++ b/frontend/src/views/dashboard/canvas/CanvasCore.vue
@@ -1069,8 +1069,6 @@ const forceComputed = () => {
}
function addItemBox(item: CanvasItem) {
- // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
- item.x = findPositionX(item)
canvasComponentData.value.push(item)
forceComputed()
nextTick(() => {
@@ -1177,8 +1175,7 @@ function tabMoveInCheckSQ() {
/**
* Find position box
*/
-function findPositionX(item: CanvasItem) {
- const width = item.sizeX
+function findPositionX(width: number) {
let resultX = 1
let checkPointYIndex = -1 // -1 means not occupying any Y-direction canvas
// Component width
@@ -1245,6 +1242,7 @@ defineExpose({
removeItem,
findClosetCoords,
makeCoordinate,
+ findPositionX,
})
diff --git a/frontend/src/views/dashboard/editor/DashboardEditor.vue b/frontend/src/views/dashboard/editor/DashboardEditor.vue
index d4a4a49a..6f7fd5db 100644
--- a/frontend/src/views/dashboard/editor/DashboardEditor.vue
+++ b/frontend/src/views/dashboard/editor/DashboardEditor.vue
@@ -85,6 +85,11 @@ const addItemToBox = (item: CanvasItem) => {
canvasCoreRef.value.addItemBox(item)
}
+const findPositionX = (width: number) => {
+ // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ return canvasCoreRef.value.findPositionX(width)
+}
+
useEmitt({
name: 'custom-canvas-resize',
callback: canvasSizeInit,
@@ -93,6 +98,7 @@ useEmitt({
defineExpose({
canvasSizeInit,
addItemToBox,
+ findPositionX,
})
onMounted(() => {
diff --git a/frontend/src/views/dashboard/editor/Toolbar.vue b/frontend/src/views/dashboard/editor/Toolbar.vue
index 4dc96031..74830acd 100644
--- a/frontend/src/views/dashboard/editor/Toolbar.vue
+++ b/frontend/src/views/dashboard/editor/Toolbar.vue
@@ -22,7 +22,7 @@ const { dashboardInfo, fullscreenFlag } = storeToRefs(dashboardStore)
const snapshotStore = snapshotStoreWithOut()
const { snapshotIndex } = storeToRefs(snapshotStore)
-const emits = defineEmits(['addComponent'])
+const emits = defineEmits(['addComponents'])
const resourceGroupOptRef = ref(null)
const chatChartSelectionRef = ref(null)
const openViewDialog = () => {
@@ -139,10 +139,11 @@ const backToMain = () => {
}
const addChatChart = (views: any) => {
+ emits('addComponents', 'SQView', views)
views.forEach((view: any) => {
const target = cloneDeep(view)
delete target.chart.sourceType
- emits('addComponent', 'SQView', target)
+ emits('addComponents', 'SQView', target)
})
ElMessage({
type: 'success',
@@ -212,14 +213,14 @@ const previewInner = () => {
:title="t('dashboard.text')"
themes="light"
is-label
- @custom-click="() => emits('addComponent', 'SQText')"
+ @custom-click="() => emits('addComponents', 'SQText')"
>
emits('addComponent', 'SQTab')"
+ @custom-click="() => emits('addComponents', 'SQTab')"
>
diff --git a/frontend/src/views/dashboard/editor/index.vue b/frontend/src/views/dashboard/editor/index.vue
index 98c80b3e..f5167e09 100644
--- a/frontend/src/views/dashboard/editor/index.vue
+++ b/frontend/src/views/dashboard/editor/index.vue
@@ -13,7 +13,8 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const dashboardStore = dashboardStoreWithOut()
-const { componentData, canvasViewInfo, fullscreenFlag } = storeToRefs(dashboardStore)
+const { componentData, canvasViewInfo, fullscreenFlag, baseMatrixCount } =
+ storeToRefs(dashboardStore)
const dataInitState = ref(true)
const state = reactive({
@@ -23,8 +24,26 @@ const state = reactive({
})
const dashboardEditorInnerRef = ref(null)
-const addComponent = (componentType: string, viewInfo?: any) => {
+const addComponents = (componentType: string, views?: any) => {
const component = cloneDeep(findNewComponentFromList(componentType))
+ // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ component.x = findPositionX(component.sizeX)
+ if (views) {
+ views.forEach((view: any, index: number) => {
+ const target = cloneDeep(view)
+ delete target.chart.sourceType
+ if (index > 0) {
+ // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ component.x = ((component.x + component?.sizeX - 1) % baseMatrixCount.value.x) + 1
+ }
+ addComponent(component, target)
+ })
+ } else {
+ addComponent(component)
+ }
+}
+const addComponent = (componentSource: any, viewInfo?: any) => {
+ const component = cloneDeep(componentSource)
if (component && dashboardEditorInnerRef.value) {
component.id = guid()
// add view
@@ -34,9 +53,7 @@ const addComponent = (componentType: string, viewInfo?: any) => {
dashboardStore.addCanvasViewInfo(viewInfo)
} else if (component.component === 'SQTab') {
const subTabName = guid('tab')
- // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
component.propValue[0].name = subTabName
- // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
component.propValue[0].title = t('dashboard.new_tab')
component.activeTabName = subTabName
}
@@ -85,12 +102,20 @@ const baseParams = computed(() => {
pid: state.routerPid,
}
})
+const findPositionX = (width: number) => {
+ // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ return dashboardEditorInnerRef.value.findPositionX(width)
+}