mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-09 01:36:57 +08:00
Merge branch 'main' of https://github.com/dataease/SQLBot
This commit is contained in:
+3
-5
@@ -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
|
||||
|
||||
@@ -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/* \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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'''<error-msg>
|
||||
{last_execute_sql_error}
|
||||
</error-msg>'''
|
||||
else:
|
||||
self.chat_question.error_msg = ''
|
||||
|
||||
self.init_messages()
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -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%[email protected]:3306/sqlbot'
|
||||
|
||||
@@ -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 未启用缓存, 可使用多进程模式")
|
||||
|
||||
|
||||
|
||||
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
|
||||
@@ -98,7 +98,7 @@ template:
|
||||
- 柱状图或折线图:适合展示在横轴的字段优先排序,若SQL包含分类字段,则分类字段次一级排序
|
||||
</rule>
|
||||
<rule>
|
||||
如果用户没有指定数据条数的限制,输出的查询SQL需要加上1000条的数据条数限制
|
||||
如果用户没有指定数据条数的限制,输出的查询SQL必须加上1000条的数据条数限制
|
||||
如果用户指定的限制大于1000,则按1000处理
|
||||
<example>
|
||||
以PostgreSQL为例,查询Schema为TEST表TABLE下id字段,则生成的SQL为:
|
||||
@@ -235,6 +235,7 @@ template:
|
||||
{current_time}
|
||||
</current-time>
|
||||
<background-infos>
|
||||
{error_msg}
|
||||
<user-question>
|
||||
{question}
|
||||
</user-question>
|
||||
|
||||
+5
-24
@@ -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:
|
||||
|
||||
@@ -13,6 +13,10 @@ export const dashboardStore = defineStore('dashboard', {
|
||||
canvasViewInfo: {},
|
||||
fullscreenFlag: false,
|
||||
dataPrepareState: false,
|
||||
baseMatrixCount: {
|
||||
x: 72,
|
||||
y: 36,
|
||||
},
|
||||
dashboardInfo: {
|
||||
id: null,
|
||||
name: null,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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')"
|
||||
></component-button-label>
|
||||
<component-button-label
|
||||
:icon-name="dvTab"
|
||||
title="Tab"
|
||||
themes="light"
|
||||
is-label
|
||||
@custom-click="() => emits('addComponent', 'SQTab')"
|
||||
@custom-click="() => emits('addComponents', 'SQTab')"
|
||||
>
|
||||
</component-button-label>
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="editor-content" :class="{ 'editor-content-fullscreen': fullscreenFlag }">
|
||||
<div class="editor-main">
|
||||
<Toolbar :base-params="baseParams" @add-component="addComponent"></Toolbar>
|
||||
<Toolbar
|
||||
:base-params="baseParams"
|
||||
:find-position-x="findPositionX"
|
||||
@add-components="addComponents"
|
||||
></Toolbar>
|
||||
<DashboardEditor
|
||||
v-if="dataInitState"
|
||||
ref="dashboardEditorInnerRef"
|
||||
|
||||
Reference in New Issue
Block a user