diff --git a/ui/src/api/API_README.md b/ui/src/api/API_README.md index f284b3ae5fc..eb6b8c910ab 100644 --- a/ui/src/api/API_README.md +++ b/ui/src/api/API_README.md @@ -21,7 +21,7 @@ src/api/ │ │ ├── application/ # 智能体接口 │ │ ├── knowledge/ # 知识库接口 │ │ ├── model/ # 模型接口 -│ │ ├── trigger/ # 触发器分页查询接口 +│ │ ├── trigger/ # 触发器查询及维护接口 │ │ ├── tool/ # 工具、工具工作流及工具商店接口 │ │ └── .ts # 工作空间公共资源接口 │ └── provider.ts # Workspace 与 System 共用的模型供应商接口 @@ -186,7 +186,8 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文 `resource_user_permission/resource//resource/`;与 System 用户视角的 `user_resource_permission` 区分。分页使用 `ParamsPage`,直接返回 `ResponsePage`;提交 `ResourceUserPermissionPayload[]`。 -查询和更新接口内部调用 `getWorkspaceId()` 获取路由工作空间,调用方不传工作空间 ID。 +用户及用户组的查询和更新接口均以必填的 `workspaceId` 为首个参数,由调用方从资源或文件夹 +接口数据的 `workspace_id` 传入,不读取或回退到路由工作空间。 `ResourceAuthorizationTargetType` 包含资源类型和三个 `_FOLDER` 类型,文件夹类型用于后端 鉴权。包含子资源时传 `include_children: true` 及经过管理权限筛选的 `folder_ids`, 普通资源或仅当前文件夹不传子文件夹 ID。loading、刷新及成功提示由抽屉负责。 @@ -200,9 +201,10 @@ API 枚举与类型统一在 `src/api` 范围内管理,相关规则由本文 System 资源管理的用户授权维护在 `admin/system/resource-management/resource-authorization.ts`, 使用 `/system/workspace//resource_management/resource//resource/`。 -查询与保存签名、分页类型、响应类型及提交类型与 Workspace 用户授权保持一致;工作空间 ID -由接口内部读取,loading 由组件管理。`ResourceAuthorizationDrawer` 内部通过 -`isSystemResource()` 选择完整的用户授权 API 对象,作为该抽屉的范围选择例外;用户组仍使用 +查询与保存方法以必填的 `workspaceId` 为首个参数,由调用方从资源数据的 `workspace_id` 传入, +不得读取或回退到路由工作空间。其余参数、分页类型、响应类型及提交类型与 Workspace 用户授权一致。 +loading 由组件管理。`ResourceAuthorizationDrawer` 内部通过 `isSystemResource()` 选择完整的用户授权 +API 对象和工作空间上下文,作为该抽屉的范围选择例外;用户组仍使用 现有 Workspace 接口,不推测 System 用户组路径。 ### 关联资源 @@ -210,5 +212,16 @@ System 资源管理的用户授权维护在 `admin/system/resource-management/re `admin/workspace/related-resources.ts` 维护关联资源查询: `getResourceDependencies` 查询当前资源依赖的资源,对应后端 `mapping_resource`; `getResourceDependents` 查询引用当前资源的资源,对应后端 `resource_mapping`。 -前端按关联资源语义命名,后端接口路径保持不变。该文件当前保留旧版请求签名, -尚未完成 v3 请求适配及页面接入。 +前端按关联资源语义命名,后端接口路径保持不变。方法接收工作空间 ID、资源类型、资源 ID、`ParamsPage` +和查询参数;工作空间 ID 由目标资源数据的 `workspace_id` 显式传入,不读取路由。返回 `ResponsePage`, +不传 loading,不重复解包响应。`RelatedResource` 通过 `@/api/types` 导出,保留 +`source_*`、`target_*` 字段。依赖查询按 `target_type` 筛选,被依赖查询按 `source_type` +筛选,类型数组沿用请求层的数组序列化。页面负责按资源范围传入完整 API,未推测 System 接口。 + +### 触发器维护 + +`workspace/trigger/trigger.ts` 维护分页、详情、新建、编辑、删除及批量接口。 +`putBatchActivateTrigger(ids, isActive)` 提交 `{ id_list, is_active }` 到 `batch_activate`; +`putBatchDeleteTrigger(ids)` 提交 `{ id_list }` 到 `batch_delete`。单项启停通过 `putTrigger` +仅提交 `is_active`。`Trigger` 为分页摘要,`TriggerDetail` 为含任务参数的完整详情; +`TriggerPayload` 用于新建和编辑,ID 在新建前生成以展示事件回调 URL。 diff --git a/ui/src/api/admin/system/resource-management/resource-authorization.ts b/ui/src/api/admin/system/resource-management/resource-authorization.ts index acf003ed64b..b61c12673f6 100644 --- a/ui/src/api/admin/system/resource-management/resource-authorization.ts +++ b/ui/src/api/admin/system/resource-management/resource-authorization.ts @@ -1,22 +1,32 @@ import { get, put } from '../../core/request' import type { ParamsPage, ResponsePage } from '../../core/types' import type { Dict, ResourceAuthorizationTargetType, ResourceUserPermission, ResourceUserPermissionPayload } from '@/api/types' -import { getWorkspaceId } from '@/utils/resource-context' -const getPrefix = () => `/system/workspace/${getWorkspaceId()}/resource_management` +const getPrefix = (workspaceId: string) => `/system/workspace/${workspaceId}/resource_management` /** 获取 System 资源管理中指定资源或文件夹的用户权限分页列表。 */ -const getResourceAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, page: ParamsPage, query?: Dict) => { +const getResourceAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + page: ParamsPage, + query?: Dict, +) => { return get>( - `${getPrefix()}/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`, + `${getPrefix(workspaceId)}/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`, query, ) } /** 更新 System 资源管理中的用户权限及文件夹生效范围。 */ -const putResourceAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, permissions: ResourceUserPermissionPayload[]) => { +const putResourceAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + permissions: ResourceUserPermissionPayload[], +) => { return put( - `${getPrefix()}/resource/${targetId}/resource/${resource}`, + `${getPrefix(workspaceId)}/resource/${targetId}/resource/${resource}`, permissions, ) } diff --git a/ui/src/api/admin/workspace/related-resources.ts b/ui/src/api/admin/workspace/related-resources.ts index 05e7598a4ca..51588dea1f8 100644 --- a/ui/src/api/admin/workspace/related-resources.ts +++ b/ui/src/api/admin/workspace/related-resources.ts @@ -1,33 +1,21 @@ -import { Result } from '@/request/Result' -import { get, put, post, del } from '@/request/index' -import type { Ref } from 'vue' -import type { pageRequest } from '@/api/type/common' -const prefix = '/workspace' +import { get } from '../core/request' +import type { ParamsPage, ResponsePage } from '../core/types' +import type { Dict, RelatedResource, ResourceType } from '@/api/types' /** 获取引用当前资源的资源。 */ -const getResourceDependents: ( - workspace_id: string, - resource: string, - resource_id: string, - page: pageRequest, - params?: any, - loading?: Ref, -) => Promise> = (workspace_id, resource, resource_id, page, params, loading) => { - return get(`${prefix}/${workspace_id}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`, params, loading) +const getResourceDependents = (workspaceId: string, resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { + return get>( + `/workspace/${workspaceId}/resource_mapping/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, + query, + ) } + /** 获取当前资源依赖的资源。 */ -const getResourceDependencies: ( - workspace_id: string, - resource: string, - resource_id: string, - page: pageRequest, - params?: any, - loading?: Ref, -) => Promise> = (workspace_id, resource, resource_id, page, params, loading) => { - return get(`${prefix}/${workspace_id}/mapping_resource/${resource}/${resource_id}/${page.current_page}/${page.page_size}`, params, loading) +const getResourceDependencies = (workspaceId: string, resource: ResourceType, resourceId: string, page: ParamsPage, query?: Dict) => { + return get>( + `/workspace/${workspaceId}/mapping_resource/${resource}/${resourceId}/${page.currentPage}/${page.pageSize}`, + query, + ) } -export default { - getResourceDependents, - getResourceDependencies, -} +export default { getResourceDependents, getResourceDependencies } diff --git a/ui/src/api/admin/workspace/resource-authorization.ts b/ui/src/api/admin/workspace/resource-authorization.ts index 8d76f8ffb80..1019c9223e7 100644 --- a/ui/src/api/admin/workspace/resource-authorization.ts +++ b/ui/src/api/admin/workspace/resource-authorization.ts @@ -8,44 +8,58 @@ import type { ResourceUserPermission, ResourceUserPermissionPayload, } from '@/api/types' -import { getWorkspaceId } from '@/utils/resource-context' -const getPrefix = () => { - const workspaceId = getWorkspaceId() - return `/workspace/${workspaceId}` -} +const getPrefix = (workspaceId: string) => `/workspace/${workspaceId}` /** 获取指定资源或文件夹的用户权限分页列表。 */ -const getResourceAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, page: ParamsPage, query?: Dict) => { +const getResourceAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + page: ParamsPage, + query?: Dict, +) => { return get>( - `${getPrefix()}/resource_user_permission/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`, + `${getPrefix(workspaceId)}/resource_user_permission/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`, query, ) } /** 更新资源的用户权限,可同时应用到有管理权限的子文件夹及资源。 */ -const putResourceAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, permissions: ResourceUserPermissionPayload[]) => { +const putResourceAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + permissions: ResourceUserPermissionPayload[], +) => { return put( - `${getPrefix()}/resource_user_permission/resource/${targetId}/resource/${resource}`, + `${getPrefix(workspaceId)}/resource_user_permission/resource/${targetId}/resource/${resource}`, permissions, ) } /** 获取指定资源或文件夹的用户组权限分页列表。 */ -const getResourceUserGroupAuthorization = (targetId: string, resource: ResourceAuthorizationTargetType, page: ParamsPage, query?: Dict) => { +const getResourceUserGroupAuthorization = ( + workspaceId: string, + targetId: string, + resource: ResourceAuthorizationTargetType, + page: ParamsPage, + query?: Dict, +) => { return get>( - `${getPrefix()}/resource_user_group_permission/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`, + `${getPrefix(workspaceId)}/resource_user_group_permission/resource/${targetId}/resource/${resource}/${page.currentPage}/${page.pageSize}`, query, ) } /** 更新资源的用户组权限,可应用到有管理权限的子文件夹及资源。 */ const putResourceUserGroupAuthorization = ( + workspaceId: string, targetId: string, resource: ResourceAuthorizationTargetType, permissions: ResourceUserGroupPermissionPayload[], ) => { return put( - `${getPrefix()}/resource_user_group_permission/resource/${targetId}/resource/${resource}`, + `${getPrefix(workspaceId)}/resource_user_group_permission/resource/${targetId}/resource/${resource}`, permissions, ) } diff --git a/ui/src/api/admin/workspace/trigger/trigger.ts b/ui/src/api/admin/workspace/trigger/trigger.ts index 0426aa83f70..b55ee53ad65 100644 --- a/ui/src/api/admin/workspace/trigger/trigger.ts +++ b/ui/src/api/admin/workspace/trigger/trigger.ts @@ -1,11 +1,26 @@ -import { get } from '../../core/request' +import { get, post, put, del } from '../../core/request' import type { ParamsPage, ResponsePage } from '../../core/types' -import type { Dict, Trigger } from '@/api/types' +import type { Dict, Trigger, TriggerDetail, TriggerPayload } from '@/api/types' import { getWorkspaceId } from '@/utils/resource-context' +const getPrefix = () => `/workspace/${getWorkspaceId()}/trigger` + /** 获取当前工作空间的触发器分页列表。 */ -const getTriggerPage = (page: ParamsPage, query: Dict = {}) => { - return get>(`/workspace/${getWorkspaceId()}/trigger/${page.currentPage}/${page.pageSize}`, query) -} +const getTriggerPage = (page: ParamsPage, query: Dict = {}) => + get>(`${getPrefix()}/${page.currentPage}/${page.pageSize}`, query) +/** 获取触发器配置和关联任务详情。 */ +const getTriggerDetail = (triggerId: string) => get(`${getPrefix()}/${triggerId}`) +/** 创建触发器及关联任务。 */ +const postTrigger = (payload: TriggerPayload) => post(getPrefix(), payload) +/** 更新触发器配置或启用状态。 */ +const putTrigger = (triggerId: string, payload: Partial) => + put, TriggerDetail>(`${getPrefix()}/${triggerId}`, payload) +/** 删除触发器及关联记录。 */ +const deleteTrigger = (triggerId: string) => del(`${getPrefix()}/${triggerId}`) +/** 批量删除触发器。 */ +const putBatchDeleteTrigger = (triggerIds: string[]) => put<{ id_list: string[] }, boolean>(`${getPrefix()}/batch_delete`, { id_list: triggerIds }) +/** 批量启用或禁用触发器。 */ +const putBatchActivateTrigger = (triggerIds: string[], isActive: boolean) => + put<{ id_list: string[]; is_active: boolean }, boolean>(`${getPrefix()}/batch_activate`, { id_list: triggerIds, is_active: isActive }) -export default { getTriggerPage } +export default { getTriggerPage, getTriggerDetail, postTrigger, putTrigger, deleteTrigger, putBatchDeleteTrigger, putBatchActivateTrigger } diff --git a/ui/src/api/enums/trigger.ts b/ui/src/api/enums/trigger.ts index 083570de38f..885938d815d 100644 --- a/ui/src/api/enums/trigger.ts +++ b/ui/src/api/enums/trigger.ts @@ -3,3 +3,12 @@ export const TRIGGER_TYPE = { SCHEDULED: 'SCHEDULED', EVENT: 'EVENT', } as const + +/** 定时触发周期。 */ +export const TRIGGER_SCHEDULE_TYPE = { DAILY: 'daily', WEEKLY: 'weekly', MONTHLY: 'monthly', INTERVAL: 'interval', CRON: 'cron' } as const +/** 触发任务参数来源。 */ +export const TRIGGER_PARAMETER_SOURCE = { CUSTOM: 'custom', REFERENCE: 'reference' } as const +/** 事件请求参数类型。 */ +export const TRIGGER_BODY_TYPE = { STRING: 'string', INT: 'int', DICT: 'dict', ARRAY: 'array', FLOAT: 'float', BOOLEAN: 'boolean' } as const +/** 定时间隔单位。 */ +export const TRIGGER_INTERVAL_UNIT = { MINUTES: 'minutes', HOURS: 'hours' } as const diff --git a/ui/src/api/types/index.ts b/ui/src/api/types/index.ts index 9be14eff71a..2f8f026044f 100644 --- a/ui/src/api/types/index.ts +++ b/ui/src/api/types/index.ts @@ -17,3 +17,4 @@ export type * from './folder' export type * from './tool' export type * from './knowledge' export type * from './trigger' +export type * from './related-resources' diff --git a/ui/src/api/types/related-resources.ts b/ui/src/api/types/related-resources.ts new file mode 100644 index 00000000000..5d6762b5841 --- /dev/null +++ b/ui/src/api/types/related-resources.ts @@ -0,0 +1,18 @@ +/** 关联资源分页查询返回的资源关系及展示信息。 */ +import type { ResourceType } from './resource-authorization' + +export interface RelatedResource { + id: string + name: string | null + desc?: string | null + source_id: string + source_type: ResourceType + target_id: string + target_type: ResourceType + type?: string | null + icon?: string | null + username?: string | null + workspace_id?: string | null + workspace_name?: string | null + folder_id?: string | null +} diff --git a/ui/src/api/types/trigger.ts b/ui/src/api/types/trigger.ts index b153e22f46a..2edc6a29ebd 100644 --- a/ui/src/api/types/trigger.ts +++ b/ui/src/api/types/trigger.ts @@ -1,4 +1,11 @@ -import type { TRIGGER_TYPE } from '@/api/enums' +import type { + RESOURCE_TYPE, + TRIGGER_SCHEDULE_TYPE, + TRIGGER_PARAMETER_SOURCE, + TRIGGER_BODY_TYPE, + TRIGGER_INTERVAL_UNIT, + TRIGGER_TYPE, +} from '@/api/enums' export type TriggerType = (typeof TRIGGER_TYPE)[keyof typeof TRIGGER_TYPE] @@ -21,3 +28,48 @@ export interface Trigger { create_user: string | null create_time: string } + +export type TriggerTaskSource = typeof RESOURCE_TYPE.APPLICATION | typeof RESOURCE_TYPE.TOOL +export interface TriggerParameter { + source: (typeof TRIGGER_PARAMETER_SOURCE)[keyof typeof TRIGGER_PARAMETER_SOURCE] + value: string | string[] +} +export type TriggerParameters = Record> +export interface TriggerTaskPayload { + id?: string + source_type: TriggerTaskSource + source_id: string + is_active?: boolean + parameter: TriggerParameters + meta?: Record +} +export interface TriggerBodyField { + field: string + type: (typeof TRIGGER_BODY_TYPE)[keyof typeof TRIGGER_BODY_TYPE] + desc?: string + required?: boolean +} +export interface TriggerSetting { + schedule_type?: (typeof TRIGGER_SCHEDULE_TYPE)[keyof typeof TRIGGER_SCHEDULE_TYPE] + interval_unit?: (typeof TRIGGER_INTERVAL_UNIT)[keyof typeof TRIGGER_INTERVAL_UNIT] + interval_value?: number + days?: (number | string)[] + time?: string[] + cron_expression?: string + token?: string + body?: TriggerBodyField[] +} +export interface TriggerPayload { + id: string + name: string + desc: string + trigger_type: TriggerType + trigger_setting: TriggerSetting + trigger_task: TriggerTaskPayload[] + is_active?: boolean + meta?: Record +} +export interface TriggerDetail extends TriggerPayload { + application_task_list?: Partial[] + tool_task_list?: Partial[] +} diff --git a/ui/src/assets/trigger/icon_event.svg b/ui/src/assets/trigger/icon_event.svg new file mode 100644 index 00000000000..34a49a90d38 --- /dev/null +++ b/ui/src/assets/trigger/icon_event.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ui/src/assets/trigger/icon_scheduled.svg b/ui/src/assets/trigger/icon_scheduled.svg new file mode 100644 index 00000000000..281d18dc517 --- /dev/null +++ b/ui/src/assets/trigger/icon_scheduled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/src/components.d.ts b/ui/src/components.d.ts index 2fd49a81b92..26b3ab8399e 100644 --- a/ui/src/components.d.ts +++ b/ui/src/components.d.ts @@ -45,6 +45,7 @@ declare module 'vue' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] ToolIcon: typeof import('./components/global/mk-icon/ToolIcon.vue')['default'] + TriggerIcon: typeof import('./components/global/mk-icon/TriggerIcon.vue')['default'] } } @@ -83,4 +84,5 @@ declare global { const RouterLink: typeof import('vue-router')['RouterLink'] const RouterView: typeof import('vue-router')['RouterView'] const ToolIcon: typeof import('./components/global/mk-icon/ToolIcon.vue')['default'] + const TriggerIcon: typeof import('./components/global/mk-icon/TriggerIcon.vue')['default'] } \ No newline at end of file diff --git a/ui/src/components/COMPONENT_README.md b/ui/src/components/COMPONENT_README.md index 62580a4931c..dbede148323 100644 --- a/ui/src/components/COMPONENT_README.md +++ b/ui/src/components/COMPONENT_README.md @@ -51,8 +51,10 @@ src/components/ │ │ └── ModelParamsDialog.vue # 模型参数动态表单弹窗 │ ├── workspace-dropdown/ │ │ └── index.vue # 工作空间选择下拉框 -│ ├── related-resources-drawer/ # 关联资源查看抽屉(待完成 v3 接入) -│ │ └── index.vue +│ ├── related-resources-drawer/ # 关联资源查看抽屉 +│ │ ├── index.vue +│ │ ├── ResourceIcon.vue # 两个关系方向共用的资源图标 +│ │ └── types.ts # 抽屉目标与规范化表格行类型 │ ├── resource-authorization-drawer/ # 资源用户组、用户授权抽屉与权限配置弹窗 │ │ ├── index.vue │ │ ├── UserAuthorization.vue # 按用户查询与授权 @@ -94,6 +96,7 @@ src/components/ │ ├── mk-icon/ │ │ ├── ApplicationIcon.vue # 智能体头像与默认图标 │ │ ├── KnowledgeIcon.vue # 知识库类型与自定义图标 +│ │ ├── TriggerIcon.vue # 触发器类型图标 │ │ ├── ToolIcon.vue # 工具类型与自定义图标 │ │ └── index.vue # SVG Symbol 与 Element Plus 图标统一入口 │ ├── mk-infinite-scroll/ @@ -1208,7 +1211,7 @@ Workspace 的文件夹虚拟树业务组件。组件根据当前资源上下文 移动目录树继续以调用方传入的 `v-model` 为准。 可编辑文件夹的菜单提供“资源授权”,根据对应资源模块的 `folderAuth(folder.id)` 控制入口。 -点击后复用 `ResourceAuthorizationDrawer`,传入 Workspace 资源授权 API、当前 `source` 和 +点击后复用 `ResourceAuthorizationDrawer`,传入当前 `source`,通过 `workspaceId` Prop 传入文件夹的 `workspace_id`,调用 `open(folder.id, folder)` 传入 原始完整文件夹子树;搜索过滤不缩减子资源授权范围。只读选择场景不挂载授权抽屉。 ```vue @@ -1347,20 +1350,24 @@ import WorkspaceRelationTags from '@/components/business/workspace-relation-tags 抽屉内部使用 `isSystemResource()` 判断:用户授权在 System 资源管理中选择 `api/admin/system/resource-management/resource-authorization.ts`,其他范围选择 Workspace 授权接口。 用户组授权继续使用 Workspace 的 `resource_user_group_permission` 接口,尚无独立 System 接口。 -两个标签子组件只调用入口传入的完整 API 对象,不自行判断资源范围。 -接口请求与文件夹鉴权均通过 `getWorkspaceId()` 读取路由中的工作空间。 +用户授权子组件接收入口选择的完整 `api` 对象和 `workspaceId`,统一显式传参; +用户组子组件接收完整 Workspace API 对象及工作空间 ID。 +Workspace 与 System 授权均通过 `workspaceId` Prop 传入资源或文件夹接口数据的 `workspace_id`, +不从 `open` 参数或路由读取工作空间。 +用户、用户组查询与保存、用户组成员查询及子文件夹鉴权均使用该工作空间;缺少时不发送授权请求。 `isFolder`、`isRootFolder` 标记文件夹及根目录,传入文件夹类型也可识别文件夹。 -入口 `index.vue` 只管理抽屉、标签切换和公共资源权限上下文;`UserGroupAuthorization.vue` -与 `UserAuthorization.vue` 分别管理各自的查询、分页、选择、单项及批量保存,复用 -`PermissionConfigDialog`。仅挂载当前标签组件,切换后重新查询并重置临时状态;子组件通过 -`v-model:submitting` 同步保存状态,通过 `refresh` 通知入口转发刷新。 +入口 `index.vue` 管理抽屉、标签切换、公共权限上下文及唯一的 `PermissionConfigDialog`, +统一处理单项与批量提交、成功提示、关闭配置弹窗和列表刷新。`UserGroupAuthorization.vue` +与 `UserAuthorization.vue` 负责查询、分页和选择,通过 `configure` 事件传出对象 ID 与可选权限, +通过 `submitting` Prop 接收保存状态,并暴露 `refresh()` 供保存成功后清空选择、重新查询。 +仅挂载当前标签组件,切换后重新查询并重置临时状态。 默认打开“按用户组”,展示用户组名称、成员数和操作权限,支持名称搜索;成员数链接打开共享 `UserGroupMembersDrawer`。原用户授权放在“按用户”,保留姓名、用户名、权限及商业版本角色搜索。 两个标签均支持跨页选择、单项和批量授权;切换标签重置搜索、分页及选择,保存期间禁止切换。 查询仅应用最新响应,避免快速切换标签后串用用户与用户组数据;搜索后清空选择。 根目录隐藏“不授权”,返回的“不授权”按“查看”展示。包含子资源时必须传入完整文件夹子树, -抽屉根据当前路由工作空间筛选有管理权限的文件夹 ID;没有可管理文件夹时禁用该范围。 +抽屉根据文件夹所属工作空间筛选有管理权限的文件夹 ID;没有可管理文件夹时禁用该范围。 `PermissionConfigDialog` 只收集权限和范围,保存成功才关闭并刷新,失败保留配置。 保存成功触发 `refresh`,关闭动画结束后重置临时数据并触发 `closed`,供资源 Action 卸载按需挂载的抽屉。 @@ -1370,13 +1377,26 @@ import WorkspaceRelationTags from '@/components/business/workspace-relation-tags `SystemUserGroup`,按其 `workspace_id` 查询成员。支持用户名、姓名搜索和分页,角色使用 `MkTagGroup` 展示;供系统资源授权页面和资源授权抽屉共同复用。 -资源授权抽屉内部公共展示 Props 放在同目录 `types.ts`,权限选项类型从 -`RESOURCE_PERMISSION_OPTIONS` 派生;用户和用户组 API Props 分别保留完整 API 对象类型, -用户组组件只接收具备用户组方法的 Workspace API。 +资源授权列表只通过 `permissionOptions` 接收可选权限,不再使用 `config` 包装;根目录未提供 +“不授权”选项时,接口返回的“不授权”按“查看”展示。用户权限搜索选项在用户组件内按版本生成, +保留“不授权”搜索项。文件夹生效范围和可管理文件夹 ID 保留在入口,用于统一提交。 +权限选项类型从 `RESOURCE_PERMISSION_OPTIONS` 派生,保留在同目录 `types.ts`; +用户授权与用户组 API Props 均保留完整 API 对象类型,用户组组件只接收具备用户组方法的 Workspace API。 ### RelatedResourcesDrawer 关联资源查看抽屉,路径为 `business/related-resources-drawer/index.vue`,组件名为 `RelatedResourcesDrawer`。标题为“关联资源”,操作入口文案统一为“查看关联资源”; 两个方向分别显示“依赖的资源”和“引用此资源的资源”。 -当前保留旧版实现,尚未完成 v3 请求、国际化依赖适配及页面接入。 +通过 `open(resourceType, resource)` 传入资源类型和包含 `workspace_id` 的快照;查询接口使用该资源所属工作空间。 +`close()` 关闭,关闭动画结束后 +清理搜索、分页和筛选并触发 `closed`。模型和非工作流工具默认展示“引用此资源的资源”。 +复用 `MkDrawer`、`MkComplexSearch`、`MkTable`;两种关系共用表格和分页查询,切换方向 +重置搜索和页码,过期响应不写回当前列表。 + +调用页面传入完整的 `api: typeof RelatedResourcesApi`,按所属资源范围选择真实 API; +抽屉不拼接 System URL。`showWorkspace` 由页面按范围及版本决定;企业版开启后查询 +工作空间选项,使用 `MkTableFilter` 提供多选、全选、确认和重置,提交 `workspace_ids`。 +资源名称仅作为文本展示,暂不提供目标资源跳转或相关回调;表格内部按关系方向统一资源 ID 和类型。 +`ResourceIcon.vue` 仅负责展示,供应商由抽屉查询后传入;模型供应商标识来自关系记录的 `icon`。 +工作空间模型列表已通过 `RelatedResourcesModelAction` 接入;其他资源页面入口尚未接入。 diff --git a/ui/src/components/business/folder-tree/index.vue b/ui/src/components/business/folder-tree/index.vue index 99c9c4a2fa5..5736ad263c2 100644 --- a/ui/src/components/business/folder-tree/index.vue +++ b/ui/src/components/business/folder-tree/index.vue @@ -1,5 +1,5 @@ + + diff --git a/ui/src/components/business/related-resources-drawer/index.vue b/ui/src/components/business/related-resources-drawer/index.vue index 32a691c33c0..d72bda86798 100644 --- a/ui/src/components/business/related-resources-drawer/index.vue +++ b/ui/src/components/business/related-resources-drawer/index.vue @@ -1,612 +1,193 @@ - -watch( - [() => workspaceOptions.value, () => filterText.value], - () => { - if (!filterText.value.length) { - filterData.value = workspaceOptions.value - } - filterData.value = workspaceOptions.value.filter((v: any) => v.label.toLowerCase().includes(filterText.value.toLowerCase())) - }, - { immediate: true }, -) + diff --git a/ui/src/components/business/related-resources-drawer/types.ts b/ui/src/components/business/related-resources-drawer/types.ts new file mode 100644 index 00000000000..0ac0cb7e844 --- /dev/null +++ b/ui/src/components/business/related-resources-drawer/types.ts @@ -0,0 +1,12 @@ +import type { ToolType } from '@/api/types' + +/** 打开抽屉时传入的资源快照。 */ +export interface RelatedResourceTarget { + id: string + workspace_id: string + name: string + icon?: string | null + type?: string | number + tool_type?: ToolType + provider?: string +} diff --git a/ui/src/components/business/resource-authorization-drawer/UserAuthorization.vue b/ui/src/components/business/resource-authorization-drawer/UserAuthorization.vue index b36c5062156..07d44375472 100644 --- a/ui/src/components/business/resource-authorization-drawer/UserAuthorization.vue +++ b/ui/src/components/business/resource-authorization-drawer/UserAuthorization.vue @@ -1,31 +1,34 @@ @@ -123,6 +100,7 @@ onMounted(() => loadPermissions()) diff --git a/ui/src/components/business/resource-authorization-drawer/index.vue b/ui/src/components/business/resource-authorization-drawer/index.vue index cb1df015a9a..4029d62e610 100644 --- a/ui/src/components/business/resource-authorization-drawer/index.vue +++ b/ui/src/components/business/resource-authorization-drawer/index.vue @@ -1,24 +1,27 @@ diff --git a/ui/src/components/global/mk-icon/TriggerIcon.vue b/ui/src/components/global/mk-icon/TriggerIcon.vue new file mode 100644 index 00000000000..9c43aafc4c9 --- /dev/null +++ b/ui/src/components/global/mk-icon/TriggerIcon.vue @@ -0,0 +1,16 @@ + + + diff --git a/ui/src/permission/modules/index.ts b/ui/src/permission/modules/index.ts index 6234e868b2f..f887e4d2983 100644 --- a/ui/src/permission/modules/index.ts +++ b/ui/src/permission/modules/index.ts @@ -10,5 +10,6 @@ import knowledge from './knowledge' import tool from './tool' import model from './model' import system from './system' +import trigger from './trigger' -export default { application, knowledge, tool, model, ...system } +export default { application, knowledge, tool, model, trigger, ...system } diff --git a/ui/src/permission/modules/trigger.ts b/ui/src/permission/modules/trigger.ts new file mode 100644 index 00000000000..50db7ec85dd --- /dev/null +++ b/ui/src/permission/modules/trigger.ts @@ -0,0 +1,8 @@ +/** 工作空间触发器操作权限。 */ +import { can } from '../policy' +import { PermissionConstants as P } from '../core' +export default { + create: () => can(P.TRIGGER_CREATE), + edit: () => can(P.TRIGGER_EDIT), + delete: () => can(P.TRIGGER_DELETE), +} diff --git a/ui/src/views/VIEW_README.md b/ui/src/views/VIEW_README.md index 4b73a538aa7..4b94e2c7a3f 100644 --- a/ui/src/views/VIEW_README.md +++ b/ui/src/views/VIEW_README.md @@ -310,7 +310,7 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功 | `system/settings/email/EmailSettingsView.vue` | 系统邮件 SMTP 服务配置页面 | | `system/operate-logs/OperateLogListView.vue` | 系统操作日志查询与清理页面 | | `system/shared-resources/SharedModelview.vue` | System 共享模型查询与模型卡片页面 | -| `trigger/TriggerView.vue` | 工作空间触发器筛选与分页列表 | +| `trigger/TriggerView.vue` | 工作空间触发器查询、新建、编辑及单项和批量启停、删除 | | `tool/ToolView.vue` | 工作空间工具目录与工具卡片页面 | ## 备注要求 @@ -392,4 +392,26 @@ Dialog。新增或重命名文件时,应同步更新所有导入和页面功 `AuthorizeKnowledgeAction` 和 `AuthorizeApplicationAction`,由列表页面组合,显式传入 `label` 和当前资源,保存成功后通过 `refresh` 刷新列表。Action 使用对应资源的 Workspace 或 System `auth` 权限判断,共享资源不展示入口。点击后按需挂载公共 `ResourceAuthorizationDrawer`, -传入资源类型并调用 `open(id)`;在 `closed` 后卸载,不再复制授权表格或请求逻辑。 +传入资源类型及 `workspaceId` Prop(来自资源数据的 `workspace_id`),调用 `open(resource.id)`; +Workspace 与 System 授权均使用该工作空间 ID,不读取路由工作空间。 +在 `closed` 后卸载,不再复制授权表格或请求逻辑。 + +## 触发器维护 + +`trigger/TriggerView.vue` 负责列表查询、跨页选择、单项启停/删除及批量操作,通过 +`$perm.trigger` 控制工作空间级操作权限。删除最后一页数据后自动回退到有效页码; +状态更新以接口成功为准,操作失败时保留原状态或选择。 +`trigger/TriggerFormDrawer.vue` 共用新建和编辑流程,编辑时查询详情并保留任务 ID、参数、 +启用状态及 meta。定时支持每日、每周、每月、间隔和五段 Cron;事件支持 URL、Token 和请求参数。 +任务选择复用 `SelectApplicationDialog` 与 `SelectToolDialog`,工具限定自定义和工作流类型。 +`trigger/components/TaskParameters.vue` 展示任务输入及事件参数引用, +`trigger/task-parameters.ts` 从资源输入定义生成字段,只补全缺失值,不覆盖已有配置。 +保存时校验所有任务,包括折叠的任务;接口失败保持抽屉打开。 + +## 模型关联资源 Action + +`model/model-card/action-dropdown/RelatedResourcesModelAction.vue` 在工作空间模型卡片的更多菜单中 +提供“查看关联资源”,由 `ModelView` 按 `model.workspace.relateMap(model.id)` 控制入口权限, +共享模型不展示。页面传入完整 `RelatedResourcesApi`;Action 点击后 +按需挂载 `RelatedResourcesDrawer`,以模型类型打开,在 `closed` 后卸载。 +默认展示引用该模型的资源,资源名称仅展示文本,暂不支持打开目标资源。 diff --git a/ui/src/views/application/ApplicationView.vue b/ui/src/views/application/ApplicationView.vue index 52ceb85e93c..ac797cab2e6 100644 --- a/ui/src/views/application/ApplicationView.vue +++ b/ui/src/views/application/ApplicationView.vue @@ -220,11 +220,16 @@ function handleBatchDelete() { @selected="handleApplicationSelect(application.id, $event)" > diff --git a/ui/src/views/knowledge/KnowledgeView.vue b/ui/src/views/knowledge/KnowledgeView.vue index 46421056a35..d896b13d578 100644 --- a/ui/src/views/knowledge/KnowledgeView.vue +++ b/ui/src/views/knowledge/KnowledgeView.vue @@ -209,7 +209,7 @@ function handleBatchDelete() { > diff --git a/ui/src/views/model/ModelView.vue b/ui/src/views/model/ModelView.vue index 01fb400ccba..4a4e21aa420 100644 --- a/ui/src/views/model/ModelView.vue +++ b/ui/src/views/model/ModelView.vue @@ -7,10 +7,17 @@ import ModelApi from '@/api/admin/workspace/model/model' import CommonSystemApi from '@/api/admin/system/common' import SharedApi from '@/api/admin/workspace/shared.ts' import ProviderApi from '@/api/admin/model-provider.ts' +import RelatedResourcesApi from '@/api/admin/workspace/related-resources' import ModelCard from './model-card/ModelCard.vue' import ModelCreateButton from './create-model/ModelCreateButton.vue' import ModelProvider from './components/ModelProvider.vue' -import { AuthorizeModelAction, DeleteModelAction, EditModelAction, ParamSettingAction } from './model-card/action-dropdown' +import { + AuthorizeModelAction, + DeleteModelAction, + EditModelAction, + ParamSettingAction, + RelatedResourcesModelAction, +} from './model-card/action-dropdown' const DEFAULT_MODEL_PROVIDER: ModelProviderItem = { icon: '', name: '全部模型', provider: 'all' } @@ -112,8 +119,9 @@ onMounted(() => { - - + + + diff --git a/ui/src/views/model/model-card/action-dropdown/AuthorizeModelAction.vue b/ui/src/views/model/model-card/action-dropdown/AuthorizeModelAction.vue index 1fb132ff7c2..4e879ead9d1 100644 --- a/ui/src/views/model/model-card/action-dropdown/AuthorizeModelAction.vue +++ b/ui/src/views/model/model-card/action-dropdown/AuthorizeModelAction.vue @@ -6,7 +6,6 @@ import ResourceAuthorizationDrawer from '@/components/business/resource-authoriz defineOptions({ name: 'AuthorizeModelAction' }) const props = defineProps<{ model: ModelItem; label: string }>() -const emit = defineEmits<{ refresh: [] }>() const drawerMounted = ref(false) const authorizationDrawerRef = useTemplateRef>('authorizationDrawerRef') @@ -31,7 +30,7 @@ function handleDrawerClosed() { v-if="drawerMounted" ref="authorizationDrawerRef" :type="RESOURCE_TYPE.MODEL" + :workspace-id="model.workspace_id" @closed="handleDrawerClosed" - @refresh="emit('refresh')" /> diff --git a/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue b/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue new file mode 100644 index 00000000000..1c3c90ca969 --- /dev/null +++ b/ui/src/views/model/model-card/action-dropdown/RelatedResourcesModelAction.vue @@ -0,0 +1,41 @@ + + + diff --git a/ui/src/views/model/model-card/action-dropdown/index.ts b/ui/src/views/model/model-card/action-dropdown/index.ts index d0ec5bf723e..0c633755ed7 100644 --- a/ui/src/views/model/model-card/action-dropdown/index.ts +++ b/ui/src/views/model/model-card/action-dropdown/index.ts @@ -2,3 +2,4 @@ export { default as DeleteModelAction } from './DeleteModelAction.vue' export { default as EditModelAction } from './edit-model-action/EditModelAction.vue' export { default as ParamSettingAction } from './param-setting-action/ParamSettingAction.vue' export { default as AuthorizeModelAction } from './AuthorizeModelAction.vue' +export { default as RelatedResourcesModelAction } from './RelatedResourcesModelAction.vue' diff --git a/ui/src/views/tool/ToolView.vue b/ui/src/views/tool/ToolView.vue index 8586ab32bd5..5a290635062 100644 --- a/ui/src/views/tool/ToolView.vue +++ b/ui/src/views/tool/ToolView.vue @@ -251,7 +251,7 @@ onMounted(() => { :tool="tool" /> - + diff --git a/ui/src/views/tool/tool-card/action-dropdown/AuthorizeToolAction.vue b/ui/src/views/tool/tool-card/action-dropdown/AuthorizeToolAction.vue index b3b5481a3b3..b228289ca78 100644 --- a/ui/src/views/tool/tool-card/action-dropdown/AuthorizeToolAction.vue +++ b/ui/src/views/tool/tool-card/action-dropdown/AuthorizeToolAction.vue @@ -6,7 +6,6 @@ import ResourceAuthorizationDrawer from '@/components/business/resource-authoriz defineOptions({ name: 'AuthorizeToolAction' }) const props = defineProps<{ tool: ToolItem; label: string }>() -const emit = defineEmits<{ refresh: [] }>() const drawerMounted = ref(false) const authorizationDrawerRef = useTemplateRef>('authorizationDrawerRef') @@ -31,7 +30,7 @@ function handleDrawerClosed() { v-if="drawerMounted" ref="authorizationDrawerRef" :type="RESOURCE_TYPE.TOOL" + :workspace-id="tool.workspace_id" @closed="handleDrawerClosed" - @refresh="emit('refresh')" /> diff --git a/ui/src/views/trigger/TriggerFormDrawer.vue b/ui/src/views/trigger/TriggerFormDrawer.vue new file mode 100644 index 00000000000..b7e5d0964f4 --- /dev/null +++ b/ui/src/views/trigger/TriggerFormDrawer.vue @@ -0,0 +1,426 @@ + + + diff --git a/ui/src/views/trigger/TriggerView.vue b/ui/src/views/trigger/TriggerView.vue index 729054b484d..b96a0e7d7ea 100644 --- a/ui/src/views/trigger/TriggerView.vue +++ b/ui/src/views/trigger/TriggerView.vue @@ -1,13 +1,101 @@ diff --git a/ui/src/views/trigger/components/TaskParameters.vue b/ui/src/views/trigger/components/TaskParameters.vue new file mode 100644 index 00000000000..1e87e744dd5 --- /dev/null +++ b/ui/src/views/trigger/components/TaskParameters.vue @@ -0,0 +1,87 @@ + + + diff --git a/ui/src/views/trigger/task-parameters.ts b/ui/src/views/trigger/task-parameters.ts new file mode 100644 index 00000000000..66db855e6be --- /dev/null +++ b/ui/src/views/trigger/task-parameters.ts @@ -0,0 +1,63 @@ +import { get, set } from 'lodash' +import { RESOURCE_TYPE, TRIGGER_PARAMETER_SOURCE } from '@/api/enums' +import type { ApplicationDetail, ToolItem, TriggerParameter, TriggerParameters, TriggerTaskSource } from '@/api/types' + +export interface TaskParameterField { + path: string[] + label: string + required: boolean + defaultValue: string +} + +/** 根据资源输入定义生成触发任务参数,兼容普通工具和工作流工具。 */ +export function getTaskParameterFields(source: TriggerTaskSource, resource?: Partial): TaskParameterField[] { + const fields: TaskParameterField[] = [] + const isApplication = source === RESOURCE_TYPE.APPLICATION + const baseNode = resource?.work_flow?.nodes?.find((node) => node.type === (isApplication ? 'base-node' : 'tool-base-node')) + const properties = baseNode?.properties ?? {} + if (isApplication) { + fields.push({ path: ['question'], label: '问题', required: true, defaultValue: '' }) + const nodeData = properties.node_data ?? {} + if (nodeData.file_upload_enable) { + const fileLabels = { document: '文档', image: '图片', audio: '音频', video: '视频', other: '其他文件' } + Object.entries(fileLabels).forEach(([type, label]) => { + if (nodeData.file_upload_setting?.[type]) fields.push({ path: [`${type}_list`], label, required: true, defaultValue: '[]' }) + }) + } + } else { + resource?.input_field_list?.forEach((field) => { + fields.push({ path: [field.name], label: field.name, required: field.is_required, defaultValue: '' }) + }) + } + const userFields = properties.user_input_field_list as + | { field: string; required?: boolean; is_required?: boolean; default_value?: unknown; label?: string | { label?: string } }[] + | undefined + userFields?.forEach((field) => { + fields.push({ + path: ['user_input_field_list', field.field], + label: typeof field.label === 'string' ? field.label : field.label?.label || field.field, + required: field.required ?? field.is_required ?? false, + defaultValue: + typeof field.default_value === 'string' ? field.default_value : field.default_value === undefined ? '' : JSON.stringify(field.default_value), + }) + }) + if (isApplication) { + const apiFields = properties.api_input_field_list as { variable: string; is_required?: boolean }[] | undefined + apiFields?.forEach((field) => + fields.push({ path: ['api_input_field_list', field.variable], label: field.variable, required: field.is_required ?? false, defaultValue: '' }), + ) + } + return fields +} + +/** 只补全缺失输入,编辑和折叠时不覆盖已保存参数。 */ +export function initializeTaskParameters(parameters: TriggerParameters, fields: TaskParameterField[]) { + fields.forEach((field) => { + if (!get(parameters, field.path)) set(parameters, field.path, { source: TRIGGER_PARAMETER_SOURCE.CUSTOM, value: field.defaultValue }) + }) + return parameters +} + +export function getTaskParameter(parameters: TriggerParameters, field: TaskParameterField): TriggerParameter { + return get(parameters, field.path) as TriggerParameter +} diff --git a/ui/src/workflow-canvas/nodes/base-node/index.vue b/ui/src/workflow-canvas/nodes/base-node/index.vue index d5f279abeb6..c4a888932c1 100644 --- a/ui/src/workflow-canvas/nodes/base-node/index.vue +++ b/ui/src/workflow-canvas/nodes/base-node/index.vue @@ -256,13 +256,13 @@ onMounted(() => { - + />