perf: Place the assistant menu in the workspace

This commit is contained in:
fit2cloud-chenyw
2026-01-09 17:40:42 +08:00
committed by fit2cloud-chenyw
parent b8b3727d17
commit e54a5fe46f
12 changed files with 121 additions and 34 deletions
@@ -0,0 +1,48 @@
"""061_assistant_oid_ddl
Revision ID: 547df942eb90
Revises: b40e41c67db3
Create Date: 2026-01-09 15:02:19.891766
"""
import json
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '547df942eb90'
down_revision = 'b40e41c67db3'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('sys_assistant', sa.Column('oid', sa.BigInteger(), nullable=True))
conn = op.get_bind()
conn.execute(sa.text("UPDATE sys_assistant SET oid = 1 WHERE oid IS NULL"))
rows = conn.execute(
sa.text("SELECT id, configuration FROM sys_assistant WHERE type = 0 AND configuration IS NOT NULL")
)
for row in rows:
try:
config = json.loads(row.configuration) if isinstance(row.configuration, str) else row.configuration
oid_value = config.get('oid', 1) if isinstance(config, dict) else 1
if isinstance(oid_value, int) and oid_value != 1:
conn.execute(
sa.text("UPDATE sys_assistant SET oid = :oid WHERE id = :id"),
{"oid": oid_value, "id": row.id}
)
except (json.JSONDecodeError, TypeError, AttributeError):
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('sys_assistant', 'oid')
# ### end Alembic commands ###
+6 -5
View File
@@ -15,7 +15,7 @@ from apps.system.models.system_model import AssistantModel
from apps.system.schemas.auth import CacheName, CacheNamespace
from apps.system.schemas.system_schema import AssistantBase, AssistantDTO, AssistantUiSchema, AssistantValidator
from common.core.config import settings
from common.core.deps import SessionDep, Trans
from common.core.deps import SessionDep, Trans, CurrentUser
from common.core.security import create_access_token
from common.core.sqlbot_cache import clear_cache
from common.utils.utils import get_origin_from_referer, origin_match_domain
@@ -165,8 +165,8 @@ async def clear_ui_cache(id: int):
@router.get("", response_model=list[AssistantModel], summary=f"{PLACEHOLDER_PREFIX}assistant_grid_api", description=f"{PLACEHOLDER_PREFIX}assistant_grid_api")
async def query(session: SessionDep):
list_result = session.exec(select(AssistantModel).where(AssistantModel.type != 4).order_by(AssistantModel.name,
async def query(session: SessionDep, current_user: CurrentUser):
list_result = session.exec(select(AssistantModel).where(AssistantModel.oid == current_user.oid, AssistantModel.type != 4).order_by(AssistantModel.name,
AssistantModel.create_time)).all()
return list_result
@@ -180,8 +180,9 @@ async def query_advanced_application(session: SessionDep):
@router.post("", summary=f"{PLACEHOLDER_PREFIX}assistant_create_api", description=f"{PLACEHOLDER_PREFIX}assistant_create_api")
@system_log(LogConfig(operation_type=OperationType.CREATE, module=OperationModules.APPLICATION, result_id_expr="id"))
async def add(request: Request, session: SessionDep, creator: AssistantBase):
return await save(request, session, creator)
async def add(request: Request, session: SessionDep, current_user: CurrentUser, creator: AssistantBase):
oid = current_user.oid if creator.type != 4 else 1
return await save(request, session, creator, oid)
@router.put("", summary=f"{PLACEHOLDER_PREFIX}assistant_update_api", description=f"{PLACEHOLDER_PREFIX}assistant_update_api")
+3 -1
View File
@@ -1,5 +1,6 @@
from typing import Optional
from fastapi import FastAPI, Request
from sqlmodel import Session, select
from starlette.middleware.cors import CORSMiddleware
@@ -32,9 +33,10 @@ def dynamic_upgrade_cors(request: Request, session: Session):
cors_middleware.kwargs['allow_origins'] = updated_origins
async def save(request: Request, session: Session, creator: AssistantBase):
async def save(request: Request, session: Session, creator: AssistantBase, oid: Optional[int] = 1):
db_model = AssistantModel.model_validate(creator)
db_model.create_time = get_timestamp()
db_model.oid = oid
session.add(db_model)
session.commit()
dynamic_upgrade_cors(request=request, session=session)
@@ -52,6 +52,7 @@ class AssistantBaseModel(SQLModel):
create_time: int = Field(default=0, sa_type=BigInteger())
app_id: Optional[str] = Field(default=None, max_length=255, nullable=True)
app_secret: Optional[str] = Field(default=None, max_length=255, nullable=True)
oid: Optional[int] = Field(nullable=True, sa_type=BigInteger(), default=1)
class AssistantModel(SnowflakeBase, AssistantBaseModel, table=True):
__tablename__ = "sys_assistant"
+2 -1
View File
@@ -668,7 +668,8 @@
"welcome_description": "Welcome Description",
"data_analysis_now": "I can query data, generate charts, detect data anomalies, predict data, and more! Enable Smart Data Analysis now!",
"window_entrance_icon": "Floating window entrance icon",
"preview_error": "The current page prohibits embedding using Iframe!"
"preview_error": "The current page prohibits embedding using Iframe!",
"assistant_app": "Assistant App"
},
"chat": {
"type": "Chart Type",
+2 -1
View File
@@ -668,7 +668,8 @@
"welcome_description": "환영 메시지 설명",
"data_analysis_now": "저는 데이터 조회, 차트 생성, 데이터 이상 감지, 데이터 예측 등을 할 수 있습니다. 빨리 스마트 데이터 조회를 시작하세요~",
"window_entrance_icon": "플로팅 윈도우 입구 아이콘",
"preview_error": "현재 페이지는 Iframe 임베딩을 허용하지 않습니다!"
"preview_error": "현재 페이지는 Iframe 임베딩을 허용하지 않습니다!",
"assistant_app": "어시스턴트 앱"
},
"chat": {
"type": "차트 유형",
+2 -1
View File
@@ -668,7 +668,8 @@
"welcome_description": "欢迎语描述",
"data_analysis_now": "我可以查询数据、生成图表、检测数据异常、预测数据等赶快开启智能问数吧~",
"window_entrance_icon": "浮窗入口图标",
"preview_error": "当前页面禁止使用 Iframe 嵌入!"
"preview_error": "当前页面禁止使用 Iframe 嵌入!",
"assistant_app": "小助手应用"
},
"chat": {
"type": "图表类型",
+19 -1
View File
@@ -1,6 +1,7 @@
import LayoutDsl from '@/components/layout/LayoutDsl.vue'
import Datasource from '@/views/ds/Datasource.vue'
import SetAssistant from '@/views/system/embedded/iframe.vue'
import { i18n } from '@/i18n'
import { useUserStore } from '@/stores/user'
import type { Router } from 'vue-router'
@@ -22,6 +23,13 @@ const dynamicRouterList = [
},
],
},
{
parent: 'set',
path: '/set/assistant',
name: 'setAssistant',
component: SetAssistant,
meta: { title: t('embedded.assistant_app') },
},
] as any[]
const reduceRouters = (router: Router, invalid_router_name_list: string[]) => {
@@ -71,7 +79,17 @@ const reduceRouters = (router: Router, invalid_router_name_list: string[]) => {
export const generateDynamicRouters = (router: Router) => {
if (userStore.isAdmin || userStore.isSpaceAdmin) {
dynamicRouterList.forEach((item: any) => router.addRoute(item))
dynamicRouterList.forEach((item: any) => {
if (!item.parent) {
router.addRoute(item)
} else {
router.addRoute(item.parent, item)
const parentRoute: any = router.getRoutes().find((r: any) => r.name === item.parent)
if (parentRoute?.children) {
parentRoute.children.push(item)
}
}
})
} else {
const router_name_list = [] as string[]
const stack = [...dynamicRouterList]
+11 -2
View File
@@ -9,7 +9,10 @@ import DashboardEditor from '@/views/dashboard/editor/index.vue'
import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
import Dashboard from '@/views/dashboard/index.vue'
import Model from '@/views/system/model/Model.vue'
import Embedded from '@/views/system/embedded/index.vue'
// import Embedded from '@/views/system/embedded/index.vue'
// import SetAssistant from '@/views/system/embedded/iframe.vue'
import SystemEmbedded from '@/views/system/embedded/Page.vue'
import assistantTest from '@/views/system/embedded/Test.vue'
import assistant from '@/views/embedded/index.vue'
import EmbeddedPage from '@/views/embedded/page.vue'
@@ -116,6 +119,12 @@ export const routes = [
component: Permission,
meta: { title: t('workspace.permission_configuration') },
},
/* {
path: '/set/assistant',
name: 'setAssistant',
component: SetAssistant,
meta: { title: t('embedded.assistant_app') },
}, */
{
path: '/set/professional',
name: 'professional',
@@ -184,7 +193,7 @@ export const routes = [
{
path: 'embedded',
name: 'embedded',
component: Embedded,
component: SystemEmbedded,
meta: {
title: t('embedded.embedded_management'),
iconActive: 'embedded',
+1 -1
View File
@@ -39,7 +39,7 @@ export const watchRouter = (router: Router) => {
if (!userStore.getUid) {
await userStore.info()
generateDynamicRouters(router)
isFirstDynamicPath = to?.path === '/ds/index'
isFirstDynamicPath = to?.path && ['/ds/index', '/set/assistant'].includes(to.path)
if (isFirstDynamicPath) {
next({ ...to, replace: true })
return
+9 -4
View File
@@ -31,7 +31,7 @@ interface Form {
description?: string | null
}
const emits = defineEmits(['btnSelectChange'])
// const emits = defineEmits(['btnSelectChange'])
const { t } = useI18n()
const multipleSelectionAll = ref<any[]>([])
const keywords = ref('')
@@ -313,7 +313,8 @@ const copyCode = (row: any, key: any = 'app_secret') => {
<template>
<div v-loading="searchLoading" class="embedded-page">
<div class="tool-left">
<div class="btn-select">
<span class="page-title">{{ t('embedded.embedded_management') }}</span>
<!-- <div class="btn-select">
<el-button
:class="[btnSelect === 'd' && 'is-active']"
text
@@ -328,7 +329,7 @@ const copyCode = (row: any, key: any = 'app_secret') => {
>
{{ t('embedded.embedded_page') }}
</el-button>
</div>
</div> -->
<div>
<el-input
v-model="keywords"
@@ -642,7 +643,11 @@ const copyCode = (row: any, key: any = 'app_secret') => {
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.page-title {
font-weight: 500;
font-size: 20px;
line-height: 28px;
}
.btn-select {
height: 32px;
padding-left: 4px;
+17 -17
View File
@@ -19,7 +19,9 @@ import DsCard from './DsCard.vue'
import { getList, updateAssistant, saveAssistant, delOne, dsApi } from '@/api/embedded'
import { useI18n } from 'vue-i18n'
import { cloneDeep } from 'lodash-es'
import { useUserStore } from '@/stores/user.ts'
const userStore = useUserStore()
defineProps({
btnSelect: {
type: String,
@@ -27,7 +29,7 @@ defineProps({
},
})
const emits = defineEmits(['btnSelectChange'])
// const emits = defineEmits(['btnSelectChange'])
const { t } = useI18n()
const { copy } = useClipboard({ legacy: true })
@@ -49,7 +51,6 @@ const activeMode = ref('full')
const embeddedList = ref<any[]>([])
const systemCredentials = ['localStorage', 'custom', 'cookie', 'sessionStorage']
const credentials = ['header', 'cookie', 'param']
const workspaces = ref<any[]>([])
const defaultEmbedded = {
id: '',
@@ -112,11 +113,7 @@ const userTypeList = [
value: 1,
},
]
const initWorkspace = () => {
workspaceList().then((res) => {
workspaces.value = res
})
}
const handleAddEmbedded = (val: any) => {
Object.assign(currentEmbedded, cloneDeep(defaultEmbedded))
Object.keys(dsForm).forEach((ele) => {
@@ -132,11 +129,7 @@ const handleAddEmbedded = (val: any) => {
handleAdvancedEmbedded(null)
}
}
const wsChanged = (val: any) => {
dsForm.public_list = []
dsForm.oid = val
getDsList()
}
const getDsList = () => {
dsApi(dsForm.oid).then((res: any) => {
dsListOptions.value = res || []
@@ -144,9 +137,10 @@ const getDsList = () => {
}
const handleBaseEmbedded = (row: any) => {
advancedApplication.value = false
initWorkspace()
if (row) {
Object.assign(dsForm, JSON.parse(row.configuration))
} else {
Object.assign(dsForm, { oid: userStore.getOid })
}
getDsList()
ruleConfigvVisible.value = true
@@ -557,7 +551,8 @@ const saveHandler = () => {
<template>
<div v-loading="searchLoading" class="embedded-index no-padding">
<div class="tool-left">
<div class="btn-select">
<span class="page-title">{{ t('embedded.assistant_app') }}</span>
<!-- <div class="btn-select">
<el-button
:class="[btnSelect === 'd' && 'is-active']"
text
@@ -572,7 +567,7 @@ const saveHandler = () => {
>
{{ t('embedded.embedded_page') }}
</el-button>
</div>
</div> -->
<div>
<el-input
v-model="keywords"
@@ -922,7 +917,7 @@ const saveHandler = () => {
class="form-content_error"
@submit.prevent
>
<el-form-item prop="oid" :label="t('user.workspace')">
<!-- <el-form-item prop="oid" :label="t('user.workspace')">
<el-select
v-model="dsForm.oid"
filterable
@@ -938,7 +933,7 @@ const saveHandler = () => {
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form-item> -->
<el-form-item class="private-list_form">
<template #label>
@@ -1166,6 +1161,11 @@ const saveHandler = () => {
margin-bottom: 16px;
padding: 0 24px 0 24px;
.page-title {
font-weight: 500;
font-size: 20px;
line-height: 28px;
}
.title {
font-weight: 500;
font-size: 20px;