Merge remote-tracking branch 'origin/main'

This commit is contained in:
wangjiahao
2025-07-21 15:37:44 +08:00
8 changed files with 169 additions and 34 deletions
@@ -0,0 +1,43 @@
"""030_permission_oid
Revision ID: c1d7ac00b3a8
Revises: 77d4c39ec22f
Create Date: 2025-07-21 11:49:43.115524
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'c1d7ac00b3a8'
down_revision = '77d4c39ec22f'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('ds_rules', sa.Column('oid', sa.BigInteger(), nullable=True))
op.alter_column('ds_permission', 'id',
existing_type=sa.INTEGER(),
type_=sa.BigInteger(),
existing_nullable=False,
autoincrement=True,
existing_server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1,
maxvalue=2147483647, cycle=False, cache=1))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('ds_permission', 'id',
existing_type=sa.BigInteger(),
type_=sa.INTEGER(),
existing_nullable=False,
autoincrement=True,
existing_server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1,
maxvalue=2147483647, cycle=False, cache=1))
op.drop_column('ds_rules', 'oid')
# ### end Alembic commands ###
+3 -2
View File
@@ -11,7 +11,7 @@ import requests
from langchain.chat_models.base import BaseChatModel
from langchain_community.utilities import SQLDatabase
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage, AIMessage, BaseMessageChunk
from sqlalchemy import and_, cast
from sqlalchemy import and_, cast, or_
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import load_only
@@ -489,7 +489,8 @@ class LLMService:
# check permission and user in same rules
obj = self.session.query(DsRules).filter(
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
DsRules.user_list.op('@>')(cast([self.current_user.id], JSONB)))
or_(DsRules.user_list.op('@>')(cast([f'{self.current_user.id}'], JSONB)),
DsRules.user_list.op('@>')(cast([self.current_user.id], JSONB))))
).first()
if obj is not None:
res.append(transRecord2DTO(self.session, permission))
+3 -2
View File
@@ -3,7 +3,7 @@ import json
from typing import List
from fastapi import HTTPException
from sqlalchemy import and_, text, cast
from sqlalchemy import and_, text, cast, or_
from sqlalchemy.dialects.postgresql import JSONB
from sqlbot_xpack.permissions.models.ds_permission import DsPermission
from sqlbot_xpack.permissions.models.ds_rules import DsRules
@@ -295,7 +295,8 @@ def get_table_obj_by_ds(session: SessionDep, current_user: CurrentUser, ds: Core
# check permission and user in same rules
obj = session.query(DsRules).filter(
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
DsRules.user_list.op('@>')(cast([current_user.id], JSONB)))
or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)),
DsRules.user_list.op('@>')(cast([current_user.id], JSONB))))
).first()
if obj is not None:
permission_list = json.loads(permission.permissions)
+1 -1
View File
@@ -36,7 +36,7 @@ dependencies = [
"pyyaml (>=6.0.2,<7.0.0)",
"fastapi-mcp (>=0.3.4,<0.4.0)",
"tabulate>=0.9.0",
"sqlbot-xpack==0.0.3.5",
"sqlbot-xpack==0.0.3.6",
"fastapi-cache2>=0.2.2",
"sqlparse>=0.5.3",
]
+6 -2
View File
@@ -83,7 +83,7 @@
"error": "An error occurred, please try again later",
"question_placeholder": "Press Enter to submit, or use Ctrl + Enter for a new line",
"greeting": "Hello, I'm SQLBot. How can I assist you today?",
"hint_description": "I can query data, generate charts, detect anomalies, predict trends, and more. Please select a data source to begin!",
"hint_description": "You can query data, generate charts, analyze data, predict trends, and more. Please select a data source to start your smart data exploration~",
"select_datasource": "Select Data Source",
"view_more": "View More",
"selected_datasource": "Selected Data Source",
@@ -96,7 +96,11 @@
"chat_search": "Search",
"thinking": "Thinking",
"thinking_step": "Thought Process",
"ask_again": "Regenerate"
"ask_again": "Regenerate",
"today": "Today",
"week": "This Week",
"earlier": "Earlier",
"no_time": "No Time"
},
"ds": {
"title": "Data Source",
+6 -2
View File
@@ -90,7 +90,7 @@
"error": "发生错误,请稍后再试",
"question_placeholder": "按下 Enter 提交问题, 或使用 Ctrl + Enter 换行",
"greeting": "你好,我是SQLBot,很高兴为你服务",
"hint_description": "我可以查询数据、生成图表、检测数据异常、预测数据等,请选择一个数据源,开启智能问数吧~",
"hint_description": "我可以查询数据、生成图表、分析数据、预测数据等,请选择一个数据源,开启智能问数吧~",
"select_datasource": "选择数据源",
"view_more": "查看更多",
"selected_datasource": "已选择数据源",
@@ -103,7 +103,11 @@
"chat_search": "搜索",
"thinking": "思考中",
"thinking_step": "思考过程",
"ask_again": "重新生成"
"ask_again": "重新生成",
"today": "今天",
"week": "7天内",
"earlier": "更早以前",
"no_time": "没有时间"
},
"ds": {
"title": "数据源",
+106 -25
View File
@@ -2,6 +2,10 @@
import { Delete, EditPen, MoreFilled } from '@element-plus/icons-vue'
import { type Chat, chatApi } from '@/api/chat.ts'
import { computed } from 'vue'
import dayjs from 'dayjs'
import { getDate } from '@/utils/utils.ts'
import { groupBy } from 'lodash-es'
import { useI18n } from 'vue-i18n'
const props = withDefaults(
defineProps<{
@@ -16,6 +20,64 @@ const props = withDefaults(
}
)
const { t } = useI18n()
function groupByDate(chat: Chat) {
const todayStart = dayjs(dayjs().format('YYYY-MM-DD') + ' 00:00:00').toDate()
const todayEnd = dayjs(dayjs().format('YYYY-MM-DD') + ' 23:59:59').toDate()
const weekStart = dayjs(dayjs().subtract(7, 'day').format('YYYY-MM-DD') + ' 00:00:00').toDate()
const time = getDate(chat.create_time)
if (time) {
if (time >= todayStart && time <= todayEnd) {
return t('qa.today')
}
if (time < todayStart && time >= weekStart) {
return t('qa.week')
}
if (time < weekStart) {
return t('qa.earlier')
}
}
return t('qa.no_time')
}
const computedChatGroup = computed(() => {
return groupBy(props.chatList, groupByDate)
})
const computedChatList = computed(() => {
const _list = []
if (computedChatGroup.value[t('qa.today')]) {
_list.push({
key: t('qa.today'),
list: computedChatGroup.value[t('qa.today')],
})
}
if (computedChatGroup.value[t('qa.week')]) {
_list.push({
key: t('qa.week'),
list: computedChatGroup.value[t('qa.week')],
})
}
if (computedChatGroup.value[t('qa.earlier')]) {
_list.push({
key: t('qa.earlier'),
list: computedChatGroup.value[t('qa.earlier')],
})
}
if (computedChatGroup.value[t('qa.no_time')]) {
_list.push({
key: t('qa.no_time'),
list: computedChatGroup.value[t('qa.no_time')],
})
}
return _list
})
const emits = defineEmits(['chatSelected', 'chatRenamed', 'chatDeleted', 'update:loading'])
const _loading = computed({
@@ -106,28 +168,31 @@ function handleCommand(command: string | number | object, chat: Chat) {
<template>
<el-scrollbar ref="chatListRef">
<div class="chat-list-inner">
<template v-for="chat in chatList" :key="chat.id">
<div
class="chat-list-item"
:class="{ active: currentChatId === chat.id }"
@click="onClickHistory(chat)"
>
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
<el-button class="icon-more" link type="primary" @click.stop>
<el-dropdown trigger="click" @command="(cmd: any) => handleCommand(cmd, chat)">
<el-icon>
<MoreFilled />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="EditPen" command="rename">Rename</el-dropdown-item>
<el-dropdown-item :icon="Delete" command="delete">Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-button>
</div>
</template>
<div v-for="group in computedChatList" :key="group.key" class="group">
<div class="group-title">{{ group.key }}</div>
<template v-for="chat in group.list" :key="chat.id">
<div
class="chat-list-item"
:class="{ active: currentChatId === chat.id }"
@click="onClickHistory(chat)"
>
<span class="title">{{ chat.brief ?? 'Untitled' }}</span>
<el-button class="icon-more" link type="primary" @click.stop>
<el-dropdown trigger="click" @command="(cmd: any) => handleCommand(cmd, chat)">
<el-icon>
<MoreFilled />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :icon="EditPen" command="rename">Rename</el-dropdown-item>
<el-dropdown-item :icon="Delete" command="delete">Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-button>
</div>
</template>
</div>
</div>
</el-scrollbar>
</template>
@@ -144,15 +209,31 @@ function handleCommand(command: string | number | object, chat: Chat) {
display: flex;
flex-direction: column;
gap: 8px;
gap: 16px;
.group {
display: flex;
flex-direction: column;
.group-title {
padding: 0 8px;
color: rgba(100, 106, 115, 1);
line-height: 20px;
font-weight: 500;
font-size: 12px;
margin-bottom: 4px;
}
}
.chat-list-item {
width: 100%;
height: 42px;
height: 40px;
cursor: pointer;
border-radius: 6px;
line-height: 1em;
line-height: 22px;
font-size: 14px;
font-weight: 400;
display: flex;
align-items: center;
@@ -194,6 +194,7 @@ function onChatRenamed(chat: Chat) {
name="quick-search"
autocomplete="off"
:placeholder="t('qa.chat_search')"
clearable
/>
</el-header>
<el-main class="chat-list">