Merge remote-tracking branch 'origin/main'

# Conflicts:
#	frontend/src/views/dashboard/preview/SQPreviewShow.vue
This commit is contained in:
wangjiahao
2025-06-11 17:01:13 +08:00
10 changed files with 63 additions and 44 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat
)
@router.post("/question")
@router.post("/question", operation_id="question")
async def stream_sql(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion):
"""Stream SQL analysis results
+1 -1
View File
@@ -244,7 +244,7 @@ def get_table_schema(session: SessionDep, ds: CoreDatasource) -> str:
db_name = table_objs[0].schema
schema_str += f"【DB_ID】 {db_name}\n【Schema】\n"
for obj in table_objs:
schema_str += f"# Table: {db_name}.{obj.table.table_name}"
schema_str += f"# Table: {db_name}.{obj.table.table_name}" if ds.type != "mysql" else f"# Table: {obj.table.table_name}"
table_comment = ''
if obj.table.custom_comment:
table_comment = obj.table.custom_comment.strip()
+26 -17
View File
@@ -1,6 +1,6 @@
from datetime import datetime
from fastapi import APIRouter
from sqlmodel import select
from sqlmodel import select
from apps.system.models.system_model import AiModelDetail
from apps.system.schemas.system_schema import model_status
from common.core.deps import SessionDep
@@ -13,9 +13,9 @@ router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[AiModelDetail])
async def pager(
session: SessionDep,
pageNum: int,
pageSize: int
session: SessionDep,
pageNum: int,
pageSize: int
):
pagination = PaginationParams(page=pageNum, size=pageSize)
paginator = Paginator(session)
@@ -24,19 +24,21 @@ async def pager(
model=AiModelDetail,
pagination=pagination,
**filters)
@router.get("/{id}", response_model=AiModelDetail)
async def get_model_by_id(
session: SessionDep,
id: int
session: SessionDep,
id: int
):
term = session.get(AiModelDetail, id)
return term
@router.post("", response_model=AiModelDetail)
async def add_model(
session: SessionDep,
creator: AiModelDetail
session: SessionDep,
creator: AiModelDetail
):
data = AiModelDetail.model_validate(creator)
data.create_time = get_timestamp()
@@ -44,10 +46,11 @@ async def add_model(
session.commit()
return creator
@router.put("", response_model=AiModelDetail)
async def update_terminology(
session: SessionDep,
model: AiModelDetail
session: SessionDep,
model: AiModelDetail
):
model.id = int(model.id)
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == model.id)).first()
@@ -58,10 +61,11 @@ async def update_terminology(
session.commit()
return model
@router.delete("/{id}", response_model=AiModelDetail)
async def delete_terminology(
session: SessionDep,
id: int
session: SessionDep,
id: int
):
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == id)).first()
session.delete(term)
@@ -69,7 +73,7 @@ async def delete_terminology(
return {
"message": f"AiModel with ID {id} deleted successfully."
}
@router.patch("/status", response_model=dict)
async def status(session: SessionDep, dto: model_status):
@@ -83,4 +87,9 @@ async def status(session: SessionDep, dto: model_status):
term.status = status
session.add(term)
session.commit()
return {"message": f"AiModel with IDs {ids} updated successfully."}
return {"message": f"AiModel with IDs {ids} updated successfully."}
@router.get("/list", operation_id="get_model_list")
async def get_model_list(session: SessionDep):
return session.query(AiModelDetail).all()
+4 -1
View File
@@ -22,7 +22,10 @@ wlist = [
"*.eot",
"*.otf",
"/mcp",
"/mcp/message"
"/mcp/message",
"/datasource/list",
"/system/aimodel/list",
"/chat/question"
]
class WhitelistChecker:
+3 -2
View File
@@ -49,7 +49,7 @@ mcp = FastApiMCP(
app,
name="SQLBot MCP Server",
description="SQLBot MCP Server",
include_operations=["get_datasource_list"]
include_operations=["get_datasource_list", "get_model_list", "question"]
)
mcp.mount(mcp_app)
@@ -76,7 +76,7 @@ frontend_dist = os.path.abspath("../frontend/dist")
if not os.path.exists(frontend_dist):
logging.warning(f"The front-end build directory does not exist: {frontend_dist}")
logging.warning("Please make sure you have built the front-end project")
else:
@app.get("/", include_in_schema=False)
@@ -92,3 +92,4 @@ if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
# uvicorn.run("main:mcp_app", host="0.0.0.0", port=8001) # mcp server
+5 -5
View File
@@ -46,19 +46,19 @@ template:
您的任务是通过给定的问题和SQL生成 JSON 以进行数据可视化。
请遵守以下规则:
- 如果需要表格,则生成的 JSON 格式应为:
{{"type":"table", "title": "标题", "columns": [{{"name":"中文字段名1", "value": "SQL 查询列 1(有别名用别名)"}}, {{"name": "中文字段名 2", "value": "SQL 查询列 2(有别名用别名)"}}]}}
{{"type":"table", "title": "标题", "columns": [{{"name":"中文字段名1", "value": "SQL 查询列 1(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, {{"name": "中文字段名 2", "value": "SQL 查询列 2(有别名用别名,去掉外层的反引号、双引号、方括号)"}}]}}
必须从 SQL 查询列中提取“columns”。
- 如果需要柱状图,则生成的 JSON 格式应为:
{{"type":"column", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}}
{{"type":"column", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}}
必须从 SQL 查询列中提取“x”和“y”。
- 如果需要条形图,则生成的 JSON 格式应为:
{{"type":"bar", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}}
{{"type":"bar", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}}
必须从 SQL 查询列中提取“x”和“y”。
- 如果需要折线图,则生成的 JSON 格式应为:
{{"type":"line", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称","value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}}
{{"type":"line", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称","value": "SQL 查询 x 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}}
其中“x”和“y”必须从SQL查询列中提取。
- 如果需要饼图,则生成的 JSON 格式应为:
{{"type":"pie", "title": "标题", "axis": {{"y": {{"值轴的中文名称","value":"SQL 查询数值的列(有别名用别名)"}}, "series": {{"name":"分类的中文名称","value":"SQL 查询分类的列(有别名用别名)"}}}}
{{"type":"pie", "title": "标题", "axis": {{"y": {{"值轴的中文名称","value":"SQL 查询数值的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "series": {{"name":"分类的中文名称","value":"SQL 查询分类的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}}
其中“column”必须从SQL查询列中提取。
- 如果答案未知或者与生成JSON无关,则生成的 JSON 格式应为:
{{"type":"error", "reason": "抱歉,我无法回答您的问题。"}}
+3 -4
View File
@@ -149,7 +149,6 @@
import {ref, computed, onMounted} from 'vue'
import {useRouter, useRoute} from 'vue-router'
import {useUserStore} from '@/stores/user'
import folder from '@/assets/svg/folder.svg'
import ds from '@/assets/svg/ds.svg'
import dashboard from '@/assets/svg/dashboard.svg'
import chat from '@/assets/svg/chat.svg'
@@ -184,12 +183,12 @@ const sysRouterList = computed(() => {
const showSubmenu = computed(() => {
return route.path.includes('/system')
})
const workspace = ref('1')
const options = [
// const workspace = ref('1')
/* const options = [
{value: '1', label: 'Default workspace'},
{value: '2', label: 'Workspace 2'},
{value: '3', label: 'Workspace 3'}
]
] */
const currentPageTitle = computed(() => {
if (route.path.includes('/system')) {
return 'System Settings'
+1 -1
View File
@@ -8,7 +8,7 @@ import ds from "@/views/ds/index.vue";
import DashboardEditor from "@/views/dashboard/editor/index.vue";
import Dashboard from "@/views/dashboard/index.vue";
import Model from "@/views/system/model/index.vue";
import User from "@/views/system/user/index.vue";
// import User from "@/views/system/user/index.vue";
import { watchRouter } from "./watch";
const router = createRouter({
history: createWebHashHistory(),
+18 -11
View File
@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
// import { ref } from 'vue'
import { AuthApi } from '@/api/login'
import { useCache } from '@/utils/useCache'
import { i18n } from '@/i18n'
@@ -17,18 +17,19 @@ interface UserState {
language: string
exp: number
time: number
[key: string]: string | number
}
export const UserStore = defineStore('user', {
state: (): UserState => {
return {
token: null,
uid: null,
name: null,
oid: null,
token: '',
uid: '',
name: '',
oid: '',
language: 'zh-CN',
exp: null,
time: null
exp: 0,
time: 0
}
},
getters: {
@@ -69,13 +70,19 @@ export const UserStore = defineStore('user', {
const res: any = await AuthApi.info()
const res_data = res || {}
const keys: string[] = ['uid', 'name', 'oid', 'language', 'exp', 'time']
const keys = ['uid', 'name', 'oid', 'language', 'exp', 'time'] as const
keys.forEach(key => {
const dkey = key === 'uid' ? 'id' : key
this[key] = res_data[dkey]
wsCache.set('user.' + key, this[key])
const value = res_data[dkey]
if (key === 'exp' || key === 'time') {
this[key] = Number(value)
} else {
this[key] = String(value)
}
wsCache.set('user.' + key, value)
})
this.setLanguage(this.language)
},
setToken(token: string) {
+1 -1
View File
@@ -75,7 +75,7 @@ onMounted(() => {
</div>
<div class="ds-row-container">
<template v-for="(item, _index) in dsList" :key="_index">
<DatasourceItemCard :ds="item" @click="selectDs(item)" v-if="_index<3 || item?.id===modelValue"
<DatasourceItemCard :ds="item" @click="selectDs(item)"
class="ds-card" :class="[item?.id===modelValue? 'ds-card-selected': '']"/>
</template>
</div>