From f78e24105fb69fe012dcee8f4ecd6b7a3a828b5b Mon Sep 17 00:00:00 2001 From: wxg0103 <727495428@qq.com> Date: Fri, 15 Aug 2025 15:43:25 +0800 Subject: [PATCH 01/18] feat: enhance WeCom Bot configuration with updated URL info and additional guidance --- ui/src/locales/lang/en-US/views/application.ts | 2 +- ui/src/locales/lang/zh-CN/views/application.ts | 4 ++-- ui/src/locales/lang/zh-Hant/views/application.ts | 6 +++--- .../application/component/AccessSettingDrawer.vue | 10 ++++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ui/src/locales/lang/en-US/views/application.ts b/ui/src/locales/lang/en-US/views/application.ts index cd185ddee..c5c9bcab9 100644 --- a/ui/src/locales/lang/en-US/views/application.ts +++ b/ui/src/locales/lang/en-US/views/application.ts @@ -212,7 +212,7 @@ export default { }, wecomBotSetting: { title: 'WeCom Bot Configuration', - urlInfo: '-Security and Management-Management Tools-Intelligent Bots-API Mode', + urlInfo: '-Management Tools-Smart Bot-Create Bot-API Mode Create "URL"', }, larkSetting: { title: 'Lark Configuration', diff --git a/ui/src/locales/lang/zh-CN/views/application.ts b/ui/src/locales/lang/zh-CN/views/application.ts index 9c2b4afb8..11226f971 100644 --- a/ui/src/locales/lang/zh-CN/views/application.ts +++ b/ui/src/locales/lang/zh-CN/views/application.ts @@ -179,8 +179,8 @@ export default { urlInfo: '-应用管理-自建-创建的应用-接收消息-设置 API 接收的 "URL" 中', }, wecomBotSetting: { - title: '企业微信应用配置', - urlInfo: '-安全与管理-管理工具-智能机器人- API 模式创建的 "URL" 中', + title: '企业微信智能机器人配置', + urlInfo: '-管理工具-智能机器人-创建机器人-API模式创建的 "URL" 中', }, dingtalkSetting: { title: '钉钉应用配置', diff --git a/ui/src/locales/lang/zh-Hant/views/application.ts b/ui/src/locales/lang/zh-Hant/views/application.ts index 82b23beda..d7a7684c4 100644 --- a/ui/src/locales/lang/zh-Hant/views/application.ts +++ b/ui/src/locales/lang/zh-Hant/views/application.ts @@ -204,9 +204,9 @@ export default { verificationTokenPlaceholder: '請輸入Verification Token', urlInfo: '-事件與回呼-事件配置-配置訂閱方式的 "請求位址" 中', }, - wecomBotSetting: { - title: '企業微信機器人配置', - urlInfo: '-安全與管理-管理工具-智能機器人- API 模式建立的 "URL" 中', + wecomBotSetting: { + title: '企業微信智能機器人配置', + urlInfo: '-管理工具-智能机器人-创建机器人-API模式创建的 "URL" 中', }, slackSetting: { title: 'Slack 應用配置', diff --git a/ui/src/views/application/component/AccessSettingDrawer.vue b/ui/src/views/application/component/AccessSettingDrawer.vue index f530fc4f2..73fb3247d 100644 --- a/ui/src/views/application/component/AccessSettingDrawer.vue +++ b/ui/src/views/application/component/AccessSettingDrawer.vue @@ -84,6 +84,16 @@ }}{{ $t('views.application.applicationAccess.larkSetting.urlInfo') }} + + {{ $t('views.application.applicationAccess.copyUrl') }} + {{ $t('views.application.applicationAccess.wecomPlatform') }}{{ $t('views.application.applicationAccess.wecomBotSetting.urlInfo') }} + + From c0b2aa3688ae514ea44fe9de7ce51d4c37600865 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:03:03 +0800 Subject: [PATCH 02/18] feat: System resource authorization function (#3861) Co-authored-by: wangdan-fit2cloud --- ui/src/api/system/resource-authorization.ts | 29 +- ui/src/components/ai-chat/index.vue | 3 +- ui/src/enums/system.ts | 1 + ui/src/locales/lang/en-US/views/system.ts | 12 +- ui/src/locales/lang/zh-CN/views/system.ts | 13 +- ui/src/locales/lang/zh-Hant/views/system.ts | 16 +- ui/src/views/chat-log/index.vue | 4 +- ui/src/views/document/index.vue | 75 ++-- ui/src/views/knowledge/component/BaseForm.vue | 5 +- .../component/PermissionTable.vue | 308 +++++++++++++++ .../system/resource-authorization/constant.ts | 25 ++ .../system/resource-authorization/index.vue | 369 +++--------------- 12 files changed, 463 insertions(+), 397 deletions(-) create mode 100644 ui/src/views/system/resource-authorization/component/PermissionTable.vue create mode 100644 ui/src/views/system/resource-authorization/constant.ts diff --git a/ui/src/api/system/resource-authorization.ts b/ui/src/api/system/resource-authorization.ts index b12e72358..a53a9d10a 100644 --- a/ui/src/api/system/resource-authorization.ts +++ b/ui/src/api/system/resource-authorization.ts @@ -1,8 +1,7 @@ -import { Permission } from '@/utils/permission/type' import { Result } from '@/request/Result' import { get, put, post, del } from '@/request/index' -import type { pageRequest } from '@/api/type/common' import type { Ref } from 'vue' +import type { pageRequest } from '@/api/type/common' const prefix = '/workspace' /** @@ -13,11 +12,13 @@ const getResourceAuthorization: ( workspace_id: string, user_id: string, resource: string, + page: pageRequest, + params?: any, loading?: Ref, -) => Promise> = (workspace_id, user_id, resource, loading) => { +) => Promise> = (workspace_id, user_id, resource, page, params, loading) => { return get( - `${prefix}/${workspace_id}/user_resource_permission/user/${user_id}/resource/${resource}`, - undefined, + `${prefix}/${workspace_id}/user_resource_permission/user/${user_id}/resource/${resource}/${page.current_page}/${page.page_size}`, + params, loading, ) } @@ -26,18 +27,12 @@ const getResourceAuthorization: ( * 修改成员权限 * @param 参数 member_id * @param 参数 { - "team_resource_permission_list": [ - { - "auth_target_type": "KNOWLEDGE", - "target_id": "string", - "auth_type": "ROLE", - "permission": { - "VIEW": true, - "MANAGE": true, - "ROLE": true - } - } - ] + [ + { + "target_id": "string", + "permission": "NOT_AUTH" + } + ] } */ const putResourceAuthorization: ( diff --git a/ui/src/components/ai-chat/index.vue b/ui/src/components/ai-chat/index.vue index 0c1b8c08b..3af8b9c1b 100644 --- a/ui/src/components/ai-chat/index.vue +++ b/ui/src/components/ai-chat/index.vue @@ -22,7 +22,8 @@ :type="type" :send-message="sendMessage" :chat-management="ChatManagement" :executionIsRightPanel="props.executionIsRightPanel" @open-execution-detail="emit('openExecutionDetail', chatList[index])" - @openParagraph="emit('openParagraph', chatList[index])" @openParagraphDocument=" + @openParagraph="emit('openParagraph', chatList[index])" + @openParagraphDocument=" (val: any) => emit('openParagraphDocument', chatList[index], val) "> diff --git a/ui/src/enums/system.ts b/ui/src/enums/system.ts index 3fcbf7217..5a4fd920d 100644 --- a/ui/src/enums/system.ts +++ b/ui/src/enums/system.ts @@ -2,6 +2,7 @@ export enum AuthorizationEnum { MANAGE = 'MANAGE', VIEW = 'VIEW', ROLE = 'ROLE', + NOT_AUTH = 'NOT_AUTH', KNOWLEDGE = 'KNOWLEDGE', APPLICATION = 'APPLICATION', MODEL = 'MODEL', diff --git a/ui/src/locales/lang/en-US/views/system.ts b/ui/src/locales/lang/en-US/views/system.ts index 8b2efe24f..045d7a59d 100644 --- a/ui/src/locales/lang/en-US/views/system.ts +++ b/ui/src/locales/lang/en-US/views/system.ts @@ -109,18 +109,20 @@ export default { enableSSL: 'Enable SSL (if the SMTP port is 465, you usually need to enable SSL)', enableTLS: 'Enable TLS (if the SMTP port is 587, you usually need to enable TLS)', }, + resourceAuthorization: { title: 'Resource Authorization', member: 'Member', permissionSetting: 'Permission Setting', setting: { management: 'management', + managementDesc: 'Can delete or modify this resource', check: 'check', - authorization: 'authorization', - }, - priority: { - label: 'Resource permission priority', - role: 'Role', + checkDesc: 'Can only view the resource', + role: 'User Role', + roleDesc: 'Authorize users based on their roles to access this resource', + notAuthorized: 'Not Authorized', + configure: 'Configure Permission', }, }, resource_management: { diff --git a/ui/src/locales/lang/zh-CN/views/system.ts b/ui/src/locales/lang/zh-CN/views/system.ts index 666d0874f..4bf583d1e 100644 --- a/ui/src/locales/lang/zh-CN/views/system.ts +++ b/ui/src/locales/lang/zh-CN/views/system.ts @@ -1,3 +1,5 @@ +import role from './role' + export default { title: '系统管理', subTitle: '系统设置', @@ -115,12 +117,13 @@ export default { permissionSetting: '资源权限配置', setting: { management: '管理', + managementDesc: '可对该资源进行删改操作', check: '查看', - authorization: '授权', - }, - priority: { - label: '资源权限优先级', - role: '按角色', + checkDesc: '仅能查看使用该资源', + role: '按用户角色', + roleDesc: '根据用户角色中的权限授权用户对该资源的操作权限', + notAuthorized: '不授权', + configure: '配置权限', }, }, resource_management: { diff --git a/ui/src/locales/lang/zh-Hant/views/system.ts b/ui/src/locales/lang/zh-Hant/views/system.ts index 1527b507e..4fb0c9323 100644 --- a/ui/src/locales/lang/zh-Hant/views/system.ts +++ b/ui/src/locales/lang/zh-Hant/views/system.ts @@ -109,18 +109,20 @@ export default { enableSSL: '啟用 SSL(如果 SMTP 端口是 465,通常需要啟用 SSL)', enableTLS: '啟用 TLS(如果 SMTP 端口是 587,通常需要啟用 TLS)', }, + resourceAuthorization: { - title: '資源授权', + title: '資源授權', member: '成員', - permissionSetting: '資源权限配置', + permissionSetting: '資源權限配置', setting: { management: '管理', + managementDesc: '可對該資源進行刪改操作', check: '查看', - authorization: '授权', - }, - priority: { - label: '資源权限优先级', - role: '按角色', + checkDesc: '僅能查看使用該資源', + role: '按用戶角色', + roleDesc: '根據用戶角色中的權限授權用戶對該資源的操作權限', + notAuthorized: '不授權', + configure: '配置權限', }, }, resource_management: { diff --git a/ui/src/views/chat-log/index.vue b/ui/src/views/chat-log/index.vue index 55575ff5a..03d37f601 100644 --- a/ui/src/views/chat-log/index.vue +++ b/ui/src/views/chat-log/index.vue @@ -359,12 +359,12 @@ const nextChatRecord = () => { } } const pre_disable = computed(() => { - let index = tableIndexMap.value[currentChatId.value] - 1 + const index = tableIndexMap.value[currentChatId.value] - 1 return index < 0 && paginationConfig.current_page <= 1 }) const next_disable = computed(() => { - let index = tableIndexMap.value[currentChatId.value] + 1 + const index = tableIndexMap.value[currentChatId.value] + 1 return ( index >= tableData.value.length && index + (paginationConfig.current_page - 1) * paginationConfig.page_size >= diff --git a/ui/src/views/document/index.vue b/ui/src/views/document/index.vue index 8d87f0ad9..b30a64384 100644 --- a/ui/src/views/document/index.vue +++ b/ui/src/views/document/index.vue @@ -51,10 +51,11 @@ >{{ $t('views.document.generateQuestion.title') }} {{ $t('views.document.setting.migration') }} + v-if="permissionPrecise.doc_edit(id)" + > + {{ $t('common.setting') }} @@ -63,11 +64,11 @@ - {{ $t('common.setting') }} + {{ $t('views.document.setting.migration') }} - + {{ $t('views.document.setting.cancelGenerateQuestion') }} - - - - - - - + + + + + + + @@ -546,11 +543,11 @@ - - {{ $t('common.setting') }} + {{ $t('views.knowledge.setting.sync') }} - + {{ $t('views.document.setting.cancelGenerateQuestion') }} - + {{ $t('views.document.generateQuestion.title') }} - + {{ $t('views.document.setting.migration') }} - + {{ $t('views.document.setting.export') }} Excel - + {{ $t('views.document.setting.export') }} Zip - + {{ $t('common.delete') }} @@ -712,7 +711,7 @@ const permissionPrecise = computed(() => { const MoreFilledPermission0 = (id: string) => { return ( - permissionPrecise.value.doc_edit(id) || + permissionPrecise.value.doc_migrate(id) || (knowledgeDetail?.value.type === 1 && permissionPrecise.value.doc_sync(id)) || (knowledgeDetail?.value.type === 2 && permissionPrecise.value.doc_sync(id)) || permissionPrecise.value.doc_delete(id) @@ -731,7 +730,7 @@ const MoreFilledPermission1 = (id: string) => { const MoreFilledPermission2 = (id: string) => { return ( - permissionPrecise.value.doc_edit(id) || + permissionPrecise.value.sync(id) || permissionPrecise.value.doc_generate(id) || permissionPrecise.value.doc_migrate(id) || permissionPrecise.value.doc_export(id) || diff --git a/ui/src/views/knowledge/component/BaseForm.vue b/ui/src/views/knowledge/component/BaseForm.vue index 771c63437..e6449ba84 100644 --- a/ui/src/views/knowledge/component/BaseForm.vue +++ b/ui/src/views/knowledge/component/BaseForm.vue @@ -47,11 +47,10 @@ import { groupBy } from 'lodash' import type { knowledgeData } from '@/api/type/knowledge' import { t } from '@/locales' import { loadSharedApi } from '@/utils/dynamics-api/shared-api' -import modelResourceApi from '@/api/system-resource-management/model' const props = defineProps<{ data?: { - type: Object - default: () => {} + type: object + default: () => null } apiType: 'systemShare' | 'workspace' | 'systemManage' }>() diff --git a/ui/src/views/system/resource-authorization/component/PermissionTable.vue b/ui/src/views/system/resource-authorization/component/PermissionTable.vue new file mode 100644 index 000000000..ec1e41b75 --- /dev/null +++ b/ui/src/views/system/resource-authorization/component/PermissionTable.vue @@ -0,0 +1,308 @@ + + + + {{ $t('views.system.resourceAuthorization.permissionSetting') }} + + {{ $t('views.system.resourceAuthorization.setting.configure') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ row?.name }} + + + + + + + submitPermissions(val, row)" + > + + {{ item.label }} + + + + + + + + + + + + + {{ item.label }} + {{ item.desc }} + + + + + + + + + + + diff --git a/ui/src/views/system/resource-authorization/constant.ts b/ui/src/views/system/resource-authorization/constant.ts new file mode 100644 index 000000000..49908d3c8 --- /dev/null +++ b/ui/src/views/system/resource-authorization/constant.ts @@ -0,0 +1,25 @@ +import { AuthorizationEnum } from '@/enums/system' +import { t } from '@/locales' + +export const permissionOptions = [ + { + label: t('views.system.resourceAuthorization.setting.notAuthorized'), + value: AuthorizationEnum.NOT_AUTH, + desc: '', + }, + { + label: t('views.system.resourceAuthorization.setting.check'), + value: AuthorizationEnum.VIEW, + desc: t('views.system.resourceAuthorization.setting.checkDesc'), + }, + { + label: t('views.system.resourceAuthorization.setting.management'), + value: AuthorizationEnum.MANAGE, + desc: t('views.system.resourceAuthorization.setting.managementDesc'), + }, + { + label: t('views.system.resourceAuthorization.setting.role'), + value: AuthorizationEnum.ROLE, + desc: t('views.system.resourceAuthorization.setting.roleDesc'), + }, +] diff --git a/ui/src/views/system/resource-authorization/index.vue b/ui/src/views/system/resource-authorization/index.vue index f8e896d1a..aaea942e1 100644 --- a/ui/src/views/system/resource-authorization/index.vue +++ b/ui/src/views/system/resource-authorization/index.vue @@ -21,8 +21,8 @@ /> - - + + {{ $t('views.system.resourceAuthorization.member') }} @@ -45,11 +45,15 @@ - {{ row.nick_name }} - {{ + row.nick_name + }} + + >({{ row.roles?.join(',') }}) @@ -58,46 +62,13 @@ - - - {{ $t('views.system.resourceAuthorization.permissionSetting') }} - - - - - - - - {{ $t('common.save') }} - - + @@ -107,18 +78,17 @@ import { onMounted, ref, reactive, watch, computed } from 'vue' import { useRoute } from 'vue-router' import AuthorizationApi from '@/api/system/resource-authorization' -import PermissionSetting from './component/PermissionSetting.vue' +import PermissionTable from '@/views/system/resource-authorization/component/PermissionTable.vue' import { MsgSuccess, MsgConfirm } from '@/utils/message' import { AuthorizationEnum } from '@/enums/system' import { t } from '@/locales' -import useStore from '@/stores' -import { cloneDeep } from 'lodash' -import { EditionConst, RoleConst, PermissionConst } from '@/utils/permission/data' +import { EditionConst } from '@/utils/permission/data' import { hasPermission } from '@/utils/permission/index' import type { WorkspaceItem } from '@/api/type/workspace' -import { ComplexPermission } from '@/utils/permission/type' import { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts' +import useStore from '@/stores' + const route = useRoute() const { user } = useStore() const loading = ref(false) @@ -128,79 +98,27 @@ const filterMember = ref([]) // 搜索过滤后列表 const currentUser = ref('') const currentType = ref('') const filterText = ref('') -const tableHeight = ref(0) +const permissionData = ref([]) -const permissionObj = ref({ - APPLICATION: new ComplexPermission( - [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE], - [ - PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT, - PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT - .getWorkspacePermissionWorkspaceManageRole, - ], - [], - 'OR', - ), - KNOWLEDGE: new ComplexPermission( - [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE], - [ - PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT, - PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT - .getWorkspacePermissionWorkspaceManageRole, - ], - [], - 'OR', - ), - TOOL: new ComplexPermission( - [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE], - [ - PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT, - PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT - .getWorkspacePermissionWorkspaceManageRole, - ], - [], - 'OR', - ), - MODEL: new ComplexPermission( - [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE], - [ - PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT, - PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT - .getWorkspacePermissionWorkspaceManageRole, - ], - [], - 'OR', - ), -}) const settingTags = reactive([ { label: t('views.knowledge.title'), type: AuthorizationEnum.KNOWLEDGE, - data: [] as any, - isRole: false, }, { label: t('views.application.title'), type: AuthorizationEnum.APPLICATION, - data: [] as any, - isRole: false, }, { label: t('views.tool.title'), type: AuthorizationEnum.TOOL, - data: [] as any, - isRole: false, }, { label: t('views.model.title'), type: AuthorizationEnum.MODEL, - data: [] as any, - isRole: false, }, ]) - // 当前激活的数据类型(应用/知识库/模型/工具) - const activeData = computed(() => { const lastIndex = route.path.lastIndexOf('/') const currentPathType = route.path.substring(lastIndex + 1).toUpperCase() @@ -219,51 +137,46 @@ watch(filterText, (val: any) => { } }) -function isManage(type: string) { - return type === 'manage' -} - -const flotTree = (tree: Array, result: Array) => { - tree.forEach((tItem) => { - result.push(tItem) - if (tItem.children) { - flotTree(tItem.children, result) - } - }) - return result -} -function submitPermissions() { - const user_resource_permission_list = settingTags - .map((item: any, index: number) => { - return flotTree(item.data, []) - .filter((v: any) => !v.isFolder) - .map((v: any) => { - return { - target_id: v.id, - auth_target_type: item.value, - permission: v.permission, - auth_type: item.isRole ? 'ROLE' : 'RESOURCE_PERMISSION_GROUP', - } - }) - }) - .reduce((pre: any, next: any) => [...pre, ...next], []) +function submitPermissions(obj: any) { const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default' AuthorizationApi.putResourceAuthorization( workspaceId, currentUser.value, (route.meta?.resource as string) || 'APPLICATION', - { user_resource_permission_list: user_resource_permission_list }, + obj, rLoading, ).then(() => { MsgSuccess(t('common.submitSuccess')) - getWholeTree(currentUser.value) + getPermissionList() + }) +} + +const PermissionTableRef = ref() + +const getPermissionList = () => { + const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default' + const params: any = {} + if (PermissionTableRef.value.searchForm[PermissionTableRef.value.searchType]) { + params[PermissionTableRef.value.searchType] = + PermissionTableRef.value.searchForm[PermissionTableRef.value.searchType] + } + AuthorizationApi.getResourceAuthorization( + workspaceId, + currentUser.value, + (route.meta?.resource as string) || 'APPLICATION', + PermissionTableRef.value.paginationConfig, + params, + rLoading, + ).then((res) => { + permissionData.value = res.data.records || [] + PermissionTableRef.value.paginationConfig.total = res.data.total || 0 }) } function clickMemberHandle(item: any) { currentUser.value = item.id currentType.value = item.type - getWholeTree(item.id) + getPermissionList() } function getMember(id?: string) { @@ -275,175 +188,13 @@ function getMember(id?: string) { const member = (id && memberList.value.find((p: any) => p.user_id === id)) || null currentUser.value = member ? member.id : memberList.value?.[0]?.id currentType.value = member ? member.type : memberList.value?.[0]?.type - getWholeTree(currentUser.value) + getPermissionList() } else { - activeData.value.data = [] + permissionData.value = [] } }) } -const dfsPermissionIndeterminateTrue = (arr: any = [], type: string) => { - return arr.every((item: any) => { - if (item.children?.length) { - item.permission[type] = dfsPermissionIndeterminateTrue(item.children, type) - } - return item.permission[type] - }) -} - -const dfsPermissionIndeterminate = ( - arr: any = [], - type: string, - permissionHalf: any, - permissionHalfMap: any, - id: string, -) => { - arr.forEach((item: any) => { - if (item.isFolder) { - if (!permissionHalfMap[item.id]) { - permissionHalfMap[item.id] = cloneDeep(permissionHalf) - } - } - - if (item.children?.length) { - dfsPermissionIndeterminate(item.children, type, permissionHalf, permissionHalfMap, item.id) - } - - if (!item.isFolder) { - permissionHalfMap[id][type] = [...permissionHalfMap[id][type], item.permission[type]] - } - - if (item.isFolder) { - // 判断是否存在子项且全部选中或全部未选中 - const hasPermissions = permissionHalfMap[item.id][type] - const allTrue = hasPermissions.length && hasPermissions.every((p: boolean) => p) - const allFalse = hasPermissions.length && hasPermissions.every((p: boolean) => !p) - - // 只有在既有选中又有未选中的情况下才设置为半选状态 - item.permissionHalf[type] = hasPermissions.length && !allTrue && !allFalse - - // 检查子文件夹是否有半选状态 - if (item.children.some((ele: any) => ele.isFolder && ele.permissionHalf[type])) { - item.permissionHalf[type] = true - } - - // 如果所有子项都已选中,确保当前项也被选中而不是半选 - if (allTrue) { - item.permission[type] = true - item.permissionHalf[type] = false - } - - // 如果子项中有选中的也有未选中的,则设置为半选状态 - if ( - item.children.some((ele: any) => ele.permission[type]) && - item.children.some((ele: any) => !ele.permission[type]) - ) { - item.permissionHalf[type] = true - } - } - }) -} - -const dfsFolder = (arr: any[] = [], folderIdMap: any) => { - arr.forEach((ele) => { - if (ele.permission) return - if (ele.children?.length) { - if (folderIdMap[ele.id]) { - ele.children = [...ele.children, ...folderIdMap[ele.id]] - } - dfsFolder(ele.children, folderIdMap) - } else { - ele.children = folderIdMap[ele.id] || [] - } - ele.isFolder = true - ele.permission = { - VIEW: false, - MANAGE: false, - ROLE: false, - } - - ele.permissionHalf = { - VIEW: false, - MANAGE: false, - ROLE: false, - } - }) -} - -function getFolder() { - const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default' - return AuthorizationApi.getSystemFolder(workspaceId, activeData.value.type, {}, loading) -} -function getResourcePermissions(user_id: string) { - const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default' - return AuthorizationApi.getResourceAuthorization( - workspaceId, - user_id, - (route.meta?.resource as string) || 'APPLICATION', - rLoading, - ) -} -const getWholeTree = async (user_id: string) => { - const [parentRes, childrenRes] = await Promise.all([getFolder(), getResourcePermissions(user_id)]) - // if (!childrenRes.data || Object.keys(childrenRes.data).length > 0) { - // settingTags.map((item: any) => { - let folderIdMap = [] - const folderTree = cloneDeep((parentRes as unknown as any).data) - if (Object.keys(childrenRes.data).indexOf(activeData.value.type) !== -1) { - activeData.value.isRole = - childrenRes.data[activeData.value.type].length > 0 && - hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR') - ? childrenRes.data[activeData.value.type][0].auth_type == 'ROLE' - : false - folderIdMap = getFolderIdMap(childrenRes.data[activeData.value.type]) - dfsFolder(folderTree, folderIdMap) - const permissionHalf = { - VIEW: [], - MANAGE: [], - ROLE: [], - } - Object.keys(permissionHalf).forEach((ele) => { - dfsPermissionIndeterminateTrue(folderTree, ele) - dfsPermissionIndeterminate(folderTree, ele, cloneDeep(permissionHalf), {}, 'default') - }) - if (activeData.value.type === AuthorizationEnum.MODEL) { - activeData.value.data = folderTree[0].children - } else { - activeData.value.data = folderTree - } - } else { - activeData.value.data = [] - } - // }) - // } -} - -const refreshData = () => { - settingTags.map((item: any) => { - if (activeData.value.type === item.type) { - const permissionHalf = { - VIEW: [], - MANAGE: [], - ROLE: [], - } - Object.keys(permissionHalf).forEach((ele) => { - dfsPermissionIndeterminateTrue(item.data, ele) - dfsPermissionIndeterminate(item.data, ele, cloneDeep(permissionHalf), {}, 'default') - }) - } - }) -} -const getFolderIdMap = (arr: any = []) => { - return arr.reduce((pre: any, next: any) => { - if (pre[next.folder_id]) { - pre[next.folder_id].push(next) - } else { - pre[next.folder_id] = [next] - } - return pre - }, {}) -} - const workspaceList = ref([]) const currentWorkspaceId = ref('') const currentWorkspace = computed(() => { @@ -461,12 +212,6 @@ function changeWorkspace(item: WorkspaceItem) { } onMounted(() => { - tableHeight.value = window.innerHeight - 300 - window.onresize = () => { - return (() => { - tableHeight.value = window.innerHeight - 300 - })() - } if (user.isEE()) { getWorkspaceList() } @@ -481,20 +226,6 @@ onMounted(() => { width: var(--setting-left-width); min-width: var(--setting-left-width); } - - .permission-setting { - flex: 1; - overflow: hidden; - box-sizing: border-box; - width: 100%; - flex-direction: column; - position: relative; - .submit-button { - position: absolute; - top: 24px; - right: 24px; - } - } .list-height-left { height: calc(100vh - 240px); } From 6def3eecdaa4887a96ab0dc7b30d874544101ec8 Mon Sep 17 00:00:00 2001 From: justin0u0 <38185674+justin0u0@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:03:23 +0800 Subject: [PATCH 03/18] fix(ui): markdown unordered list does not show correctly (#3857) --- ui/src/styles/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index a97439aef..4c782bbae 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -56,7 +56,7 @@ div:focus { } ul { - list-style: none; + list-style: circle; margin: 0; padding: 0; } From a700bc126f43e033472bc94eea6f66aaee3edd1a Mon Sep 17 00:00:00 2001 From: CaptainB Date: Fri, 15 Aug 2025 16:26:32 +0800 Subject: [PATCH 04/18] fix: update pypdf dependency to version 6.0.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4f4a3052..91a1cfd84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ python-docx = "1.2.0" xlrd = "2.0.2" xlwt = "1.3.0" pymupdf = "1.26.3" -pypdf = "5.7.0" +pypdf = "6.0.0" # 音频处理 pydub = "0.25.1" From 33806f66d05f7b02cb3b19206e52ce849f1efd68 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Fri, 15 Aug 2025 17:48:52 +0800 Subject: [PATCH 05/18] feat: Workplace resource authorization (#3864) Co-authored-by: wangdan-fit2cloud --- ui/src/api/system/resource-authorization.ts | 52 +++- .../resource-authorization-drawer/index.vue | 291 ++++++++++++++++++ ui/src/enums/common.ts | 1 + ui/src/enums/system.ts | 4 - ui/src/views/application/index.vue | 20 +- .../component/KnowledgeListContainer.vue | 22 +- ui/src/views/model/component/ModelCard.vue | 20 +- .../component/PermissionSetting.vue | 9 +- .../component/PermissionTable.vue | 20 +- .../system/resource-authorization/index.vue | 12 +- .../tool/component/ToolListContainer.vue | 21 ++ 11 files changed, 441 insertions(+), 31 deletions(-) create mode 100644 ui/src/components/resource-authorization-drawer/index.vue diff --git a/ui/src/api/system/resource-authorization.ts b/ui/src/api/system/resource-authorization.ts index a53a9d10a..dc9d3b357 100644 --- a/ui/src/api/system/resource-authorization.ts +++ b/ui/src/api/system/resource-authorization.ts @@ -5,7 +5,53 @@ import type { pageRequest } from '@/api/type/common' const prefix = '/workspace' /** - * 获取资源权限 + * 工作空间下各资源获取资源权限 + * @query 参数 + */ +const getWorkspaceResourceAuthorization: ( + workspace_id: string, + target: string, + resource: string, + page: pageRequest, + params?: any, + loading?: Ref, +) => Promise> = (workspace_id, target, resource, page, params, loading) => { + return get( + `${prefix}/${workspace_id}/resource_user_permission/resource/${target}/resource/${resource}/${page.current_page}/${page.page_size}`, + params, + loading, + ) +} + +/** + * 工作空间下各资源修改成员权限 + * @param 参数 member_id + * @param 参数 { + [ + { + "user_id": "string", + "permission": "NOT_AUTH" + } + ] + } + */ +const putWorkspaceResourceAuthorization: ( + workspace_id: string, + target: string, + resource: string, + body: any, + loading?: Ref, +) => Promise> = (workspace_id, target, resource, body, loading) => { + return put( + `${prefix}/${workspace_id}/resource_user_permission/resource/${target}/resource/${resource}`, + body, + {}, + loading, + ) +} + +/** + * 系统资源授权获取资源权限 * @query 参数 */ const getResourceAuthorization: ( @@ -24,7 +70,7 @@ const getResourceAuthorization: ( } /** - * 修改成员权限 + * 系统资源授权修改成员权限 * @param 参数 member_id * @param 参数 { [ @@ -102,4 +148,6 @@ export default { getUserList, getUserMember, getSystemFolder, + getWorkspaceResourceAuthorization, + putWorkspaceResourceAuthorization } diff --git a/ui/src/components/resource-authorization-drawer/index.vue b/ui/src/components/resource-authorization-drawer/index.vue new file mode 100644 index 000000000..aa38b2e64 --- /dev/null +++ b/ui/src/components/resource-authorization-drawer/index.vue @@ -0,0 +1,291 @@ + + + + {{ $t('views.system.resourceAuthorization.setting.configure') }} + + + + + + + + + + + + + + + + + + + + + + + + + + permissionsHandle(val, row)" + > + + {{ item.label }} + + + + + + + + + + + + {{ item.label }} + {{ item.desc }} + + + + + + + + + + + diff --git a/ui/src/enums/common.ts b/ui/src/enums/common.ts index 8510339f1..0791a235e 100644 --- a/ui/src/enums/common.ts +++ b/ui/src/enums/common.ts @@ -19,4 +19,5 @@ export enum SourceTypeEnum { KNOWLEDGE = 'KNOWLEDGE', APPLICATION = 'APPLICATION', TOOL = 'TOOL', + MODEL = 'MODEL', } diff --git a/ui/src/enums/system.ts b/ui/src/enums/system.ts index 5a4fd920d..b1e3701eb 100644 --- a/ui/src/enums/system.ts +++ b/ui/src/enums/system.ts @@ -3,10 +3,6 @@ export enum AuthorizationEnum { VIEW = 'VIEW', ROLE = 'ROLE', NOT_AUTH = 'NOT_AUTH', - KNOWLEDGE = 'KNOWLEDGE', - APPLICATION = 'APPLICATION', - MODEL = 'MODEL', - TOOL = 'TOOL', } export enum RoleTypeEnum { diff --git a/ui/src/views/application/index.vue b/ui/src/views/application/index.vue index 9044e90d3..0b0a81aa5 100644 --- a/ui/src/views/application/index.vue +++ b/ui/src/views/application/index.vue @@ -249,6 +249,14 @@ {{ $t('common.setting') }} + + + + {{ $t('views.system.resourceAuthorization.title') }} + + @@ -310,6 +322,7 @@ import CreateApplicationDialog from '@/views/application/component/CreateApplica import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue' import CopyApplicationDialog from '@/views/application/component/CopyApplicationDialog.vue' import MoveToDialog from '@/components/folder-tree/MoveToDialog.vue' +import ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue' import ApplicationApi from '@/api/application/application' import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message' import useStore from '@/stores' @@ -318,7 +331,7 @@ import { useRouter, useRoute } from 'vue-router' import { isWorkFlow } from '@/utils/application' import { resetUrl } from '@/utils/common' import { dateFormat } from '@/utils/time' -import { SourceTypeEnum, ValidType, ValidCount } from '@/enums/common' +import { SourceTypeEnum } from '@/enums/common' import permissionMap from '@/permission' import WorkspaceApi from '@/api/workspace/workspace' import { hasPermission } from '@/utils/permission' @@ -358,6 +371,11 @@ const folderList = ref([]) const applicationList = ref([]) const CopyApplicationDialogRef = ref() +const ResourceAuthorizationDrawerRef = ref() +function openAuthorization(item: any) { + ResourceAuthorizationDrawerRef.value.open(item.id) +} + const MoveToDialogRef = ref() function openMoveToDialog(data: any) { diff --git a/ui/src/views/knowledge/component/KnowledgeListContainer.vue b/ui/src/views/knowledge/component/KnowledgeListContainer.vue index a1f0cf583..c3d46185d 100644 --- a/ui/src/views/knowledge/component/KnowledgeListContainer.vue +++ b/ui/src/views/knowledge/component/KnowledgeListContainer.vue @@ -258,7 +258,16 @@ {{ $t('views.shared.authorized_workspace') }} - + + + {{ $t('views.system.resourceAuthorization.title') }} + + From a85c36f289873919be7ad9270a411a8088882328 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Fri, 15 Aug 2025 18:08:18 +0800 Subject: [PATCH 08/18] feat: Workflow form nodes support reference assignment (#3866) #2439 --- .../form_node/impl/base_form_node.py | 37 ++++++++++++ .../items/MultiSelectConstructor.vue | 57 +++++++++++++++++- .../items/RadioCardConstructor.vue | 59 ++++++++++++++++++- .../items/SingleSelectConstructor.vue | 57 +++++++++++++++++- .../dynamics-form/items/radio/RadioCard.vue | 4 +- ui/src/locales/lang/en-US/dynamics-form.ts | 53 ++++++++++------- ui/src/locales/lang/zh-CN/dynamics-form.ts | 53 ++++++++++------- ui/src/locales/lang/zh-Hant/dynamics-form.ts | 53 ++++++++++------- ui/src/workflow/nodes/form-node/index.vue | 3 +- 9 files changed, 301 insertions(+), 75 deletions(-) diff --git a/apps/application/flow/step_node/form_node/impl/base_form_node.py b/apps/application/flow/step_node/form_node/impl/base_form_node.py index dcf35dd3c..9a0e4ba8b 100644 --- a/apps/application/flow/step_node/form_node/impl/base_form_node.py +++ b/apps/application/flow/step_node/form_node/impl/base_form_node.py @@ -17,6 +17,19 @@ from application.flow.i_step_node import NodeResult from application.flow.step_node.form_node.i_form_node import IFormNode +def get_default_option(option_list, _type, value_field): + if option_list is not None and len(option_list) > 0: + default_value_list = [o.get(value_field) for o in option_list if o.get('default')] + if len(default_value_list) == 0: + return option_list[0].get(value_field) + else: + if _type == 'MultiSelect': + return default_value_list + else: + return default_value_list[0] + return [] + + def write_context(step_variable: Dict, global_variable: Dict, node, workflow): if step_variable is not None: for key in step_variable: @@ -44,6 +57,28 @@ class BaseFormNode(IFormNode): for key in form_data: self.context[key] = form_data[key] + def reset_field(self, field): + if ['SingleSelect', 'MultiSelect', 'RadioCard'].__contains__(field.get('input_type')): + if field.get('assignment_method') == 'ref_variables': + option_list = self.workflow_manage.get_reference_field(field.get('option_list')[0], + field.get('option_list')[1:]) + field['option_list'] = option_list + field['default_value'] = get_default_option(option_list, field.get('input_type'), + field.get('value_field')) + + reset_field = ['field', 'label', 'default_value'] + for f in reset_field: + _value = field[f] + if isinstance(_value, str): + field[f] = self.workflow_manage.generate_prompt(_value) + else: + _label_value = _value.get('label') + _value['label'] = self.workflow_manage.generate_prompt(_label_value) + tooltip = _value.get('attrs').get('tooltip') + if tooltip is not None: + _value.get('attrs')['tooltip'] = self.workflow_manage.generate_prompt(tooltip) + return field + def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult: if form_data is not None: self.context['is_submit'] = True @@ -52,6 +87,7 @@ class BaseFormNode(IFormNode): self.context[key] = form_data.get(key) else: self.context['is_submit'] = False + form_field_list = [self.reset_field(field) for field in form_field_list] form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), "is_submit": self.context.get("is_submit", False)} @@ -60,6 +96,7 @@ class BaseFormNode(IFormNode): form_content_format = self.workflow_manage.reset_prompt(form_content_format) prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') value = prompt_template.format(form=form, context=context) + return NodeResult( {'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {}, _write_context=write_context) diff --git a/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue b/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue index 5ff808668..e5ef2b407 100644 --- a/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue +++ b/ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue @@ -1,5 +1,28 @@ - + + + + {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }} + + + + + + {{ + item.label + }} + + + + + {{ $t('dynamicsForm.Select.label') }} @@ -51,6 +74,7 @@ + diff --git a/ui/src/components/dynamics-form/constructor/items/UploadInputConstructor.vue b/ui/src/components/dynamics-form/constructor/items/UploadInputConstructor.vue new file mode 100644 index 000000000..d27349d81 --- /dev/null +++ b/ui/src/components/dynamics-form/constructor/items/UploadInputConstructor.vue @@ -0,0 +1,153 @@ + + + + + + + + + + + {{ tag }} + + + + + {{ $t('common.fileUpload.addExtensions') }} + + + + + + diff --git a/ui/src/components/dynamics-form/items/TextareaInput.vue b/ui/src/components/dynamics-form/items/TextareaInput.vue new file mode 100644 index 000000000..c477c96ec --- /dev/null +++ b/ui/src/components/dynamics-form/items/TextareaInput.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/components/dynamics-form/items/upload/UploadInput.vue b/ui/src/components/dynamics-form/items/upload/UploadInput.vue new file mode 100644 index 000000000..54d099719 --- /dev/null +++ b/ui/src/components/dynamics-form/items/upload/UploadInput.vue @@ -0,0 +1,123 @@ + + + {{ $t('chat.uploadFile.label') }} + + + + + {{ file.name }} + + + {{ formatSize(file.size) }} + + + + + + + + diff --git a/ui/src/locales/lang/en-US/dynamics-form.ts b/ui/src/locales/lang/en-US/dynamics-form.ts index a18d72bb6..b34a4762a 100644 --- a/ui/src/locales/lang/en-US/dynamics-form.ts +++ b/ui/src/locales/lang/en-US/dynamics-form.ts @@ -10,6 +10,8 @@ export default { JsonInput: 'JSON', RadioCard: 'Radio Card', RadioRow: 'Radio Row', + UploadInput: 'File upload', + TextareaInput: 'Multiline Input', }, default: { label: 'Default', @@ -99,6 +101,20 @@ export default { requiredMessage4: 'Text length is a required parameter', }, }, + UploadInput: { + limit: { + label: 'Maximum number of files per upload', + required: 'Maximum number of files is required', + }, + max_file_size: { + label: 'Maximum file size (MB)', + required: 'Maximum file size is required', + }, + accept: { + label: 'File type', + required: 'File type is required', + }, + }, AssignmentMethod: { label: 'Assignment Method', custom: { @@ -106,6 +122,10 @@ export default { }, ref_variables: { label: 'Reference Variables', + popover: 'Variable values must comply with', + popover_label: 'Label', + popover_value: 'Value', + popover_default: 'Is Default', }, }, } diff --git a/ui/src/locales/lang/zh-CN/ai-chat.ts b/ui/src/locales/lang/zh-CN/ai-chat.ts index 398d8f790..72524044d 100644 --- a/ui/src/locales/lang/zh-CN/ai-chat.ts +++ b/ui/src/locales/lang/zh-CN/ai-chat.ts @@ -71,6 +71,7 @@ export default { imageMessage: '请解析图片内容', fileMessage: '请解析文件内容', errorMessage: '上传失败', + fileRepeat: '文件已存在', }, executionDetails: { title: '执行详情', diff --git a/ui/src/locales/lang/zh-CN/dynamics-form.ts b/ui/src/locales/lang/zh-CN/dynamics-form.ts index b0d29c1f9..24fc5e83b 100644 --- a/ui/src/locales/lang/zh-CN/dynamics-form.ts +++ b/ui/src/locales/lang/zh-CN/dynamics-form.ts @@ -10,6 +10,8 @@ export default { JsonInput: 'JSON文本框', RadioCard: '选项卡', RadioRow: '单行选项卡', + UploadInput: '文件上传', + TextareaInput: '多行文本框', }, default: { label: '默认值', @@ -99,13 +101,32 @@ export default { requiredMessage4: '文本长度为必填参数', }, }, + UploadInput: { + limit: { + label: '单次上传最多文件数', + required: '单次上传最多文件数必填', + }, + max_file_size: { + label: '每个文件最大(MB)', + required: '每个文件最大(MB)必填', + }, + accept: { + label: '文件类型', + required: '文件类型必填', + }, + }, AssignmentMethod: { label: '赋值方式', + custom: { label: '自定义', }, ref_variables: { label: '引用变量 ', + popover: '变量的值必须符合', + popover_label: '标签', + popover_value: '值', + popover_default: '是否为默认值', }, }, } diff --git a/ui/src/locales/lang/zh-Hant/dynamics-form.ts b/ui/src/locales/lang/zh-Hant/dynamics-form.ts index 740abe22d..837541ae4 100644 --- a/ui/src/locales/lang/zh-Hant/dynamics-form.ts +++ b/ui/src/locales/lang/zh-Hant/dynamics-form.ts @@ -10,6 +10,8 @@ export default { JsonInput: 'JSON文字框', RadioCard: '選項卡', RadioRow: '單行選項卡', + UploadInput: '文件上傳', + TextareaInput: '多行文字方塊', }, default: { label: '預設值', @@ -99,13 +101,31 @@ export default { requiredMessage4: '文字長度為必填參數', }, }, + UploadInput: { + limit: { + label: '單次上傳最多文件數', + required: '單次上傳最多文件數必填', + }, + max_file_size: { + label: '每個文件最大(MB)', + required: '每個文件最大必填', + }, + accept: { + label: '文件類型', + required: '文件類型必填', + }, + }, AssignmentMethod: { label: '賦值方式', custom: { label: '自定義', }, ref_variables: { - label: '引用變量', + label: '參考變量', + popover: '變量的值必須符合', + popover_label: '標籤', + popover_value: '值', + popover_default: '是否為預設值', }, }, }
{{ item.label }}